Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Source vs require #273

Merged
merged 3 commits into from
Aug 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/abs.wasm
Binary file not shown.
52 changes: 46 additions & 6 deletions docs/types/builtin-function.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,15 +229,54 @@ Halts the process for as many `ms` you specified:
sleep(1000) # sleeps for 1 second
```

### source(path_to_file) aka require(path_to_file)
### require(path_to_file.abs)

Evaluates the script at `path_to_file` in the context of the ABS global
environment. The results of any expressions in the file become
available to other commands in the REPL command line or to other
Evaluates the script at `path_to_file.abs`, and makes
its return value available to the caller.

For example, suppose we have a `module.abs` file:

``` bash
adder = f(a, b) { a + b }
multiplier = f(a, b) { a * b }

return {"adder": adder, "multiplier": multiplier}
```

and a `main.abs` such as:

``` bash
mod = require("module.abs")

echo(mod.adder(1, 2)) # 3
```

This is mostly useful to create external library
functions, like NPM modules or PIP packages, that
do not have access to the global environment. Any
variable set outside of the module will not be
available inside it, and vice-versa. The only
variable available to the caller (the script requiring
the module) is the module's return value.

Note that `require` uses paths that are relative to
the current script. Say that you have 2 files (`a.abs` and `b.abs`)
in the `/tmp` folder, `a.abs` can `require("./b.abs")`
without having to specify the full path (eg. `require("/tmp/b.abs")`).

### source(path_to_file.abs)

Evaluates the script at `path_to_file.abs` in the context of the
ABS global environment. The results of any expressions in the file
become available to other commands in the REPL command line or to other
scripts in the current script execution chain.

This is most useful for creating `library functions` in a script
that can be used by many other scripts. Often the library functions
This is very similar to `require`, but allows the module to access
and edit the global environment. Any variable set inside the module
will also be available outside of it.

This is most useful for creating library functions in a startup script,
or variables that can be used by many other scripts. Often these library functions
are loaded via the ABS Init File `~/.absrc` (see [ABS Init File](/introduction/how-to-run-abs-code)).

For example:
Expand Down Expand Up @@ -291,6 +330,7 @@ For example an ABS Init File may contain:
ABS_SOURCE_DEPTH = 15
source("~/path/to/abs/lib")
```

This will limit the source inclusion depth to 15 levels for this
`source()` statement and will also apply to future `source()`
statements until changed.
Expand Down
17 changes: 6 additions & 11 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ var (
Fns map[string]*object.Builtin
)

// This program's global environment can be used by builtin's to modify the env
var globalEnv *object.Environment

// This program's lexer used for error location in Eval(program)
var lex *lexer.Lexer

Expand All @@ -55,8 +52,6 @@ func newContinueError(tok token.Token, format string, a ...interface{}) *object.
// REPL and testing modules call this function to init the global lexer pointer for error location
// NB. Eval(node, env) is recursive
func BeginEval(program ast.Node, env *object.Environment, lexer *lexer.Lexer) object.Object {
// global environment
globalEnv = env
// global lexer
lex = lexer
// run the evaluator
Expand Down Expand Up @@ -178,7 +173,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
return args[0]
}

return applyFunction(node.Token, function, args)
return applyFunction(node.Token, function, env, args)

case *ast.MethodExpression:
o := Eval(node.Object, env)
Expand All @@ -191,7 +186,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
return args[0]
}

return applyMethod(node.Token, o, node.Method.String(), args)
return applyMethod(node.Token, o, node.Method.String(), env, args)

case *ast.PropertyExpression:
return evalPropertyExpression(node, env)
Expand Down Expand Up @@ -1004,7 +999,7 @@ func evalPropertyExpression(pe *ast.PropertyExpression, env *object.Environment)
return newError(pe.Token, "invalid property '%s' on type %s", pe.Property.String(), o.Type())
}

func applyFunction(tok token.Token, fn object.Object, args []object.Object) object.Object {
func applyFunction(tok token.Token, fn object.Object, env *object.Environment, args []object.Object) object.Object {
switch fn := fn.(type) {

case *object.Function:
Expand All @@ -1017,14 +1012,14 @@ func applyFunction(tok token.Token, fn object.Object, args []object.Object) obje
return unwrapReturnValue(evaluated)

case *object.Builtin:
return fn.Fn(tok, args...)
return fn.Fn(tok, env, args...)

default:
return newError(tok, "not a function: %s", fn.Type())
}
}

func applyMethod(tok token.Token, o object.Object, method string, args []object.Object) object.Object {
func applyMethod(tok token.Token, o object.Object, method string, env *object.Environment, args []object.Object) object.Object {
f, ok := Fns[method]

if !ok {
Expand All @@ -1036,7 +1031,7 @@ func applyMethod(tok token.Token, o object.Object, method string, args []object.
}

args = append([]object.Object{o}, args...)
return f.Fn(tok, args...)
return f.Fn(tok, env, args...)
}

func extendFunctionEnv(
Expand Down
6 changes: 5 additions & 1 deletion evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,10 @@ c")`, []string{"a", "b", "c"}},
{`sleep(0.01)`, nil},
{`$()`, ""},
{`a = 1; eval("a")`, 1},
{`"a = 2; return 10" >> "test-source-vs-require.abs.ignore"; a = 1; x = source("test-source-vs-require.abs.ignore"); a`, 2},
{`"a = 2; return 10" >> "test-source-vs-require.abs.ignore"; a = 1; x = require("test-source-vs-require.abs.ignore"); a`, 1},
{`"a = 2; return 10" >> "test-source-vs-require.abs.ignore"; a = 1; x = source("test-source-vs-require.abs.ignore"); x`, 10},
{`"a = 2; return 10" >> "test-source-vs-require.abs.ignore"; a = 1; x = require("test-source-vs-require.abs.ignore"); x`, 10},
}
for _, tt := range tests {
evaluated := testEval(tt.input)
Expand Down Expand Up @@ -1582,7 +1586,7 @@ func TestStringIndexExpressions(t *testing.T) {
}

func testEval(input string) object.Object {
env := object.NewEnvironment(os.Stdout)
env := object.NewEnvironment(os.Stdout, "")
lex := lexer.New(input)
p := parser.New(lex)
program := p.ParseProgram()
Expand Down
Loading