Skip to content

Commit

Permalink
Merge 5edaccc into 92de353
Browse files Browse the repository at this point in the history
  • Loading branch information
ntwrick committed Feb 17, 2019
2 parents 92de353 + 5edaccc commit 81b8214
Show file tree
Hide file tree
Showing 14 changed files with 440 additions and 52 deletions.
80 changes: 75 additions & 5 deletions docs/introduction/how-to-run-abs-code.md
Expand Up @@ -20,7 +20,10 @@ Afterwards, you can run ABS scripts with:
$ abs path/to/scripts.abs
```
You can also run an executable abs script directly from bash
using a bash shebang line at the top of the script file:
using a bash shebang line at the top of the script file.

In this example the abs executable is linked to `/usr/local/bin/abs`
and the abs script `~/bin/remote.abs` has its execute permissions set.
```bash
$ cat ~/bin/remote.abs
#! /usr/local/bin/abs
Expand Down Expand Up @@ -80,9 +83,12 @@ by using the up and down arrow keys at the prompt.
lines, but the saved history will only contain a single command
when the previous command and the current command are the same.
The history file name and the maximum number of lines are
configurable through the OS environment. The default values are
`ABS_HISTORY_FILE="~/.abs_history"` and `ABS_MAX_HISTORY_LINES=1000`.
The history file name and the maximum number of history lines are
configurable through
1) the ABS environment (set by the ABS init file; see below)
2) the OS environment
3) The default values are `ABS_HISTORY_FILE="~/.abs_history"`
and `ABS_MAX_HISTORY_LINES=1000`.
+ If you wish to suppress the command line history completely, just
set `ABS_MAX_HISTORY_LINES=0`. In this case the history file
Expand Down Expand Up @@ -111,9 +117,73 @@ echo("hello")
$
```
## ABS Init File
When the ABS interpreter starts running, it will load an optional
ABS script as its init file. The ABS init file path can be
configured via the OS environment variable `ABS_INIT_FILE`. The
default value is `ABS_INIT_FILE=~/.absrc`.
If the `ABS_INIT_FILE` exists, it will be evaluated before the
interpreter begins in both interactive REPL or script modes.
The result of all expressions evaluated in the init file become
part of the ABS global environment which are available to command
line expressions or script programs.
Also, note that the `ABS_INTERACTIVE` global environment variable
is pre-set to `true` or `false` so that the init file can determine
which mode is running. This is useful if you wish to set the ABS REPL
command line prompt or history configuration variables in the init file.
This will preset the prompt and history parameters for the interactive
REPL (see [REPL Command History](#REPL_Command_History) above).
### Configuring the ABS REPL Command Line Prompt
The ABS REPL command line prompt may be configured at start up using
`ABS_PROMPT_LIVE_PREFIX` and `ABS_PROMPT_PREFIX` variables from either
the ABS or OS environments. The default values are
`ABS_PROMPT_LIVE_PREFIX=false` and `ABS_PROMPT_PREFIX="⧐ "`.
REPL "static prompt" mode will be configured if `ABS_PROMPT_PREFIX`
contains no live prompt `template string` or if
`ABS_PROMPT_LIVE_PREFIX=false`. The `static prompt` will be the
value of the `ABS_PROMPT_PREFIX` string (if present) or the default
prompt `"⧐ "`. Static prompt mode is the default for the REPL.
REPL "live prompt" mode follows the current working directory
set by `cd()` when both `ABS_PROMPT_LIVE_PREFIX=true` and the
`ABS_PROMPT_PREFIX` variable contains a live prompt `template string`.
A live prompt `template string` may contain the following
named placeholders:
* `{user}`: the current userId
* `{host}`: the local hostname
* `{dir}`: the current working directory following `cd()`
For example, you can create a `bash`-style live prompt:
```bash
$ cat ~/.absrc
# ABS init script ~/.absrc
# For interactive REPL, override default prompt, history filename and size
if ABS_INTERACTIVE {
ABS_PROMPT_LIVE_PREFIX = true
ABS_PROMPT_PREFIX = "{user}@{host}:{dir}$ "
ABS_HISTORY_FILE = "~/.abs_hist"
ABS_MAX_HISTORY_LINES = 500
}
$ abs
Hello user, welcome to the ABS (1.1.0) programming language!
Type 'quit' when you are done, 'help' if you get lost!
user@hostname:~/git/abs$ cwd = cd()
user@hostname:~$ `ls .absrc`
.absrc
user@hostname:~$
```
Also see a `template ABS Init File` at [examples](https://github.com/abs-lang/abs/tree/master/examples/absrc.abs).
## Why is abs interpreted?
ABS' goal is to be a portable, pragmatic, coincise, simple language:
ABS' goal is to be a portable, pragmatic, concise, simple language:
great performance comes second.
With this in mind, we made a deliberate choice to avoid
Expand Down
66 changes: 66 additions & 0 deletions docs/types/builtin-function.md
Expand Up @@ -212,6 +212,72 @@ Halts the process for as many `ms` you specified:
sleep(1000) # sleeps for 1 second
```
### source(fileName) aka require(filename)
Evaluates the ABS script `fileName` 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
are loaded via the ABS Init File `~/.absrc`. See [ABS Init File](/introduction/how-to-run-abs-code).
For example:
```bash
$ cat ~/abs/lib/library.abs
# Useful function library ~/abs/lib/library.abs
adder = f(n, i) { n + i }
$ cat ~/.absrc
# ABS init file ~/.absrc
source("~/abs/lib/library.abs")
$ abs
Hello user, welcome to the ABS (1.1.0) programming language!
Type 'quit' when you are done, 'help' if you get lost!
⧐ adder(1, 2)
3
```
In addition to source file inclusion in scripts, you can also use
`source()` in the interactive REPL to load a script being
debugged. When the loaded script completes, the REPL command line
will have access to all variables and functions evaluated in the
script.
For example:
```bash
⧐ source("~/git/abs/tests/test-strings.abs")
...
=====================
>>> Testing split and join strings with expanded LFs:
s = split("a\nb\nc", "\n")
echo(s)
[a, b, c]
...
⧐ s
[a, b, c]
```
Note well that nested source files must not create a circular
inclusion condition. You can configure the intended source file
inclusion depth using the `ABS_SOURCE_DEPTH` OS or ABS environment
variables. The default is `ABS_SOURCE_DEPTH=10`. This will prevent
a panic in the ABS interpreter if there is an unintended circular
source inclusion.
For example an ABS Init File may contain:
```bash
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.
## Next
That's about it for this section!
Expand Down
5 changes: 5 additions & 0 deletions evaluator/evaluator.go
Expand Up @@ -26,6 +26,9 @@ 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 @@ -44,6 +47,8 @@ func newError(tok token.Token, format string, a ...interface{}) *object.Error {
// 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
101 changes: 96 additions & 5 deletions evaluator/functions.go
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"crypto/rand"
"fmt"
"io/ioutil"
"math"
"math/big"
"os"
Expand Down Expand Up @@ -302,6 +303,17 @@ func getFns() map[string]*object.Builtin {
Types: []string{object.NUMBER_OBJ},
Fn: sleepFn,
},
// source("fileName")
// aka require()
"source": &object.Builtin{
Types: []string{object.STRING_OBJ},
Fn: sourceFn,
},
// require("fileName") -- alias for source()
"require": &object.Builtin{
Types: []string{object.STRING_OBJ},
Fn: sourceFn,
},
}
}

Expand Down Expand Up @@ -497,7 +509,7 @@ func intFn(tok token.Token, args ...object.Object) object.Object {
return err
}

return applyMathFunction(args[0], func(n float64) float64 {
return applyMathFunction(tok, args[0], func(n float64) float64 {
return float64(int64(n))
}, "int")
}
Expand All @@ -523,7 +535,7 @@ func roundFn(tok token.Token, args ...object.Object) object.Object {
decimal = float64(math.Pow(10, args[1].(*object.Number).Value))
}

return applyMathFunction(args[0], func(n float64) float64 {
return applyMathFunction(tok, args[0], func(n float64) float64 {
return math.Round(n*decimal) / decimal
}, "round")
}
Expand All @@ -536,7 +548,7 @@ func floorFn(tok token.Token, args ...object.Object) object.Object {
return err
}

return applyMathFunction(args[0], math.Floor, "floor")
return applyMathFunction(tok, args[0], math.Floor, "floor")
}

// ceil(string:"123.1")
Expand All @@ -547,15 +559,16 @@ func ceilFn(tok token.Token, args ...object.Object) object.Object {
return err
}

return applyMathFunction(args[0], math.Ceil, "ceil")
return applyMathFunction(tok, args[0], math.Ceil, "ceil")
}

// Base function to do math operations. This is here
// so that we abstract away some of the common logic
// between all math functions, for example:
// - allowing to be called on strings as well ("1.23".ceil())
// - handling errors
func applyMathFunction(arg object.Object, fn func(float64) float64, fname string) object.Object {
// NB. callers must pass the token that is used for error line reporting
func applyMathFunction(tok token.Token, arg object.Object, fn func(float64) float64, fname string) object.Object {
switch arg := arg.(type) {
case *object.Number:
return &object.Number{Token: tok, Value: float64(fn(arg.Value))}
Expand Down Expand Up @@ -1408,3 +1421,81 @@ func sleepFn(tok token.Token, args ...object.Object) object.Object {

return NULL
}

// source("fileName")
// aka require()
const ABS_SOURCE_DEPTH = "10"

var sourceDepth, _ = strconv.Atoi(ABS_SOURCE_DEPTH)
var sourceLevel = 0

func sourceFn(tok token.Token, args ...object.Object) object.Object {
err := validateArgs(tok, "source", args, 1, [][]string{{object.STRING_OBJ}})
if err != nil {
// reset the source level
sourceLevel = 0
return err
}

// get configured source depth if any
sourceDepthStr := util.GetEnvVar(globalEnv, "ABS_SOURCE_DEPTH", ABS_SOURCE_DEPTH)
sourceDepth, _ = strconv.Atoi(sourceDepthStr)

// limit source file inclusion depth
if sourceLevel >= sourceDepth {
// reset the source level
sourceLevel = 0
// use errObj.Message instead of errObj.Inspect() to avoid nested "ERROR: " prefixes
errObj := newError(tok, "maximum source file inclusion depth exceeded at %d levels", sourceDepth)
errObj = &object.Error{Message: errObj.Message}
return errObj
}
// mark this source level
sourceLevel++

// load the source file
fileName, _ := util.ExpandPath(args[0].Inspect())
code, error := ioutil.ReadFile(fileName)
if error != nil {
// reset the source level
sourceLevel = 0
// cannot read source file
return newError(tok, "cannot read source file: %s:\n%s", fileName, error.Error())
}
// parse it
l := lexer.New(string(code))
p := parser.New(l)
program := p.ParseProgram()
errors := p.Errors()
if len(errors) != 0 {
// reset the source level
sourceLevel = 0
errMsg := fmt.Sprintf("%s", " parser errors:\n")
for _, msg := range errors {
errMsg += fmt.Sprintf("%s", "\t"+msg+"\n")
}
return newError(tok, "error found in source file: %s\n%s", fileName, errMsg)
}
// invoke BeginEval() passing in the sourced program, globalEnv, and our lexer
// we save the current global lexer and restore it after we return from BeginEval()
// NB. saving the lexer allows error line numbers to be relative to any nested source files
savedLexer := lex
evaluated := BeginEval(program, globalEnv, l)
lex = savedLexer
if evaluated != nil {
isError := evaluated.Type() == object.ERROR_OBJ
if isError {
// reset the source level
sourceLevel = 0
// use errObj.Message instead of errObj.Inspect() to avoid nested "ERROR: " prefixes
evalErrMsg := evaluated.(*object.Error).Message
sourceErrMsg := newError(tok, "error found in source file: %s", fileName).Message
errObj := &object.Error{Message: fmt.Sprintf("%s\n\t%s", evalErrMsg, sourceErrMsg)}
return errObj
}
}
// restore this source level
sourceLevel--

return evaluated
}
12 changes: 12 additions & 0 deletions examples/absrc.abs
@@ -0,0 +1,12 @@
## ABS init script ~/.absrc

## For interactive REPL, override default prompt, history filename and size
# if ABS_INTERACTIVE {
# ABS_PROMPT_LIVE_PREFIX = true
# ABS_PROMPT_PREFIX = "{user}@{host}:{dir}$ "
# ABS_HISTORY_FILE = "~/.abs_history"
# ABS_MAX_HISTORY_LINES = 1000
# }

## source your function libraries here
# source("/path/to/abs/lib/library.abs")

0 comments on commit 81b8214

Please sign in to comment.