forked from defunkt/ReefVM
scope.vars()
This commit is contained in:
parent
f439c25742
commit
d7a971db24
|
|
@ -30,6 +30,15 @@ export class Scope {
|
|||
has(name: string): boolean {
|
||||
return this.locals.has(name) || this.parent?.has(name) || false
|
||||
}
|
||||
|
||||
vars(): string[] {
|
||||
const vars = new Set(this.parent?.vars())
|
||||
|
||||
for (const name of this.locals.keys())
|
||||
vars.add(name)
|
||||
|
||||
return [...vars].sort()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,14 @@ export class VM {
|
|||
}
|
||||
}
|
||||
|
||||
has(name: string): boolean {
|
||||
return this.scope.has(name)
|
||||
}
|
||||
|
||||
vars(): string[] {
|
||||
return this.scope.vars()
|
||||
}
|
||||
|
||||
get(name: string) {
|
||||
return this.scope.get(name)
|
||||
}
|
||||
|
|
|
|||
243
tests/scope.test.ts
Normal file
243
tests/scope.test.ts
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
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")
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user