diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index da2f822..b4c29dc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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) diff --git a/di/container.go b/di/container.go index 8abf9dd..22cc968 100644 --- a/di/container.go +++ b/di/container.go @@ -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( @@ -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 + } } } diff --git a/evaluator/eval_args.go b/evaluator/eval_args.go index 5d6c598..25ce69d 100644 --- a/evaluator/eval_args.go +++ b/evaluator/eval_args.go @@ -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 } diff --git a/evaluator/eval_propcall.go b/evaluator/eval_propcall.go index ae53474..891ab6d 100644 --- a/evaluator/eval_propcall.go +++ b/evaluator/eval_propcall.go @@ -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 } @@ -256,6 +258,7 @@ func evalCallArgs( if err != nil { return nil, nil, err } + kwargs.AddPairs(unpackedKwargs) return args, kwargs, nil } diff --git a/evaluator/eval_test.go b/evaluator/eval_test.go index 0464ac1..cedc1e4 100644 --- a/evaluator/eval_test.go +++ b/evaluator/eval_test.go @@ -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 diff --git a/main.go b/main.go index ebdeb68..0c72615 100644 --- a/main.go +++ b/main.go @@ -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 != "" { @@ -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 diff --git a/native/Arr.pangaea b/native/Arr.pangaea index 39f3e5f..912ade8 100644 --- a/native/Arr.pangaea +++ b/native/Arr.pangaea @@ -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}, } diff --git a/native/Iterable.pangaea b/native/Iterable.pangaea new file mode 100644 index 0000000..74073ba --- /dev/null +++ b/native/Iterable.pangaea @@ -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)+}, +} diff --git a/object/builtinobj.go b/object/builtinobj.go index 56313bc..30781e8 100644 --- a/object/builtinobj.go +++ b/object/builtinobj.go @@ -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} diff --git a/object/env.go b/object/env.go index 8a8e46f..72e259c 100644 --- a/object/env.go +++ b/object/env.go @@ -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) diff --git a/object/env_test.go b/object/env_test.go index ca8de4a..6d2438e 100644 --- a/object/env_test.go +++ b/object/env_test.go @@ -96,6 +96,7 @@ func TestEnvWithConsts(t *testing.T) { {"Range", BuiltInRangeObj}, {"Func", BuiltInFuncObj}, {"Iter", BuiltInIterObj}, + {"Iterable", BuiltInIterableObj}, {"Match", BuiltInMatchObj}, {"Obj", BuiltInObjObj}, {"BaseObj", BuiltInBaseObj}, diff --git a/parser/parser.go.y b/parser/parser.go.y index 3b78a3f..cc6e3db 100644 --- a/parser/parser.go.y +++ b/parser/parser.go.y @@ -1628,6 +1628,76 @@ callArgs $$ = $2 yylex.(*Lexer).curRule = "callArgs -> lParen argList RET RPAREN" } + | lParen kwargExpansionList RPAREN %prec GROUPING + { + expansionList := []ast.Expr{} + for _, exp := range $2 { + prefixExpr := &ast.PrefixExpr{ + Token: "**", + Operator: "**", + Right: exp, + Src: yylex.(*Lexer).Source, + } + expansionList = append(expansionList, prefixExpr) + } + + argList := ast.ExprToArgList(expansionList[0]) + for _, e := range expansionList[1:] { + argList.AppendArg(e) + } + $$ = argList + yylex.(*Lexer).curRule = "callArgs -> lParen kwargExpansionList RPAREN" + } + | lParen kwargExpansionList RET RPAREN %prec GROUPING + { + expansionList := []ast.Expr{} + for _, exp := range $2 { + prefixExpr := &ast.PrefixExpr{ + Token: "**", + Operator: "**", + Right: exp, + Src: yylex.(*Lexer).Source, + } + expansionList = append(expansionList, prefixExpr) + } + + argList := ast.ExprToArgList(expansionList[0]) + for _, e := range expansionList[1:] { + argList.AppendArg(e) + } + $$ = argList + yylex.(*Lexer).curRule = "callArgs -> lParen kwargExpansionList RET RPAREN" + } + | lParen argList comma kwargExpansionList RPAREN %prec GROUPING + { + argList := $2 + for _, exp := range $4 { + prefixExpr := &ast.PrefixExpr{ + Token: "**", + Operator: "**", + Right: exp, + Src: yylex.(*Lexer).Source, + } + argList.AppendArg(prefixExpr) + } + $$ = argList + yylex.(*Lexer).curRule = "callArgs -> lParen kwargExpansionList RPAREN" + } + | lParen argList comma kwargExpansionList RET RPAREN %prec GROUPING + { + argList := $2 + for _, exp := range $4 { + prefixExpr := &ast.PrefixExpr{ + Token: "**", + Operator: "**", + Right: exp, + Src: yylex.(*Lexer).Source, + } + argList.AppendArg(prefixExpr) + } + $$ = argList + yylex.(*Lexer).curRule = "callArgs -> lParen argList kwargExpansionList RET RPAREN" + } | callArgs funcLiteral %prec GROUPING { $$ = $1.AppendArg($2) diff --git a/parser/y_test.go b/parser/y_test.go index cdcf448..3048c9d 100644 --- a/parser/y_test.go +++ b/parser/y_test.go @@ -6,8 +6,8 @@ package parser import ( - "github.com/Syuparn/pangaea/ast" "fmt" + "github.com/Syuparn/pangaea/ast" "math" "reflect" "strings" @@ -2103,6 +2103,80 @@ func TestArgOrders(t *testing.T) { } } +func TestArgUnpack(t *testing.T) { + tests := []struct { + input string + args []string + kwargs map[string]string + printed string + }{ + // embedded elements are set in args + { + `5.a(*[1])`, + []string{"(*[1])"}, + map[string]string{}, + `5.a((*[1]))`, + }, + { + `5.a(**{b: 2})`, + []string{"(**{b: 2})"}, + map[string]string{}, + `5.a((**{b: 2}))`, + }, + { + `5.a(**{c: 3}, **{d: 4})`, + []string{"(**{c: 3})", "(**{d: 4})"}, + map[string]string{}, + `5.a((**{c: 3}), (**{d: 4}))`, + }, + // kwarg expansion must be at the end + { + `5.a(1, **{c: 3}, **{d: 4})`, + []string{"1", "(**{c: 3})", "(**{d: 4})"}, + map[string]string{}, + `5.a(1, (**{c: 3}), (**{d: 4}))`, + }, + { + `5.a(1, *[2], **{c: 3}, **{d: 4})`, + []string{"1", "(*[2])", "(**{c: 3})", "(**{d: 4})"}, + map[string]string{}, + `5.a(1, (*[2]), (**{c: 3}), (**{d: 4}))`, + }, + } + + for _, tt := range tests { + program := testParse(t, tt.input) + expr := extractExprStmt(t, program) + + callExpr, ok := expr.(*ast.PropCallExpr) + if !ok { + t.Fatalf("expr is not *ast.PropCallExpr. got=%T", expr) + } + + if len(callExpr.Args) != len(tt.args) { + t.Fatalf("wrong arity of args, expected=%d, got=%d", + len(tt.args), len(callExpr.Args)) + } + + if len(callExpr.Kwargs) != len(tt.kwargs) { + t.Fatalf("wrong arity of kwargs, expected=%d, got=%d", + len(tt.kwargs), len(callExpr.Kwargs)) + } + + if callExpr.String() != tt.printed { + t.Errorf("wrong output.expected=\n%s,\ngot=\n%s", + tt.printed, callExpr.String()) + } + + for i, expArg := range tt.args { + if callExpr.Args[i].String() != expArg { + t.Errorf("args[%d] must be `%s`. got=`%s`", + i, expArg, callExpr.Args[i].String()) + } + } + } +} + func TestCallWithArgs(t *testing.T) { tests := []struct { input string diff --git a/runscript/run.go b/runscript/run.go index f1be29e..5dc0098 100644 --- a/runscript/run.go +++ b/runscript/run.go @@ -4,6 +4,8 @@ import ( "fmt" "io" "os" + "path/filepath" + "strings" "github.com/Syuparn/pangaea/di" "github.com/Syuparn/pangaea/evaluator" @@ -13,17 +15,35 @@ import ( // Run runs Pangaea script source file. func Run(fileName string, in io.Reader, out io.Writer) int { - env := object.NewEnvWithConsts() - // setup object `IO` - env.InjectIO(in, out) + env := setup(in, out) + exitCode := run(fileName, in, out, env) + return exitCode +} - // necessary to setup built-in object props - di.InjectBuiltInProps(env) +// RunTest runs all test script files in path until error is raised. +func RunTest(path string, in io.Reader, out io.Writer) int { + env := setup(in, out) + exitCode := 0 - // enable to use Kernel props directly in top-level - // NOTE: InjectFrom must be called after BuiltInKernelObj is set up - env.InjectFrom(object.BuiltInKernelObj) + filepath.Walk(path, func(path string, info os.FileInfo, err error) error { + if !strings.HasSuffix(path, ".pangaea") { + return nil + } + + out.Write([]byte(fmt.Sprintf("run: %s\n", path))) + + exitCode = run(path, in, out, env) + if exitCode != 0 { + return fmt.Errorf("test in %s failed", path) + } + + out.Write([]byte(fmt.Sprintf("pass: %s\n", path))) + return nil + }) + return exitCode +} +func run(fileName string, in io.Reader, out io.Writer, env *object.Env) int { fp, err := os.Open(fileName) if err != nil { fmt.Fprint(os.Stderr, err.Error()+"\n") @@ -46,3 +66,18 @@ func Run(fileName string, in io.Reader, out io.Writer) int { return 0 } + +func setup(in io.Reader, out io.Writer) *object.Env { + env := object.NewEnvWithConsts() + // setup object `IO` + env.InjectIO(in, out) + + // necessary to setup built-in object props + di.InjectBuiltInProps(env) + + // enable to use Kernel props directly in top-level + // NOTE: InjectFrom must be called after BuiltInKernelObj is set up + env.InjectFrom(object.BuiltInKernelObj) + + return env +} diff --git a/tests/Arr_A_test.pangaea b/tests/Arr_A_test.pangaea new file mode 100644 index 0000000..945af30 --- /dev/null +++ b/tests/Arr_A_test.pangaea @@ -0,0 +1 @@ +assert([1, 2, 3].A == [1, 2, 3]) diff --git a/tests/Arr_unwrap_test.pangaea b/tests/Arr_unwrap_test.pangaea new file mode 100644 index 0000000..2d52e85 --- /dev/null +++ b/tests/Arr_unwrap_test.pangaea @@ -0,0 +1,3 @@ +assert([].unwrap == []) +assert([1, 2].unwrap == [1, 2]) +assert([1].unwrap == 1) diff --git a/tests/Int_A_test.pangaea b/tests/Int_A_test.pangaea new file mode 100644 index 0000000..f18b5fd --- /dev/null +++ b/tests/Int_A_test.pangaea @@ -0,0 +1 @@ +assert(3.A == [1, 2, 3]) diff --git a/tests/Int_allp_test.pangaea b/tests/Int_allp_test.pangaea new file mode 100644 index 0000000..9aeeed6 --- /dev/null +++ b/tests/Int_allp_test.pangaea @@ -0,0 +1,2 @@ +assert(10.all? {\ != 11} == true) +assert(10.all? {.odd?} == false) diff --git a/tests/Int_anyp_test.pangaea b/tests/Int_anyp_test.pangaea new file mode 100644 index 0000000..8bf1d14 --- /dev/null +++ b/tests/Int_anyp_test.pangaea @@ -0,0 +1,2 @@ +assert(10.any? {.odd?} == true) +assert(3.any? {\ == 0} == false) diff --git a/tests/Int_emptyp_test.pangaea b/tests/Int_emptyp_test.pangaea new file mode 100644 index 0000000..e1931b7 --- /dev/null +++ b/tests/Int_emptyp_test.pangaea @@ -0,0 +1,2 @@ +assert(0.empty? == true) +assert(1.empty? == false) diff --git a/tests/Int_exclude_test.pangaea b/tests/Int_exclude_test.pangaea new file mode 100644 index 0000000..74850f5 --- /dev/null +++ b/tests/Int_exclude_test.pangaea @@ -0,0 +1 @@ +assert(4.exclude {|n| n.odd?} == [2, 4]) diff --git a/tests/Int_map_test.pangaea b/tests/Int_map_test.pangaea new file mode 100644 index 0000000..eeb1511 --- /dev/null +++ b/tests/Int_map_test.pangaea @@ -0,0 +1 @@ +assert(3.map {|i| i * 2} == [2, 4, 6]) diff --git a/tests/Int_select_test.pangaea b/tests/Int_select_test.pangaea new file mode 100644 index 0000000..d22be92 --- /dev/null +++ b/tests/Int_select_test.pangaea @@ -0,0 +1 @@ +assert(3.select {|n| n.odd?} == [1, 3]) diff --git a/tests/Int_sum_test.pangaea b/tests/Int_sum_test.pangaea new file mode 100644 index 0000000..221f0b2 --- /dev/null +++ b/tests/Int_sum_test.pangaea @@ -0,0 +1,3 @@ +assert(0.sum == nil) +assert(1.sum == 1) +assert(3.sum == 6) diff --git a/tests/Map_A_test.pangaea b/tests/Map_A_test.pangaea new file mode 100644 index 0000000..d4a93f3 --- /dev/null +++ b/tests/Map_A_test.pangaea @@ -0,0 +1,3 @@ +# NOTE: cannot test multiple pairs testcases +# because order of map is not guaranteed. +assert(%{'a: 'b}.A == [["a", "b"]]) diff --git a/tests/Map_allp_test.pangaea b/tests/Map_allp_test.pangaea new file mode 100644 index 0000000..74173f7 --- /dev/null +++ b/tests/Map_allp_test.pangaea @@ -0,0 +1,2 @@ +assert(%{'a: 2, 'b: 4}.all? {|k, v| v.even?} == true) +assert(%{'a: 2, 'b: 4}.all? {|k, v| k == 'a} == false) diff --git a/tests/Map_anyp_test.pangaea b/tests/Map_anyp_test.pangaea new file mode 100644 index 0000000..476b17b --- /dev/null +++ b/tests/Map_anyp_test.pangaea @@ -0,0 +1,2 @@ +assert(%{"a": 1, "b": 2}.any? {|k, v| v.even?} == true) +assert(%{"a": 1, "b": 3}.any? {|k, v| v.even?} == false) diff --git a/tests/Map_emptyp_test.pangaea b/tests/Map_emptyp_test.pangaea new file mode 100644 index 0000000..d662f1f --- /dev/null +++ b/tests/Map_emptyp_test.pangaea @@ -0,0 +1,2 @@ +assert(%{}.empty? == true) +assert(%{1: 2}.empty? == false) diff --git a/tests/Map_exclude_test.pangaea b/tests/Map_exclude_test.pangaea new file mode 100644 index 0000000..e0233a6 --- /dev/null +++ b/tests/Map_exclude_test.pangaea @@ -0,0 +1 @@ +assert(%{"a": 1, "b": 2}.exclude {|k, v| v.odd?} == [["b", 2]]) diff --git a/tests/Map_map_test.pangaea b/tests/Map_map_test.pangaea new file mode 100644 index 0000000..644843f --- /dev/null +++ b/tests/Map_map_test.pangaea @@ -0,0 +1,3 @@ +# NOTE: cannot test multiple elements because +# order of map elements is not guaranteed. +assert(%{"a": 1}.map {|k, v| [k, v * 2]} == [["a", 2]]) diff --git a/tests/Map_select_test.pangaea b/tests/Map_select_test.pangaea new file mode 100644 index 0000000..7bd7026 --- /dev/null +++ b/tests/Map_select_test.pangaea @@ -0,0 +1 @@ +assert(%{"a": 1, "b": 2}.select {|k, v| v.odd?} == [["a", 1]]) diff --git a/tests/Map_sum_test.pangaea b/tests/Map_sum_test.pangaea new file mode 100644 index 0000000..4eb9e25 --- /dev/null +++ b/tests/Map_sum_test.pangaea @@ -0,0 +1,2 @@ +assert(%{}.sum == nil) +assert(%{"a": 1}.sum == ["a", 1]) diff --git a/tests/Obj_A_test.pangaea b/tests/Obj_A_test.pangaea new file mode 100644 index 0000000..0094612 --- /dev/null +++ b/tests/Obj_A_test.pangaea @@ -0,0 +1 @@ +assert({a: 'b, c: 'd}.A == [["a", "b"], ["c", "d"]]) diff --git a/tests/Obj_allp_test.pangaea b/tests/Obj_allp_test.pangaea new file mode 100644 index 0000000..c898657 --- /dev/null +++ b/tests/Obj_allp_test.pangaea @@ -0,0 +1,2 @@ +assert({a: 2, b: 4}.all? {|k, v| v.even?} == true) +assert({a: 2, b: 4}.all? {|k, v| k == 'a} == false) diff --git a/tests/Obj_anyp_test.pangaea b/tests/Obj_anyp_test.pangaea new file mode 100644 index 0000000..73d64b0 --- /dev/null +++ b/tests/Obj_anyp_test.pangaea @@ -0,0 +1,2 @@ +assert({a: 1, b: 2}.any? {|k, v| v.even?} == true) +assert({a: 1, b: 3}.any? {|k, v| v.even?} == false) diff --git a/tests/Obj_emptyp_test.pangaea b/tests/Obj_emptyp_test.pangaea new file mode 100644 index 0000000..3af3b60 --- /dev/null +++ b/tests/Obj_emptyp_test.pangaea @@ -0,0 +1,2 @@ +assert({}.empty? == true) +assert({a: 1}.empty? == false) diff --git a/tests/Obj_exclude_test.pangaea b/tests/Obj_exclude_test.pangaea new file mode 100644 index 0000000..da3c8dd --- /dev/null +++ b/tests/Obj_exclude_test.pangaea @@ -0,0 +1 @@ +assert({a: 1, b: 2}.exclude {|k, v| v.odd?} == [["b", 2]]) diff --git a/tests/Obj_map_test.pangaea b/tests/Obj_map_test.pangaea new file mode 100644 index 0000000..02fb1e9 --- /dev/null +++ b/tests/Obj_map_test.pangaea @@ -0,0 +1 @@ +assert({a: 1, b: 2, c: 3}.map {|k, v| v * 2} == [2, 4, 6]) diff --git a/tests/Obj_select_test.pangaea b/tests/Obj_select_test.pangaea new file mode 100644 index 0000000..8de3ba9 --- /dev/null +++ b/tests/Obj_select_test.pangaea @@ -0,0 +1 @@ +assert({a: 1, b: 2, c: 3}.select {|k, v| v.odd?} == [["a", 1], ["c", 3]]) diff --git a/tests/Obj_sum_test.pangaea b/tests/Obj_sum_test.pangaea new file mode 100644 index 0000000..c4d0a3f --- /dev/null +++ b/tests/Obj_sum_test.pangaea @@ -0,0 +1,3 @@ +assert({}.sum == nil) +assert({a: 1}.sum == ["a", 1]) +assert({a: 1, b: 2}.sum == ["a", 1, "b", 2]) diff --git a/tests/Range_A_test.pangaea b/tests/Range_A_test.pangaea new file mode 100644 index 0000000..791e153 --- /dev/null +++ b/tests/Range_A_test.pangaea @@ -0,0 +1 @@ +assert((1:4).A == [1, 2, 3]) diff --git a/tests/Range_allp_test.pangaea b/tests/Range_allp_test.pangaea new file mode 100644 index 0000000..f07496d --- /dev/null +++ b/tests/Range_allp_test.pangaea @@ -0,0 +1,2 @@ +assert((1:5:2).all? {.odd?} == true) +assert((1:10).all? {.odd?} == false) diff --git a/tests/Range_anyp_test.pangaea b/tests/Range_anyp_test.pangaea new file mode 100644 index 0000000..61028ed --- /dev/null +++ b/tests/Range_anyp_test.pangaea @@ -0,0 +1,2 @@ +assert((1:10).any? {.odd?} == true) +assert((2:10:2).any? {.odd?} == false) diff --git a/tests/Range_emptyp_test.pangaea b/tests/Range_emptyp_test.pangaea new file mode 100644 index 0000000..8035946 --- /dev/null +++ b/tests/Range_emptyp_test.pangaea @@ -0,0 +1,2 @@ +assert((0:0).empty? == true) +assert((0:2).empty? == false) diff --git a/tests/Range_exclude_test.pangaea b/tests/Range_exclude_test.pangaea new file mode 100644 index 0000000..a235441 --- /dev/null +++ b/tests/Range_exclude_test.pangaea @@ -0,0 +1 @@ +assert((2:5).exclude {|n| n.odd?} == [2, 4]) diff --git a/tests/Range_map_test.pangaea b/tests/Range_map_test.pangaea new file mode 100644 index 0000000..b75d1c1 --- /dev/null +++ b/tests/Range_map_test.pangaea @@ -0,0 +1 @@ +assert((1:4).map {|i| i * 2} == [2, 4, 6]) diff --git a/tests/Range_select_test.pangaea b/tests/Range_select_test.pangaea new file mode 100644 index 0000000..a2df30a --- /dev/null +++ b/tests/Range_select_test.pangaea @@ -0,0 +1 @@ +assert((1:4).select {|n| n.odd?} == [1, 3]) diff --git a/tests/Range_sum_test.pangaea b/tests/Range_sum_test.pangaea new file mode 100644 index 0000000..ad2a5e7 --- /dev/null +++ b/tests/Range_sum_test.pangaea @@ -0,0 +1,4 @@ +assert((1:1).sum == nil) +assert((1:2).sum == 1) +assert((1:4).sum == 6) +assert(('a:'d).sum == "abc") diff --git a/tests/Str_A_test.pangaea b/tests/Str_A_test.pangaea new file mode 100644 index 0000000..ea1e955 --- /dev/null +++ b/tests/Str_A_test.pangaea @@ -0,0 +1 @@ +assert("abc".A == ["a", "b", "c"]) diff --git a/tests/Str_allp_test.pangaea b/tests/Str_allp_test.pangaea new file mode 100644 index 0000000..1426abb --- /dev/null +++ b/tests/Str_allp_test.pangaea @@ -0,0 +1,2 @@ +assert("aaaaa".all? {\ == "a"} == true) +assert("abc".all? {\ == "a"} == false) diff --git a/tests/Str_anyp_test.pangaea b/tests/Str_anyp_test.pangaea new file mode 100644 index 0000000..bc7dacc --- /dev/null +++ b/tests/Str_anyp_test.pangaea @@ -0,0 +1,2 @@ +assert("abc".any? {\ == "a"} == true) +assert("def".any? {\ == "a"} == false) diff --git a/tests/Str_emptyp_test.pangaea b/tests/Str_emptyp_test.pangaea new file mode 100644 index 0000000..617fdbf --- /dev/null +++ b/tests/Str_emptyp_test.pangaea @@ -0,0 +1,2 @@ +assert("".empty? == true) +assert("a".empty? == false) diff --git a/tests/Str_exclude_test.pangaea b/tests/Str_exclude_test.pangaea new file mode 100644 index 0000000..0b979a3 --- /dev/null +++ b/tests/Str_exclude_test.pangaea @@ -0,0 +1 @@ +assert("abc".exclude {|n| n == "a"} == ["b", "c"]) diff --git a/tests/Str_map_test.pangaea b/tests/Str_map_test.pangaea new file mode 100644 index 0000000..a209ede --- /dev/null +++ b/tests/Str_map_test.pangaea @@ -0,0 +1 @@ +assert("abc".map { ._incBy(1) } == ["b", "c", "d"]) diff --git a/tests/Str_select_test.pangaea b/tests/Str_select_test.pangaea new file mode 100644 index 0000000..47bf32f --- /dev/null +++ b/tests/Str_select_test.pangaea @@ -0,0 +1 @@ +assert("hello".select {|c| c == "l"} == ["l", "l"]) diff --git a/tests/Str_sum_test.pangaea b/tests/Str_sum_test.pangaea new file mode 100644 index 0000000..761cd5d --- /dev/null +++ b/tests/Str_sum_test.pangaea @@ -0,0 +1,3 @@ +# When is this method useful? 🤭 +assert("".sum == nil) +assert("abc".sum == "abc")