Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
2.7 kB · 103 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104import { type AstNode, type Binding, referenceError, typeError } from "./model.js"import type { Value } from "./objects.js"
export class ScopeStack { private readonly scopes: Array<Map<string, Binding>>
constructor(scopes: Array<Map<string, Binding>>) { this.scopes = scopes }
reserve(name: string, mutable: boolean, node: AstNode): void { const scope = this.current() if (scope.has(name)) { throw typeError(`Identifier '${name}' has already been declared.`, node) } scope.set(name, { mutable, value: undefined, initialized: false }) }
initialize(name: string, value: Value, node: AstNode): void { const binding = this.current().get(name) if (!binding || binding.initialized !== false) { throw typeError(`Identifier '${name}' has not been reserved for initialization.`, node) } binding.value = value binding.initialized = true }
declare(name: string, value: Value, mutable: boolean, node: AstNode): void { const scope = this.current() if (scope.has(name)) { throw typeError(`Identifier '${name}' has already been declared.`, node) } scope.set(name, { mutable, value, initialized: true }) }
get(name: string, node: AstNode): Value { const binding = this.resolve(name)
if (!binding) { throw referenceError(`Unknown identifier '${name}'.`, node) }
if (binding.initialized === false) { throw referenceError(`Cannot access '${name}' before initialization.`, node) }
return binding.value }
set(name: string, value: Value, node: AstNode): Value { const binding = this.resolve(name)
if (!binding) { throw referenceError(`Unknown identifier '${name}'.`, node) }
if (binding.initialized === false) { throw referenceError(`Cannot access '${name}' before initialization.`, node) }
if (!binding.mutable) { throw typeError(`Cannot assign to constant '${name}'.`, node) }
binding.value = value return value }
resolve(name: string): Binding | undefined { for (let index = this.scopes.length - 1; index >= 0; index -= 1) { const scope = this.scopes[index] const binding = scope?.get(name)
if (binding) { return binding } }
return undefined }
current(): Map<string, Binding> { const scope = this.scopes[this.scopes.length - 1]
if (!scope) { throw typeError("Interpreter scope stack is empty.") }
return scope }
push(scope: Map<string, Binding> = new Map()): void { this.scopes.push(scope) }
pop(): void { this.scopes.pop() }
capture(): Array<Map<string, Binding>> { return this.scopes.slice() }}