Skip to content

Commit

Permalink
cleanup API, get rid of LiteralFunctions again
Browse files Browse the repository at this point in the history
  • Loading branch information
xrstf committed Dec 5, 2023
1 parent 12194f9 commit 063a5f5
Show file tree
Hide file tree
Showing 16 changed files with 263 additions and 321 deletions.
29 changes: 13 additions & 16 deletions aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ package rudi
import (
"go.xrstf.de/rudi/pkg/builtin"
"go.xrstf.de/rudi/pkg/coalescing"
"go.xrstf.de/rudi/pkg/eval/functions"
"go.xrstf.de/rudi/pkg/eval/types"
"go.xrstf.de/rudi/pkg/eval/util"
)

// Context is the evaluation context for a Rudi program, consisting of
Expand Down Expand Up @@ -61,21 +61,18 @@ func NewDocument(data any) (Document, error) {
return types.NewDocument(data)
}

// RawFunction is a function that receives its raw, unevaluated child expressions as arguments.
// This is the lowest level a function can be, allowing to selectively evaluate the arguments to
// control side effects.
type RawFunction = util.RawFunction

// NewRawFunction wraps a raw function to be used in Rudi.
func NewRawFunction(f RawFunction, description string) util.Function {
return util.NewRawFunction(f, description)
// NewFunctionBuilder is the recommended way to define new Rudi functions. The function builder can
// take multiple forms (e.g. if you have (foo INT) and (foo STRING)) and will create a function that
// automatically evaluates and coalesces Rudi expressions and matches them to the given forms. The
// first matching form is then evaluated.
func NewFunctionBuilder(forms ...any) *functions.Builder {
return functions.NewBuilder(forms...)
}

// LiteralFunction is a function that receives all of its arguments already evaluated, but not yet
// coalesced into specific types.
type LiteralFunction = util.LiteralFunction

// NewLiteralFunction wraps a literal function to be used in Rudi.
func NewLiteralFunction(f LiteralFunction, description string) util.Function {
return util.NewLiteralFunction(f, description)
// NewLowLevelFunction wraps a raw tuple function to be used in Rudi. This is mostly useful for
// defining really low-level functions and functions with special side effects. Most of the time,
// you'd want to use NewFunctionBuilder(), which will use reflection to make it much more straight
// forward to make a Go function available in Rudi.
func NewLowLevelFunction(f types.TupleFunction, description string) Function {
return types.NewFunction(f, description)
}
18 changes: 4 additions & 14 deletions pkg/builtin/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,20 +194,10 @@ func setFunction(ctx types.Context, target, value ast.Expression) (any, error) {

// (delete VAR:Variable)
// (delete EXPR:PathExpression)
type deleteFunction struct{}

func (deleteFunction) Description() string {
return "removes a key from an object or an item from a vector"
}

func (deleteFunction) Evaluate(ctx types.Context, args []ast.Expression) (any, error) {
if size := len(args); size != 1 {
return nil, fmt.Errorf("expected 1 argument, got %d", size)
}

symbol, ok := args[0].(ast.Symbol)
func deleteFunction(ctx types.Context, expr ast.Expression) (any, error) {
symbol, ok := expr.(ast.Symbol)
if !ok {
return nil, fmt.Errorf("argument #0 is not a symbol, but %T", args[0])
return nil, fmt.Errorf("argument #0 is not a symbol, but %T", expr)
}

// catch symbols that are technically invalid
Expand Down Expand Up @@ -249,7 +239,7 @@ func (deleteFunction) Evaluate(ctx types.Context, args []ast.Expression) (any, e
return updatedValue, nil
}

func (deleteFunction) BangHandler(ctx types.Context, sym ast.Symbol, value any) (types.Context, any, error) {
func deleteBangHandler(ctx types.Context, sym ast.Symbol, value any) (types.Context, any, error) {
updatedValue := value

// if the symbol has a path to traverse, do so
Expand Down
151 changes: 80 additions & 71 deletions pkg/builtin/functions.go

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 Christoph Mewes
// SPDX-License-Identifier: MIT

package native
package functions

import (
"reflect"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 Christoph Mewes
// SPDX-License-Identifier: MIT

package native
package functions

import (
"testing"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 Christoph Mewes
// SPDX-License-Identifier: MIT

package native
package functions

import (
"errors"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 Christoph Mewes
// SPDX-License-Identifier: MIT

package native
package functions

import (
"testing"
Expand Down
68 changes: 68 additions & 0 deletions pkg/eval/functions/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: 2023 Christoph Mewes
// SPDX-License-Identifier: MIT

package functions

import (
"fmt"

"go.xrstf.de/rudi/pkg/coalescing"
"go.xrstf.de/rudi/pkg/eval/types"
)

type Builder struct {
forms []form
coalescer coalescing.Coalescer
bangHandler BangHandlerFunc
description string
}

func NewBuilder(forms ...any) *Builder {
b := &Builder{
forms: []form{},
}

for _, f := range forms {
b.AddForm(f)
}

return b
}

func (b *Builder) AddForm(form any) *Builder {
funcForm, err := newForm(form)
if err != nil {
panic(fmt.Sprintf("invalid form: %v", err))
}

b.forms = append(b.forms, funcForm)
return b
}

func (b *Builder) WithDescription(s string) *Builder {
b.description = s
return b
}

func (b *Builder) WithCoalescer(c coalescing.Coalescer) *Builder {
b.coalescer = c
return b
}

func (b *Builder) WithBangHandler(h BangHandlerFunc) *Builder {
b.bangHandler = h
return b
}

func (b *Builder) Build() types.Function {
f := newRegularFunction(b.forms, b.coalescer, b.description)

if b.bangHandler != nil {
return &extendedFunction{
regularFunction: f,
bangHandler: b.bangHandler,
}
}

return &f
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 Christoph Mewes
// SPDX-License-Identifier: MIT

package native
package functions

import (
"go.xrstf.de/rudi/pkg/eval"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 Christoph Mewes
// SPDX-License-Identifier: MIT

package native
package functions

import (
"fmt"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: 2023 Christoph Mewes
// SPDX-License-Identifier: MIT

package native
package functions

import (
"errors"
Expand Down
72 changes: 72 additions & 0 deletions pkg/eval/functions/function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: 2023 Christoph Mewes
// SPDX-License-Identifier: MIT

package functions

import (
"errors"
"fmt"

"go.xrstf.de/rudi/pkg/coalescing"
"go.xrstf.de/rudi/pkg/eval"
"go.xrstf.de/rudi/pkg/eval/types"
"go.xrstf.de/rudi/pkg/lang/ast"
)

type regularFunction struct {
forms []form
coalescer coalescing.Coalescer
description string
}

var _ types.Function = &regularFunction{}

func newRegularFunction(forms []form, coalescer coalescing.Coalescer, description string) regularFunction {
return regularFunction{
forms: forms,
coalescer: coalescer,
description: description,
}
}

func (b *regularFunction) Description() string {
return b.description
}

func (b *regularFunction) Evaluate(ctx types.Context, args []ast.Expression) (any, error) {
cachedArgs := convertArgs(args)

if b.coalescer != nil {
ctx = ctx.WithCoalescer(b.coalescer)
}

for i, form := range b.forms {
matched, err := form.Match(ctx, cachedArgs)
if err != nil {
return nil, fmt.Errorf("form#%d: %w", i, err)
}

if matched {
return form.Call(ctx)
}
}

return nil, errors.New("none of the available forms matched the given expressions")
}

type BangHandlerFunc func(ctx types.Context, sym ast.Symbol, value any) (types.Context, any, error)

type extendedFunction struct {
regularFunction

bangHandler BangHandlerFunc
}

var (
_ types.Function = &extendedFunction{}
_ eval.BangHandler = &extendedFunction{}
)

func (f *extendedFunction) BangHandler(ctx types.Context, sym ast.Symbol, value any) (types.Context, any, error) {
return f.bangHandler(ctx, sym, value)
}
23 changes: 17 additions & 6 deletions pkg/eval/test/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,36 @@
package test

import (
"fmt"

"go.xrstf.de/rudi/pkg/eval"
"go.xrstf.de/rudi/pkg/eval/types"
"go.xrstf.de/rudi/pkg/eval/util"
"go.xrstf.de/rudi/pkg/lang/ast"
)

var (
dummyFunctions = types.Functions{
"eval": util.NewLiteralFunction(func(ctx types.Context, args []any) (any, error) {
return args[0], nil
}, "").MinArgs(1).MaxArgs(1),
"eval": types.NewFunction(func(ctx types.Context, args []ast.Expression) (any, error) {
if len(args) != 1 {
return nil, fmt.Errorf("expected 1 argument, got %d", len(args))
}

_, value, err := eval.EvalExpression(ctx, args[0])

return value, err
}, "evaluates the given expression and returns its value"),

// Funny enough, due to the way functions work in Rudi, "set" does not
// actually set anything, it relies on the function magic behind the
// scenes to handle the bang modifier.
"set": util.NewRawFunction(func(ctx types.Context, args []ast.Expression) (any, error) {
"set": types.NewFunction(func(ctx types.Context, args []ast.Expression) (any, error) {
if len(args) != 2 {
return nil, fmt.Errorf("expected 2 arguments, got %d", len(args))
}

_, value, err := eval.EvalExpression(ctx, args[1])

return value, err
}, "").MinArgs(2).MaxArgs(2),
}, "sets a variable or accesses the global document, most often used with the bang modifier"),
}
)
3 changes: 2 additions & 1 deletion pkg/eval/types/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ type basicFunc struct {
}

// NewFunction creates the lowest of low level functions in Rudi and should rarely be used by
// integrators/library developers. Use the helpers in the root package instead.
// integrators/library developers. Use the helpers in the root package to define functions
// using reflection and pattern matching instead.
func NewFunction(f TupleFunction, description string) Function {
return basicFunc{
f: f,
Expand Down
Loading

0 comments on commit 063a5f5

Please sign in to comment.