Skip to content
/ nj Public

NJ is a simple script engine in golang with Lua-like syntax.

License

Notifications You must be signed in to change notification settings

coyove/nj

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Nov 10, 2022
c7f8db1 · Nov 10, 2022
Nov 10, 2022
Oct 25, 2022
Nov 10, 2022
Nov 4, 2022
Nov 4, 2022
Nov 10, 2022
Nov 7, 2021
Oct 10, 2022
Oct 10, 2022
Nov 10, 2022
Apr 28, 2022
Apr 28, 2022
Oct 25, 2022
Oct 27, 2022
Oct 25, 2022
Oct 25, 2022
Jun 21, 2022
Nov 10, 2022
Sep 29, 2022

Repository files navigation

NJ is a simple script engine written in golang with Lua-like syntax.

(If you are looking for a Lua 5.2 compatible engine, refer to tag v0.2)

Differ from Lua

  • There is no table, instead there are array and object respectively:
    • a=[1, 2, 3].
    • a={a=1, b=2}.
    • Empty array and empty object are true when used as booleans.
  • There are typed array and untyped array:
    • Untyped arrays are generic arrays created by [...], it is the builtin array type.
    • Typed arrays are special arrays created by Go, say []byte:
      • a = bytes(16) creates a 16-byte long []byte.
      • a.append(1) appends 1 to it.
      • a.append(true) will panic.
      • a.untype().append(true) will untype the array into a (new) generic array.
  • Functions are callable objects:
    • function foo() end; assert(type(foo), "object")
    • function foo() end; assert(foo is callable)
  • Closures are created by capturing all symbols seen in the current scope and binding them to the returned function:
    • function foo(a, b) return function(c) return self.a + self.b + c end end
    • assert(foo(1, 2)(3), 6)
  • Syntax of calling functions strictly requires no spaces between the callee and '(':
    • print(1) is the only right way of calling a function.
    • print (1) literally means two things: 1) get value of print and discard it, 2) evaluate (1).
    • Also note that all required arguments must be provided to call a function properly:
      • function foo(a, b) end; foo(1) is invalid.
  • Spacing rule also applies to unary operator -:
    • a = 1-a <=> a = 1 - a means assign the result of 1-a to a.
    • a = 1 -a means assign 1 to a and negate a.
    • a = 1 -a+1 means assign 1 to a and eval -a+1.
    • a = -a means negate a and assign the result to a.
    • a = - a is invalid.
  • There are two ways to write if:
    • if cond then true else false end as a statement.
    • local a = if(cond, true, false) as an expression.
    • if(cond) then ... end is invalid, spaces after if statement is mandatory.
    • if (cond, true, false) is invalid, spaces after if expression is not allowed.
  • To write variadic functions:
    • function foo(a, b...) end; args = [1, 2, 3]; foo(args...).
    • Parameters after ... are optional:
      • function foo(a ... b, c) end; foo(1); foo(1, 2); foo(1, 2, 3)
  • Returning multiple arguments will be translated into returning an array, e.g.:
    • function foo() return 1, 2 end <=> function foo() return [1, 2] end
    • local a, b, c = d <=> local a, b, c = d[0], d[1], d[2]
  • Everything starts at ZERO. For-loops start inclusively and end exclusively, e.g.:
    • a=[1, 2]; assert(a[0] == 1).
    • for i=0,n do ... end ranges [0, n-1].
    • for i=n-1,-1,-1 do ... end ranges [n-1, 0].
  • For method function, it can access this which points to the receiver:
    • a={}; function a.foo(x) this.x = x end; a.foo(1); assert(a.x, 1)
  • For any function, use self to get itself in the body:
    • function foo(x) self.x = x end; foo(1); assert(foo.x, 1)
  • You can define up to 32000 variables (varies depending on the number of temporal variables generated by interpreter) in a function.
  • Numbers are int64 + float64 internally, interpreter may promote it to float64 when needed and downgrade it to int64 when possible.
  • You can return anywhere inside functions, continue inside for-loops, goto any label within the same function.

Run

program, err := nj.LoadString("return 1")
v, err := program.Run() // v == 1

Global Values

bas.AddGlobal("G", bas.ValueOf(func() int { return 1 }))

program, _ := nj.LoadString("return G() + 1")
v, err := program.Run() // v == 2

program, _ = nj.LoadString("return G() + 2")
v, err = program.Run() // v == 3

program, _ = nj.LoadString("return G + 2", &CompileOptions{
	Globals: bas.NewObject(0).SetProp("G", 10).ToMap(), // override the global 'G'
})
v, err = program.Run() // v == 12

Benchmarks

Refer to here.