Skip to content

Commit

Permalink
allow more than one expression in user-defined functions (automatical…
Browse files Browse the repository at this point in the history
…ly assume a "(do...)")
  • Loading branch information
xrstf committed Jan 3, 2024
1 parent 0111ec3 commit 66b1b02
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 11 deletions.
Binary file modified cmd/rudi/docs/data/functions/func.md.gz
Binary file not shown.
11 changes: 7 additions & 4 deletions docs/stdlib/rudifunc/func.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ embedding Rudi into other Go applications.
`func!` creates a new function, which can be used in all subseqeuent statements
in the same Rudi program. Function creation is bound to its parent scope, so
defining a function within an `if` statement for example will make the function
available onside inside that statement, not globally.
available only inside that statement, not globally.

Dynamically defined functions cannot overwrite statically defined functions, i.e.
you cannot define a custom `concat` function using `func!` if `concat` already
Expand All @@ -34,6 +34,10 @@ Since defining a new function is a side effect, `func` must always be used with
the bang modifier (`func!`). The behaviour of Rudi programs that use `func`
without the bang modifier is undefined.

Functions can contain an arbitrary number of expressions, which will be evaluated
in sequence and share a single context. This means user-defined functions form
a "sub-program" the same way the [`do`](core-do.md) function does.

## Examples

```
Expand All @@ -56,12 +60,11 @@ inject a Go function statically into Rudi instead of defining it at runtime.

## Forms

### `(func! name:identifier params:vector body:expression)``null`
### `(func! name:identifier params:vector body:expression)``null`

* `name` is an identifier giving the function its name.
* `params` is a vector containing identifiers that hold the parameter names.
* `body` is a single expression (use `do` for multiple statements) that forms
the function body.
* `body` is one or more expressions that form the function body.

This form will create a new function called `name` with as many parameters as
`params` has identifiers. `params` can be empty, but must otherwise contain only
Expand Down
11 changes: 7 additions & 4 deletions pkg/builtin/rudifunc/docs/func.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ embedding Rudi into other Go applications.
`func!` creates a new function, which can be used in all subseqeuent statements
in the same Rudi program. Function creation is bound to its parent scope, so
defining a function within an `if` statement for example will make the function
available onside inside that statement, not globally.
available only inside that statement, not globally.

Dynamically defined functions cannot overwrite statically defined functions, i.e.
you cannot define a custom `concat` function using `func!` if `concat` already
Expand All @@ -34,6 +34,10 @@ Since defining a new function is a side effect, `func` must always be used with
the bang modifier (`func!`). The behaviour of Rudi programs that use `func`
without the bang modifier is undefined.

Functions can contain an arbitrary number of expressions, which will be evaluated
in sequence and share a single context. This means user-defined functions form
a "sub-program" the same way the [`do`](core-do.md) function does.

## Examples

```
Expand All @@ -56,12 +60,11 @@ inject a Go function statically into Rudi instead of defining it at runtime.

## Forms

### `(func! name:identifier params:vector body:expression)``null`
### `(func! name:identifier params:vector body:expression)``null`

* `name` is an identifier giving the function its name.
* `params` is a vector containing identifiers that hold the parameter names.
* `body` is a single expression (use `do` for multiple statements) that forms
the function body.
* `body` is one or more expressions that form the function body.

This form will create a new function called `name` with as many parameters as
`params` has identifiers. `params` can be empty, but must otherwise contain only
Expand Down
20 changes: 17 additions & 3 deletions pkg/builtin/rudifunc/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var (

// funcFunction should never be called without the bang modifier, as without it, the created function
// just instantly vanishes into thin air.
func funcFunction(ctx types.Context, name ast.Expression, namingVector ast.Expression, body ast.Expression) (any, error) {
func funcFunction(ctx types.Context, name ast.Expression, namingVector ast.Expression, body ...ast.Expression) (any, error) {
nameIdent, ok := name.(ast.Identifier)
if !ok {
return nil, fmt.Errorf("first argument must be an identifier that specifies the function name, but got %T instead", name)
Expand Down Expand Up @@ -60,7 +60,7 @@ func funcBangHandler(ctx types.Context, originalArgs []ast.Expression, value any
type rudispaceFunc struct {
name string
params []string
body ast.Expression
body []ast.Expression
}

var _ types.Function = rudispaceFunc{}
Expand All @@ -84,7 +84,21 @@ func (f rudispaceFunc) Evaluate(ctx types.Context, args []ast.Expression) (any,
funcArgs[paramName] = arg
}

_, result, err := ctx.Runtime().EvalExpression(ctx.WithVariables(funcArgs), f.body)
// user-defined functions form a sub-program and all statements share the same context
funcCtx := ctx.WithVariables(funcArgs)
runtime := ctx.Runtime()

var (
result any
err error
)

for _, expr := range f.body {
funcCtx, result, err = runtime.EvalExpression(funcCtx, expr)
if err != nil {
return nil, err
}
}

return result, err
}

0 comments on commit 66b1b02

Please sign in to comment.