diff options
Diffstat (limited to 'src/interpreter/environment.ts')
-rw-r--r-- | src/interpreter/environment.ts | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/src/interpreter/environment.ts b/src/interpreter/environment.ts new file mode 100644 index 0000000..1c451aa --- /dev/null +++ b/src/interpreter/environment.ts @@ -0,0 +1,83 @@ +import { UnknownSymbolError, InvalidType, type TracingLogger } from '@/utils'; +import { matchSignature, type Denotable } from '.'; + +export class Environment { + private scope: Map<string, Denotable>; + private parent: Environment | null; + private logger: TracingLogger; + + constructor(logger: TracingLogger, parent: Environment | null = null) { + this.logger = logger; + this.parent = parent; + this.scope = new Map(); + } + + public set(name: string, value: Denotable) { + this.scope.set(name, value); + } + + public get(name: string): Denotable { + if (this.scope.has(name)) { + this.logger.debug(`Found Name=(${name}) in current scope`); + return this.scope.get(name)!; + } + + if (this.parent) { + this.logger.debug(`Looking for Name=(${name}) in parent scope`); + return this.parent.get(name); + } + + throw new UnknownSymbolError(`Undefined variable: ${name}`); + } + + public has(name: string): boolean { + if (this.scope.has(name)) { + this.logger.debug(`Found Name=(${name}) in current scope`); + return true; + } + + if (this.parent) { + this.logger.debug(`Found Name=(${name}) in current scope`); + return this.parent.has(name); + } + + this.logger.debug(`Name=(${name}) not found in any scope`); + return false; + } + + public createChild(): Environment { + return new Environment(this.logger.createChild('Env'), this); + } + + public apply(name: string, args: Denotable[]): Denotable { + const fn = this.get(name); + if (typeof fn.value !== 'object' || !fn.value || !('body' in fn.value)) { + throw new InvalidType(name + ' is not a valid function'); + } + + const argTypes = args.map(arg => { + const { type, value } = arg; + const isFunction = + type === 'function' && + typeof value === 'object' && + value && + 'signatures' in value; + if (isFunction) { + return value.signatures; + } + return type; + }); + + const appliedSignature = matchSignature(argTypes, fn.value.signatures); + if (!appliedSignature) { + throw new InvalidType(`No matching signature for ${name}`); + } + + this.logger.debug( + `Applying Function=(${name}) with Args=(${JSON.stringify(args)}) with Signature=(${JSON.stringify(appliedSignature)})`, + ); + + const value = fn.value.body.apply(this, args); + return { type: appliedSignature.return, value }; + } +} |