Lush
Lush is an embeddable scripting language for Go with minimal dependencies.
Lush will provide the basis for https://github.com/gobuffalo/plush v4.
WIP: Lush is currently a work in progress. We would love your feedback (and PR's), but be warned that things are liable to shift.
Table of Contents
- CLI
- Comments
- Identifiers
- Variables
- Strings
- Numbers
- Booleans
- If Nodes
- Operators
- Arrays
- Maps
- For Loops
- Functions
- Returns
- Scope
- Custom Helpers
- Errors
- Goroutines
- Calling
- Helpers
- Imports
- Built-ins
CLI
Installation
$ go get -u github.com/gobuffalo/lush/cmd/lush
Usage
Run Lush Scripts
$ lush run ./path/to/file.lush
Format Lush Scripts
$ lush fmt ./path/to/file.lush
Flags
-w
- write result to (source) file instead of stdout-d
- display diffs instead of rewriting files
Print AST
$ lush ast ./path/to/file.lush
Comments
// my comment
// (Deprecated):
# my comment
Identifiers
- must start with
[a-zA-z]
[0-9]
is allowed after first char_
is allowed after first char
// Valid
a := 1
a1 := 1
ab1 := 1
ab1a := 1
ab1c2 := 1
a_1 := 1
a_1_b := 1
a_B_1_C_2 := 1
// Invalid (not a complete list - just examples)
true := 1
false := 1
nil := 1
_x := 1
1x := 1
x! := 1
Variables
TODO:
- Allow multiple variable assignment:
x, y := a, b
:=
(Declaration and Assignment)
The :=
operator will create a new variable and assign it the given value. If the variable already exists in the context, an error will be returned
a := 1
let
(Declaration and/or Assignment)
The let
keyword will create a new variable, if it doesn't already exist, and assign the given value to it. If the variable already exists its value will be replaced with the new value.
let a = 1
=
(Assignment)
The =
will assign a new value to an existing variable. If the variable doesn't already exist in the context an error will be returned.
When assigning to an existing variable it's value will be modified in all parent contexts that contain that variable.
x := 0
func() {
if true {
x = 42
}
}()
return x // 42
Strings
"my string"
- Interpreted string literal
- Multiline string literalmultiline string
"foo"
`this
is
a
multi
line
string`
Numbers
42
,-42
-int
values4.2
,-4.2
-float64
values
Booleans
true
false
Nil
nil
if
Nodes
// with bool
if (true) {
// do work
}
// check equality
if (a == b) {
// do work
}
// optional parens
if a == b {
// do work
}
// var declaration as pre-condition
if a := 1; a == 1 {
return true
}
else
Node
An if
statement can only have one else
statement attached to it.
if (false) {
fmt.Println("in if")
} else {
fmt.Println("in else")
}
else if
Nodes
An if
statement can have N
else if
statements.
if false {
fmt.Println("in if")
} else if (1 == 2) {
fmt.Println("in else")
} else if true {
fmt.Println("2 == 2")
} else {
fmt.Println("in other else")
}
Operators
&&
- Requires both expressions to betrue
.||
- Requires one of the expressions to betrue
.==
- Equality operator. Usesgithub.com/google/go-cmp/cmp
for comparison.!=
- Inequality operator. Usesgithub.com/google/go-cmp/cmp
for comparison.+
- Adds statements together. Supports number types (int
,float64
), string concatenation, and array appending.-
- Subtracts statements. Supports only number types (int
,float64
)/
- Divides statements. Supports only number types (int
,float64
)*
- Multiplys statements. Supports only number types (int
,float64
)%
- Modulus operator. Supports only number types (int
,float64
)>
- Greater than operator. Supports all types with astring
comparison.<
- Less than operator. Supports all types with astring
comparison.<=
- Less than or equal operator. Supports all types with astring
comparison.>=
- Greater than or equal operator. Supports all types with astring
comparison.~=
- Regular expression operator. Supportsstring
type only."abc" ~= "^A"
Arrays
Arrays are backed by []interface{}
so they can contain any known type.
Defining
a := [1, "a", true, [4, 5, nil], {"x": "y", "z": "Z"}]
Iterating
There are multiple ways to iterate over an array.
// `x` is `index` of array
for x := range [1, 2, 3] {
// do work
}
// `i` is index of loop, `x` is `value` of array
for i, x := range myArray {
// do work
}
// `x` is `value` of array
for (x) in [1, 2, 3] {
fmt.Println(x, y)
}
// `i` is index of loop, `x` is `value` of array
for (i, x) in myArray {
// do work
}
Maps
Maps are backed by map[string]interface{}
and can contain any legal Go value.
Defining
j := {"a": "b", "h": 1, "foo": "bar", "y": func(x) {}}
Iterating
// `v` is `value` of the map
for v := range {foo: "bar", "x": 1} {
// do work
}
// `k` is key, `v` is `value` of the map
for k, v := range myMap {
// do work
}
// `v` is `value` of the map
for (v) in [1, 2, 3] {
fmt.Println(v)
}
// `k` is key, `v` is `value` of the map
for (k, v) in myMap {
// do work
}
For Loops
break
The break
keyword works with both Maps and Arrays.
for x := range [1, 2, 3] {
// do work
break
}
for v := range {foo: "bar", "x": 1} {
// do work
break
}
continue
The continue
keyword works with both Maps and Arrays.
for x := range [1, 2, 3] {
// do work
break
}
for v := range {foo: "bar", "x": 1} {
// do work
break
}
Infinite
for {
if (i == nil) {
let i = 0
}
i = (i + 1)
if (i == 4) {
return i // breaks the loop and returns `i`
}
}
Iterators
The range
keyword supports an Iterator
interface.
type Iterator interface {
Next() interface{}
}
Functions
Functions can be defined using the func
keyword. They can take and return N
arguments.
myFunc := func(x) {
return strings.ToUpper(x)
}
x := myFunc("hi")
fmt.fmt.Printlnln(x) // HI
Returns
The return
keyword can return N
number of items.
return 1, "A", true
Inside Functions
A return
inside of a function can be used to return a value from the function. This will not stop the execution of the script.
f := func() {
return 42 // returns 42 to when the function is executed
}
f() // does not exit the script
Outside Functions
A return
outside of a function will be returned automatically, and the execution of the script will stop.
if true {
return 42 // returns 42 to the caller
}
Scope
When a new code block, defined by { ... }
, is called, a new clone of the current Context is created for that block.
Custom Helpers
Custom helper functions can be added to the Context before the script is executed.
c := ast.NewContext(context.Background(), os.Stdout)
c.Set("myFunc", func(s string) string {
return strings.ToUpper(s)
})
x := "a string"
fmt.fmt.Printlnln(myFunc(x)) // A STRING
Optional Map
Custom helper functions can also take an optional map of type map[string]interface{}
as the last, or second to last, argument.
c := ast.NewContext(context.Background(), os.Stdout)
c.Set("myFunc", func(s string, opts map[string]interface{}) string {
if _, ok := opts["lower"]; ok {
return strings.ToLower(s)
}
return strings.ToUpper(s)
})
x := "A String"
fmt.fmt.Printlnln(myFunc(x)) // A STRING
fmt.fmt.Printlnln(myFunc(x, {lower: true})) // a string
Optional Context
Custom helper functions can gain access to the current Context by accepting it as an optional last argument.
c := ast.NewContext(context.Background(), os.Stdout)
c.Set("myFunc", func(s string, c *ast.Context) (string, error) {
if c.Block != nil {
res, err := c.Block.Exec(c)
if err != nil {
return "", err
}
return fmt.Sfmt.Println(res), nil
}
return strings.ToUpper(s), nil
})
x := "A String"
fmt.fmt.Printlnln(myFunc(x)) // A STRING
s := myFunc(x) {
return "another string"
}
fmt.fmt.Printlnln(s) // another string
Errors
First you must import the fmt
built-in then you can use the github.com/gobuffalo/lush/builtins#Fmt.Errorf
function to create a new error.
import "fmt"
return fmt.Errorf("stop %s", "dragging my heart around")
Goroutines
When using Goroutines within a Lush script, Lush will wait until all Goroutines have completed before exiting.
go func() {
let d = time.ParseDuration("1s")
i := 0
for {
fmt.Println("xxx")
time.Sleep(d)
i = (i + 1)
if (i == 5) {
break
}
}
}()
Calling
Helpers
Imports
Imports differ from helpers in that helpers are automatically available inside of a script, whereas imports need to be explicitly included.
For example to use the built-in implementation of the fmt
package, you would first import "fmt"
.
import "fmt"
fmt.Println("foo")
See github.com/gobuffalo/lush/builtins#Available
for a full list of packages that are available for import.
Adding Imports
To make something available for import, it must first be added to github.com/gobuffalo/lush/ast#Context.Imports
.
c := ast.NewContext(context.Background(), os.Stdout)
c.Imports.Store("mypkg", mypkg{})
CLI Imports
When running a Lush script using the CLI tool, the -import
flag allows for making the built-in package implementations available for importing into the script.
Of example the github.com/gobuffalo/lush/builtins#OS
built-in isn't include by default for security/safety reasons. To allow this to be imported by the script you can use the -import
flag to allow access.
$ lush run -import os ./examples/big.lush
Built-ins
The github.com/gobuffalo/lush/builtins
package provides implementations of a small set of the Go standard library.