Compare commits
23 commits
optimizati
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c461a7fe60 | ||
![]() |
47062da36a | ||
![]() |
e9b03930ce | ||
![]() |
9d35005ffe | ||
![]() |
18b953c7be | ||
![]() |
da09c300a5 | ||
![]() |
18424deb5a | ||
![]() |
92cbb9bff0 | ||
![]() |
0177bdb4e1 | ||
![]() |
48308d45d9 | ||
![]() |
55f2519b16 | ||
![]() |
f90bc00af2 | ||
![]() |
82b543fd98 | ||
![]() |
ecc3d9181e | ||
![]() |
3f4245d962 | ||
![]() |
2edd39e0c3 | ||
![]() |
7108e9c50d | ||
![]() |
f980e7e724 | ||
![]() |
a419bfc93a | ||
![]() |
dfcfd6661c | ||
![]() |
8a3f5bdb11 | ||
![]() |
e338512259 | ||
![]() |
6fc8053992 |
27 changed files with 405 additions and 118 deletions
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
@ -14,11 +14,11 @@ jobs:
|
|||
- name: set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.18
|
||||
- name: run goreleaser
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
|||
- name: set up Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.14
|
||||
go-version: 1.18
|
||||
id: go
|
||||
- name: set up Go module cache
|
||||
uses: actions/cache@v1
|
||||
|
@ -29,6 +29,6 @@ jobs:
|
|||
- name: check out code
|
||||
uses: actions/checkout@v2
|
||||
- name: install golint
|
||||
run: go get -u golang.org/x/lint/golint
|
||||
run: go install golang.org/x/lint/golint@latest
|
||||
- name: run tests
|
||||
run: make test
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1 +1,3 @@
|
|||
dist/
|
||||
dist/
|
||||
|
||||
.idea
|
33
compiler.go
33
compiler.go
|
@ -141,25 +141,7 @@ func (c *Compiler) Compile(node parser.Node) error {
|
|||
if node.Token == token.LAnd || node.Token == token.LOr {
|
||||
return c.compileLogical(node)
|
||||
}
|
||||
if node.Token == token.Less {
|
||||
if err := c.Compile(node.RHS); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Compile(node.LHS); err != nil {
|
||||
return err
|
||||
}
|
||||
c.emit(node, parser.OpBinaryOp, int(token.Greater))
|
||||
return nil
|
||||
} else if node.Token == token.LessEq {
|
||||
if err := c.Compile(node.RHS); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Compile(node.LHS); err != nil {
|
||||
return err
|
||||
}
|
||||
c.emit(node, parser.OpBinaryOp, int(token.GreaterEq))
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := c.Compile(node.LHS); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -182,6 +164,10 @@ func (c *Compiler) Compile(node parser.Node) error {
|
|||
c.emit(node, parser.OpBinaryOp, int(token.Greater))
|
||||
case token.GreaterEq:
|
||||
c.emit(node, parser.OpBinaryOp, int(token.GreaterEq))
|
||||
case token.Less:
|
||||
c.emit(node, parser.OpBinaryOp, int(token.Less))
|
||||
case token.LessEq:
|
||||
c.emit(node, parser.OpBinaryOp, int(token.LessEq))
|
||||
case token.Equal:
|
||||
c.emit(node, parser.OpEqual)
|
||||
case token.NotEqual:
|
||||
|
@ -692,12 +678,15 @@ func (c *Compiler) compileAssign(
|
|||
return c.errorf(node, "operator ':=' not allowed with selector")
|
||||
}
|
||||
|
||||
_, isFunc := rhs[0].(*parser.FuncLit)
|
||||
symbol, depth, exists := c.symbolTable.Resolve(ident, false)
|
||||
if op == token.Define {
|
||||
if depth == 0 && exists {
|
||||
return c.errorf(node, "'%s' redeclared in this block", ident)
|
||||
}
|
||||
symbol = c.symbolTable.Define(ident)
|
||||
if isFunc {
|
||||
symbol = c.symbolTable.Define(ident)
|
||||
}
|
||||
} else {
|
||||
if !exists {
|
||||
return c.errorf(node, "unresolved reference '%s'", ident)
|
||||
|
@ -718,6 +707,10 @@ func (c *Compiler) compileAssign(
|
|||
}
|
||||
}
|
||||
|
||||
if op == token.Define && !isFunc {
|
||||
symbol = c.symbolTable.Define(ident)
|
||||
}
|
||||
|
||||
switch op {
|
||||
case token.AddAssign:
|
||||
c.emit(node, parser.OpBinaryOp, int(token.Add))
|
||||
|
|
|
@ -107,12 +107,12 @@ func TestCompiler_Compile(t *testing.T) {
|
|||
concatInsts(
|
||||
tengo.MakeInstruction(parser.OpConstant, 0),
|
||||
tengo.MakeInstruction(parser.OpConstant, 1),
|
||||
tengo.MakeInstruction(parser.OpBinaryOp, 39),
|
||||
tengo.MakeInstruction(parser.OpBinaryOp, 38),
|
||||
tengo.MakeInstruction(parser.OpPop),
|
||||
tengo.MakeInstruction(parser.OpSuspend)),
|
||||
objectsArray(
|
||||
intObject(2),
|
||||
intObject(1))))
|
||||
intObject(1),
|
||||
intObject(2))))
|
||||
|
||||
expectCompile(t, `1 >= 2`,
|
||||
bytecode(
|
||||
|
@ -131,12 +131,12 @@ func TestCompiler_Compile(t *testing.T) {
|
|||
concatInsts(
|
||||
tengo.MakeInstruction(parser.OpConstant, 0),
|
||||
tengo.MakeInstruction(parser.OpConstant, 1),
|
||||
tengo.MakeInstruction(parser.OpBinaryOp, 44),
|
||||
tengo.MakeInstruction(parser.OpBinaryOp, 43),
|
||||
tengo.MakeInstruction(parser.OpPop),
|
||||
tengo.MakeInstruction(parser.OpSuspend)),
|
||||
objectsArray(
|
||||
intObject(2),
|
||||
intObject(1))))
|
||||
intObject(1),
|
||||
intObject(2))))
|
||||
|
||||
expectCompile(t, `1 == 2`,
|
||||
bytecode(
|
||||
|
@ -204,11 +204,11 @@ func TestCompiler_Compile(t *testing.T) {
|
|||
expectCompile(t, `if true { 10 }; 3333`,
|
||||
bytecode(
|
||||
concatInsts(
|
||||
tengo.MakeInstruction(parser.OpTrue), // 0000
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 8), // 0001
|
||||
tengo.MakeInstruction(parser.OpConstant, 0), // 0004
|
||||
tengo.MakeInstruction(parser.OpPop), // 0007
|
||||
tengo.MakeInstruction(parser.OpConstant, 1), // 0008
|
||||
tengo.MakeInstruction(parser.OpTrue), // 0000
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 10), // 0001
|
||||
tengo.MakeInstruction(parser.OpConstant, 0), // 0004
|
||||
tengo.MakeInstruction(parser.OpPop), // 0007
|
||||
tengo.MakeInstruction(parser.OpConstant, 1), // 0008
|
||||
tengo.MakeInstruction(parser.OpPop),
|
||||
tengo.MakeInstruction(parser.OpSuspend)), // 0011
|
||||
objectsArray(
|
||||
|
@ -219,10 +219,10 @@ func TestCompiler_Compile(t *testing.T) {
|
|||
bytecode(
|
||||
concatInsts(
|
||||
tengo.MakeInstruction(parser.OpTrue), // 0000
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 11), // 0001
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 15), // 0001
|
||||
tengo.MakeInstruction(parser.OpConstant, 0), // 0004
|
||||
tengo.MakeInstruction(parser.OpPop), // 0007
|
||||
tengo.MakeInstruction(parser.OpJump, 15), // 0008
|
||||
tengo.MakeInstruction(parser.OpJump, 19), // 0008
|
||||
tengo.MakeInstruction(parser.OpConstant, 1), // 0011
|
||||
tengo.MakeInstruction(parser.OpPop), // 0014
|
||||
tengo.MakeInstruction(parser.OpConstant, 2), // 0015
|
||||
|
@ -577,12 +577,12 @@ func TestCompiler_Compile(t *testing.T) {
|
|||
intObject(1),
|
||||
intObject(2),
|
||||
compiledFunction(0, 0,
|
||||
tengo.MakeInstruction(parser.OpTrue), // 0000
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 9), // 0001
|
||||
tengo.MakeInstruction(parser.OpConstant, 0), // 0004
|
||||
tengo.MakeInstruction(parser.OpReturn, 1), // 0007
|
||||
tengo.MakeInstruction(parser.OpConstant, 1), // 0009
|
||||
tengo.MakeInstruction(parser.OpReturn, 1))))) // 0012
|
||||
tengo.MakeInstruction(parser.OpTrue), // 0000
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 11), // 0001
|
||||
tengo.MakeInstruction(parser.OpConstant, 0), // 0004
|
||||
tengo.MakeInstruction(parser.OpReturn, 1), // 0007
|
||||
tengo.MakeInstruction(parser.OpConstant, 1), // 0009
|
||||
tengo.MakeInstruction(parser.OpReturn, 1))))) // 0012
|
||||
|
||||
expectCompile(t, `func() { 1; if(true) { 2 } else { 3 }; 4 }`,
|
||||
bytecode(
|
||||
|
@ -599,10 +599,10 @@ func TestCompiler_Compile(t *testing.T) {
|
|||
tengo.MakeInstruction(parser.OpConstant, 0), // 0000
|
||||
tengo.MakeInstruction(parser.OpPop), // 0003
|
||||
tengo.MakeInstruction(parser.OpTrue), // 0004
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 15), // 0005
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 19), // 0005
|
||||
tengo.MakeInstruction(parser.OpConstant, 1), // 0008
|
||||
tengo.MakeInstruction(parser.OpPop), // 0011
|
||||
tengo.MakeInstruction(parser.OpJump, 19), // 0012
|
||||
tengo.MakeInstruction(parser.OpJump, 23), // 0012
|
||||
tengo.MakeInstruction(parser.OpConstant, 2), // 0015
|
||||
tengo.MakeInstruction(parser.OpPop), // 0018
|
||||
tengo.MakeInstruction(parser.OpConstant, 3), // 0019
|
||||
|
@ -929,10 +929,10 @@ func() {
|
|||
concatInsts(
|
||||
tengo.MakeInstruction(parser.OpConstant, 0),
|
||||
tengo.MakeInstruction(parser.OpSetGlobal, 0),
|
||||
tengo.MakeInstruction(parser.OpConstant, 1),
|
||||
tengo.MakeInstruction(parser.OpGetGlobal, 0),
|
||||
tengo.MakeInstruction(parser.OpBinaryOp, 39),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 31),
|
||||
tengo.MakeInstruction(parser.OpConstant, 1),
|
||||
tengo.MakeInstruction(parser.OpBinaryOp, 38),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 35),
|
||||
tengo.MakeInstruction(parser.OpGetGlobal, 0),
|
||||
tengo.MakeInstruction(parser.OpConstant, 2),
|
||||
tengo.MakeInstruction(parser.OpBinaryOp, 11),
|
||||
|
@ -954,7 +954,7 @@ func() {
|
|||
tengo.MakeInstruction(parser.OpSetGlobal, 1),
|
||||
tengo.MakeInstruction(parser.OpGetGlobal, 1),
|
||||
tengo.MakeInstruction(parser.OpIteratorNext),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 37),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 41),
|
||||
tengo.MakeInstruction(parser.OpGetGlobal, 1),
|
||||
tengo.MakeInstruction(parser.OpIteratorKey),
|
||||
tengo.MakeInstruction(parser.OpSetGlobal, 2),
|
||||
|
@ -973,14 +973,14 @@ func() {
|
|||
tengo.MakeInstruction(parser.OpGetGlobal, 0),
|
||||
tengo.MakeInstruction(parser.OpConstant, 0),
|
||||
tengo.MakeInstruction(parser.OpEqual),
|
||||
tengo.MakeInstruction(parser.OpAndJump, 23),
|
||||
tengo.MakeInstruction(parser.OpAndJump, 25),
|
||||
tengo.MakeInstruction(parser.OpGetGlobal, 0),
|
||||
tengo.MakeInstruction(parser.OpConstant, 1),
|
||||
tengo.MakeInstruction(parser.OpNotEqual),
|
||||
tengo.MakeInstruction(parser.OpOrJump, 34),
|
||||
tengo.MakeInstruction(parser.OpConstant, 1),
|
||||
tengo.MakeInstruction(parser.OpOrJump, 38),
|
||||
tengo.MakeInstruction(parser.OpGetGlobal, 0),
|
||||
tengo.MakeInstruction(parser.OpBinaryOp, 39),
|
||||
tengo.MakeInstruction(parser.OpConstant, 1),
|
||||
tengo.MakeInstruction(parser.OpBinaryOp, 38),
|
||||
tengo.MakeInstruction(parser.OpPop),
|
||||
tengo.MakeInstruction(parser.OpSuspend)),
|
||||
objectsArray(
|
||||
|
@ -1023,6 +1023,8 @@ func TestCompilerErrorReport(t *testing.T) {
|
|||
|
||||
expectCompileError(t, `a = 1`,
|
||||
"Compile Error: unresolved reference 'a'\n\tat test:1:1")
|
||||
expectCompileError(t, `a := a`,
|
||||
"Compile Error: unresolved reference 'a'\n\tat test:1:6")
|
||||
expectCompileError(t, `a, b := 1, 2`,
|
||||
"Compile Error: tuple assignment not allowed\n\tat test:1:1")
|
||||
expectCompileError(t, `a.b := 1`,
|
||||
|
@ -1087,7 +1089,7 @@ func() {
|
|||
intObject(4),
|
||||
compiledFunction(0, 0,
|
||||
tengo.MakeInstruction(parser.OpTrue),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 9),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 11),
|
||||
tengo.MakeInstruction(parser.OpConstant, 0),
|
||||
tengo.MakeInstruction(parser.OpReturn, 1),
|
||||
tengo.MakeInstruction(parser.OpConstant, 1),
|
||||
|
@ -1121,7 +1123,7 @@ func() {
|
|||
tengo.MakeInstruction(parser.OpGetLocal, 0),
|
||||
tengo.MakeInstruction(parser.OpConstant, 1),
|
||||
tengo.MakeInstruction(parser.OpEqual),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 19),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 21),
|
||||
tengo.MakeInstruction(parser.OpConstant, 2),
|
||||
tengo.MakeInstruction(parser.OpReturn, 1),
|
||||
tengo.MakeInstruction(parser.OpConstant, 1),
|
||||
|
@ -1154,7 +1156,7 @@ func() {
|
|||
intObject(4),
|
||||
compiledFunction(0, 0,
|
||||
tengo.MakeInstruction(parser.OpTrue),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 9),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 11),
|
||||
tengo.MakeInstruction(parser.OpConstant, 0),
|
||||
tengo.MakeInstruction(parser.OpReturn, 1),
|
||||
tengo.MakeInstruction(parser.OpConstant, 1),
|
||||
|
@ -1178,7 +1180,7 @@ func() {
|
|||
intObject(123),
|
||||
compiledFunction(0, 0,
|
||||
tengo.MakeInstruction(parser.OpTrue),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 6),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 8),
|
||||
tengo.MakeInstruction(parser.OpReturn, 0),
|
||||
tengo.MakeInstruction(parser.OpReturn, 0),
|
||||
tengo.MakeInstruction(parser.OpConstant, 0),
|
||||
|
@ -1198,12 +1200,12 @@ if a := 1; a {
|
|||
tengo.MakeInstruction(parser.OpConstant, 0),
|
||||
tengo.MakeInstruction(parser.OpSetGlobal, 0),
|
||||
tengo.MakeInstruction(parser.OpGetGlobal, 0),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 27),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 31),
|
||||
tengo.MakeInstruction(parser.OpConstant, 1),
|
||||
tengo.MakeInstruction(parser.OpSetGlobal, 0),
|
||||
tengo.MakeInstruction(parser.OpGetGlobal, 0),
|
||||
tengo.MakeInstruction(parser.OpSetGlobal, 1),
|
||||
tengo.MakeInstruction(parser.OpJump, 39),
|
||||
tengo.MakeInstruction(parser.OpJump, 43),
|
||||
tengo.MakeInstruction(parser.OpConstant, 2),
|
||||
tengo.MakeInstruction(parser.OpSetGlobal, 0),
|
||||
tengo.MakeInstruction(parser.OpGetGlobal, 0),
|
||||
|
@ -1236,12 +1238,12 @@ func() {
|
|||
tengo.MakeInstruction(parser.OpConstant, 0),
|
||||
tengo.MakeInstruction(parser.OpDefineLocal, 0),
|
||||
tengo.MakeInstruction(parser.OpGetLocal, 0),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 22),
|
||||
tengo.MakeInstruction(parser.OpJumpFalsy, 26),
|
||||
tengo.MakeInstruction(parser.OpConstant, 1),
|
||||
tengo.MakeInstruction(parser.OpSetLocal, 0),
|
||||
tengo.MakeInstruction(parser.OpGetLocal, 0),
|
||||
tengo.MakeInstruction(parser.OpDefineLocal, 1),
|
||||
tengo.MakeInstruction(parser.OpJump, 31),
|
||||
tengo.MakeInstruction(parser.OpJump, 35),
|
||||
tengo.MakeInstruction(parser.OpConstant, 2),
|
||||
tengo.MakeInstruction(parser.OpSetLocal, 0),
|
||||
tengo.MakeInstruction(parser.OpGetLocal, 0),
|
||||
|
|
|
@ -297,9 +297,11 @@ to add `StringArray` to the script:
|
|||
```golang
|
||||
// script that uses 'my_list'
|
||||
s := tengo.NewScript([]byte(`
|
||||
print(my_list + "three")
|
||||
fmt := import("fmt")
|
||||
fmt.println(my_list, ", three")
|
||||
`))
|
||||
|
||||
s.SetImports(stdlib.GetModuleMap("fmt"))
|
||||
myList := &StringArray{Value: []string{"one", "two"}}
|
||||
s.Add("my_list", myList) // add StringArray value 'my_list'
|
||||
s.Run() // prints "one, two, three"
|
||||
|
|
|
@ -11,7 +11,7 @@ json := import("json")
|
|||
- `encode(o object) => bytes`: Returns the JSON string (bytes) of the object.
|
||||
Unlike Go's JSON package, this function does not HTML-escape texts, but, one
|
||||
can use `html_escape` function if needed.
|
||||
- `indent(b string/bytes) => bytes`: Returns an indented form of input JSON
|
||||
- `indent(b string/bytes, prefix string, indent string) => bytes`: Returns an indented form of input JSON
|
||||
bytes string.
|
||||
- `html_escape(b string/bytes) => bytes`: Return an HTML-safe form of input
|
||||
JSON bytes string.
|
||||
|
@ -22,7 +22,7 @@ json := import("json")
|
|||
json := import("json")
|
||||
|
||||
encoded := json.encode({a: 1, b: [2, 3, 4]}) // JSON-encoded bytes string
|
||||
indentded := json.indent(encoded) // indented form
|
||||
indentded := json.indent(encoded, "", " ") // indented form
|
||||
html_safe := json.html_escape(encoded) // HTML escaped form
|
||||
|
||||
decoded := json.decode(encoded) // {a: 1, b: [2, 3, 4]}
|
||||
|
|
|
@ -18,6 +18,28 @@ math := import("math")
|
|||
- `ln10`
|
||||
- `ln10E`
|
||||
|
||||
Mathematical constants.
|
||||
|
||||
- `maxFloat32`
|
||||
- `smallestNonzeroFloat32`
|
||||
- `maxFloat64`
|
||||
- `smallestNonzeroFloat64`
|
||||
|
||||
Floating-point limit values. Max is the largest finite value representable by the type. SmallestNonzero is the smallest positive, non-zero value representable by the type.
|
||||
|
||||
- `maxInt`
|
||||
- `minInt`
|
||||
- `maxInt8`
|
||||
- `minInt8`
|
||||
- `maxInt16`
|
||||
- `minInt16`
|
||||
- `maxInt32`
|
||||
- `minInt32`
|
||||
- `maxInt64`
|
||||
- `minInt64`
|
||||
|
||||
Integer limit values.
|
||||
|
||||
## Functions
|
||||
|
||||
- `abs(x float) => float`: returns the absolute value of x.
|
||||
|
|
|
@ -6,6 +6,8 @@ os := import("os")
|
|||
|
||||
## Constants
|
||||
|
||||
- `platform`
|
||||
- `arch`
|
||||
- `o_rdonly`
|
||||
- `o_wronly`
|
||||
- `o_rdwr`
|
||||
|
|
|
@ -62,9 +62,10 @@ times := import("times")
|
|||
duration.
|
||||
- `month_string(month int) => string`: returns the English name of the month
|
||||
("January", "February", ...).
|
||||
- `date(year int, month int, day int, hour int, min int, sec int, nsec int) => time`:
|
||||
returns the Time corresponding to "yyyy-mm-dd hh:mm:ss + nsec nanoseconds".
|
||||
Current location is used.
|
||||
- `date(year int, month int, day int, hour int, min int, sec int, nsec int, loc string) => time`:
|
||||
returns the Time corresponding to "yyyy-mm-dd hh:mm:ss + nsec nanoseconds" in
|
||||
the appropriate zone for that Time in the given (optional) location.
|
||||
The Local time zone will be used if executed without specifying a location.
|
||||
- `now() => time`: returns the current local time.
|
||||
- `parse(format string, s string) => time`: parses a formatted string and
|
||||
returns the time value it represents. The layout defines the format by
|
||||
|
@ -116,5 +117,8 @@ times := import("times")
|
|||
string "2006-01-02 15:04:05.999999999 -0700 MST".
|
||||
- `is_zero(t time) => bool`: reports whether t represents the zero time
|
||||
instant, January 1, year 1, 00:00:00 UTC.
|
||||
- `in_location(t time, l string) => time`: returns a copy of t representing
|
||||
the same time instant, but with the copy's location information set to l for
|
||||
display purposes.
|
||||
- `to_local(t time) => time`: returns t with the location set to local time.
|
||||
- `to_utc(t time) => time`: returns t with the location set to UTC.
|
||||
|
|
|
@ -32,9 +32,9 @@ Here's a list of all available value types in Tengo.
|
|||
| bytes | byte array | `[]byte` |
|
||||
| error | [error](#error-values) value | - |
|
||||
| time | time value | `time.Time` |
|
||||
| array | value array _(mutable)_ | `[]interface{}` |
|
||||
| array | value array _(mutable)_ | `[]any` |
|
||||
| immutable array | [immutable](#immutable-values) array | - |
|
||||
| map | value map with string keys _(mutable)_ | `map[string]interface{}` |
|
||||
| map | value map with string keys _(mutable)_ | `map[string]any` |
|
||||
| immutable map | [immutable](#immutable-values) map | - |
|
||||
| undefined | [undefined](#undefined-values) value | - |
|
||||
| function | [function](#function-values) value | - |
|
||||
|
|
|
@ -28,6 +28,12 @@ func MakeInstruction(opcode parser.Opcode, operands ...int) []byte {
|
|||
n := uint16(o)
|
||||
instruction[offset] = byte(n >> 8)
|
||||
instruction[offset+1] = byte(n)
|
||||
case 4:
|
||||
n := uint32(o)
|
||||
instruction[offset] = byte(n >> 24)
|
||||
instruction[offset+1] = byte(n >> 16)
|
||||
instruction[offset+2] = byte(n >> 8)
|
||||
instruction[offset+3] = byte(n)
|
||||
}
|
||||
offset += width
|
||||
}
|
||||
|
|
|
@ -351,7 +351,7 @@ func (e *ImportExpr) End() Pos {
|
|||
}
|
||||
|
||||
func (e *ImportExpr) String() string {
|
||||
return `import("` + e.ModuleName + `")"`
|
||||
return `import("` + e.ModuleName + `")`
|
||||
}
|
||||
|
||||
// IndexExpr represents an index expression.
|
||||
|
|
|
@ -106,10 +106,10 @@ var OpcodeOperands = [...][]int{
|
|||
OpNotEqual: {},
|
||||
OpMinus: {},
|
||||
OpLNot: {},
|
||||
OpJumpFalsy: {2},
|
||||
OpAndJump: {2},
|
||||
OpOrJump: {2},
|
||||
OpJump: {2},
|
||||
OpJumpFalsy: {4},
|
||||
OpAndJump: {4},
|
||||
OpOrJump: {4},
|
||||
OpJump: {4},
|
||||
OpNull: {},
|
||||
OpGetGlobal: {2},
|
||||
OpSetGlobal: {2},
|
||||
|
@ -149,6 +149,8 @@ func ReadOperands(numOperands []int, ins []byte) (operands []int, offset int) {
|
|||
operands = append(operands, int(ins[offset]))
|
||||
case 2:
|
||||
operands = append(operands, int(ins[offset+1])|int(ins[offset])<<8)
|
||||
case 4:
|
||||
operands = append(operands, int(ins[offset+3])|int(ins[offset+2])<<8|int(ins[offset+1])<<16|int(ins[offset])<<24)
|
||||
}
|
||||
offset += width
|
||||
}
|
||||
|
|
|
@ -143,6 +143,7 @@ func (p *Parser) ParseFile() (file *File, err error) {
|
|||
}
|
||||
|
||||
stmts := p.parseStmtList()
|
||||
p.expect(token.EOF)
|
||||
if p.errors.Len() > 0 {
|
||||
return nil, p.errors.Err()
|
||||
}
|
||||
|
|
|
@ -1631,6 +1631,20 @@ func TestParseFloat(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMismatchBrace(t *testing.T) {
|
||||
expectParseError(t, `
|
||||
fmt := import("fmt")
|
||||
out := 0
|
||||
if 3 == 1 {
|
||||
out = 1
|
||||
}
|
||||
} else {
|
||||
out = 2
|
||||
}
|
||||
fmt.println(out)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestParseNumberExpressions(t *testing.T) {
|
||||
expectParse(t, `0x15e+2`, func(p pfn) []Stmt {
|
||||
return stmts(
|
||||
|
|
|
@ -247,8 +247,8 @@ func (c *Compiled) RunContext(ctx context.Context) (err error) {
|
|||
// Clone creates a new copy of Compiled. Cloned copies are safe for concurrent
|
||||
// use by multiple goroutines.
|
||||
func (c *Compiled) Clone() *Compiled {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
clone := &Compiled{
|
||||
globalIndexes: c.globalIndexes,
|
||||
|
@ -259,7 +259,7 @@ func (c *Compiled) Clone() *Compiled {
|
|||
// copy global objects
|
||||
for idx, g := range c.globals {
|
||||
if g != nil {
|
||||
clone.globals[idx] = g
|
||||
clone.globals[idx] = g.Copy()
|
||||
}
|
||||
}
|
||||
return clone
|
||||
|
|
119
script_test.go
119
script_test.go
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
@ -479,6 +480,99 @@ func TestCompiled_RunContext(t *testing.T) {
|
|||
require.Equal(t, context.DeadlineExceeded, err)
|
||||
}
|
||||
|
||||
func TestCompiled_CustomObject(t *testing.T) {
|
||||
c := compile(t, `r := (t<130)`, M{"t": &customNumber{value: 123}})
|
||||
compiledRun(t, c)
|
||||
compiledGet(t, c, "r", true)
|
||||
|
||||
c = compile(t, `r := (t>13)`, M{"t": &customNumber{value: 123}})
|
||||
compiledRun(t, c)
|
||||
compiledGet(t, c, "r", true)
|
||||
}
|
||||
|
||||
// customNumber is a user defined object that can compare to tengo.Int
|
||||
// very shitty implementation, just to test that token.Less and token.Greater in BinaryOp works
|
||||
type customNumber struct {
|
||||
tengo.ObjectImpl
|
||||
value int64
|
||||
}
|
||||
|
||||
func (n *customNumber) TypeName() string {
|
||||
return "Number"
|
||||
}
|
||||
|
||||
func (n *customNumber) String() string {
|
||||
return strconv.FormatInt(n.value, 10)
|
||||
}
|
||||
|
||||
func (n *customNumber) BinaryOp(op token.Token, rhs tengo.Object) (tengo.Object, error) {
|
||||
tengoInt, ok := rhs.(*tengo.Int)
|
||||
if !ok {
|
||||
return nil, tengo.ErrInvalidOperator
|
||||
}
|
||||
return n.binaryOpInt(op, tengoInt)
|
||||
}
|
||||
|
||||
func (n *customNumber) binaryOpInt(op token.Token, rhs *tengo.Int) (tengo.Object, error) {
|
||||
i := n.value
|
||||
|
||||
switch op {
|
||||
case token.Less:
|
||||
if i < rhs.Value {
|
||||
return tengo.TrueValue, nil
|
||||
}
|
||||
return tengo.FalseValue, nil
|
||||
case token.Greater:
|
||||
if i > rhs.Value {
|
||||
return tengo.TrueValue, nil
|
||||
}
|
||||
return tengo.FalseValue, nil
|
||||
case token.LessEq:
|
||||
if i <= rhs.Value {
|
||||
return tengo.TrueValue, nil
|
||||
}
|
||||
return tengo.FalseValue, nil
|
||||
case token.GreaterEq:
|
||||
if i >= rhs.Value {
|
||||
return tengo.TrueValue, nil
|
||||
}
|
||||
return tengo.FalseValue, nil
|
||||
}
|
||||
return nil, tengo.ErrInvalidOperator
|
||||
}
|
||||
|
||||
func TestScript_ImportError(t *testing.T) {
|
||||
m := `
|
||||
exp := import("expression")
|
||||
r := exp(ctx)
|
||||
`
|
||||
|
||||
src := `
|
||||
export func(ctx) {
|
||||
closure := func() {
|
||||
if ctx.actiontimes < 0 { // an error is thrown here because actiontimes is undefined
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return closure()
|
||||
}`
|
||||
|
||||
s := tengo.NewScript([]byte(m))
|
||||
mods := tengo.NewModuleMap()
|
||||
mods.AddSourceModule("expression", []byte(src))
|
||||
s.SetImports(mods)
|
||||
|
||||
err := s.Add("ctx", map[string]interface{}{
|
||||
"ctx": 12,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.Run()
|
||||
require.True(t, strings.Contains(err.Error(), "expression:4:6"))
|
||||
}
|
||||
|
||||
func compile(t *testing.T, input string, vars M) *tengo.Compiled {
|
||||
s := tengo.NewScript([]byte(input))
|
||||
for vn, vv := range vars {
|
||||
|
@ -546,3 +640,28 @@ func compiledIsDefined(
|
|||
) {
|
||||
require.Equal(t, expected, c.IsDefined(name))
|
||||
}
|
||||
func TestCompiled_Clone(t *testing.T) {
|
||||
script := tengo.NewScript([]byte(`
|
||||
count += 1
|
||||
data["b"] = 2
|
||||
`))
|
||||
|
||||
err := script.Add("data", map[string]interface{}{"a": 1})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = script.Add("count", 1000)
|
||||
require.NoError(t, err)
|
||||
|
||||
compiled, err := script.Compile()
|
||||
require.NoError(t, err)
|
||||
|
||||
clone := compiled.Clone()
|
||||
err = clone.RunContext(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1000, compiled.Get("count").Int())
|
||||
require.Equal(t, 1, len(compiled.Get("data").Map()))
|
||||
|
||||
require.Equal(t, 1001, clone.Get("count").Int())
|
||||
require.Equal(t, 2, len(clone.Get("data").Map()))
|
||||
}
|
||||
|
|
|
@ -62,9 +62,12 @@ func (d *decodeState) scanNext() {
|
|||
|
||||
// scanWhile processes bytes in d.data[d.off:] until it
|
||||
// receives a scan code not equal to op.
|
||||
func (d *decodeState) scanWhile(op int) {
|
||||
func (d *decodeState) scanWhile(op int) (isFloat bool) {
|
||||
s, data, i := &d.scan, d.data, d.off
|
||||
for i < len(data) {
|
||||
if data[i] == '.' || data[i] == 'e' || data[i] == 'E' {
|
||||
isFloat = true
|
||||
}
|
||||
newOp := s.step(s, data[i])
|
||||
i++
|
||||
if newOp != op {
|
||||
|
@ -76,6 +79,7 @@ func (d *decodeState) scanWhile(op int) {
|
|||
|
||||
d.off = len(data) + 1 // mark processed EOF with len+1
|
||||
d.opcode = d.scan.eof()
|
||||
return
|
||||
}
|
||||
|
||||
func (d *decodeState) value() (tengo.Object, error) {
|
||||
|
@ -185,7 +189,7 @@ func (d *decodeState) object() (tengo.Object, error) {
|
|||
func (d *decodeState) literal() (tengo.Object, error) {
|
||||
// All bytes inside literal return scanContinue op code.
|
||||
start := d.readIndex()
|
||||
d.scanWhile(scanContinue)
|
||||
isFloat := d.scanWhile(scanContinue)
|
||||
|
||||
item := d.data[start:d.readIndex()]
|
||||
|
||||
|
@ -210,8 +214,12 @@ func (d *decodeState) literal() (tengo.Object, error) {
|
|||
if c != '-' && (c < '0' || c > '9') {
|
||||
panic(phasePanicMsg)
|
||||
}
|
||||
n, _ := strconv.ParseFloat(string(item), 10)
|
||||
return &tengo.Float{Value: n}, nil
|
||||
if isFloat {
|
||||
n, _ := strconv.ParseFloat(string(item), 10)
|
||||
return &tengo.Float{Value: n}, nil
|
||||
}
|
||||
n, _ := strconv.ParseInt(string(item), 10, 64)
|
||||
return &tengo.Int{Value: n}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,9 @@ func TestJSON(t *testing.T) {
|
|||
"arr": ARR{1, 2, 3, "four"}})
|
||||
testJSONEncodeDecode(t, MAP{"a": 0, "b": "bee",
|
||||
"arr": ARR{1, 2, 3, MAP{"a": false, "b": 109.4}}})
|
||||
|
||||
testJSONEncodeDecode(t, MAP{"id1": 7075984636689534001, "id2": 7075984636689534002})
|
||||
testJSONEncodeDecode(t, ARR{1e3, 1E7})
|
||||
}
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
|
|
|
@ -48,13 +48,13 @@ func TestJSON(t *testing.T) {
|
|||
expect([]byte("[[\"bar\",1],[\"bar\",1]]"))
|
||||
|
||||
module(t, "json").call("decode", `5`).
|
||||
expect(5.0)
|
||||
expect(5)
|
||||
module(t, "json").call("decode", `"foo"`).
|
||||
expect("foo")
|
||||
module(t, "json").call("decode", `[1,2,3,"bar"]`).
|
||||
expect(ARR{1.0, 2.0, 3.0, "bar"})
|
||||
expect(ARR{1, 2, 3, "bar"})
|
||||
module(t, "json").call("decode", `{"foo":5}`).
|
||||
expect(MAP{"foo": 5.0})
|
||||
expect(MAP{"foo": 5})
|
||||
module(t, "json").call("decode", `{"foo":2.5}`).
|
||||
expect(MAP{"foo": 2.5})
|
||||
module(t, "json").call("decode", `{"foo":true}`).
|
||||
|
@ -62,7 +62,7 @@ func TestJSON(t *testing.T) {
|
|||
module(t, "json").call("decode", `{"foo":"bar"}`).
|
||||
expect(MAP{"foo": "bar"})
|
||||
module(t, "json").call("decode", `{"foo":[1,2,3,"bar"]}`).
|
||||
expect(MAP{"foo": ARR{1.0, 2.0, 3.0, "bar"}})
|
||||
expect(MAP{"foo": ARR{1, 2, 3, "bar"}})
|
||||
|
||||
module(t, "json").
|
||||
call("indent", []byte("{\"foo\":[\"bar\",1,1.8,56,true]}"), "", " ").
|
||||
|
|
|
@ -7,17 +7,31 @@ import (
|
|||
)
|
||||
|
||||
var mathModule = map[string]tengo.Object{
|
||||
"e": &tengo.Float{Value: math.E},
|
||||
"pi": &tengo.Float{Value: math.Pi},
|
||||
"phi": &tengo.Float{Value: math.Phi},
|
||||
"sqrt2": &tengo.Float{Value: math.Sqrt2},
|
||||
"sqrtE": &tengo.Float{Value: math.SqrtE},
|
||||
"sqrtPi": &tengo.Float{Value: math.SqrtPi},
|
||||
"sqrtPhi": &tengo.Float{Value: math.SqrtPhi},
|
||||
"ln2": &tengo.Float{Value: math.Ln2},
|
||||
"log2E": &tengo.Float{Value: math.Log2E},
|
||||
"ln10": &tengo.Float{Value: math.Ln10},
|
||||
"log10E": &tengo.Float{Value: math.Log10E},
|
||||
"e": &tengo.Float{Value: math.E},
|
||||
"pi": &tengo.Float{Value: math.Pi},
|
||||
"phi": &tengo.Float{Value: math.Phi},
|
||||
"sqrt2": &tengo.Float{Value: math.Sqrt2},
|
||||
"sqrtE": &tengo.Float{Value: math.SqrtE},
|
||||
"sqrtPi": &tengo.Float{Value: math.SqrtPi},
|
||||
"sqrtPhi": &tengo.Float{Value: math.SqrtPhi},
|
||||
"ln2": &tengo.Float{Value: math.Ln2},
|
||||
"log2E": &tengo.Float{Value: math.Log2E},
|
||||
"ln10": &tengo.Float{Value: math.Ln10},
|
||||
"log10E": &tengo.Float{Value: math.Log10E},
|
||||
"maxFloat32": &tengo.Float{Value: math.MaxFloat32},
|
||||
"smallestNonzeroFloat32": &tengo.Float{Value: math.SmallestNonzeroFloat32},
|
||||
"maxFloat64": &tengo.Float{Value: math.MaxFloat64},
|
||||
"smallestNonzeroFloat64": &tengo.Float{Value: math.SmallestNonzeroFloat64},
|
||||
"maxInt": &tengo.Int{Value: math.MaxInt},
|
||||
"minInt": &tengo.Int{Value: math.MinInt},
|
||||
"maxInt8": &tengo.Int{Value: math.MaxInt8},
|
||||
"minInt8": &tengo.Int{Value: math.MinInt8},
|
||||
"maxInt16": &tengo.Int{Value: math.MaxInt16},
|
||||
"minInt16": &tengo.Int{Value: math.MinInt16},
|
||||
"maxInt32": &tengo.Int{Value: math.MaxInt32},
|
||||
"minInt32": &tengo.Int{Value: math.MinInt32},
|
||||
"maxInt64": &tengo.Int{Value: math.MaxInt64},
|
||||
"minInt64": &tengo.Int{Value: math.MinInt64},
|
||||
"abs": &tengo.UserFunction{
|
||||
Name: "abs",
|
||||
Value: FuncAFRF(math.Abs),
|
||||
|
|
|
@ -6,11 +6,14 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/d5/tengo/v2"
|
||||
)
|
||||
|
||||
var osModule = map[string]tengo.Object{
|
||||
"platform": &tengo.String{Value: runtime.GOOS},
|
||||
"arch": &tengo.String{Value: runtime.GOARCH},
|
||||
"o_rdonly": &tengo.Int{Value: int64(os.O_RDONLY)},
|
||||
"o_wronly": &tengo.Int{Value: int64(os.O_WRONLY)},
|
||||
"o_rdwr": &tengo.Int{Value: int64(os.O_RDWR)},
|
||||
|
|
|
@ -180,6 +180,10 @@ var timesModule = map[string]tengo.Object{
|
|||
Name: "to_utc",
|
||||
Value: timesToUTC,
|
||||
}, // to_utc(time) => time
|
||||
"in_location": &tengo.UserFunction{
|
||||
Name: "in_location",
|
||||
Value: timesInLocation,
|
||||
}, // in_location(time, location) => time
|
||||
}
|
||||
|
||||
func timesSleep(args ...tengo.Object) (ret tengo.Object, err error) {
|
||||
|
@ -430,7 +434,7 @@ func timesDate(args ...tengo.Object) (
|
|||
ret tengo.Object,
|
||||
err error,
|
||||
) {
|
||||
if len(args) != 7 {
|
||||
if len(args) < 7 || len(args) > 8 {
|
||||
err = tengo.ErrWrongNumArguments
|
||||
return
|
||||
}
|
||||
|
@ -499,9 +503,29 @@ func timesDate(args ...tengo.Object) (
|
|||
return
|
||||
}
|
||||
|
||||
var loc *time.Location
|
||||
if len(args) == 8 {
|
||||
i8, ok := tengo.ToString(args[7])
|
||||
if !ok {
|
||||
err = tengo.ErrInvalidArgumentType{
|
||||
Name: "eighth",
|
||||
Expected: "string(compatible)",
|
||||
Found: args[7].TypeName(),
|
||||
}
|
||||
return
|
||||
}
|
||||
loc, err = time.LoadLocation(i8)
|
||||
if err != nil {
|
||||
ret = wrapError(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
loc = time.Now().Location()
|
||||
}
|
||||
|
||||
ret = &tengo.Time{
|
||||
Value: time.Date(i1,
|
||||
time.Month(i2), i3, i4, i5, i6, i7, time.Now().Location()),
|
||||
time.Month(i2), i3, i4, i5, i6, i7, loc),
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -1113,6 +1137,46 @@ func timesTimeLocation(args ...tengo.Object) (
|
|||
return
|
||||
}
|
||||
|
||||
func timesInLocation(args ...tengo.Object) (
|
||||
ret tengo.Object,
|
||||
err error,
|
||||
) {
|
||||
if len(args) != 2 {
|
||||
err = tengo.ErrWrongNumArguments
|
||||
return
|
||||
}
|
||||
|
||||
t1, ok := tengo.ToTime(args[0])
|
||||
if !ok {
|
||||
err = tengo.ErrInvalidArgumentType{
|
||||
Name: "first",
|
||||
Expected: "time(compatible)",
|
||||
Found: args[0].TypeName(),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
s2, ok := tengo.ToString(args[1])
|
||||
if !ok {
|
||||
err = tengo.ErrInvalidArgumentType{
|
||||
Name: "second",
|
||||
Expected: "string(compatible)",
|
||||
Found: args[1].TypeName(),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
location, err := time.LoadLocation(s2)
|
||||
if err != nil {
|
||||
ret = wrapError(err)
|
||||
return
|
||||
}
|
||||
|
||||
ret = &tengo.Time{Value: t1.In(location)}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func timesTimeString(args ...tengo.Object) (ret tengo.Object, err error) {
|
||||
if len(args) != 1 {
|
||||
err = tengo.ErrWrongNumArguments
|
||||
|
|
|
@ -11,6 +11,8 @@ import (
|
|||
func TestTimes(t *testing.T) {
|
||||
time1 := time.Date(1982, 9, 28, 19, 21, 44, 999, time.Now().Location())
|
||||
time2 := time.Now()
|
||||
location, _ := time.LoadLocation("Pacific/Auckland")
|
||||
time3 := time.Date(1982, 9, 28, 19, 21, 44, 999, location)
|
||||
|
||||
module(t, "times").call("sleep", 1).expect(tengo.UndefinedValue)
|
||||
|
||||
|
@ -35,6 +37,9 @@ func TestTimes(t *testing.T) {
|
|||
|
||||
module(t, "times").call("date", 1982, 9, 28, 19, 21, 44, 999).
|
||||
expect(time1)
|
||||
module(t, "times").call("date", 1982, 9, 28, 19, 21, 44, 999, "Pacific/Auckland").
|
||||
expect(time3)
|
||||
|
||||
nowD := time.Until(module(t, "times").call("now").
|
||||
o.(*tengo.Time).Value).Nanoseconds()
|
||||
require.True(t, 0 > nowD && nowD > -100000000) // within 100ms
|
||||
|
@ -80,4 +85,5 @@ func TestTimes(t *testing.T) {
|
|||
module(t, "times").call("time_location", time1).
|
||||
expect(time1.Location().String())
|
||||
module(t, "times").call("time_string", time1).expect(time1.String())
|
||||
module(t, "times").call("in_location", time1, location.String()).expect(time1.In(location))
|
||||
}
|
||||
|
|
38
vm.go
38
vm.go
|
@ -218,30 +218,30 @@ func (v *VM) run() {
|
|||
return
|
||||
}
|
||||
case parser.OpJumpFalsy:
|
||||
v.ip += 2
|
||||
v.ip += 4
|
||||
v.sp--
|
||||
if v.stack[v.sp].IsFalsy() {
|
||||
pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8
|
||||
pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 | int(v.curInsts[v.ip-2])<<16 | int(v.curInsts[v.ip-3])<<24
|
||||
v.ip = pos - 1
|
||||
}
|
||||
case parser.OpAndJump:
|
||||
v.ip += 2
|
||||
v.ip += 4
|
||||
if v.stack[v.sp-1].IsFalsy() {
|
||||
pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8
|
||||
pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 | int(v.curInsts[v.ip-2])<<16 | int(v.curInsts[v.ip-3])<<24
|
||||
v.ip = pos - 1
|
||||
} else {
|
||||
v.sp--
|
||||
}
|
||||
case parser.OpOrJump:
|
||||
v.ip += 2
|
||||
v.ip += 4
|
||||
if v.stack[v.sp-1].IsFalsy() {
|
||||
v.sp--
|
||||
} else {
|
||||
pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8
|
||||
pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 | int(v.curInsts[v.ip-2])<<16 | int(v.curInsts[v.ip-3])<<24
|
||||
v.ip = pos - 1
|
||||
}
|
||||
case parser.OpJump:
|
||||
pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
|
||||
pos := int(v.curInsts[v.ip+4]) | int(v.curInsts[v.ip+3])<<8 | int(v.curInsts[v.ip+2])<<16 | int(v.curInsts[v.ip+1])<<24
|
||||
v.ip = pos - 1
|
||||
case parser.OpSetGlobal:
|
||||
v.ip += 2
|
||||
|
@ -376,8 +376,8 @@ func (v *VM) run() {
|
|||
|
||||
var lowIdx int64
|
||||
if low != UndefinedValue {
|
||||
if low, ok := low.(*Int); ok {
|
||||
lowIdx = low.Value
|
||||
if lowInt, ok := low.(*Int); ok {
|
||||
lowIdx = lowInt.Value
|
||||
} else {
|
||||
v.err = fmt.Errorf("invalid slice index type: %s",
|
||||
low.TypeName())
|
||||
|
@ -391,8 +391,8 @@ func (v *VM) run() {
|
|||
var highIdx int64
|
||||
if high == UndefinedValue {
|
||||
highIdx = numElements
|
||||
} else if high, ok := high.(*Int); ok {
|
||||
highIdx = high.Value
|
||||
} else if highInt, ok := high.(*Int); ok {
|
||||
highIdx = highInt.Value
|
||||
} else {
|
||||
v.err = fmt.Errorf("invalid slice index type: %s",
|
||||
high.TypeName())
|
||||
|
@ -428,8 +428,8 @@ func (v *VM) run() {
|
|||
var highIdx int64
|
||||
if high == UndefinedValue {
|
||||
highIdx = numElements
|
||||
} else if high, ok := high.(*Int); ok {
|
||||
highIdx = high.Value
|
||||
} else if highInt, ok := high.(*Int); ok {
|
||||
highIdx = highInt.Value
|
||||
} else {
|
||||
v.err = fmt.Errorf("invalid slice index type: %s",
|
||||
high.TypeName())
|
||||
|
@ -465,8 +465,8 @@ func (v *VM) run() {
|
|||
var highIdx int64
|
||||
if high == UndefinedValue {
|
||||
highIdx = numElements
|
||||
} else if high, ok := high.(*Int); ok {
|
||||
highIdx = high.Value
|
||||
} else if highInt, ok := high.(*Int); ok {
|
||||
highIdx = highInt.Value
|
||||
} else {
|
||||
v.err = fmt.Errorf("invalid slice index type: %s",
|
||||
high.TypeName())
|
||||
|
@ -502,8 +502,8 @@ func (v *VM) run() {
|
|||
var highIdx int64
|
||||
if high == UndefinedValue {
|
||||
highIdx = numElements
|
||||
} else if high, ok := high.(*Int); ok {
|
||||
highIdx = high.Value
|
||||
} else if highInt, ok := high.(*Int); ok {
|
||||
highIdx = highInt.Value
|
||||
} else {
|
||||
v.err = fmt.Errorf("invalid slice index type: %s",
|
||||
high.TypeName())
|
||||
|
@ -534,6 +534,9 @@ func (v *VM) run() {
|
|||
}
|
||||
v.stack[v.sp] = val
|
||||
v.sp++
|
||||
default:
|
||||
v.err = fmt.Errorf("not indexable: %s", left.TypeName())
|
||||
return
|
||||
}
|
||||
case parser.OpCall:
|
||||
numArgs := int(v.curInsts[v.ip+1])
|
||||
|
@ -767,6 +770,7 @@ func (v *VM) run() {
|
|||
NumLocals: fn.NumLocals,
|
||||
NumParameters: fn.NumParameters,
|
||||
VarArgs: fn.VarArgs,
|
||||
SourceMap: fn.SourceMap,
|
||||
Free: free,
|
||||
}
|
||||
v.allocs--
|
||||
|
|
20
vm_test.go
20
vm_test.go
|
@ -1090,6 +1090,15 @@ export func() {
|
|||
b := 5
|
||||
return b + "foo"
|
||||
}`), "Runtime Error: invalid operation: int + string\n\tat mod2:4:9")
|
||||
|
||||
expectError(t, `a := [1, 2, 3]; b := a[:"invalid"];`, nil,
|
||||
"Runtime Error: invalid slice index type: string")
|
||||
expectError(t, `a := immutable([4, 5, 6]); b := a[:false];`, nil,
|
||||
"Runtime Error: invalid slice index type: bool")
|
||||
expectError(t, `a := "hello"; b := a[:1.23];`, nil,
|
||||
"Runtime Error: invalid slice index type: float")
|
||||
expectError(t, `a := bytes("world"); b := a[:time(1)];`, nil,
|
||||
"Runtime Error: invalid slice index type: time")
|
||||
}
|
||||
|
||||
func TestVMErrorUnwrap(t *testing.T) {
|
||||
|
@ -2745,10 +2754,10 @@ export func(a) {
|
|||
Opts().Module("mod0", `if 1 { } else { export true }`),
|
||||
tengo.UndefinedValue)
|
||||
expectRun(t, `out = import("mod0")`,
|
||||
Opts().Module("mod0", `for v:=0;;v++ { if v == 3 { export true } } }`),
|
||||
Opts().Module("mod0", `for v:=0;;v++ { if v == 3 { export true } }`),
|
||||
true)
|
||||
expectRun(t, `out = import("mod0")`,
|
||||
Opts().Module("mod0", `for v:=0;;v++ { if v == 3 { break } } }`),
|
||||
Opts().Module("mod0", `for v:=0;;v++ { if v == 3 { break } }`),
|
||||
tengo.UndefinedValue)
|
||||
|
||||
// duplicate compiled functions
|
||||
|
@ -3625,6 +3634,13 @@ func TestSpread(t *testing.T) {
|
|||
"Runtime Error: wrong number of arguments: want=3, got=2")
|
||||
}
|
||||
|
||||
func TestSliceIndex(t *testing.T) {
|
||||
expectError(t, `undefined[:1]`, nil, "Runtime Error: not indexable")
|
||||
expectError(t, `123[-1:2]`, nil, "Runtime Error: not indexable")
|
||||
expectError(t, `{}[:]`, nil, "Runtime Error: not indexable")
|
||||
expectError(t, `a := 123[-1:2] ; a += 1`, nil, "Runtime Error: not indexable")
|
||||
}
|
||||
|
||||
func expectRun(
|
||||
t *testing.T,
|
||||
input string,
|
||||
|
|
Loading…
Reference in a new issue