From ad751e427bf0b0b48227cb4ba06bd053c009c1a6 Mon Sep 17 00:00:00 2001 From: odino Date: Fri, 3 Apr 2020 20:03:02 +0400 Subject: [PATCH] Introducing the ABS standard library --- ast/ast.go | 23 ++------ docs/_includes/toc.md | 6 ++ docs/stdlib/cli.md | 107 ++++++++++++++++++++++++++++++++++++ docs/stdlib/intro.md | 104 +++++++++++++++++++++++++++++++++++ docs/stdlib/runtime.md | 32 +++++++++++ docs/types/decorator.md | 70 +++++++++++++++++------ evaluator/evaluator.go | 60 ++++++++++---------- evaluator/evaluator_test.go | 41 +++++++++++--- evaluator/functions.go | 38 ++++++++++--- evaluator/pkged.go | 10 ++++ evaluator/stdlib_test.go | 94 +++++++++++++++++++++++++++++++ examples/args.abs | 1 + examples/cli-app.abs | 15 +++++ examples/decorators.abs | 12 ++-- go.mod | 1 + go.sum | 39 +++++++------ js/js.go | 5 +- object/environment.go | 13 ++++- parser/parser.go | 28 +++------- parser/parser_test.go | 37 ++----------- repl/repl.go | 7 ++- scripts/release.abs | 9 +++ stdlib/cli/index.abs | 69 +++++++++++++++++++++++ stdlib/runtime/index.abs | 4 ++ util/util_test.go | 2 +- 25 files changed, 665 insertions(+), 162 deletions(-) create mode 100644 docs/stdlib/cli.md create mode 100644 docs/stdlib/intro.md create mode 100644 docs/stdlib/runtime.md create mode 100644 evaluator/pkged.go create mode 100644 evaluator/stdlib_test.go create mode 100644 examples/cli-app.abs create mode 100644 stdlib/cli/index.abs create mode 100644 stdlib/runtime/index.abs diff --git a/ast/ast.go b/ast/ast.go index f6d541ce..5e2d684d 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -435,30 +435,15 @@ func (fl *FunctionLiteral) String() string { } type Decorator struct { - Token token.Token // @ - Name string - Arguments []Expression - Decorated Expression + Token token.Token // @ + Expression Expression + Decorated Expression } func (dc *Decorator) expressionNode() {} func (dc *Decorator) TokenLiteral() string { return dc.Token.Literal } func (dc *Decorator) String() string { - var out bytes.Buffer - - args := []string{} - for _, a := range dc.Arguments { - args = append(args, a.String()) - } - - out.WriteString(dc.TokenLiteral()) - out.WriteString(dc.Name) - out.WriteString("(") - out.WriteString(strings.Join(args, ", ")) - out.WriteString(") ") - out.WriteString(dc.Decorated.String()) - - return out.String() + return dc.Expression.String() } type CurrentArgsLiteral struct { diff --git a/docs/_includes/toc.md b/docs/_includes/toc.md index fcbc8a3b..1dcb9493 100644 --- a/docs/_includes/toc.md +++ b/docs/_includes/toc.md @@ -27,6 +27,12 @@ * [Builtin functions](/types/builtin-function) * [Decorators](/types/decorator) +## Standard library + +* [Introduction](/stdlib/intro) +* [@runtime](/stdlib/runtime) +* [@cli](/stdlib/cli) + ## Miscellaneous * [Installing 3rd party libraries](/misc/3pl) diff --git a/docs/stdlib/cli.md b/docs/stdlib/cli.md new file mode 100644 index 00000000..81637485 --- /dev/null +++ b/docs/stdlib/cli.md @@ -0,0 +1,107 @@ +# @cli + +The `@cli` module provides a simple interface to +easily build CLI applications. + +## API + +```py +cli = require('@cli') +``` + +### @cli.cmd(name, description, default_flags) + +A decorator that registers a command to be executed via CLI: + +```py +@cli.cmd("date", "prints the current date", {format: ''}) +f date(args, flags) { + format = flags.format + return `date ${format}` +} +``` + +The `name` of the command matches the command entered +by the user on the CLI (eg. `$ ./cli my_command`), while +the description is used when printing a help message, +available through `$ ./cli help`. + +The user can pass however many arguments and flags they want, +and they will be passed on to the function defined as command. +For example, when the user issues the command `$ ./cli a b c --flag 25`, +args will be `["a", "b", "c", "--flag", "25"]` and flags will +be `{"flag": "25"}`. Default flags are there so that, if a flag +is not passed, it will be populated with a default value. + +### @cli.run() + +Runs the CLI application: + +```py +cli.run() +``` + +The application will, by default, have an `help` command +that lists all available commands and is called if no command +is provided. + +## Example CLI app + +Here is an example app with 3 commands: + +* the default `help` +* `date`, to print the current date in a specific format +* `ip`, to fetch our IP address + +```py +#!/usr/bin/env abs +cli = require('@cli') + +@cli.cmd("ip", "finds our IP address", {}) +f ip_address(arguments, flags) { + return `curl icanhazip.com` +} + +@cli.cmd("date", "Is it Friday already?", {"format": ""}) +f date(arguments, flags) { + format = flags.format + return `date ${format}` +} + +cli.run() +``` + +You can save this script as `./cli` and make it executable +with `chmod +x ./cli`. Then you will be able to use the CLI +app: + +``` +$ ./cli +Available commands: + + * date - Is it Friday already? + * help - print this help message + * ip - finds our IP address + +$ ./cli help +Available commands: + + * date - Is it Friday already? + * help - print this help message + * ip - finds our IP address + +$ ./cli ip +87.201.252.69 + +$ ./cli date +Sat Apr 4 18:06:35 +04 2020 + +$ ./cli date --format +%s +1586009212 +``` + +## Next + +That's about it for this section! + +You can now head over to read a little bit about [how to install 3rd party libraries](/misc/3pl). \ No newline at end of file diff --git a/docs/stdlib/intro.md b/docs/stdlib/intro.md new file mode 100644 index 00000000..dba6de19 --- /dev/null +++ b/docs/stdlib/intro.md @@ -0,0 +1,104 @@ +# Standard library + +ABS comes bundled not only with an array of builtin types and functions, +but also with a few modules to ease your development process: we refer +to this modules as the "standard library", "standard modules" or `stdlib`. + +## Requiring a standard library module + +Standard library modules are required the same way you'd require +any other external module, by using the `require(...)` function; +the only difference is that standard modules use the `@` character +as prefix, for example: + +```py +mod = require('@module') # Loads "module" from the standard library +mod = require('./module') # Loads "module" from the current directory +mod = require('module') # Loads "module" that was installed through the ABS package manager +``` + +## Technical details + +The ABS standard library is developed in ABS itself and available +for everyone to see (and poke with) at [github.com/abs-lang/abs/tree/master/stdlib](https://github.com/abs-lang/abs/tree/master/stdlib). + +The `@cli` library, for example, is a simple ABS script of less +than 100 lines of code: + +```bash +# CLI app +cli = {} + +# Commands registered within this CLI app +cli.commands = {} + +# Function used to register a command +cli.cmd = f(name, description, flags) { + return f(fn) { + cli.commands[name] = {}; + cli.commands[name].cmd = f() { + # Create flags with default values + for k, _ in flags { + v = flag(k) + + if v { + flags[k] = v + } + } + + # Call the original cmd + result = fn.call([args()[3:], flags]) + + # If there's a result we print it out + if result { + echo(result) + } + } + + cli.commands[name].description = description + } +} + +# Run the CLI app +cli.run = f() { + # ABS sees "abs script.abs xyz" + # so the command is the 3rd argument + cmd = arg(2) + + # Not passing a command? Let's print the help + if !cmd { + return cli.commands['help'].cmd() + } + + if !cli.commands[cmd] { + exit(99, "command '${cmd}' not found") + } + + return cli.commands[cmd].cmd() +} + +# Add a default help command that can be +# overridden by the caller +@cli.cmd("help", "print this help message", {}) +f help() { + echo("Available commands:\n") + + for cmd in cli.commands.keys().sort() { + s = " * ${cmd}" + + if cli.commands[cmd].description { + s += " - " + cli.commands[cmd].description + } + + echo(s) + } +} + +return cli +``` + +## Next + +That's about it for this section! + +You can now explore ABS' first standard library module, the [@runtime](/stdlib/runtime). \ No newline at end of file diff --git a/docs/stdlib/runtime.md b/docs/stdlib/runtime.md new file mode 100644 index 00000000..e048fae1 --- /dev/null +++ b/docs/stdlib/runtime.md @@ -0,0 +1,32 @@ +# @runtime + +The `@runtime` module provides information about the ABS +runtime that is currently executing the script. + +## API + +```py +runtime = require('@runtime') +``` + +### @runtime.version + +Returns the version of the ABS interpreter: + +```py +runtime.version # "x.y.z" eg. 1.0.0 +``` + +### @runtime.name + +Returns the name of the runtime: + +```py +runtime.name # "abs" +``` + +## Next + +That's about it for this section! + +You can now explore how to easily build command-line applications through the [@cli](/stdlib/cli) module. \ No newline at end of file diff --git a/docs/types/decorator.md b/docs/types/decorator.md index 08aa2f15..c5b9dcbe 100644 --- a/docs/types/decorator.md +++ b/docs/types/decorator.md @@ -1,7 +1,8 @@ # Decorator Decorators are a feature built on top of -ABS' functions -- they're not a type *per se*. +ABS' functions -- they're not a type *per se* +but they do have their own *syntactic sugar*. A decorator is a function that "wraps" another function, allowing you to enhance the original @@ -12,25 +13,59 @@ An example could be a decorator that logs how long a function takes to execute, or delays execution altogether. -## Declaring decorators +## Simple decorators A decorator is a plain-old function that -accepts `1 + N` arguments, where `1` is the -function being wrapped, and returns a new -function that wraps the original one: +accepts the original function and returns a new +function that wraps the original one with its +own behaviour. After defining it, you can +"decorate" other functions through the convenient +`@` syntax: ```py -f log_if_slow(original_fn, treshold_ms) { +f uppercase(fn) { return f() { - start = `date +%s%3N`.int() - res = original_fn(...) - end = `date +%s%3N`.int() + return fn(...).upper() + } +} - if end - start > treshold_ms { - echo("mmm, we were pretty slow...") - } +@uppercase +f stringer(x) { + return x.str() +} + +stringer({}) # "{}" +stringer(12) # "12" +stringer("hello") # "HELLO" +``` - return res +As you see, `stringer`'s behaviour has been altered: +it will now output uppercase strings. + +## Decorators with arguments + +As we've just seen, a decorator simply needs to +be a function that accepts the original +function and returns a new one, "enhancing" +the original behavior. If you wish to +configure decorators with arguments, it +is as simple as adding another level +of "wrapping": + +```py +f log_if_slow(treshold_ms) { + return f(original_fn) { + return f() { + start = `date +%s%3N`.int() + res = original_fn(...) + end = `date +%s%3N`.int() + + if end - start > treshold_ms { + echo("mmm, we were pretty slow...") + } + + return res + } } } ``` @@ -41,8 +76,6 @@ decorated one (`original_fn`) and returns its result, while logging if it takes longer than a few milliseconds. -## Using decorators - Now that we've declared our decorator, it's time to use it, through the `@` notation: @@ -54,7 +87,7 @@ f return_random_number_after_sleeping(seconds) { } ``` -and we can test our decorator has takn the stage: +and we can test our decorator has taken the stage: ```console ⧐ return_random_number_after_sleeping(0) @@ -64,10 +97,11 @@ mmm, we were pretty slow... 371 ``` -Decorators are heavily inspired by [Python](https://www.python.org/dev/peps/pep-0318/). +Decorators are heavily inspired by [Python](https://www.python.org/dev/peps/pep-0318/) -- if you wish to understand +how they work more in depth we'd recommend reading this [primer on Python decorators](https://realpython.com/primer-on-python-decorators). ## Next That's about it for this section! -You can now head over to read a little bit about [how to install 3rd party libraries](/misc/3pl). \ No newline at end of file +You can now head over to read a little bit about [ABS' standard library](/stdlib/intro). \ No newline at end of file diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 1b380022..3b08e5bd 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -325,46 +325,44 @@ func evalDecorator(node *ast.Decorator, env *object.Environment) object.Object { // has many more implications and it's 2.55 AM so // let's call it for today... func doEvalDecorator(node *ast.Decorator, env *object.Environment) (string, object.Object, object.Object) { - decorator, ok := env.Get(node.Name) + var decorator object.Object - if !ok { - return "", nil, newError(node.Token, "function '%s' is not defined (used as decorator)", node.Name) + evaluated := Eval(node.Expression, env) + switch evaluated.(type) { + case *object.Function: + decorator = evaluated + case *object.Error: + return "", nil, evaluated + default: + return "", nil, newError(node.Token, "decorator '%s' is not a function", evaluated.Inspect()) } - switch d := decorator.(type) { - case *object.Function: - name, ok := getDecoratedName(node.Decorated) + name, ok := getDecoratedName(node.Decorated) - if !ok { - return "", nil, newError(node.Token, "error while processing decorator: unable to find the name of the function you're trying to decorate") - } + if !ok { + return "", nil, newError(node.Token, "error while processing decorator: unable to find the name of the function you're trying to decorate") + } - switch decorated := node.Decorated.(type) { - case *ast.FunctionLiteral: - // Here we have a single decorator - return name, Eval(&ast.CallExpression{ - Function: d.Node, - Arguments: append([]ast.Expression{decorated}, node.Arguments...), - }, env), nil - case *ast.Decorator: - // Here we have a decorator of another decorator - decoratorObj, _ := env.Get(node.Name) - decorator := decoratorObj.(*object.Function) - - // First eval the later decorator(s). - fnName, fn, err := doEvalDecorator(node.Decorated.(*ast.Decorator), env) + switch decorated := node.Decorated.(type) { + case *ast.FunctionLiteral: + // Here we have a single decorator + fn := &object.Function{Token: decorated.Token, Parameters: decorated.Parameters, Env: env, Body: decorated.Body, Name: name, Node: decorated} + return name, applyFunction(decorated.Token, decorator, env, []object.Object{fn}), nil + case *ast.Decorator: + // Here we have a decorator of another decorator + // decoratorObj, _ := env.Get(node.Name) + // decorator := decoratorObj.(*object.Function) - if isError(err) { - return "", nil, err - } + // First eval the later decorator(s). + fnName, fn, err := doEvalDecorator(decorated, env) - args := evalExpressions(node.Arguments, env) - return fnName, applyFunction(node.Token, decorator, env, append([]object.Object{fn}, args...)), nil - default: - return "", nil, newError(node.Token, "a decorator must decorate a named function or another decorator") + if isError(err) { + return "", nil, err } + + return fnName, applyFunction(node.Token, decorator, env, append([]object.Object{fn})), nil default: - return "", nil, newError(node.Token, "decorator '%s' must be a function, %s given", node.Name, decorator.Type()) + return "", nil, newError(node.Token, "a decorator must decorate a named function or another decorator") } } diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index c8883c52..4006e9b5 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -109,6 +109,26 @@ func TestEvalNumberExpression(t *testing.T) { {"a = 5; a *= 2; a", 10}, {"a = 5; a **= 2; a", 25}, {"a = 5; a %= 3; a", 2}, + {"a = 0; a += 1 + 1; a", 2}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testNumberObject(t, evaluated, tt.expected) + } +} + +func TestCompoundExpression(t *testing.T) { + tests := []struct { + input string + expected float64 + }{ + {"a = 0; a += 1 + 1; a", 2}, + {"a = 0; a -= 1 + 1; a", -2}, + {"a = 1; a *= 2 + 1; a", 3}, + {"a = 2; a /= 1 + 1; a", 1}, + {"a = 2; a **= 1 + 1; a", 4}, + {"a = 19; a %= 4 + 1; a", 4}, } for _, tt := range tests { @@ -737,16 +757,19 @@ func TestDecorators(t *testing.T) { input string expected interface{} }{ - {"@decorator f hello() {}", "function 'decorator' is not defined (used as decorator)"}, - {"a = 1; @a f hello(){}", "decorator 'a' must be a function, NUMBER given"}, + {"@decorator f hello() {}", "identifier not found"}, + {"a = 1; @a f hello(){}", "decorator '1' is not a function"}, {"f decorator(fn) { return f () { return fn() * 2 } }; @decorator f test() { return 1 }; test()", 2}, + {"f decorator(x) { return f (fn) { return f() {fn() * x} } }; @decorator(10) f test() { return 1 }; test()", 10}, {"f decorator(fn) { return f () { return fn(...) * 2 } }; @decorator f test(x) { return x }; test(1)", 2}, - {"f decorator(fn, multiplier) { return f () { return fn() * multiplier } }; @decorator(4) f test() { return 1 }; test()", 4}, - {"f decorator(fn, multiplier) { return f () { return fn(...) * multiplier } }; @decorator(1000) f test(x) { return x }; test(1)", 1000}, - {"f decorator(fn, multiplier) { return f () { return fn(...) * multiplier } }; @decorator(2) @decorator(2) f test(x) { return x }; test(1)", 4}, - {"f decorator(fn, multiplier) { return f () { return fn(...) * multiplier } }; @decorator(2) @decorator(2) @decorator(2) f test(x) { return x }; test(1)", 8}, - {"f multiply(fn, multiplier) { return f () { return fn(...) * multiplier } }; f divide(fn, div) { return f () { return fn(...) / div } }; @multiply(10) @divide(5) f test(x) { return x }; test(1)", 2}, - {"f decorator(fn) { return f () { return fn(...) } }; @decorator() @decorator_not_existing() f test(x) { return x }; test(1)", "function 'decorator_not_existing' is not defined (used as decorator)"}, + {"f decorator(multiplier) { return f (fn) { return f() {return fn() * multiplier} } }; @decorator(4) f test() { return 1 }; test()", 4}, + {"f decorator(multiplier) { return f (fn) { return f() { fn(...) * multiplier } } }; @decorator(1000) f test(x) { return x }; test(10)", 10000}, + {"f decorator(fn) { return f () { return fn() * 2 } }; @decorator @decorator f test() { return 1 }; test()", 4}, + {"f decorator1(x) { return f (fn) { return f() { fn(...) * x } } }; f decorator2(x) { return f (fn) { return f() { fn(...) * x } } }; @decorator1(2) @decorator2(10) f test(x) { return x }; test(10)", 200}, + {"f decorator(multiplier) { return f (fn) { return f() { fn(...) * multiplier } } }; @decorator(2) @decorator(2) f test(x) { return x }; test(1)", 4}, + {"f decorator(multiplier) { return f (fn) { return f() { fn(...) * multiplier } } }; @decorator(2) @decorator(2) @decorator(2) f test(x) { return x }; test(1)", 8}, + {"f multiply(multiplier) { return f (fn) { return f() { fn(...) * multiplier } } }; f divide(div) { return f (fn) { return f() {fn(...) / div} } }; @multiply(10) @divide(5) f test(x) { return x }; test(1)", 2}, + {"f decorator(fn) { return f () { return fn(...) } }; @decorator @decorator_not_existing() f test(x) { return x }; test(1)", "identifier not found: decorator_not_existing"}, } for _, tt := range tests { @@ -1529,7 +1552,7 @@ func TestStringIndexExpressions(t *testing.T) { } func testEval(input string) object.Object { - env := object.NewEnvironment(os.Stdout, "") + env := object.NewEnvironment(os.Stdout, "", "test_version") lex := lexer.New(input) p := parser.New(lex) program := p.ParseProgram() diff --git a/evaluator/functions.go b/evaluator/functions.go index f2ca697b..b6e5924f 100644 --- a/evaluator/functions.go +++ b/evaluator/functions.go @@ -26,6 +26,7 @@ import ( "github.com/abs-lang/abs/token" "github.com/abs-lang/abs/util" "github.com/iancoleman/strcase" + "github.com/markbates/pkger" ) var scanner *bufio.Scanner @@ -646,7 +647,7 @@ func flagFn(tok token.Token, env *object.Environment, args ...object.Object) obj // it means no value was assigned to it, // so let's default to true if found { - return &object.Boolean{Token: tok, Value: true} + return object.TRUE } // else a flag that's not found is NULL @@ -1082,7 +1083,7 @@ func jsonFn(tok token.Token, env *object.Environment, args ...object.Object) obj s := args[0].(*object.String) str := strings.TrimSpace(s.Value) - env = object.NewEnvironment(env.Writer, env.Dir) + env = object.NewEnvironment(env.Writer, env.Dir, env.Version) l := lexer.New(str) p := parser.New(l) var node ast.Node @@ -2207,9 +2208,13 @@ func requireFn(tok token.Token, env *object.Environment, args ...object.Object) packageAliasesLoaded = true } - a := util.UnaliasPath(args[0].Inspect(), packageAliases) - file := filepath.Join(env.Dir, a) - e := object.NewEnvironment(env.Writer, filepath.Dir(file)) + file := util.UnaliasPath(args[0].Inspect(), packageAliases) + + if !strings.HasPrefix(file, "@") { + file = filepath.Join(env.Dir, file) + } + + e := object.NewEnvironment(env.Writer, filepath.Dir(file), env.Version) return doSource(tok, e, file, args...) } @@ -2237,8 +2242,27 @@ func doSource(tok token.Token, env *object.Environment, fileName string, args .. // mark this source level sourceLevel++ - // load the source file - code, error := ioutil.ReadFile(fileName) + var code []byte + var error error + + // Manage std library requires starting with + // a '@' eg. require('@runtime') + if strings.HasPrefix(fileName, "@") { + f, err := pkger.Open("/stdlib/" + fileName[1:]) + + if err == nil { + code, error = ioutil.ReadAll(f) + if error == nil { + defer f.Close() + } + } else { + error = err + } + } else { + // load the source file + code, error = ioutil.ReadFile(fileName) + } + if error != nil { // reset the source level sourceLevel = 0 diff --git a/evaluator/pkged.go b/evaluator/pkged.go new file mode 100644 index 00000000..8ccdbf15 --- /dev/null +++ b/evaluator/pkged.go @@ -0,0 +1,10 @@ +// Code generated by pkger; DO NOT EDIT. + +package evaluator + +import ( + "github.com/markbates/pkger" + "github.com/markbates/pkger/pkging/mem" +) + +var _ = pkger.Apply(mem.UnmarshalEmbed([]byte(`1f8b08000000000000ffec5a5f73e238b6ff2a29bd5e3afe032681aa7d88e98e3134ec267463f0d654972c0b5b41b6bc964c3053fddd6fc936604820dc9dae9a9ab93c8075a49f8ea4a3737ed601fd0e48bc601c747f07011161e6dd221629d0e39f288c0359904d9f490aba400959841548f15a39856d003b4a582afe054508ba275536c0184618744104490c1ae03343a00b40037c836980c5d1584cf1485cf57b66ecb8f9e41823285008baff06b7e0b70698084831e88a34c395f08c216731e88298891b12730129c5fe8d97891bb88284428fe21b12df7819a1fe0d8228c4a0012cf64828e652af9cfd6dc0a4f672d945ed2212a0716ae94a35ce19448a13d9ccb8d4fb1927855298a290acb0b2210968002f5b10269fb9c072a58845498a395716140a5caf08ca0e88c5029218a70a255c5415785d94d23c116c575060a9b1141049429cee65bfdee873b817303a147ddd30b4ce9b0a85c402a731a40af65f61eaf36318a5241104ed6bc208d6a45df714c67e26087da789679ea078df10f9c65e90fd6a126ad584fa027808b5034937da07b2a1e935f96848416b765a1b6ae750529225598306c031623e89835a51813cd6eab207396eb70e6a480cd3bc5e83f8aa2e86b8ae5c79915e5e93131c49314d592a6779de5f213fd78a57906650b0f40f793cc56b7c4e03f35e303a378d04a6fcac862aa64e350bb6c4f199f6cad16acde89307055402f62949599488f3ad7bdff4087fb394d3681f7b597029988b94c401bf142e701a1d6203e6658b05a44c09718a0fdb088c11a33882b11c07417ed41ec174e94181b9922c83e3ad386adccf218209bf102ae012b35879171ae204fe2235ca825081d30f66952c83326a3fc42838f2b07f1132c2d145382e7cf6667e42c47293d32cc6afc417e1617bb20c8afd2ebe48d99b49efbe6569a0ac952d8bf1b044c4901e4378ce952c2e582b843cac1e0a4a5153f2e0dec17381210dea5528c9eae222129ca5a25e156321528870bd8ef182a1ea55092b68a4f60e38ec92e205c54850220eaa3989038a179404e1c1a83ce70852ba5d562d32b8a0ac58022bbe14c22a0628ab235818583e148f04fba2e0db72f59a894884ab871265549004162b2f2afe933181fd2425b190470dd0003116e5b7120a91d48ac5d7d644bbcaed8cab3af9424f5256bccca59ca5d539427e29788d5159e224283b31ae64256f26e58ae4434600aee4caa04529c0eb645750781e0b28ad9666b1281759951414b09ab4b32a142c2adeeb6f5a2a73bea9e7b99c7ae52b927558bc2a4b15cff13c46d563afbeda55d000d5bcb29820e6d74a4a26165afb50be2f440e1712b7c2b1cf52e5bd1041214421d4d5cb5009a3b9d6548d0fd0c54306cea5b8ed21e11c384b57787bb83a830b97fee23ce2edb9ea0cf883154bb7f4632e3f11e61c06a7d41d387e901581f5212e49d93aff00a82b6102d1f20c8af8313cd12c49b064b3f75a65fc291ca32cc58a477c926627ad5540450a63be60c56bf82468eba352e125b858eafbad01be612e76a94a9c515a56edf294b26ac47c39c9eeefe0c38c6d2453b52a7dfa3fe483161b31ff22b012b0db88f9459f294e39297233ed56d3c1cf9f3f1b6051aee564a2da956f464a3c099149ad7cfa5840428b5e719971569806e0648341b7a576da0d10497ee8ea5aebae75dfd28cbba2e647c11f5da0abbafa496d7d529bdf74b5abdd7735f5b6ddba6bdea9cd66eb7fd45657956c40f80f5f5aa53490a42e9937e315e8b6db4dd568003b66a0db6c358d66cb6835c098927809baad620f30e86aedfb4eb301be131f743555551bc0da17673f7e24d05741576d80675f2a551b60525b804997fc406468c941f7be011e0489e454261881ae66dc1b9da6daeea80d30e6b2a6dd6e36ef54fdbef5b301461f40b76bfed900bdcba1b31f3fb238e3d807dd7fab0db5a1fe56eca64c2bfffabf2c9c829cffbd41e643f8c6c7098e7d1ca3bc7bb37f8b7ef0f34219b6db5f05b6ddfe7ef15e1d49e44497c125fbbd0dfe9f0de04301b70b4e608a63b1d7b3ef500cf20195288892f37422017f1a97b4ee55a373bfe312fdca25572eb972c92fe39222fa7f399f2824f6f1fab6fa65fd34b3ec615b7e9194b2e59756b3fd3eb1b4be69775d4dedb63ab7c65debceb83354ed0db12c20e51f334b67c72cda96599a4d5d6dfd17cc524efd726669abaad1d13b5b0ed0d43be35ebb6b19ef308b84deb776d0dda2df619633d05fc32c75c7392299bda3948d6fc964cf1b7b56289dae0aff6ad70ee3bf1ee625faddf8de47f135b08fc37017e2c0cecdcfdfbe2c83f9cc7ced45630e270f1dbb2f3abd80bdd85fc62bcfd1422f7aded8fd017575baf1ad69ee3a4f81af53155a34b3fbcf0cce46c193ba1ed8d6e32b7a485e3c6b99cdf58ef09cc7cced8f827f4dccbbc5131bdab969f98ef1e25b74e5115343fa7469f79f5775ddb6f525d8f5dde9d296f66735704998cd1d8d7eed994b77367e41117d2d75bd066eb40edde628b1fb7c68f71e02a9d3ef4f738f98d1d072b3e1c4bceb9187a068eb3d04737d9d7c8df6eb737423f49ca92fc7c14df522ecd7682cdcde43c7b6e6ec587ff919054fcd019dcf9ea96db97ceef81bbbef27be1504ae358de6b329f77ba63e77d69a3b1b0d0ffb3e046ed4c96d8b73fbf13e804e6bbbc600bfb2e0182b3f3e917391e32006f3e5f0ed7ce4c74c5c62ea3b3bbdfb3123cf7a8c9126d259e10ff3f7b17df54dfde2e9cddcdef6cdcdcf7367cda5efb8137385221a43c708bd9ef9e2394f6ff0289a6e7c67ad163e10b5b2b9fec8bd5ee8cd678318e541e26c46edd964eb03637f287deded980397982a94fe3bf137d2cf767afb3eb5fb660e1d43b52daada5647f31f8ed751d82d776763cdeb3f9ddc03d719332f0fb7b8e4cd1e1cd8cc9471b66fb364fc192f9eae89b9632c91260a7f9f4d8c237f97fbfc4c913ecee1cc54a1d3c9ca71cc4e2f56873d320abec7d3acb2ef517c1bb9efb4644c46c3de72bf8edc349fe82840fa94a2fc81cc9dc1c6ee5763f68c701e8f02dca76d3bd8e1375e2eedf97d1faf3d334155dda83958dad663ee36a7c2750cb59a9fdc5f39768822c44664e7a32ff61763e5f7ccd7f96cbc818e111ff0c0e7fbe09b355507cd5180fa83c48b9f02df0aa96d85d4eb57fe65d1c8ee3d16fa0ff666cf0347bc34f6067ad13f9e499b3b4f6c58f9ee62eb3f5b9d47fd241fcd8ee2dd9d8589df0b8c7f4e5e037bc7154fc1207fbe93f8c504055ed4516dcb5df98eb1b4c972b8f587ca36b93b7bd650d47a876fc6c27d540b1e1cf696c38a4b4dd77a0ae61373e93a6e58f871698ffa7ea8d07a546d6b1c7ac424ee137bb1ad8eeece0639749e97aed30ae6f1b2b2e538f4ac354541f2508d2fdc5e40a035e5a86773bb37784511cdfc4227dd6cc7f29ce906e98fb13bb1b97c6f0c9f92a86aab71a249e77ab81af606a61f3d269ef548bcbadff447ed99d522bbb8b5dc1522a5af48de3bb6c7d748503c1bb1e1c4d878cd817ae0c7854de53be781d8bd87ffd8bdadfded033e84cefca49d5d6bba99370709ea3f279ede7a27d6cd8ddde31dbb67075f8b71f8a5bacec4fef4055af70ce57bbf90fb8ca2a9eacf0659c10d4ffff807f8c307f26dd673f628becfa8febc44bf2d4fd8d744ff9ae85f13fd5f9f0f6c4df26b93fdfd1f9aff65c26fb43f4cf72fa2974bd2fdb6aafdba74dff8ffc02ec7ce7332e1df03ae49ff9f1fe4ef26febb83c52e111c14c986fdc2023b7a2428b7797560227e34cd912e130fbb6d7f79ec7dd73acef374f06df2bdf3cf5eac7e7828293d0dff0d5e64d77bb5d77bb5d77bb5d77bb5d77bb5d77bb5d77bb5d77bb5d77bb5d77bb5d77bb5d77bb5d77bb57f996cfae7ff020000ffff010000ffff09c79338013a0000`))) diff --git a/evaluator/stdlib_test.go b/evaluator/stdlib_test.go new file mode 100644 index 00000000..a09b84b3 --- /dev/null +++ b/evaluator/stdlib_test.go @@ -0,0 +1,94 @@ +package evaluator + +import ( + "testing" + + "github.com/abs-lang/abs/object" +) + +type tests struct { + input string + expected interface{} +} + +func TestRuntime(t *testing.T) { + tests := []tests{ + {`require('@runtime').version`, "test_version"}, + {`require('@runtime').name`, "abs"}, + } + + testStdLib(tests, t) +} + +func testStdLib(tests []tests, t *testing.T) { + for _, tt := range tests { + evaluated := testEval(tt.input) + switch expected := tt.expected.(type) { + case int: + testNumberObject(t, evaluated, float64(expected)) + case float64: + testNumberObject(t, evaluated, float64(expected)) + case nil: + testNullObject(t, evaluated) + case bool: + testBooleanObject(t, evaluated, expected) + case string: + s, ok := evaluated.(*object.String) + if ok { + if s.Value != tt.expected.(string) { + t.Errorf("result is not the right string for '%s'. got='%s', want='%s'", tt.input, s.Value, tt.expected) + } + continue + } + + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", evaluated, evaluated) + continue + } + logErrorWithPosition(t, errObj.Message, tt.expected) + case []int: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated) + continue + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", + len(expected), len(array.Elements)) + continue + } + + for i, expectedElem := range expected { + testNumberObject(t, array.Elements[i], float64(expectedElem)) + } + case []string: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated) + continue + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", len(expected), len(array.Elements)) + continue + } + + for i, expectedElem := range expected { + testStringObject(t, array.Elements[i], expectedElem) + } + case []interface{}: + array, ok := evaluated.(*object.Array) + if !ok { + t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated) + continue + } + + if len(array.Elements) != len(expected) { + t.Errorf("wrong num of elements. want=%d, got=%d", len(expected), len(array.Elements)) + continue + } + } + } +} diff --git a/examples/args.abs b/examples/args.abs index 38eef188..13b7e1d2 100644 --- a/examples/args.abs +++ b/examples/args.abs @@ -1,3 +1,4 @@ +#!/usr/bin/env abs echo(arg(0)) echo(arg(1)) echo(arg(2)) diff --git a/examples/cli-app.abs b/examples/cli-app.abs new file mode 100644 index 00000000..c96ca707 --- /dev/null +++ b/examples/cli-app.abs @@ -0,0 +1,15 @@ +#!/usr/bin/env abs +cli = require('@cli') + +@cli.cmd("ip", "finds our IP address", {}) +f ip_address(arguments, flags) { + return `curl icanhazip.com` +} + +@cli.cmd("date", "Is it Friday already?", {"format": ""}) +f date(arguments, flags) { + format = flags.format + return `date ${format}` +} + +cli.run() \ No newline at end of file diff --git a/examples/decorators.abs b/examples/decorators.abs index 3423cfcb..82a1b0cf 100644 --- a/examples/decorators.abs +++ b/examples/decorators.abs @@ -16,11 +16,13 @@ f timer(fn) { } # A decorator that sleeps before a function -f sleeper(fn, duration) { - return f() { - `sleep $duration` - echo("Yawn, I slept for $duration second") - fn(...) +f sleeper(duration) { + return f(fn) { + return f() { + `sleep $duration` + echo("Yawn, I slept for $duration second") + fn(...) + } } } diff --git a/go.mod b/go.mod index 06f5abe0..fee68f7b 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.12 require ( github.com/c-bata/go-prompt v0.2.4-0.20190826134812-0f95e1d1de2e github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 + github.com/markbates/pkger v0.15.0 github.com/mattn/go-colorable v0.1.6 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mattn/go-tty v0.0.3 // indirect diff --git a/go.sum b/go.sum index dcc8f464..7579a79f 100644 --- a/go.sum +++ b/go.sum @@ -1,42 +1,45 @@ github.com/c-bata/go-prompt v0.2.4-0.20190826134812-0f95e1d1de2e h1:wISxI1PW3d8yWV0aY+Vxwzt56LD+SlMiLWXwNgtdGTU= github.com/c-bata/go-prompt v0.2.4-0.20190826134812-0f95e1d1de2e/go.mod h1:Fd2OKZ3h6UdKxcSflqFDkUpTbTKwrtLbvtCp3eVuTEs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8= github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/markbates/pkger v0.15.0 h1:rSXoKLBWBgYG7j/h6Be7kggju23ie1Gx3/va9xl5aUw= +github.com/markbates/pkger v0.15.0/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= -github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859 h1:smQbSzmT3EHl4EUwtFwFGmGIpiYgIiiPeVv1uguIQEE= -github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= -github.com/odino/go-prompt v0.2.4-0.20190816001457-9305aa75823e h1:mTySyY/vV0hlz9FlqLIrBthNof8fjMqVZPDhn6v2jJs= -github.com/odino/go-prompt v0.2.4-0.20190816001457-9305aa75823e/go.mod h1:r3+2ndvD23nUkN89DAba+DuXLHnaDwNrpvmSl/eYGUU= -github.com/odino/go-prompt v0.2.4-0.20190816001457-ea717205ca73412c085f2b2296f11c674f359f5c h1:VifSDBOIAnbm1vrgm0/hkiXNEd9UgBjW++10L7PDoVM= -github.com/odino/go-prompt v0.2.4-0.20190816001457-ea717205ca73412c085f2b2296f11c674f359f5c/go.mod h1:r3+2ndvD23nUkN89DAba+DuXLHnaDwNrpvmSl/eYGUU= github.com/pkg/term v0.0.0-20180423043932-cda20d4ac917/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942 h1:A7GG7zcGjl3jqAqGPmcNjd/D9hzL95SuoOQAaFNdLU0= github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -45,8 +48,6 @@ golang.org/x/sys v0.0.0-20180620133508-ad87a3a340fa/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -54,3 +55,9 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/js/js.go b/js/js.go index ec601478..af2ade88 100644 --- a/js/js.go +++ b/js/js.go @@ -11,6 +11,9 @@ import ( "github.com/abs-lang/abs/parser" ) +// Version of the ABS interpreter +var Version = "dev" + // This function takes ABS code // and evaluates it, using a buffer // to store it's output. @@ -23,7 +26,7 @@ func runCode(this js.Value, i []js.Value) interface{} { var buf bytes.Buffer // the first argument to our function code := i[0].String() - env := object.NewEnvironment(&buf, "") + env := object.NewEnvironment(&buf, "", Version) lex := lexer.New(code) p := parser.New(lex) diff --git a/object/environment.go b/object/environment.go index f2db203f..826a4ced 100644 --- a/object/environment.go +++ b/object/environment.go @@ -10,7 +10,7 @@ import ( // new environment has access to identifiers stored // in the outer one. func NewEnclosedEnvironment(outer *Environment, args []Object) *Environment { - env := NewEnvironment(outer.Writer, outer.Dir) + env := NewEnvironment(outer.Writer, outer.Dir, outer.Version) env.outer = outer env.CurrentArgs = args return env @@ -20,9 +20,14 @@ func NewEnclosedEnvironment(outer *Environment, args []Object) *Environment { // ABS in, specifying a writer for the output of the // program and the base dir (which is used to require // other scripts) -func NewEnvironment(w io.Writer, dir string) *Environment { +func NewEnvironment(w io.Writer, dir string, version string) *Environment { s := make(map[string]Object) - return &Environment{store: s, outer: nil, Writer: w, Dir: dir} + // e.Version and ABS_VERSION are duplicate, we should + // find a better way to manage it + e := &Environment{store: s, outer: nil, Writer: w, Dir: dir, Version: version} + e.Set("ABS_VERSION", &String{Value: e.Version}) + + return e } // Environment represent the environment associated @@ -54,6 +59,8 @@ type Environment struct { // wihout having to specify its full absolute path // eg. require("/tmp/B") Dir string + // Version of the ABS runtime + Version string } // Get returns an identifier stored within the environment diff --git a/parser/parser.go b/parser/parser.go index 06afc2dc..c3429341 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -50,12 +50,12 @@ var precedences = map[token.TokenType]int{ token.ASTERISK: PRODUCT, token.EXPONENT: PRODUCT, token.MODULO: PRODUCT, - token.COMP_PLUS: SUM, - token.COMP_MINUS: SUM, - token.COMP_SLASH: PRODUCT, - token.COMP_ASTERISK: PRODUCT, - token.COMP_EXPONENT: PRODUCT, - token.COMP_MODULO: PRODUCT, + token.COMP_PLUS: EQUALS, + token.COMP_MINUS: EQUALS, + token.COMP_SLASH: EQUALS, + token.COMP_ASTERISK: EQUALS, + token.COMP_EXPONENT: EQUALS, + token.COMP_MODULO: EQUALS, token.RANGE: RANGE, token.LPAREN: CALL, token.LBRACKET: INDEX, @@ -830,21 +830,9 @@ func (p *Parser) parseDecorator() ast.Expression { } })() - if p.peekTokenIs(token.IDENT) { - p.nextToken() - dc.Name = p.curToken.Literal - } - - // This is a decorator without arguments - // @func - if !p.peekTokenIs(token.LPAREN) { - return dc - } - - // This is a decorator with arguments - // @func(x, y, z) p.nextToken() - dc.Arguments = p.parseExpressionList(token.RPAREN) + exp := p.parseExpressionStatement() + dc.Expression = exp.Expression return dc } diff --git a/parser/parser_test.go b/parser/parser_test.go index 5a9eafc7..b3a1a0cd 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -1152,17 +1152,16 @@ func TestCurrentArgsParsing(t *testing.T) { func TestDecoratorParsing(t *testing.T) { tests := []struct { input string - args []string - name string + inspect string decorated string err string }{ {input: "@decorator", err: "a decorator should decorate a named function"}, {input: "@decorator('x', 'y')", err: "a decorator should decorate a named function"}, - {input: "@decorator f hello(){}", name: "decorator", args: []string{}, decorated: "hello"}, - {input: "@decorator('x', 'y') f hello(){}", name: "decorator", args: []string{"x", "y"}, decorated: "hello"}, + {input: "@decorator f hello(){}", inspect: "decorator", decorated: "hello"}, + {input: "@decorator('x', 'y') f hello(){}", inspect: "decorator(x, y)", decorated: "hello"}, {input: "@decorator('x', 'y') f (){}", err: "a decorator should decorate a named function"}, - {input: "@decorator('x', 'y') @decorator('x', 'y') f hello(){}", name: "decorator", args: []string{"x", "y"}, decorated: "hello"}, + {input: "@decorator('x', 'y') @decorator('x', 'y') f hello(){}", inspect: "decorator(x, y)", decorated: "hello"}, {input: "@decorator('x', 'y') @decorator('x', 'y')", err: "a decorator should decorate a named function"}, } @@ -1189,12 +1188,8 @@ func TestDecoratorParsing(t *testing.T) { stmt := program.Statements[0].(*ast.ExpressionStatement) decorator := stmt.Expression.(*ast.Decorator) - if len(decorator.Arguments) != len(tt.args) { - t.Errorf("length arguments wrong. want %d, got=%d\n", len(tt.args), len(decorator.Arguments)) - } - - if tt.name != decorator.Name { - t.Errorf("name parameter wrong. want %s, got=%s\n", tt.name, decorator.Name) + if tt.inspect != decorator.String() { + t.Errorf("inspection wrong. want %s, got=%s\n", tt.inspect, decorator.String()) } switch decorated := decorator.Decorated.(type) { @@ -1202,12 +1197,6 @@ func TestDecoratorParsing(t *testing.T) { if decorated.Name == "" { t.Errorf("a decorator should have a decorated function") } - - for i, arg := range tt.args { - if arg != decorator.Arguments[i].String() { - t.Errorf("wrong argument. want %s, got=%s\n", arg, decorator.Arguments[i]) - } - } case *ast.Decorator: // Here we have a double decorator: // @dec(...) @@ -1217,24 +1206,10 @@ func TestDecoratorParsing(t *testing.T) { // 2 decorators must be identical, as here we're // just testing decorators can decorate either functions // or other decorators - if len(decorator.Arguments) != len(tt.args) { - t.Errorf("length arguments wrong. want %d, got=%d\n", len(tt.args), len(decorator.Arguments)) - } - - if tt.name != decorator.Name { - t.Errorf("name parameter wrong. want %s, got=%s\n", tt.name, decorator.Name) - } - realDecorated := decorated.Decorated.(*ast.FunctionLiteral) if realDecorated.Name == "" { t.Errorf("a decorator should have a realDecorated function") } - - for i, arg := range tt.args { - if arg != decorator.Arguments[i].String() { - t.Errorf("wrong argument. want %s, got=%s\n", arg, decorator.Arguments[i]) - } - } default: t.Errorf("unhandled type %T", decorated) } diff --git a/repl/repl.go b/repl/repl.go index fe005bb8..4d3ddf37 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -38,7 +38,7 @@ var ( func init() { d, _ := os.Getwd() - env = object.NewEnvironment(os.Stdout, d) + env = object.NewEnvironment(os.Stdout, d, "") } func completer(d prompt.Document) []prompt.Suggest { @@ -208,6 +208,11 @@ func BeginRepl(args []string, version string) { env.Dir = filepath.Dir(args[1]) } + // TODO this should be removed and injected in the environment + // when the module is initialized + env.Version = version + env.Set("ABS_VERSION", &object.String{Value: version}) + // get abs init file // user may test ABS_INTERACTIVE to decide what code to run getAbsInitFile(interactive) diff --git a/scripts/release.abs b/scripts/release.abs index 0952df62..f8085b11 100644 --- a/scripts/release.abs +++ b/scripts/release.abs @@ -60,6 +60,15 @@ if selection != "y" { exit(2) } +`pkger -include /stdlib -o evaluator/` + +tests = `make test` +echo(tests) + +if !tests.ok { + exit(1, "Tests failing") +} + for platform in platforms { goos, goarch = platform.split("/") output_name = "builds/abs-$goos-$goarch" diff --git a/stdlib/cli/index.abs b/stdlib/cli/index.abs new file mode 100644 index 00000000..5b770785 --- /dev/null +++ b/stdlib/cli/index.abs @@ -0,0 +1,69 @@ +# CLI app +cli = {} + +# Commands registered within this CLI app +cli.commands = {} + +# Function used to register a command +cli.cmd = f(name, description, flags) { + return f(fn) { + cli.commands[name] = {}; + cli.commands[name].cmd = f() { + # Create flags with default values + for k, _ in flags { + v = flag(k) + + if v { + flags[k] = v + } + } + + # Call the original cmd + result = fn.call([args()[3:], flags]) + + # If there's a result we print it out + if result { + echo(result) + } + } + + cli.commands[name].description = description + } +} + +# Run the CLI app +cli.run = f() { + # ABS sees "abs script.abs xyz" + # so the command is the 3rd argument + cmd = arg(2) + + # Not passing a command? Let's print the help + if !cmd { + return cli.commands['help'].cmd() + } + + if !cli.commands[cmd] { + exit(99, "command '${cmd}' not found") + } + + return cli.commands[cmd].cmd() +} + +# Add a default help command that can be +# overridden by the caller +@cli.cmd("help", "print this help message", {}) +f help() { + echo("Available commands:\n") + + for cmd in cli.commands.keys().sort() { + s = " * ${cmd}" + + if cli.commands[cmd].description { + s += " - " + cli.commands[cmd].description + } + + echo(s) + } +} + +return cli \ No newline at end of file diff --git a/stdlib/runtime/index.abs b/stdlib/runtime/index.abs new file mode 100644 index 00000000..2cf8578f --- /dev/null +++ b/stdlib/runtime/index.abs @@ -0,0 +1,4 @@ +return { + "name": "abs", + "version": ABS_VERSION +} \ No newline at end of file diff --git a/util/util_test.go b/util/util_test.go index 88f9a9ca..904fdd44 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -99,7 +99,7 @@ func TestInterpolateStringVars(t *testing.T) { {"${string x", "${string x"}, } - env := object.NewEnvironment(os.Stdout, "") + env := object.NewEnvironment(os.Stdout, "", "dev") env.Set("string", &object.String{Value: "test"}) for _, tt := range tests {