Skip to content

Commit

Permalink
Merge pull request #67 from Syuparn/add-iterable
Browse files Browse the repository at this point in the history
add Iterable
  • Loading branch information
Syuparn authored Nov 3, 2020
2 parents f6d7a11 + 26eb365 commit 7e19ea5
Show file tree
Hide file tree
Showing 56 changed files with 469 additions and 38 deletions.
6 changes: 1 addition & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ jobs:
run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...

- name: PangaeaTest
run: |
for file in `\find tests/ -name '*.pangaea'`; do
echo $file
./pangaea $file
done
run: ./pangaea test tests/

- name: Upload coverage
run: bash <(curl -s https://codecov.io/bash)
18 changes: 11 additions & 7 deletions di/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,20 @@ func injectBuiltInProps(
env *object.Env,
ctn map[string]object.PanObject,
) {
injectProps(object.BuiltInArrObj, props.ArrProps(ctn), mustReadNativeCode("Arr", env))
injectProps(object.BuiltInArrObj, props.ArrProps(ctn), mustReadNativeCode("Arr", env), mustReadNativeCode("Iterable", env))
injectProps(object.BuiltInBaseObj, props.BaseObjProps(ctn))
injectProps(object.BuiltInFloatObj, props.FloatProps(ctn))
injectProps(object.BuiltInFuncObj, props.FuncProps(ctn))
injectProps(object.BuiltInIntObj, props.IntProps(ctn), mustReadNativeCode("Int", env))
injectProps(object.BuiltInIntObj, props.IntProps(ctn), mustReadNativeCode("Int", env), mustReadNativeCode("Iterable", env))
injectProps(object.BuiltInIterObj, props.IterProps(ctn))
injectProps(object.BuiltInIterableObj, mustReadNativeCode("Iterable", env))
injectProps(object.BuiltInKernelObj, props.KernelProps(ctn))
injectProps(object.BuiltInMapObj, props.MapProps(ctn))
injectProps(object.BuiltInMapObj, props.MapProps(ctn), mustReadNativeCode("Iterable", env))
injectProps(object.BuiltInNilObj, props.NilProps(ctn))
injectProps(object.BuiltInNumObj, props.NumProps(ctn))
injectProps(object.BuiltInObjObj, props.ObjProps(ctn), mustReadNativeCode("Obj", env))
injectProps(object.BuiltInRangeObj, props.RangeProps(ctn))
injectProps(object.BuiltInStrObj, props.StrProps(ctn), mustReadNativeCode("Str", env))
injectProps(object.BuiltInObjObj, props.ObjProps(ctn), mustReadNativeCode("Obj", env), mustReadNativeCode("Iterable", env))
injectProps(object.BuiltInRangeObj, props.RangeProps(ctn), mustReadNativeCode("Iterable", env))
injectProps(object.BuiltInStrObj, props.StrProps(ctn), mustReadNativeCode("Str", env), mustReadNativeCode("Iterable", env))
}

func injectProps(
Expand All @@ -69,7 +70,10 @@ func mergePropContainers(
mergedCtn := map[string]object.PanObject{}
for _, ctn := range containers {
for k, v := range ctn {
mergedCtn[k] = v
// NOTE: if same key is found, first value is remained
if _, ok := mergedCtn[k]; !ok {
mergedCtn[k] = v
}
}
}

Expand Down
65 changes: 62 additions & 3 deletions evaluator/eval_args.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,81 @@
package evaluator

import (
"fmt"

"github.com/Syuparn/pangaea/ast"
"github.com/Syuparn/pangaea/object"
)

func evalArgs(argNodes []ast.Expr, env *object.Env) ([]object.PanObject, *object.PanErr) {
func evalArgs(
argNodes []ast.Expr,
env *object.Env,
) ([]object.PanObject, *map[object.SymHash]object.Pair, *object.PanErr) {
args := []object.PanObject{}
// NOTE: for syntactic reason, kwarg expansion is in Args as `**` prefixExpr
// (not in Kwargs)
unpackedKwargs := object.EmptyPanObjPtr()
for _, argNode := range argNodes {
// unpack kwarg expansion (like **obj)
kwargs, err, ok := unpackObjExpansion(argNode, env)
if ok {
if err != nil {
appendStackTrace(err, argNode.Source())
return []object.PanObject{}, nil, err
}
unpackedKwargs.AddPairs(kwargs)
continue
}

// try to unpack arg expansion (like *arr)
elems, err, ok := unpackArrExpansion(argNode, env)
if ok {
if err != nil {
appendStackTrace(err, argNode.Source())
return []object.PanObject{}, nil, err
}
args = append(args, elems...)
continue
}

arg := Eval(argNode, env)

if err, ok := arg.(*object.PanErr); ok {
appendStackTrace(err, argNode.Source())
return []object.PanObject{}, err
return []object.PanObject{}, nil, err
}

args = append(args, arg)
}

return args, nil
return args, unpackedKwargs.Pairs, nil
}

func unpackObjExpansion(
node ast.Node,
env *object.Env,
) (*map[object.SymHash]object.Pair, *object.PanErr, bool) {
pref, ok := node.(*ast.PrefixExpr)
if !ok {
return nil, nil, false
}
if pref.Operator != "**" {
return nil, nil, false
}

o := Eval(pref.Right, env)
if err, ok := o.(*object.PanErr); ok {
appendStackTrace(err, node.Source())
return nil, err, true
}

obj, ok := o.(*object.PanObj)
if !ok {
err := object.NewTypeErr(fmt.Sprintf(
"cannot use `**` unpacking for `%s`", o.Inspect()))
appendStackTrace(err, node.Source())
return nil, err, true
}

return obj.Pairs, nil, true
}
5 changes: 4 additions & 1 deletion evaluator/eval_propcall.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,9 @@ func evalCallArgs(
node *ast.PropCallExpr,
env *object.Env,
) ([]object.PanObject, *object.PanObj, *object.PanErr) {
args, err := evalArgs(node.Args, env)
// NOTE: for syntactic reason, kwarg expansion is in Args as `**` prefixExpr
// (not in Kwargs)
args, unpackedKwargs, err := evalArgs(node.Args, env)
if err != nil {
return nil, nil, err
}
Expand All @@ -256,6 +258,7 @@ func evalCallArgs(
if err != nil {
return nil, nil, err
}
kwargs.AddPairs(unpackedKwargs)

return args, kwargs, nil
}
Expand Down
86 changes: 86 additions & 0 deletions evaluator/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3889,6 +3889,92 @@ func TestEvalScalarPropChain(t *testing.T) {
}
}

func TestEvalArgUnpack(t *testing.T) {
tests := []struct {
input string
expected object.PanObject
}{
{
`{|i, j, k| [i, j, k]}(*[1, 2, 3])`,
&object.PanArr{Elems: []object.PanObject{
object.NewPanInt(1),
object.NewPanInt(2),
object.NewPanInt(3),
}},
},
// with other prefix
{
`{|i, j, k| [i, j, k]}(*[1, 2], !3)`,
&object.PanArr{Elems: []object.PanObject{
object.NewPanInt(1),
object.NewPanInt(2),
object.BuiltInFalse,
}},
},
}

for _, tt := range tests {
actual := testEval(t, tt.input)
testValue(t, actual, tt.expected)
}
}

func TestEvalKwargUnpack(t *testing.T) {
tests := []struct {
input string
expected object.PanObject
}{
{
`{|a: 1, b: 2| [a, b]}(**{a: 5, b: 10})`,
&object.PanArr{Elems: []object.PanObject{
object.NewPanInt(5),
object.NewPanInt(10),
}},
},
{
`{|a: 1, b: 2| [a, b]}(a: 3, **{a: 6, b: 9})`,
&object.PanArr{Elems: []object.PanObject{
object.NewPanInt(3),
object.NewPanInt(9),
}},
},
}

for _, tt := range tests {
actual := testEval(t, tt.input)
testValue(t, actual, tt.expected)
}
}

func TestEvalUnpackError(t *testing.T) {
tests := []struct {
input string
expected object.PanObject
}{
{
`{|a: 1, b: 2| [a, b]}(**c)`,
object.NewNameErr("name `c` is not defined"),
},
{
`{|a: 1, b: 2| [a, b]}(**[])`,
object.NewTypeErr("cannot use `**` unpacking for `[]`"),
},
{
`{|a, b| [a, b]}(*c)`,
object.NewNameErr("name `c` is not defined"),
},
{
`{|a, b| [a, b]}(*{})`,
object.NewTypeErr("cannot use `*` unpacking for `{}`"),
},
}

for _, tt := range tests {
actual := testEval(t, tt.input)
testValue(t, actual, tt.expected)
}
}

func TestEvalAnonPropChain(t *testing.T) {
tests := []struct {
input string
Expand Down
18 changes: 17 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,21 @@ import (
)

var (
parse = flag.Bool("parse", false, "only parse instead of eval")
parse = flag.Bool("parse", false, "only parse instead of eval")
testCmdSet = flag.NewFlagSet("test", flag.ExitOnError)
)

func main() {
// test mode
if len(os.Args) >= 2 && os.Args[1] == "test" {
testCmdSet.Parse(os.Args[2:])
if path := testCmdSet.Arg(0); path != "" {
exitCode := runTest(path)
os.Exit(exitCode)
}
}

// normal mode
flag.Parse()

if srcFileName := flag.Arg(0); srcFileName != "" {
Expand All @@ -29,6 +40,11 @@ func main() {
runRepl()
}

func runTest(path string) int {
exitCode := runscript.RunTest(path, os.Stdin, os.Stdout)
return exitCode
}

func runScript(fileName string) int {
exitCode := runscript.Run(fileName, os.Stdin, os.Stdout)
return exitCode
Expand Down
16 changes: 4 additions & 12 deletions native/Arr.pangaea
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
{
# all? returns whether all elements meet the predicate f
all?: m{|f| @^f.has?(false).!},
# any? returns whether any element meets the predicate f
any?: m{|f| @^f.has?(true)},
# A converts self into arr.
A: m{|| self},
# empty? returns whether self contains elements.
empty?: m{|| .len.!},
# exclude selects elements for which f returns false.
exclude: m{|f| @{\ if !f(\)}},
# map is a wrapper of mapchain.
map: m{|f| @^f},
# select selects elements for which f returns true.
select: m{|f| @{\ if f(\)}},
# sum returns sum of elements in self.
sum: m{|| $(nil)+},
# unwrap extracts element if there is only one element.
unwrap: m{|| self[0] if .len == 1 else self},
}
18 changes: 18 additions & 0 deletions native/Iterable.pangaea
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
# A converts self into arr.
A: m{|| @{\}},
# all? returns whether all elements meet the predicate f
all?: m{|f| @^f.has?(false).!},
# any? returns whether any element meets the predicate f
any?: m{|f| @^f.has?(true)},
# empty? returns whether self contains elements.
empty?: m{|| .A.empty?},
# exclude selects elements for which f returns false.
exclude: m{|f| @{\ if .proto == Arr else [\]}@{\.unwrap if !f(*\)}},
# map is a wrapper of mapchain.
map: m{|f| @^f},
# select selects elements for which f returns true.
select: m{|f| @{\ if .proto == Arr else [\]}@{\.unwrap if f(*\)}},
# sum returns sum of elements in self.
sum: m{|| $(nil)+},
}
3 changes: 3 additions & 0 deletions object/builtinobj.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ var BuiltInIOObj = NewPanObj(&map[SymHash]Pair{}, BuiltInObjObj)
// BuiltInKernelObj is an object of Kernel, whose props can be used in top-level.
var BuiltInKernelObj = NewPanObj(&map[SymHash]Pair{}, BuiltInObjObj)

// BuiltInIterableObj is an object of Iterable, which is mixed-in iterable objects.
var BuiltInIterableObj = NewPanObj(&map[SymHash]Pair{}, BuiltInObjObj)

// BuiltInOneInt is an int object `1`.
var BuiltInOneInt = &PanInt{1}

Expand Down
1 change: 1 addition & 0 deletions object/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func NewEnvWithConsts() *Env {
env.Set(GetSymHash("Range"), BuiltInRangeObj)
env.Set(GetSymHash("Func"), BuiltInFuncObj)
env.Set(GetSymHash("Iter"), BuiltInIterObj)
env.Set(GetSymHash("Iterable"), BuiltInIterableObj)
env.Set(GetSymHash("Match"), BuiltInMatchObj)
env.Set(GetSymHash("Obj"), BuiltInObjObj)
env.Set(GetSymHash("BaseObj"), BuiltInBaseObj)
Expand Down
1 change: 1 addition & 0 deletions object/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ func TestEnvWithConsts(t *testing.T) {
{"Range", BuiltInRangeObj},
{"Func", BuiltInFuncObj},
{"Iter", BuiltInIterObj},
{"Iterable", BuiltInIterableObj},
{"Match", BuiltInMatchObj},
{"Obj", BuiltInObjObj},
{"BaseObj", BuiltInBaseObj},
Expand Down
Loading

0 comments on commit 7e19ea5

Please sign in to comment.