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 arearray
andobject
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 anduntyped
array:- Untyped arrays are generic arrays, where any values can be stored inside.
- Typed arrays are special arrays from 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)
willuntype
the array into a (new) generic array.
- Functions are callable objects:
function foo() end; print(type(foo))
printsobject
.function foo() end; print(foo is callable)
printstrue
.
- Functions should be declared in the topmost scope:
do function foo() ... end end
is invalid.if true then function foo() function bar() ... end end
is invalid.function foo() function bar() ... end end
is invalid.
- Use
lambda
to declare anonymous functions:local foo = lambda(x) ... end
.- Last
return
can be omitted:lambda (x) x=x+1; x end <=> lambda (x) x=x+1; return x end
.
- Syntax of calling functions strictly requires no spaces between callee and '(':
print(1)
is the only right way of calling a function.print (1)
literally means two things: 1) get value ofprint
and discard it, 2) evaluate(1)
.
- Same rule applies to unary operator
-
:a = 1-a <=> a = 1 - a
means assign the result of1-a
toa
.a = 1 -a
means assign1
toa
and negatea
.a = 1 -a+1
means assign1
toa
and eval-a+1
.a = -a
means negatea
and assign the result toa
.a = - a
is invalid.
- There 2 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 afterif
statement is mandatory.if (cond, true, false)
is invalid, spaces afterif
expression is not allowed.
- To write variadic functions:
function foo(a, b...) end
.args = [1, 2, 3]; foo(args...)
.
- Simple keyword arguments syntax sugar:
foo(a, b=2, c=3)
will be converted tofoo(a, {b=2, c=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]
.
- Every function has a
this
initially pointing to itself, if the function was loaded from an object,this
will point to it instead:function foo(x) this.x = x end; foo(1); assert(foo.x, 1)
a={foo=lambda(x) this.x = x end}; a.foo(1); assert(a.x, 1)
- Use
debug.self()
to always get the caller itself:a={foo=lambda(x) debug.self().x = x end}; a.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 tofloat64
when needed and downgrade it toint64
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.Globals.SetProp("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), // override the global 'G'
})
v, err = program.Run() // v == 12
Benchmarks
Refer to here.