lush

package module
v0.0.0-...-6ae0033 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2020 License: MIT Imports: 4 Imported by: 0

README

Lush

Build StatusGoDoc Go Report Card

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

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 - Multiline string literal
"foo"
`this
is
a
multi
line
string`

Numbers

  • 42, -42 - int values
  • 4.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 be true.
  • || - Requires one of the expressions to be true.
  • == - Equality operator. Uses github.com/google/go-cmp/cmp for comparison.
  • != - Inequality operator. Uses github.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 a string comparison.
  • < - Less than operator. Supports all types with a string comparison.
  • <= - Less than or equal operator. Supports all types with a string comparison.
  • >= - Greater than or equal operator. Supports all types with a string comparison.
  • ~= - Regular expression operator. Supports string 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.

Documentation

Index

Examples

Constants

View Source
const Version = "v0.0.1"

Version of lush

Variables

This section is empty.

Functions

func Exec

func Exec(c *ast.Context, s ast.Script) (*ast.Returned, error)

Exec a script using the specified Context.

Example
c := ast.NewContext(context.Background(), os.Stdout)

in := `return "hi"`

res, err := ExecReader(c, "x.lush", strings.NewReader(in))
if err != nil {
	log.Fatal(err)
}

if res.Value != "hi" {
	log.Fatalf("expected hi got %s", res.Value)
}
Output:

Example (Arrays)
c := ast.NewContext(context.Background(), os.Stdout)

in := `
import "fmt"

a := [1, "a", true, [4, 5, nil]]
for i, v := range a {
	fmt.Println(i, v)
}
	`

_, err := ExecReader(c, "x.lush", strings.NewReader(in))
if err != nil {
	log.Fatal(err)
}
Output:

0 1
1 a
2 true
3 4 5
Example (Assignment)
c := ast.NewContext(context.Background(), os.Stdout)

in := `
import "fmt"

x := 0
func() {
	if true {
		x = 42
	}
}()

fmt.Println(x)`

_, err := ExecReader(c, "x.lush", strings.NewReader(in))
if err != nil {
	log.Fatal(err)
}
Output:

42
Example (CustomHelperOptionalContext)
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.Sprint(res), nil
	}
	return strings.ToUpper(s), nil
})

in := `
import "fmt"

x := "A String"
fmt.Println(myFunc(x)) // A STRING

s := myFunc(x) {
	return "another string"
}
fmt.Println(s) // another string
`

_, err := ExecReader(c, "x.lush", strings.NewReader(in))
if err != nil {
	log.Fatal(err)
}
Output:

A STRING
another string
Example (IfNodes)
c := ast.NewContext(context.Background(), os.Stdout)

in := `
import "fmt"

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")
}
`

_, err := ExecReader(c, "x.lush", strings.NewReader(in))
if err != nil {
	log.Fatal(err)
}
Output:

2 == 2
Example (InfiniteForLoop)
c := ast.NewContext(context.Background(), os.Stdout)

in := `
import "fmt"

for {
	if (i == nil) {
		let i = 0
	}

	i = (i + 1)

	if (i == 4) {
		fmt.Println(i)
		break
	}
}`

_, err := ExecReader(c, "x.lush", strings.NewReader(in))
if err != nil {
	log.Fatal(err)
}
Output:

4
Example (Maps)
c := ast.NewContext(context.Background(), os.Stdout)

in := `
import "fmt"

m := {"a": "b", "h": 1, "foo": "bar", "y": func(x) {}}
for k, v := range m {
	fmt.Println(k, v)
}
	`

_, err := ExecReader(c, "x.lush", strings.NewReader(in))
if err != nil {
	log.Fatal(err)
}
Output:

func ExecFile

func ExecFile(c *ast.Context, filename string) (*ast.Returned, error)

ExecFile will parse and then execute the specified file.

func ExecReader

func ExecReader(c *ast.Context, filename string, r io.Reader) (*ast.Returned, error)

ExecReader will parse the given reader and then execute it.

func Parse

func Parse(filename string, b []byte) (ast.Script, error)

Parse parses the data from b using filename as information in the error messages.

func ParseFile

func ParseFile(filename string) (ast.Script, error)

ParseFile parses the file identified by filename.

func ParseReader

func ParseReader(filename string, r io.Reader) (ast.Script, error)

ParseReader parses the data from r using filename as information in the error messages.

Types

This section is empty.

Directories

Path Synopsis
ast
cmd
Code generated by github.com/gobuffalo/lush.
Code generated by github.com/gobuffalo/lush.
internal
print

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL