Skip to content

Commit

Permalink
more documentation, improve readme
Browse files Browse the repository at this point in the history
  • Loading branch information
xrstf committed Nov 23, 2023
1 parent 7894465 commit 56ac41f
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 40 deletions.
66 changes: 31 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ Rudi is a Lisp-based, embeddable programming language that focuses on transformi
like those available in JSON (numbers, bools, objects, vectors etc.). A statement in Rudi looks like

```lisp
(set .foo[0] (+ (len .users) 42))
(set! .foo[0] (+ (len .users) 42))
```

Rudi has been named after my grandfather.

## Features

* **Safe** evaluation: Rudi is not Turing-complete and so Rudi programs are always guaranteed to
Expand Down Expand Up @@ -64,27 +62,12 @@ import (
"go.xrstf.de/rudi"
)

const script = `(+ $myvar 42 .foo)`
const script = `(+ $myvar 42 .foo) (set! .foo 42)`

func main() {
// setup the set of variables available by default in the script
vars := rudi.NewVariables().
Set("myvar", 42)

// Likewise, setup the functions available (note that this includes functions like "if" and "and",
// so running with an empty function set is generally not advisable).
funcs := rudi.NewBuiltInFunctions()

// Rudi programs are meant to manipulate a document (path expressions like ".foo" resolve within
// that document). The document can be anything, but is most often a JSON object.
documentData := map[string]any{"foo": 9000}
document, err := rudi.NewDocument(documentData)
if err != nil {
log.Fatalf("Cannot use %v as the document: %v", documentData, err)
}

// combine document, variables and functions into an execution context
ctx := rudi.NewContext(document, funcs, vars)

// parse the script (the name is used when generating error strings)
program, err := rudi.ParseScript("myscript", script)
Expand All @@ -94,15 +77,21 @@ func main() {

// evaluate the program;
// this returns an evaluated value, which is the result of the last expression that was evaluated,
// plus a new context, which contains for example newly set runtime variables; in many cases the
// new context is not that important and you'd focus on the evaluated value.
newCtx, evaluated, err := rudi.RunProgram(ctx, program)
// plus the final document state (the updatedData) after the script has finished.
updatedData, result, err := program.Run(
documentData,
// setup the set of variables available by default in the script
rudi.NewVariables().Set("myvar", 42),
// Likewise, setup the functions available (note that this includes functions like "if" and "and",
// so running with an empty function set is generally not advisable).
rudi.NewBuiltInFunctions(),
)
if err != nil {
log.Fatalf("Failed to evaluate script: %v", err)
log.Fatalf("Script failed: %v", err)
}

fmt.Println(evaluated)
fmt.Println(newCtx)
fmt.Println(result) // => 9084
fmt.Println(updatedData) // => {"foo": 42}
}
```

Expand All @@ -111,20 +100,27 @@ func main() {
Rudi doesn't exist in a vacuum; there are many other great embeddable programming/scripting languages
out there, allbeit with slightly different ideas and goals than Rudi:

* [Anko](https://github.com/mattn/anko) – Go-like syntax, allows recursion
* [ECAL](https://github.com/krotik/ecal) – event-based systems using rules which are triggered by
events, allows recursion
* [Anko](https://github.com/mattn/anko) – Go-like syntax and allows recursion, making it more
dangerous and hard to learn for non-developers than I'd like.
* [ECAL](https://github.com/krotik/ecal) – Is an event-based syste using rules which are triggered by
events; comes with recursion as well and is therefore out.
* [Expr](https://github.com/antonmedv/expr), [GVal](https://github.com/PaesslerAG/gval),
[CEL](https://github.com/google/cel-go) – great languages for writing a single expression, but not
suitable for transforming/mutating data structures
* [Gentee](https://github.com/gentee/gentee) – similar to C/Python, allows recursion
* [Jsonnet](https://github.com/google/go-jsonnet) – not built for manipulating existing objects, but
for assembling new objects
* [Starlark](https://github.com/google/starlark-go) – language behind Bazel, has optional
nun-Turing-complete mode
[CEL](https://github.com/google/cel-go) – Great languages for writing a single expression, but not
suitable for transforming/mutating data structures.
* [Gentee](https://github.com/gentee/gentee) – Is similar to C/Python and allows recursion, so both
to powerful/dangerous and not my preference in terms of syntax.
* [Jsonnet](https://github.com/google/go-jsonnet) – Probably one of the most obvious alternatives
among this list. Jsonnet shines when constructing new elements and complexer configurations
out of smaller pieces of information, less so when manipulating objects. Also I personally really
am no fan of Jsonnet's syntax, plus: NIH.
* [Starlark](https://github.com/google/starlark-go) – Is the language behind Bazel and actually has
an optional nun-Turing-complete mode. However I am really no fan of its syntax and have not
investigated it further.

## Credits

Rudi has been named after my grandfather.

Thanks to [@embik](https://github.com/embik) and [@xmudrii](https://github.com/xmudrii) for enduring
my constant questions for feedback :smile:

Expand Down
23 changes: 20 additions & 3 deletions aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,55 @@ import (
"go.xrstf.de/rudi/pkg/eval/types"
)

// alias types

// Context is the evaluation context for a Rudi program, consisting of
// the global document, variables and functions.
type Context = types.Context

// Variables is a map of Rudi variables.
type Variables = types.Variables

// Functions is a map of Rudi functions.
type Functions = types.Functions

// Function is a single Rudi function, available to be used inside a Rudi script.
type Function = types.Function

// Document is the global document that is being processed by a Rudi script.
type Document = types.Document

// NewContext wraps the document, variables and functions into a Context.
func NewContext(doc Document, variables Variables, funcs Functions) Context {
return types.NewContext(doc, variables, funcs)
}

// NewFunctions returns an empty set of runtime functions.
func NewFunctions() Functions {
return types.NewFunctions()
}

// NewBuiltInFunctions returns a copy of the built-in Rudi functions.
func NewBuiltInFunctions() Functions {
return builtin.Functions.DeepCopy()
}

// NewVariables returns an empty set of runtime variables.
func NewVariables() Variables {
return types.NewVariables()
}

// NewDocument wraps any sort of data as a Rudi document.
func NewDocument(data any) (Document, error) {
return types.NewDocument(data)
}

func UnwrapType(val any) (any, error) {
// Unwrap returns the native Go value for either native Go values or an
// Rudi AST node (like turning an ast.Number into an int64).
func Unwrap(val any) (any, error) {
return types.UnwrapType(val)
}

// WrapNative returns the Rudi node equivalent of a native Go value, like turning
// a string into ast.String.
func WrapNative(val any) (any, error) {
return types.WrapNative(val)
}
4 changes: 4 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ import (
"go.xrstf.de/rudi/pkg/lang/parser"
)

// ParseErrors can occur while parsing a Rudi program.
type ParseError struct {
script string
err error
}

var _ error = ParseError{}

// Error returns the underlying parse error.
func (p ParseError) Error() string {
return p.err.Error()
}

// Snippet is the line of the program where the error occured, marked with a
// caret and the error message in a second line below that.
func (p ParseError) Snippet() string {
var lister parser.ErrorLister
if errors.As(p.err, &lister) {
Expand Down
4 changes: 2 additions & 2 deletions program.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (p *rudiProgram) Run(data any, variables Variables, funcs Functions) (docum
// get current state of the document
docData := finalCtx.GetDocument().Data()

unwrappedDocData, err := UnwrapType(docData)
unwrappedDocData, err := Unwrap(docData)
if err != nil {
// this should never happen
return nil, nil, fmt.Errorf("failed to unwrap final document data: %w", err)
Expand All @@ -105,7 +105,7 @@ func (p *rudiProgram) RunContext(ctx Context) (finalCtx Context, result any, err
return ctx, nil, err
}

unwrappedResult, err := UnwrapType(result)
unwrappedResult, err := Unwrap(result)
if err != nil {
// this should never happen
return ctx, nil, fmt.Errorf("failed to unwrap result: %w", err)
Expand Down

0 comments on commit 56ac41f

Please sign in to comment.