From 68745f5b94b604578d6b91dd9e8f57e34fa32eb3 Mon Sep 17 00:00:00 2001 From: Lizzy Hunt Date: Wed, 28 Feb 2024 12:48:03 -0700 Subject: [PATCH] primitive operations setup --- src/interpreter/builtins.ts | 29 +++++--- src/interpreter/denotable.ts | 60 +++++++++++------ src/interpreter/environment.ts | 46 ++++++++++--- src/interpreter/interpreter.ts | 9 ++- test/signature_match.spec.ts | 119 +++++++++++++++++++++++++++++++++ 5 files changed, 222 insertions(+), 41 deletions(-) diff --git a/src/interpreter/builtins.ts b/src/interpreter/builtins.ts index e18d4a3..c5769db 100644 --- a/src/interpreter/builtins.ts +++ b/src/interpreter/builtins.ts @@ -1,4 +1,8 @@ -import { type DenotableFunctionSignature, Environment } from '.'; +import { + type DenotableFunctionSignature, + Environment, + type Denotable, +} from '.'; const addUnaryIntegerOperationsTo = (env: Environment) => { const unaryIntegerOperationSignatures: DenotableFunctionSignature[] = [ @@ -14,7 +18,10 @@ const addUnaryIntegerOperationsTo = (env: Environment) => { ]) { env.set(name, { type: 'function', - value: { signatures: unaryIntegerOperationSignatures, body: fn }, + value: { + signatures: unaryIntegerOperationSignatures, + body: ({ value }: Denotable) => fn(value as number), + }, }); } @@ -45,7 +52,11 @@ const addBinaryIntegerOperationsTo = (env: Environment) => { ]) { env.set(name, { type: 'function', - value: { signatures: binaryIntegerOperationSignatures, body: fn }, + value: { + signatures: binaryIntegerOperationSignatures, + body: ({ value: a }: Denotable, { value: b }: Denotable) => + fn(a as number, b as number), + }, }); } @@ -76,19 +87,21 @@ const addBinaryArithmeticOperationsTo = (env: Environment) => { ]) { env.set(name, { type: 'function', - value: { signatures: binaryArithmeticSignatures, body: fn }, + value: { + signatures: binaryArithmeticSignatures, + body: ({ value: a }: Denotable, { value: b }: Denotable) => + fn(a as number, b as number), + }, }); } return env; }; -export const environmentWithBuiltins = () => { - const environment = new Environment(); - +export const putBuiltinsOnEnvironemtn = (env: Environment) => { return [ addBinaryArithmeticOperationsTo, addBinaryIntegerOperationsTo, addUnaryIntegerOperationsTo, - ].reduce((acc, builtinsAdder) => builtinsAdder(acc), environment); + ].reduce((acc, builtinsAdder) => builtinsAdder(acc), env); }; diff --git a/src/interpreter/denotable.ts b/src/interpreter/denotable.ts index 5fe3a3f..65aee86 100644 --- a/src/interpreter/denotable.ts +++ b/src/interpreter/denotable.ts @@ -1,13 +1,15 @@ import type { Identifier } from '@/parser'; +import { testingLogger } from '@t/logger'; export type UnionDenotableType = | Array | DenotableType - | DenotableFunctionSignature; + | DenotableFunctionSignature + | Array; export type DenotableFunctionSignature = { arguments: Array; - return: UnionDenotableType; + return: DenotableType; }; export type DenotableFunction = { @@ -37,35 +39,55 @@ export type Denotable = { value: DenotableValue; }; -export const functionSignaturesEqual = ( - a: DenotableFunctionSignature, - b: DenotableFunctionSignature, +export const denotableTypesEquivalent = ( + a: UnionDenotableType, + b: UnionDenotableType, ): boolean => { - if (a.arguments.length !== b.arguments.length) { - return false; - } + if (typeof a !== typeof b) return false; - if (typeof a.return !== typeof b.return) { - return false; + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (!denotableTypesEquivalent(a[i], b[i])) return false; + } + return true; } if ( - typeof a.return === 'object' && - typeof b.return === 'object' && - 'return' in a.return && - 'return' in b.return + typeof a === 'object' && + typeof b === 'object' && + 'arguments' in a && + 'arguments' in b ) { - return functionSignaturesEqual(a.return, b.return); + if (a.arguments.length !== b.arguments.length) return false; + if (!denotableTypesEquivalent(a.return, b.return)) return false; + for (let i = 0; i < a.arguments.length; i++) { + if (!denotableTypesEquivalent(a.arguments[i], b.arguments[i])) { + return false; + } + } + return true; } + + if (a === b) return true; + + return false; }; export const matchSignature = ( - args: Array, + args: Array, signatures: Array, ): DenotableFunctionSignature | undefined => { return signatures.find(signature => { - if (args.length !== signature.arguments.length) { - return false; - } + if (args.length !== signature.arguments.length) return false; + + return args.every((arg, i) => { + const argSignature = signature.arguments[i]; + if (Array.isArray(argSignature)) { + return argSignature.some(a => denotableTypesEquivalent(a, arg)); + } + + return denotableTypesEquivalent(arg, signature.arguments[i]); + }); }); }; diff --git a/src/interpreter/environment.ts b/src/interpreter/environment.ts index aac0285..1c451aa 100644 --- a/src/interpreter/environment.ts +++ b/src/interpreter/environment.ts @@ -1,17 +1,13 @@ -import { UnknownSymbolError, InvalidType } from '@/utils'; -import type { - Denotable, - DenotableFunction, - DenotableFunctionSignature, - DenotableType, - DenotableValue, -} from '.'; +import { UnknownSymbolError, InvalidType, type TracingLogger } from '@/utils'; +import { matchSignature, type Denotable } from '.'; export class Environment { private scope: Map; private parent: Environment | null; + private logger: TracingLogger; - constructor(parent: Environment | null = null) { + constructor(logger: TracingLogger, parent: Environment | null = null) { + this.logger = logger; this.parent = parent; this.scope = new Map(); } @@ -22,10 +18,12 @@ export class Environment { 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); } @@ -34,18 +32,21 @@ export class Environment { 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); + return new Environment(this.logger.createChild('Env'), this); } public apply(name: string, args: Denotable[]): Denotable { @@ -54,6 +55,29 @@ export class Environment { throw new InvalidType(name + ' is not a valid function'); } - return { type: 'real', value: 0 }; + 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 }; } } diff --git a/src/interpreter/interpreter.ts b/src/interpreter/interpreter.ts index af1baf7..4a0be05 100644 --- a/src/interpreter/interpreter.ts +++ b/src/interpreter/interpreter.ts @@ -11,6 +11,7 @@ import { NotImplementedError, type TracingLogger, } from '@/utils'; +import { putBuiltinsOnEnvironemtn } from './builtins'; const evaluateValue = ( value: Value, @@ -49,7 +50,7 @@ const evaluatePrimitiveOperation = ( evaluateValue(operand, env, logger.createChild('evaluteValue')), ); - let result: Denotable = { type: 'null', value: null }; + const result = env.apply(opr, operandValues); const continuationEnvironment = env.createChild(); // return the result of the last continuation @@ -105,13 +106,15 @@ export const evaluate = async ( ast: Program, logger: TracingLogger, ): Promise => { - const environment = new Environment(); + const globalEnvironment = putBuiltinsOnEnvironemtn( + new Environment(logger.createChild('Root')), + ); return ast.reduce((_, continuation, i) => { const exprLogger = logger.createChild(`statement[${i}]`); return evaluteContinuationExpression( continuation as ContinuationExpression, - environment, + globalEnvironment, exprLogger, ); }, null); diff --git a/test/signature_match.spec.ts b/test/signature_match.spec.ts index af17a93..10be880 100644 --- a/test/signature_match.spec.ts +++ b/test/signature_match.spec.ts @@ -4,10 +4,59 @@ import { peggyParse } from '@/parser'; import { evaluate, type DenotableFunctionSignature, + denotableTypesEquivalent, matchSignature, } from '@/interpreter'; import { testingLogger } from './logger'; +test('simple denotable types are equivalent', () => { + expect(denotableTypesEquivalent('int', 'int')).toBe(true); + expect(denotableTypesEquivalent('int', 'real')).toBe(false); + expect(denotableTypesEquivalent('int', 'null')).toBe(false); + expect(denotableTypesEquivalent('null', 'null')).toBe(true); +}); + +test('union data types are equivalent', () => { + expect(denotableTypesEquivalent(['int', 'real'], ['int', 'real'])).toBe(true); + expect(denotableTypesEquivalent('int', ['int', 'real'])).toBe(false); +}); + +test('function data types are equivalent', () => { + expect( + denotableTypesEquivalent( + [ + { + arguments: ['int', 'real'], + return: 'int', + }, + ], + [ + { + arguments: ['int', 'real'], + return: 'int', + }, + ], + ), + ).toBe(true); + + expect( + denotableTypesEquivalent( + [ + { + arguments: ['int', 'real'], + return: 'real', + }, + ], + [ + { + arguments: ['int', 'real'], + return: 'int', + }, + ], + ), + ).toBe(false); +}); + test('matches simple signatures', async () => { const simpleSignature: DenotableFunctionSignature[] = [ { @@ -39,3 +88,73 @@ test('finds first match', async () => { simpleSignature[1], ); }); + +test('finds first match with a function signature', async () => { + const testSignature: DenotableFunctionSignature = { + arguments: ['int', 'real'], + return: 'int', + }; + + const simpleSignature: DenotableFunctionSignature[] = [ + { + arguments: ['int', 'int'], + return: 'int', + }, + { + arguments: [[testSignature, 'real'], 'int'], + return: 'function', + }, + ]; + + expect(matchSignature(['int', 'int'], simpleSignature)).toEqual( + simpleSignature[0], + ); + + expect(matchSignature(['real', 'int'], simpleSignature)).toEqual( + simpleSignature[1], + ); + + expect(matchSignature([testSignature, 'int'], simpleSignature)).toEqual( + simpleSignature[1], + ); +}); + +test('finds first match with a function with many signatures', async () => { + const testSignature: DenotableFunctionSignature[] = [ + { + arguments: ['int', 'real'], + return: 'int', + }, + { + arguments: ['int', 'int'], + return: 'int', + }, + ]; + + const simpleSignature: DenotableFunctionSignature[] = [ + { + arguments: ['int', 'int'], + return: 'int', + }, + { + arguments: [[testSignature, 'real'], 'int'], + return: 'function', + }, + ]; + + expect(matchSignature(['int', 'int'], simpleSignature)).toEqual( + simpleSignature[0], + ); + + expect(matchSignature(['real', 'int'], simpleSignature)).toEqual( + simpleSignature[1], + ); + + expect(matchSignature([testSignature, 'int'], simpleSignature)).toEqual( + simpleSignature[1], + ); + + expect( + matchSignature([[testSignature[0]], 'int'], simpleSignature), + ).toBeUndefined(); +});