ReefVM/tests/scope.test.ts
2025-11-08 08:40:15 -08:00

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")
})