DOT_GET
This commit is contained in:
parent
b16351ac95
commit
1a18a713d7
53
GUIDE.md
53
GUIDE.md
|
|
@ -235,6 +235,9 @@ CALL
|
||||||
- `DICT_SET` - Pop value, key, dict; mutate dict
|
- `DICT_SET` - Pop value, key, dict; mutate dict
|
||||||
- `DICT_HAS` - Pop key and dict, push boolean
|
- `DICT_HAS` - Pop key and dict, push boolean
|
||||||
|
|
||||||
|
### Unified Access
|
||||||
|
- `DOT_GET` - Pop index/key and array/dict, push value (null if missing)
|
||||||
|
|
||||||
### Strings
|
### Strings
|
||||||
- `STR_CONCAT #N` - Pop N values, convert to strings, concatenate, push result
|
- `STR_CONCAT #N` - Pop N values, convert to strings, concatenate, push result
|
||||||
|
|
||||||
|
|
@ -474,6 +477,56 @@ PUSH "!"
|
||||||
STR_CONCAT #2 ; → "Hello World!"
|
STR_CONCAT #2 ; → "Hello World!"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Unified Access (DOT_GET)
|
||||||
|
DOT_GET provides a single opcode for accessing both arrays and dicts:
|
||||||
|
|
||||||
|
```
|
||||||
|
; Array access
|
||||||
|
PUSH 10
|
||||||
|
PUSH 20
|
||||||
|
PUSH 30
|
||||||
|
MAKE_ARRAY #3
|
||||||
|
PUSH 1
|
||||||
|
DOT_GET ; → 20
|
||||||
|
|
||||||
|
; Dict access
|
||||||
|
PUSH 'name'
|
||||||
|
PUSH 'Alice'
|
||||||
|
MAKE_DICT #1
|
||||||
|
PUSH 'name'
|
||||||
|
DOT_GET ; → 'Alice'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Chained access**:
|
||||||
|
```
|
||||||
|
; Access dict['users'][0]['name']
|
||||||
|
LOAD dict
|
||||||
|
PUSH 'users'
|
||||||
|
DOT_GET ; Get users array
|
||||||
|
PUSH 0
|
||||||
|
DOT_GET ; Get first user
|
||||||
|
PUSH 'name'
|
||||||
|
DOT_GET ; Get name field
|
||||||
|
```
|
||||||
|
|
||||||
|
**With variables**:
|
||||||
|
```
|
||||||
|
LOAD data
|
||||||
|
LOAD key ; Key can be string or number
|
||||||
|
DOT_GET ; Works for both array and dict
|
||||||
|
```
|
||||||
|
|
||||||
|
**Null safety**: Returns null for missing keys or out-of-bounds indices
|
||||||
|
```
|
||||||
|
MAKE_ARRAY #0
|
||||||
|
PUSH 0
|
||||||
|
DOT_GET ; → null (empty array)
|
||||||
|
|
||||||
|
MAKE_DICT #0
|
||||||
|
PUSH 'key'
|
||||||
|
DOT_GET ; → null (missing key)
|
||||||
|
```
|
||||||
|
|
||||||
## Key Concepts
|
## Key Concepts
|
||||||
|
|
||||||
### Truthiness
|
### Truthiness
|
||||||
|
|
|
||||||
45
SPEC.md
45
SPEC.md
|
|
@ -509,6 +509,51 @@ Key is coerced to string.
|
||||||
Key is coerced to string.
|
Key is coerced to string.
|
||||||
**Errors**: Throws if not dict
|
**Errors**: Throws if not dict
|
||||||
|
|
||||||
|
### Unified Access
|
||||||
|
|
||||||
|
#### DOT_GET
|
||||||
|
**Operand**: None
|
||||||
|
**Effect**: Get value from array or dict
|
||||||
|
**Stack**: [array|dict, index|key] → [value]
|
||||||
|
|
||||||
|
**Behavior**:
|
||||||
|
- If target is array: coerce index to number and access `array[index]`
|
||||||
|
- If target is dict: coerce key to string and access `dict.get(key)`
|
||||||
|
- Returns null if index out of bounds or key not found
|
||||||
|
|
||||||
|
**Errors**: Throws if target is not array or dict
|
||||||
|
|
||||||
|
**Use Cases**:
|
||||||
|
- Unified syntax for accessing both arrays and dicts
|
||||||
|
- Chaining access operations: `obj.users.0.name`
|
||||||
|
- Generic accessor that works with any indexable type
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```
|
||||||
|
; Array access
|
||||||
|
PUSH 10
|
||||||
|
PUSH 20
|
||||||
|
PUSH 30
|
||||||
|
MAKE_ARRAY #3
|
||||||
|
PUSH 1
|
||||||
|
DOT_GET ; → 20
|
||||||
|
|
||||||
|
; Dict access
|
||||||
|
PUSH 'name'
|
||||||
|
PUSH 'Alice'
|
||||||
|
MAKE_DICT #1
|
||||||
|
PUSH 'name'
|
||||||
|
DOT_GET ; → 'Alice'
|
||||||
|
|
||||||
|
; Chained access
|
||||||
|
; dict['users'][0]
|
||||||
|
LOAD dict
|
||||||
|
PUSH 'users'
|
||||||
|
DOT_GET
|
||||||
|
PUSH 0
|
||||||
|
DOT_GET
|
||||||
|
```
|
||||||
|
|
||||||
### String Operations
|
### String Operations
|
||||||
|
|
||||||
#### STR_CONCAT
|
#### STR_CONCAT
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,9 @@ export enum OpCode {
|
||||||
DICT_SET, // operand: none | stack: [dict, key, value] → [] | mutates dict
|
DICT_SET, // operand: none | stack: [dict, key, value] → [] | mutates dict
|
||||||
DICT_HAS, // operand: none | stack: [dict, key] → [boolean]
|
DICT_HAS, // operand: none | stack: [dict, key] → [boolean]
|
||||||
|
|
||||||
|
// arrays and dicts
|
||||||
|
DOT_GET, // operand: none | stack: [array|dict, index|key] → [value] | unified accessor, returns null if missing
|
||||||
|
|
||||||
// strings
|
// strings
|
||||||
STR_CONCAT, // operand: value count (number) | stack: [val1, ..., valN] → [string] | concatenate N values
|
STR_CONCAT, // operand: value count (number) | stack: [val1, ..., valN] → [string] | concatenate N values
|
||||||
|
|
||||||
|
|
|
||||||
16
src/vm.ts
16
src/vm.ts
|
|
@ -338,6 +338,22 @@ export class VM {
|
||||||
this.stack.push({ type: 'boolean', value: hasDict.value.has(toString(hasKey)) })
|
this.stack.push({ type: 'boolean', value: hasDict.value.has(toString(hasKey)) })
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case OpCode.DOT_GET: {
|
||||||
|
const index = this.stack.pop()!
|
||||||
|
const target = this.stack.pop()!
|
||||||
|
|
||||||
|
if (target.type === 'array')
|
||||||
|
this.stack.push(toValue(target.value?.[Number(index.value)]))
|
||||||
|
|
||||||
|
else if (target.type === 'dict')
|
||||||
|
this.stack.push(toValue(target.value?.get(String(index.value))))
|
||||||
|
|
||||||
|
else
|
||||||
|
throw new Error(`DOT_GET: ${target.type} not supported`)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
case OpCode.STR_CONCAT:
|
case OpCode.STR_CONCAT:
|
||||||
let count = instruction.operand as number
|
let count = instruction.operand as number
|
||||||
let parts = []
|
let parts = []
|
||||||
|
|
|
||||||
|
|
@ -786,6 +786,459 @@ describe("DICT_HAS", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("DOT_GET", () => {
|
||||||
|
test("gets an ARRAY index", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 100
|
||||||
|
PUSH 200
|
||||||
|
PUSH 300
|
||||||
|
PUSH 400
|
||||||
|
MAKE_ARRAY #4
|
||||||
|
PUSH 2
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 300 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("gets a DICT value", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 'name'
|
||||||
|
PUSH 'Bob'
|
||||||
|
PUSH 'age'
|
||||||
|
PUSH 106
|
||||||
|
MAKE_DICT #2
|
||||||
|
PUSH 'name'
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'Bob' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("fails to work on NUMBER", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 500
|
||||||
|
PUSH 0
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await run(bytecode)
|
||||||
|
expect(true).toBe(false) // Should not reach here
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e.message).toContain('DOT_GET: number not supported')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("fails to work on STRING", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 'ShrimpVM?'
|
||||||
|
PUSH 1
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await run(bytecode)
|
||||||
|
expect(true).toBe(false) // Should not reach here
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e.message).toContain('DOT_GET: string not supported')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("array - returns null for out of bounds", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 10
|
||||||
|
PUSH 20
|
||||||
|
MAKE_ARRAY #2
|
||||||
|
PUSH 5
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'null', value: null })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("array - negative index", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 10
|
||||||
|
PUSH 20
|
||||||
|
PUSH 30
|
||||||
|
MAKE_ARRAY #3
|
||||||
|
PUSH -1
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'null', value: null })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("array - first and last element", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 100
|
||||||
|
PUSH 200
|
||||||
|
PUSH 300
|
||||||
|
MAKE_ARRAY #3
|
||||||
|
DUP
|
||||||
|
PUSH 0
|
||||||
|
DOT_GET
|
||||||
|
STORE first
|
||||||
|
PUSH 2
|
||||||
|
DOT_GET
|
||||||
|
STORE last
|
||||||
|
LOAD first
|
||||||
|
LOAD last
|
||||||
|
ADD
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 400 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("dict - returns null for missing key", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 'name'
|
||||||
|
PUSH 'Alice'
|
||||||
|
MAKE_DICT #1
|
||||||
|
PUSH 'age'
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'null', value: null })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("dict - with numeric key", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH '123'
|
||||||
|
PUSH 'value'
|
||||||
|
MAKE_DICT #1
|
||||||
|
PUSH 123
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'value' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("array - with string value", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 'foo'
|
||||||
|
PUSH 'bar'
|
||||||
|
PUSH 'baz'
|
||||||
|
MAKE_ARRAY #3
|
||||||
|
PUSH 1
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'bar' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("array - with boolean values", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH true
|
||||||
|
PUSH false
|
||||||
|
PUSH true
|
||||||
|
MAKE_ARRAY #3
|
||||||
|
PUSH 1
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'boolean', value: false })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("dict - with boolean value", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 'active'
|
||||||
|
PUSH true
|
||||||
|
PUSH 'visible'
|
||||||
|
PUSH false
|
||||||
|
MAKE_DICT #2
|
||||||
|
PUSH 'active'
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'boolean', value: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("array - nested arrays", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 1
|
||||||
|
PUSH 2
|
||||||
|
MAKE_ARRAY #2
|
||||||
|
PUSH 3
|
||||||
|
PUSH 4
|
||||||
|
MAKE_ARRAY #2
|
||||||
|
MAKE_ARRAY #2
|
||||||
|
PUSH 1
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('array')
|
||||||
|
if (result.type === 'array') {
|
||||||
|
expect(result.value).toHaveLength(2)
|
||||||
|
expect(result.value[0]).toEqual({ type: 'number', value: 3 })
|
||||||
|
expect(result.value[1]).toEqual({ type: 'number', value: 4 })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("dict - nested dicts", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 'user'
|
||||||
|
PUSH 'name'
|
||||||
|
PUSH 'Alice'
|
||||||
|
MAKE_DICT #1
|
||||||
|
MAKE_DICT #1
|
||||||
|
PUSH 'user'
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('dict')
|
||||||
|
if (result.type === 'dict') {
|
||||||
|
expect(result.value.get('name')).toEqual({ type: 'string', value: 'Alice' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("array format", async () => {
|
||||||
|
const bytecode = toBytecode([
|
||||||
|
["PUSH", "apple"],
|
||||||
|
["PUSH", "banana"],
|
||||||
|
["PUSH", "cherry"],
|
||||||
|
["MAKE_ARRAY", 3],
|
||||||
|
["PUSH", 1],
|
||||||
|
["DOT_GET"],
|
||||||
|
["HALT"]
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'banana' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with variables - array", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 10
|
||||||
|
PUSH 20
|
||||||
|
PUSH 30
|
||||||
|
MAKE_ARRAY #3
|
||||||
|
STORE arr
|
||||||
|
PUSH 1
|
||||||
|
STORE idx
|
||||||
|
LOAD arr
|
||||||
|
LOAD idx
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 20 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with variables - dict", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 'x'
|
||||||
|
PUSH 100
|
||||||
|
PUSH 'y'
|
||||||
|
PUSH 200
|
||||||
|
MAKE_DICT #2
|
||||||
|
STORE point
|
||||||
|
PUSH 'y'
|
||||||
|
STORE key
|
||||||
|
LOAD point
|
||||||
|
LOAD key
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 200 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with expression as index", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 10
|
||||||
|
PUSH 20
|
||||||
|
PUSH 30
|
||||||
|
PUSH 40
|
||||||
|
MAKE_ARRAY #4
|
||||||
|
PUSH 1
|
||||||
|
PUSH 2
|
||||||
|
ADD
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 40 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("chained access - array of dicts", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 'name'
|
||||||
|
PUSH 'Alice'
|
||||||
|
MAKE_DICT #1
|
||||||
|
PUSH 'name'
|
||||||
|
PUSH 'Bob'
|
||||||
|
MAKE_DICT #1
|
||||||
|
MAKE_ARRAY #2
|
||||||
|
PUSH 1
|
||||||
|
DOT_GET
|
||||||
|
PUSH 'name'
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'Bob' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("chained access - dict of arrays", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 'nums'
|
||||||
|
PUSH 1
|
||||||
|
PUSH 2
|
||||||
|
PUSH 3
|
||||||
|
MAKE_ARRAY #3
|
||||||
|
MAKE_DICT #1
|
||||||
|
PUSH 'nums'
|
||||||
|
DOT_GET
|
||||||
|
PUSH 1
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'number', value: 2 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with null value in array", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 10
|
||||||
|
PUSH null
|
||||||
|
PUSH 30
|
||||||
|
MAKE_ARRAY #3
|
||||||
|
PUSH 1
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'null', value: null })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("with null value in dict", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 'name'
|
||||||
|
PUSH 'Alice'
|
||||||
|
PUSH 'middle'
|
||||||
|
PUSH null
|
||||||
|
MAKE_DICT #2
|
||||||
|
PUSH 'middle'
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'null', value: null })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("array with regex values", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH /foo/
|
||||||
|
PUSH /bar/i
|
||||||
|
PUSH /baz/g
|
||||||
|
MAKE_ARRAY #3
|
||||||
|
PUSH 1
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('regex')
|
||||||
|
if (result.type === 'regex') {
|
||||||
|
expect(result.value.source).toBe('bar')
|
||||||
|
expect(result.value.ignoreCase).toBe(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("dict with regex values", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH 'email'
|
||||||
|
PUSH /^[a-z@.]+$/i
|
||||||
|
PUSH 'url'
|
||||||
|
PUSH /^https?:\\/\\//
|
||||||
|
MAKE_DICT #2
|
||||||
|
PUSH 'email'
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
const result = await run(bytecode)
|
||||||
|
expect(result.type).toBe('regex')
|
||||||
|
if (result.type === 'regex') {
|
||||||
|
expect(result.value.source).toBe('^[a-z@.]+$')
|
||||||
|
expect(result.value.ignoreCase).toBe(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("dict - key coercion from number", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH '0'
|
||||||
|
PUSH 'zero'
|
||||||
|
PUSH '1'
|
||||||
|
PUSH 'one'
|
||||||
|
MAKE_DICT #2
|
||||||
|
PUSH 0
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'string', value: 'zero' })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("fails on boolean", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH true
|
||||||
|
PUSH 0
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await run(bytecode)
|
||||||
|
expect(true).toBe(false)
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e.message).toContain('DOT_GET: boolean not supported')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("fails on null", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH null
|
||||||
|
PUSH 0
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await run(bytecode)
|
||||||
|
expect(true).toBe(false)
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e.message).toContain('DOT_GET: null not supported')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("fails on regex", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
PUSH /test/
|
||||||
|
PUSH 0
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await run(bytecode)
|
||||||
|
expect(true).toBe(false)
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e.message).toContain('DOT_GET: regex not supported')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("empty array access", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
MAKE_ARRAY #0
|
||||||
|
PUSH 0
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'null', value: null })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("empty dict access", async () => {
|
||||||
|
const bytecode = toBytecode(`
|
||||||
|
MAKE_DICT #0
|
||||||
|
PUSH 'key'
|
||||||
|
DOT_GET
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(await run(bytecode)).toEqual({ type: 'null', value: null })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("STR_CONCAT", () => {
|
describe("STR_CONCAT", () => {
|
||||||
test("concats together strings", async () => {
|
test("concats together strings", async () => {
|
||||||
const str = `
|
const str = `
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user