forked from defunkt/ReefVM
244 lines
6.6 KiB
TypeScript
244 lines
6.6 KiB
TypeScript
import { test, expect } from "bun:test"
|
|
import { Scope, toValue } from "#reef"
|
|
|
|
test("Scope - create empty scope", () => {
|
|
const scope = new Scope()
|
|
expect(scope.parent).toBeUndefined()
|
|
expect(scope.locals.size).toBe(0)
|
|
})
|
|
|
|
test("Scope - create child scope with parent", () => {
|
|
const parent = new Scope()
|
|
const child = new Scope(parent)
|
|
|
|
expect(child.parent).toBe(parent)
|
|
expect(child.locals.size).toBe(0)
|
|
})
|
|
|
|
test("Scope - set and get variable in same scope", () => {
|
|
const scope = new Scope()
|
|
const value = toValue(42)
|
|
|
|
scope.set("x", value)
|
|
expect(scope.get("x")).toBe(value)
|
|
})
|
|
|
|
test("Scope - get returns undefined for non-existent variable", () => {
|
|
const scope = new Scope()
|
|
expect(scope.get("x")).toBeUndefined()
|
|
})
|
|
|
|
test("Scope - set updates existing variable in same scope", () => {
|
|
const scope = new Scope()
|
|
|
|
scope.set("x", toValue(10))
|
|
expect(scope.get("x")).toEqual({ type: "number", value: 10 })
|
|
|
|
scope.set("x", toValue(20))
|
|
expect(scope.get("x")).toEqual({ type: "number", value: 20 })
|
|
})
|
|
|
|
test("Scope - get searches parent scope", () => {
|
|
const parent = new Scope()
|
|
const child = new Scope(parent)
|
|
|
|
parent.set("x", toValue(42))
|
|
expect(child.get("x")).toEqual({ type: "number", value: 42 })
|
|
})
|
|
|
|
test("Scope - get searches multiple levels up", () => {
|
|
const grandparent = new Scope()
|
|
const parent = new Scope(grandparent)
|
|
const child = new Scope(parent)
|
|
|
|
grandparent.set("x", toValue(100))
|
|
expect(child.get("x")).toEqual({ type: "number", value: 100 })
|
|
})
|
|
|
|
test("Scope - child updates parent variable when it exists (no shadowing)", () => {
|
|
const parent = new Scope()
|
|
const child = new Scope(parent)
|
|
|
|
parent.set("x", toValue(10))
|
|
child.set("x", toValue(20))
|
|
|
|
// set() updates parent's variable, not shadow it
|
|
expect(parent.get("x")).toEqual({ type: "number", value: 20 })
|
|
expect(child.get("x")).toEqual({ type: "number", value: 20 })
|
|
expect(child.locals.has("x")).toBe(false)
|
|
})
|
|
|
|
test("Scope - set updates parent scope variable when it exists", () => {
|
|
const parent = new Scope()
|
|
const child = new Scope(parent)
|
|
|
|
parent.set("x", toValue(10))
|
|
child.set("x", toValue(20))
|
|
|
|
// Should update parent's variable, not create new local one
|
|
expect(parent.get("x")).toEqual({ type: "number", value: 20 })
|
|
expect(child.get("x")).toEqual({ type: "number", value: 20 })
|
|
expect(child.locals.has("x")).toBe(false)
|
|
})
|
|
|
|
test("Scope - set creates new local variable when not found in parent", () => {
|
|
const parent = new Scope()
|
|
const child = new Scope(parent)
|
|
|
|
parent.set("x", toValue(10))
|
|
child.set("y", toValue(20))
|
|
|
|
expect(parent.get("x")).toEqual({ type: "number", value: 10 })
|
|
expect(parent.get("y")).toBeUndefined()
|
|
expect(child.get("x")).toEqual({ type: "number", value: 10 })
|
|
expect(child.get("y")).toEqual({ type: "number", value: 20 })
|
|
expect(child.locals.has("y")).toBe(true)
|
|
})
|
|
|
|
test("Scope - has returns true for variable in current scope", () => {
|
|
const scope = new Scope()
|
|
scope.set("x", toValue(42))
|
|
|
|
expect(scope.has("x")).toBe(true)
|
|
expect(scope.has("y")).toBe(false)
|
|
})
|
|
|
|
test("Scope - has returns true for variable in parent scope", () => {
|
|
const parent = new Scope()
|
|
const child = new Scope(parent)
|
|
|
|
parent.set("x", toValue(42))
|
|
|
|
expect(child.has("x")).toBe(true)
|
|
expect(child.has("y")).toBe(false)
|
|
})
|
|
|
|
test("Scope - has searches multiple levels up", () => {
|
|
const grandparent = new Scope()
|
|
const parent = new Scope(grandparent)
|
|
const child = new Scope(parent)
|
|
|
|
grandparent.set("x", toValue(100))
|
|
|
|
expect(child.has("x")).toBe(true)
|
|
expect(parent.has("x")).toBe(true)
|
|
expect(grandparent.has("x")).toBe(true)
|
|
})
|
|
|
|
test("Scope.vars() - returns empty array for empty scope", () => {
|
|
const scope = new Scope()
|
|
expect(scope.vars()).toEqual([])
|
|
})
|
|
|
|
test("Scope.vars() - returns single variable from current scope", () => {
|
|
const scope = new Scope()
|
|
scope.set("x", toValue(42))
|
|
|
|
expect(scope.vars()).toEqual(["x"])
|
|
})
|
|
|
|
test("Scope.vars() - returns multiple variables from current scope", () => {
|
|
const scope = new Scope()
|
|
scope.set("x", toValue(1))
|
|
scope.set("y", toValue(2))
|
|
scope.set("z", toValue(3))
|
|
|
|
const vars = scope.vars()
|
|
expect(vars.length).toBe(3)
|
|
expect(vars).toContain("x")
|
|
expect(vars).toContain("y")
|
|
expect(vars).toContain("z")
|
|
})
|
|
|
|
test("Scope.vars() - includes variables from parent scope", () => {
|
|
const parent = new Scope()
|
|
const child = new Scope(parent)
|
|
|
|
parent.set("x", toValue(1))
|
|
child.set("y", toValue(2))
|
|
|
|
const vars = child.vars()
|
|
expect(vars.length).toBe(2)
|
|
expect(vars).toContain("x")
|
|
expect(vars).toContain("y")
|
|
})
|
|
|
|
test("Scope.vars() - includes variables from multiple parent scopes", () => {
|
|
const grandparent = new Scope()
|
|
const parent = new Scope(grandparent)
|
|
const child = new Scope(parent)
|
|
|
|
grandparent.set("x", toValue(1))
|
|
parent.set("y", toValue(2))
|
|
child.set("z", toValue(3))
|
|
|
|
const vars = child.vars()
|
|
expect(vars.length).toBe(3)
|
|
expect(vars).toContain("x")
|
|
expect(vars).toContain("y")
|
|
expect(vars).toContain("z")
|
|
})
|
|
|
|
test("Scope.vars() - no duplicates when child updates parent variable", () => {
|
|
const parent = new Scope()
|
|
const child = new Scope(parent)
|
|
|
|
parent.set("x", toValue(10))
|
|
child.set("x", toValue(20)) // Updates parent, doesn't create local
|
|
|
|
// Only one "x" since child doesn't have its own local x
|
|
const vars = child.vars()
|
|
expect(vars).toEqual(["x"])
|
|
})
|
|
|
|
test("Scope.vars() - can't have duplicates if manually set in locals", () => {
|
|
const parent = new Scope()
|
|
const child = new Scope(parent)
|
|
|
|
parent.set("x", toValue(10))
|
|
child.locals.set("x", toValue(20))
|
|
const vars = child.vars()
|
|
expect(vars).toEqual(["x"])
|
|
|
|
expect(child.get("x")).toEqual({ type: "number", value: 20 })
|
|
expect(parent.get("x")).toEqual({ type: "number", value: 10 })
|
|
})
|
|
|
|
test("Scope.vars() - handles deep scope chains", () => {
|
|
let scope = new Scope()
|
|
|
|
// Create a deep chain: level0 -> level1 -> ... -> level5
|
|
for (let i = 0; i < 5; i++) {
|
|
scope.set(`var${i}`, toValue(i))
|
|
scope = new Scope(scope)
|
|
}
|
|
|
|
// Final scope should have all variables from the chain
|
|
const vars = scope.vars()
|
|
expect(vars.length).toBe(5)
|
|
expect(vars).toContain("var0")
|
|
expect(vars).toContain("var1")
|
|
expect(vars).toContain("var2")
|
|
expect(vars).toContain("var3")
|
|
expect(vars).toContain("var4")
|
|
})
|
|
|
|
test("Scope - can store and retrieve functions", () => {
|
|
const scope = new Scope()
|
|
|
|
const fnValue = {
|
|
type: 'function' as const,
|
|
value: '<function>' as const,
|
|
params: ['x'],
|
|
defaults: {},
|
|
body: 0,
|
|
variadic: false,
|
|
named: false,
|
|
parentScope: scope
|
|
}
|
|
|
|
scope.set("myFunc", fnValue)
|
|
expect(scope.get("myFunc")).toBe(fnValue)
|
|
expect(scope.get("myFunc")?.type).toBe("function")
|
|
})
|