summaryrefslogtreecommitdiff
path: root/src/interpreter/environment.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/interpreter/environment.ts')
-rw-r--r--src/interpreter/environment.ts83
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 };
+ }
+}