Skip to content

Commit

Permalink
Functions attached to hashes can now be called, fixes #284
Browse files Browse the repository at this point in the history
Previously, if an hash had a function defined (ie. `obj = {"fn": f(){ return "hello"}}`)
you couldn't directly invoke the function with `obk.fn()` but you had to
"dereference" it first (eg. `fn = obj.fn; fn()`). This has been fixed with
this PR, adding a bunch more tests, relevant docs and preparing for a new
patch release.
  • Loading branch information
odino committed Oct 31, 2019
1 parent 2d80448 commit da95e90
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 8 deletions.
Binary file modified docs/abs.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion docs/installer.sh
Expand Up @@ -27,7 +27,7 @@ if [ "${MACHINE_TYPE}" = 'x86_64' ]; then
ARCH="amd64"
fi

VERSION=1.8.0
VERSION=1.8.1

echo "Trying to detect the details of your architecture."
echo ""
Expand Down
2 changes: 1 addition & 1 deletion docs/misc/3pl.md
Expand Up @@ -66,7 +66,7 @@ another file is specified:

```
$ ~/projects/abs/builds/abs
Hello alex, welcome to the ABS (1.8.0) programming language!
Hello alex, welcome to the ABS (1.8.1) programming language!
Type 'quit' when you're done, 'help' if you get lost!
⧐ require("abs-sample-module")
Expand Down
2 changes: 1 addition & 1 deletion docs/types/builtin-function.md
Expand Up @@ -290,7 +290,7 @@ $ cat ~/.absrc
source("~/abs/lib/library.abs")
$ abs
Hello user, welcome to the ABS (1.8.0) programming language!
Hello user, welcome to the ABS (1.8.1) programming language!
Type 'quit' when you are done, 'help' if you get lost!
⧐ adder(1, 2)
3
Expand Down
14 changes: 14 additions & 0 deletions docs/types/hash.md
Expand Up @@ -93,6 +93,7 @@ h # {a: 1, b: 2, c: {x: 10, y: 20}, z: {xx: 11, yy: 21}}
## Supported functions

### str()

Returns the string representation of the hash:

``` bash
Expand All @@ -102,6 +103,7 @@ str(h) # "{k: v}"
```

### keys()

Returns an array of keys to the hash.

Note well that only the first level keys are returned.
Expand All @@ -113,6 +115,7 @@ keys(h) # [a, b, c]
```

### values()

Returns an array of values in the hash.

Note well that only the first level values are returned.
Expand All @@ -124,6 +127,7 @@ values(h) # [1, 2, 3]
```

### items()

Returns an array of [key, value] tuples for each item in the hash.

Note well that only the first level items are returned.
Expand All @@ -135,6 +139,7 @@ items(h) # [[a, 1], [b, 2], [c, 3]]
```

### pop(k)

Removes and returns the matching `{"key": value}` item from the hash. If the key does not exist `hash.pop("key")` returns `null`.

Note well that only the first level items can be popped.
Expand All @@ -153,6 +158,15 @@ h # {b: 2}

```

## User-defined functions

A useful property of being able to assign keys of any type to an hash
results in the ability to define objects with custom functions, such as:

``` bash
hash = {"greeter": f(name) { return "Hello $name!" }}
hash.greeter("Sally") # "Hello Sally!"
```
## Next
Expand Down
13 changes: 13 additions & 0 deletions evaluator/evaluator.go
Expand Up @@ -1020,16 +1020,29 @@ 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 {
// Check if the current object is an hash,
// it might have user-defined functions
hash, isHash := o.(*object.Hash)

// If so, run the user-defined function
if isHash && hash.GetKeyType(method) == object.FUNCTION_OBJ {
pair, _ := hash.GetPair(method)
return applyFunction(tok, pair.Value.(*object.Function), env, args)
}

// Now, check if there is a builtin function with the given name
f, ok := Fns[method]

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

// Make sure the builtin function can be called on the given type
if !util.Contains(f.Types, string(o.Type())) && len(f.Types) != 0 {
return newError(tok, "cannot call method '%s()' on '%s'", method, o.Type())
}

// Magic!
args = append([]object.Object{o}, args...)
return f.Fn(tok, env, args...)
}
Expand Down
27 changes: 23 additions & 4 deletions evaluator/evaluator_test.go
Expand Up @@ -1532,14 +1532,33 @@ func TestHashIndexExpressions(t *testing.T) {
`{}.foo`,
nil,
},
{
`a = {"fn": null}; a.fn`,
nil,
},
{
`a = {"fn": f() { return 1 }}; a.fn()`,
1,
},
{
`a = {"fn": f(x, y) { return y * x }}; a.fn(5, 3)`,
15,
},
{
`a = {}; a.fn()`,
`HASH does not have method 'fn()'`,
},
}

for _, tt := range tests {
evaluated := testEval(tt.input)
integer, ok := tt.expected.(int)
if ok {
testNumberObject(t, evaluated, float64(integer))
} else {

switch result := evaluated.(type) {
case *object.Number:
testNumberObject(t, evaluated, float64(tt.expected.(int)))
case *object.Error:
logErrorWithPosition(t, result.Message, tt.expected)
default:
testNullObject(t, evaluated)
}
}
Expand Down
2 changes: 1 addition & 1 deletion main.go
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/abs-lang/abs/repl"
)

var Version = "1.8.0"
var Version = "1.8.1"

// The ABS interpreter
func main() {
Expand Down
12 changes: 12 additions & 0 deletions object/object.go
Expand Up @@ -362,6 +362,18 @@ func (h *Hash) GetPair(key string) (HashPair, bool) {
return record, ok
}

// GetKeyType returns the type of a given key in the hash.
// If no key is found, it is considered to be a NULL.
func (h *Hash) GetKeyType(k string) ObjectType {
pair, ok := h.GetPair(k)

if !ok {
return NULL_OBJ
}

return pair.Value.Type()
}

func (h *Hash) Inspect() string {
var out bytes.Buffer

Expand Down

0 comments on commit da95e90

Please sign in to comment.