Skip to content

Commit

Permalink
Merge branch '1.9.x'
Browse files Browse the repository at this point in the history
  • Loading branch information
odino committed Dec 7, 2019
2 parents cb42ccc + a22808e commit aa971a3
Show file tree
Hide file tree
Showing 17 changed files with 249 additions and 25 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repository.

Since we follow [semver](https://semver.org/),
when a new feature is released we don't backport it but simply
create a new version branch, such as `1.8.x`. Bugs, instead,
create a new version branch, such as `1.9.x`. Bugs, instead,
might be backported from `1.1.0` to, for example, `1.0.x` and we
will have a new [release](https://github.com/abs-lang/abs/releases),
say `1.0.1` for the `1.0.x` version branch.
Expand Down
10 changes: 10 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ type MethodExpression struct {
Object Expression
Method Expression
Arguments []Expression
Optional bool
}

func (me *MethodExpression) expressionNode() {}
Expand All @@ -268,6 +269,9 @@ func (me *MethodExpression) String() string {
}

out.WriteString(me.Object.String())
if me.Optional {
out.WriteString("?")
}
out.WriteString(".")
out.WriteString(me.Method.String())
out.WriteString("(")
Expand Down Expand Up @@ -530,6 +534,7 @@ type PropertyExpression struct {
Token token.Token // The . token
Object Expression
Property Expression
Optional bool
}

func (pe *PropertyExpression) expressionNode() {}
Expand All @@ -539,6 +544,11 @@ func (pe *PropertyExpression) String() string {

out.WriteString("(")
out.WriteString(pe.Object.String())

if pe.Optional {
out.WriteString("?")
}

out.WriteString(".")
out.WriteString(pe.Property.String())
out.WriteString(")")
Expand Down
Binary file modified docs/abs.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion docs/installer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ if [ "${MACHINE_TYPE}" = 'x86_64' ]; then
ARCH="amd64"
fi

VERSION=1.8.3
VERSION=1.9.0

echo "Trying to detect the details of your architecture."
echo ""
Expand Down
12 changes: 5 additions & 7 deletions docs/misc/3pl.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,18 @@ Creating alias...
Install Success. You can use the module with `require("abs-sample-module")`
```

Modules will be saved under the `vendor/$MODULE-master` directory. Each module
Modules will be saved under the `vendor/$MODULE` directory. Each module
also gets an alias to facilitate requiring them in your code, meaning that
both of these forms are supported:

```
⧐ require("abs-sample-module/sample.abs")
{"another": f() {return hello world;}}
⧐ require("vendor/github.com/abs-lang/abs-sample-module-master/sample.abs")
⧐ require("vendor/github.com/abs-lang/abs-sample-module/sample.abs")
{"another": f() {return hello world;}}
```

Note that the `-master` prefix [will be removed](https://github.com/abs-lang/abs/issues/286) in future versions of ABS.

Module aliases are saved in the `packages.abs.json` file
which is created in the same directory where you run the
`abs get ...` command:
Expand All @@ -43,7 +41,7 @@ Install Success. You can use the module with `require("abs-sample-module")`
$ cat packages.abs.json
{
"abs-sample-module": "./vendor/github.com/abs-lang/abs-sample-module-master"
"abs-sample-module": "./vendor/github.com/abs-lang/abs-sample-module"
}
```

Expand All @@ -58,15 +56,15 @@ $ abs get github.com/abs-lang/abs-sample-module
Unpacking...
Creating alias...This module could not be aliased because module of same name exists
Install Success. You can use the module with `require("./vendor/github.com/abs-lang/abs-sample-module-master")`
Install Success. You can use the module with `require("./vendor/github.com/abs-lang/abs-sample-module")`
```

When requiring a module, ABS will try to load the `index.abs` file unless
another file is specified:

```
$ ~/projects/abs/builds/abs
Hello alex, welcome to the ABS (1.8.3) programming language!
Hello alex, welcome to the ABS (1.9.0) programming language!
Type 'quit' when you're done, 'help' if you get lost!
⧐ require("abs-sample-module")
Expand Down
43 changes: 43 additions & 0 deletions docs/syntax/operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,49 @@ true || false # true
"hello" || "world" # "hello"
```

## .

Property accessor, used to access properties or methods of specific variables:

``` bash
hello = {"to_who": "the world"}
hello.to_who # "the world"
```

There are some builtin functions that you can access through the property accessor:

``` bash
"hello".len() # 5
```

(a comprehensive list of function is documented in the "*Types and functions*" section of the documentation)

## ?.

Optional chaining operator, used to access properties in a "safe" way.

Given the following object:

``` bash
test = {"property": 1}
```

An error would be raised if you were trying to access a non-existing property
such as `test.something.something_else`:

```
ERROR: invalid property 'something_else' on type NULL
[1:15] test.something.something_else
```

Optional chainig prevents those errors from being raised, auto-magically
converting non-existing properties and methods to `NULL`:

``` bash
test?.something?.something_else # null
test?.something?.something_else() # null
```

## ..

Range operator, which creates an array from start to end:
Expand Down
2 changes: 1 addition & 1 deletion docs/types/builtin-function.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ $ cat ~/.absrc
source("~/abs/lib/library.abs")
$ abs
Hello user, welcome to the ABS (1.8.3) programming language!
Hello user, welcome to the ABS (1.9.0) programming language!
Type 'quit' when you are done, 'help' if you get lost!
⧐ adder(1, 2)
3
Expand Down
13 changes: 11 additions & 2 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
return args[0]
}

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

case *ast.PropertyExpression:
return evalPropertyExpression(node, env)
Expand Down Expand Up @@ -996,6 +996,10 @@ func evalPropertyExpression(pe *ast.PropertyExpression, env *object.Environment)
return evalHashIndexExpression(obj.Token, obj, &object.String{Token: pe.Token, Value: pe.Property.String()})
}

if pe.Optional {
return NULL
}

return newError(pe.Token, "invalid property '%s' on type %s", pe.Property.String(), o.Type())
}

Expand All @@ -1019,7 +1023,8 @@ func applyFunction(tok token.Token, fn object.Object, env *object.Environment, a
}
}

func applyMethod(tok token.Token, o object.Object, method string, env *object.Environment, args []object.Object) object.Object {
func applyMethod(tok token.Token, o object.Object, me *ast.MethodExpression, env *object.Environment, args []object.Object) object.Object {
method := me.Method.String()
// Check if the current object is an hash,
// it might have user-defined functions
hash, isHash := o.(*object.Hash)
Expand All @@ -1034,6 +1039,10 @@ func applyMethod(tok token.Token, o object.Object, method string, env *object.En
f, ok := Fns[method]

if !ok {
if me.Optional {
return NULL
}

return newError(tok, "%s does not have method '%s()'", o.Type(), method)
}

Expand Down
41 changes: 41 additions & 0 deletions evaluator/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,8 @@ func TestBuiltinFunctions(t *testing.T) {
{`len([])`, 0},
{`echo("hello", "world!")`, nil},
{`env("CONTEXT")`, "abs"},
{`env("FOO")`, ""},
{`env("FOO", "bar")`, "bar"},
{`type("SOME")`, "STRING"},
{`type(1)`, "NUMBER"},
{`type({})`, "HASH"},
Expand Down Expand Up @@ -1508,6 +1510,45 @@ func TestHashLiterals(t *testing.T) {
}
}

func TestOptionalChaining(t *testing.T) {
tests := []struct {
input string
expected interface{}
}{
{
`a = null; a?.b?.c`,
nil,
},
{
`a = 1; a?.b?.c`,
nil,
},
{
`a = 1; a?.b?.c()`,
nil,
},
{
`a = {"b" : {"c": 1}}; a?.b?.c`,
1,
},
{
`a = {"b": 1}; a.b`,
1,
},
}

for _, tt := range tests {
evaluated := testEval(tt.input)

switch evaluated.(type) {
case *object.Number:
testNumberObject(t, evaluated, float64(tt.expected.(int)))
default:
testNullObject(t, evaluated)
}
}
}

func TestHashIndexExpressions(t *testing.T) {
tests := []struct {
input string
Expand Down
34 changes: 28 additions & 6 deletions evaluator/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ func getFns() map[string]*object.Builtin {
Types: []string{},
Fn: stdinFn,
},
// env(variable:"PWD")
// env(variable:"PWD") or env(string:"KEY", string:"VAL")
"env": &object.Builtin{
Types: []string{object.STRING_OBJ},
Types: []string{},
Fn: envFn,
},
// arg(position:1)
Expand Down Expand Up @@ -374,6 +374,22 @@ func validateArgs(tok token.Token, name string, args []object.Object, size int,
return nil
}

func validateVarArgs(tok token.Token, name string, args []object.Object, required int, types [][][]string) object.Object {
if len(args) < required {
return newError(tok, "wrong number of arguments to %s(...): got=%d, min=%d, max=%d", name, len(args), required, len(types))
}

for i, set := range types {
for _, t := range set {
if !util.Contains(t, string(args[i].Type())) {
return newError(tok, "argument %d to %s(...) is not supported (got: %s, allowed: %s)", i, name, args[i].Inspect(), strings.Join(t, ", "))
}
}
}

return nil
}

// len(var:"hello")
func lenFn(tok token.Token, env *object.Environment, args ...object.Object) object.Object {
err := validateArgs(tok, "len", args, 1, [][]string{{object.STRING_OBJ, object.ARRAY_OBJ}})
Expand Down Expand Up @@ -704,15 +720,21 @@ func stdinNextFn() (object.Object, object.Object) {
return &object.Number{Value: float64(scannerPosition)}, &object.String{Token: tok, Value: scanner.Text()}
}

// env(variable:"PWD")
// env(variable:"PWD") or env(string:"KEY", string:"VAL")
func envFn(tok token.Token, env *object.Environment, args ...object.Object) object.Object {
err := validateArgs(tok, "env", args, 1, [][]string{{object.STRING_OBJ}})
err := validateVarArgs(tok, "env", args, 1, [][][]string{{{object.STRING_OBJ}, {object.STRING_OBJ}}})
if err != nil {
return err
}

arg := args[0].(*object.String)
return &object.String{Token: tok, Value: os.Getenv(arg.Value)}
key := args[0].(*object.String)

if len(args) > 1 {
val := args[1].(*object.String)
os.Setenv(key.Value, val.Value)
}

return &object.String{Token: tok, Value: os.Getenv(key.Value)}
}

// arg(position:1)
Expand Down
18 changes: 13 additions & 5 deletions install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func printLoader(done chan int64, message string) {
}

func getZip(module string) error {
path := fmt.Sprintf("./vendor/%s", module)
path := fmt.Sprintf("./vendor/%s-master.zip", module)
// Create all the parent directories if needed
err := os.MkdirAll(filepath.Dir(path), 0755)

Expand Down Expand Up @@ -123,7 +123,7 @@ func getZip(module string) error {
// Unzip will decompress a zip archive
func unzip(module string) error {
fmt.Printf("\nUnpacking...")
src := fmt.Sprintf("./vendor/%s", module)
src := fmt.Sprintf("./vendor/%s-master.zip", module)
dest := filepath.Dir(src)

r, err := zip.OpenReader(src)
Expand All @@ -133,8 +133,17 @@ func unzip(module string) error {
defer r.Close()

for _, f := range r.File {
filename := f.Name
parts := strings.Split(f.Name, string(os.PathSeparator))
if len(parts) > 1 {
if strings.HasSuffix(parts[0], "-master") {
// Trim "master" suffix due to github's naming convention for archives
parts[0] = strings.TrimSuffix(parts[0], "-master")
filename = strings.Join(parts, string(os.PathSeparator))
}
}
// Store filename/path for returning and using later on
fpath := filepath.Join(dest, f.Name)
fpath := filepath.Join(dest, filename)

// Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
Expand Down Expand Up @@ -188,8 +197,7 @@ func createAlias(module string) (string, error) {

data := make(map[string]string)
moduleName := filepath.Base(module)
// Appending "master" as Github zip file has "-master" suffix
modulePath := fmt.Sprintf("./vendor/%s-master", module)
modulePath := fmt.Sprintf("./vendor/%s", module)

// If package.abs.json file is empty
if len(b) == 0 {
Expand Down
2 changes: 2 additions & 0 deletions lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ func (l *Lexer) NextToken() token.Token {
} else {
tok = l.newToken(token.DOT)
}
case '?':
tok = l.newToken(token.QUESTION)
case '|':
if l.peekChar() == '|' {
tok.Type = token.OR
Expand Down
Loading

0 comments on commit aa971a3

Please sign in to comment.