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