From f5faa68baff93e4d4405265757e5de907f2ecdeb Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Wed, 28 Feb 2024 10:52:40 -0700 Subject: [PATCH] checkpoint --- src/interpreter/builtin_signatures.ts | 106 -------------------------- src/interpreter/builtins.ts | 94 +++++++++++++++++++++++ src/interpreter/denotable.ts | 71 +++++++++++++++++ src/interpreter/environment.ts | 27 +++++-- src/interpreter/index.ts | 10 +-- src/interpreter/interpreter.ts | 52 ++----------- src/utils/exception.ts | 2 +- test/interpreter.spec.ts | 16 ++++ test/programs/index.ts | 6 ++ test/programs/string-equal.cps | 1 + test/programs/string-unequal.cps | 1 + test/signature_match.spec.ts | 41 ++++++++++ 12 files changed, 258 insertions(+), 169 deletions(-) delete mode 100644 src/interpreter/builtin_signatures.ts create mode 100644 src/interpreter/builtins.ts create mode 100644 src/interpreter/denotable.ts create mode 100644 test/programs/string-equal.cps create mode 100644 test/programs/string-unequal.cps create mode 100644 test/signature_match.spec.ts diff --git a/src/interpreter/builtin_signatures.ts b/src/interpreter/builtin_signatures.ts deleted file mode 100644 index 4ed99a7..0000000 --- a/src/interpreter/builtin_signatures.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { BadArgumentError } from '@/utils'; -import type { DenotableValueType } from '.'; - -export type Signature = Array>; - -export const primitiveOperationSignatures: Record = { - '+': [ - ['int', 'int', 'int'], - ['real', 'real', 'real'], - ['int', 'real', 'real'], - ['real', 'int', 'real'], - ], - '-': [ - ['int', 'int', 'int'], - ['real', 'real', 'real'], - ['int', 'real', 'real'], - ['real', 'int', 'real'], - ], - '*': [ - ['int', 'int', 'int'], - ['real', 'real', 'real'], - ['int', 'real', 'real'], - ['real', 'int', 'real'], - ], - '/': [ - ['int', 'int', 'int'], - ['real', 'real', 'real'], - ['int', 'real', 'real'], - ['real', 'int', 'real'], - ], - '%': [['int', 'int', 'int']], - '**': [ - ['int', 'int', 'int'], - ['real', 'real', 'real'], - ['int', 'real', 'real'], - ['real', 'int', 'real'], - ], - '>>': [['int', 'int', 'int']], - '<<': [['int', 'int', 'int']], - '&': [['int', 'int', 'int']], - '|': [['int', 'int', 'int']], - '^': [['int', 'int', 'int']], - '==': [ - ['int', 'int', 'int'], - ['real', 'real', 'int'], - ['string', 'string', 'int'], - ], - '!=': [ - ['int', 'int', 'int'], - ['real', 'real', 'int'], - ['string', 'string', 'int'], - ], - '<': [ - ['int', 'int', 'int'], - ['real', 'real', 'int'], - ], - '>': [ - ['int', 'int', 'int'], - ['real', 'real', 'int'], - ], - '<=': [ - ['int', 'int', 'int'], - ['real', 'real', 'int'], - ], - '>=': [ - ['int', 'int', 'int'], - ['real', 'real', 'int'], - ], - '&&': [['int', 'int', 'int']], - '||': [['int', 'int', 'int']], - '!': [['int', 'int']], -}; - -export const getResultingType = ( - opr: string, - types: DenotableValueType[], -): DenotableValueType | Signature => { - const signature = primitiveOperationSignatures[opr]; - if (!signature) { - throw new BadArgumentError(`Invalid operation: ${opr}`); - } - - const resultingType = signature.find(sig => { - if (sig.length !== types.length) { - return false; - } - - return sig.every((type, i) => { - if (Array.isArray(type)) { - return getResultingType( - opr, - types.map((t, j) => (i === j ? t : 'int')), - ); - } - return type === 'int' || type === types[i]; - }); - }); - - if (!resultingType) { - throw new TypeError( - `Invalid types for operation ${opr}: ${types.join(', ')}`, - ); - } - - return resultingType as DenotableValueType | Signature; -}; diff --git a/src/interpreter/builtins.ts b/src/interpreter/builtins.ts new file mode 100644 index 0000000..e18d4a3 --- /dev/null +++ b/src/interpreter/builtins.ts @@ -0,0 +1,94 @@ +import { type DenotableFunctionSignature, Environment } from '.'; + +const addUnaryIntegerOperationsTo = (env: Environment) => { + const unaryIntegerOperationSignatures: DenotableFunctionSignature[] = [ + { + arguments: ['int'], + return: 'int', + }, + ]; + + for (const { name, fn } of [ + { name: '~', fn: (a: number) => ~a }, + { name: '!', fn: (a: number) => (!a ? 1 : 0) }, + ]) { + env.set(name, { + type: 'function', + value: { signatures: unaryIntegerOperationSignatures, body: fn }, + }); + } + + return env; +}; + +const addBinaryIntegerOperationsTo = (env: Environment) => { + const binaryIntegerOperationSignatures: DenotableFunctionSignature[] = [ + { + arguments: ['int', 'int'], + return: 'int', + }, + ]; + + for (const { name, fn } of [ + { name: '%', fn: (a: number, b: number) => a % b }, + { name: '>>', fn: (a: number, b: number) => a >> b }, + { name: '<<', fn: (a: number, b: number) => a << b }, + { name: '|', fn: (a: number, b: number) => a | b }, + { name: '^', fn: (a: number, b: number) => a ^ b }, + { name: '&&', fn: (a: number, b: number) => (a && b ? 1 : 0) }, + { name: '<=', fn: (a: number, b: number) => (a <= b ? 1 : 0) }, + { name: '<', fn: (a: number, b: number) => (a < b ? 1 : 0) }, + { name: '>', fn: (a: number, b: number) => (a > b ? 1 : 0) }, + { name: '>=', fn: (a: number, b: number) => (a >= b ? 1 : 0) }, + { name: '||', fn: (a: number, b: number) => (a || b ? 1 : 0) }, + { name: '==', fn: (a: number, b: number) => (a == b ? 1 : 0) }, + ]) { + env.set(name, { + type: 'function', + value: { signatures: binaryIntegerOperationSignatures, body: fn }, + }); + } + + return env; +}; + +const addBinaryArithmeticOperationsTo = (env: Environment) => { + const binaryArithmeticSignatures: DenotableFunctionSignature[] = [ + { + arguments: ['int', 'int'], + return: 'int', + }, + { + arguments: [ + ['int', 'real'], + ['int', 'real'], + ], + return: 'real', + }, + ]; + + for (const { name, fn } of [ + { name: '+', fn: (a: number, b: number) => a + b }, + { name: '-', fn: (a: number, b: number) => a - b }, + { name: '*', fn: (a: number, b: number) => a * b }, + { name: '/', fn: (a: number, b: number) => a / b }, + { name: '**', fn: (a: number, b: number) => a ** b }, + ]) { + env.set(name, { + type: 'function', + value: { signatures: binaryArithmeticSignatures, body: fn }, + }); + } + + return env; +}; + +export const environmentWithBuiltins = () => { + const environment = new Environment(); + + return [ + addBinaryArithmeticOperationsTo, + addBinaryIntegerOperationsTo, + addUnaryIntegerOperationsTo, + ].reduce((acc, builtinsAdder) => builtinsAdder(acc), environment); +}; diff --git a/src/interpreter/denotable.ts b/src/interpreter/denotable.ts new file mode 100644 index 0000000..5fe3a3f --- /dev/null +++ b/src/interpreter/denotable.ts @@ -0,0 +1,71 @@ +import type { Identifier } from '@/parser'; + +export type UnionDenotableType = + | Array + | DenotableType + | DenotableFunctionSignature; + +export type DenotableFunctionSignature = { + arguments: Array; + return: UnionDenotableType; +}; + +export type DenotableFunction = { + signatures: Array; + body: Function; +}; + +export type DenotableType = + | 'null' + | 'int' + | 'real' + | 'string' + | 'bytearray' + | 'function' + | 'reference'; + +export type DenotableValue = + | null + | number + | string + | Uint8Array + | DenotableFunction + | Identifier; + +export type Denotable = { + type: DenotableType; + value: DenotableValue; +}; + +export const functionSignaturesEqual = ( + a: DenotableFunctionSignature, + b: DenotableFunctionSignature, +): boolean => { + if (a.arguments.length !== b.arguments.length) { + return false; + } + + if (typeof a.return !== typeof b.return) { + return false; + } + + if ( + typeof a.return === 'object' && + typeof b.return === 'object' && + 'return' in a.return && + 'return' in b.return + ) { + return functionSignaturesEqual(a.return, b.return); + } +}; + +export const matchSignature = ( + args: Array, + signatures: Array, +): DenotableFunctionSignature | undefined => { + return signatures.find(signature => { + if (args.length !== signature.arguments.length) { + return false; + } + }); +}; diff --git a/src/interpreter/environment.ts b/src/interpreter/environment.ts index cd0ee5c..aac0285 100644 --- a/src/interpreter/environment.ts +++ b/src/interpreter/environment.ts @@ -1,8 +1,14 @@ -import { UnknownSymbolError } from '@/utils'; -import type { DenotableValue } from '.'; +import { UnknownSymbolError, InvalidType } from '@/utils'; +import type { + Denotable, + DenotableFunction, + DenotableFunctionSignature, + DenotableType, + DenotableValue, +} from '.'; export class Environment { - private scope: Map; + private scope: Map; private parent: Environment | null; constructor(parent: Environment | null = null) { @@ -10,13 +16,13 @@ export class Environment { this.scope = new Map(); } - public set(name: string, value: DenotableValue) { + public set(name: string, value: Denotable) { this.scope.set(name, value); } - public get(name: string): DenotableValue { + public get(name: string): Denotable { if (this.scope.has(name)) { - return this.scope.get(name); + return this.scope.get(name)!; } if (this.parent) { @@ -41,4 +47,13 @@ export class Environment { public createChild(): Environment { return new Environment(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'); + } + + return { type: 'real', value: 0 }; + } } diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index dbc2d63..1c7c9db 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -1,11 +1,3 @@ -export type DenotableValueType = - | 'null' - | 'int' - | 'real' - | 'string' - | 'function' - | 'reference'; -export type DenotableValue = { type: DenotableValueType; value: any }; - +export * from './denotable'; export * from './environment'; export * from './interpreter'; diff --git a/src/interpreter/interpreter.ts b/src/interpreter/interpreter.ts index bf76f4f..af1baf7 100644 --- a/src/interpreter/interpreter.ts +++ b/src/interpreter/interpreter.ts @@ -4,7 +4,7 @@ import { type Program, type Value, } from '@/parser'; -import { Environment, type DenotableValue } from '.'; +import { Environment, type Denotable } from '.'; import { BadArgumentError, InvalidStateError, @@ -16,7 +16,7 @@ const evaluateValue = ( value: Value, env: Environment, logger: TracingLogger, -): DenotableValue => { +): Denotable => { if (typeof value === 'string') { return { type: 'string', value }; } @@ -49,50 +49,8 @@ const evaluatePrimitiveOperation = ( evaluateValue(operand, env, logger.createChild('evaluteValue')), ); - const rightReducingOperations = { - '+': (acc: number, operand: { value: number }) => acc + operand.value, - '-': (acc: number, operand: { value: number }) => acc - operand.value, - '*': (acc: number, operand: { value: number }) => acc * operand.value, - }; - const leftReducingOperations = { - '/': (acc: number, operand: { value: number }) => acc / operand.value, - '%': (acc: number, operand: { value: number }) => acc % operand.value, - '**': (acc: number, operand: { value: number }) => acc ** operand.value, - '>>': (acc: number, operand: { value: number }) => acc >> operand.value, - '<<': (acc: number, operand: { value: number }) => acc << operand.value, - '&': (acc: number, operand: { value: number }) => acc & operand.value, - '|': (acc: number, operand: { value: number }) => acc | operand.value, - '^': (acc: number, operand: { value: number }) => acc ^ operand.value, - }; - const someNumberIsReal = operandValues.some(({ type }) => type === 'real'); - - let result: DenotableValue = { type: 'null', value: null }; + let result: Denotable = { type: 'null', value: null }; const continuationEnvironment = env.createChild(); - if (opr in rightReducingOperations) { - logger.debug('Evaluating right reducing operation: ' + opr); - const sum = operandValues.reduce( - rightReducingOperations[opr as keyof typeof rightReducingOperations], - 0, - ); - result = { type: someNumberIsReal ? 'real' : 'int', value: sum }; - - for (const binding of resultBindings) { - continuationEnvironment.set(binding.name, result); - } - } - if (opr in leftReducingOperations) { - logger.debug('Evaluating left reducing operation: ' + opr); - const [first, ...rest] = operandValues; - const sum = rest.reduce( - leftReducingOperations[opr as keyof typeof leftReducingOperations], - first.value, - ); - result = { type: someNumberIsReal ? 'real' : 'int', value: sum }; - - for (const binding of resultBindings) { - continuationEnvironment.set(binding.name, result); - } - } // return the result of the last continuation return continuations.reduce((_, continuation, i) => { @@ -109,7 +67,7 @@ const evaluteContinuationExpression = ( expr: ContinuationExpression, env: Environment, logger: TracingLogger, -): DenotableValue => { +): Denotable => { if ('primitiveOperation' in expr) { logger.debug('Evaluating primitive operation'); return evaluatePrimitiveOperation( @@ -146,7 +104,7 @@ const evaluteContinuationExpression = ( export const evaluate = async ( ast: Program, logger: TracingLogger, -): Promise => { +): Promise => { const environment = new Environment(); return ast.reduce((_, continuation, i) => { diff --git a/src/utils/exception.ts b/src/utils/exception.ts index f1ea92d..0472003 100644 --- a/src/utils/exception.ts +++ b/src/utils/exception.ts @@ -6,4 +6,4 @@ export class InvalidStateError extends Error {} export class BadArgumentError extends Error {} -export class TypeError extends Error {} +export class InvalidType extends Error {} diff --git a/test/interpreter.spec.ts b/test/interpreter.spec.ts index 4dd0028..0da25c3 100644 --- a/test/interpreter.spec.ts +++ b/test/interpreter.spec.ts @@ -10,3 +10,19 @@ test('Add (1 real) and (3 int) => (4 real)', async () => { const result = await evaluate(ast, testingLogger); expect(result).toEqual({ type: 'real', value: 4 }); }); + +/* +test('String equality', async () => { + const ast = peggyParse(await TestPrograms.StringEquality); + + const result = await evaluate(ast, testingLogger); + expect(result).toEqual({ type: 'int', value: 1 }); +}); + +test('String inequality', async () => { + const ast = peggyParse(await TestPrograms.StringInEquality); + + const result = await evaluate(ast, testingLogger); + expect(result).toEqual({ type: 'int', value: 0 }); +}); +*/ diff --git a/test/programs/index.ts b/test/programs/index.ts index e0403fd..6dcd873 100644 --- a/test/programs/index.ts +++ b/test/programs/index.ts @@ -4,4 +4,10 @@ export namespace TestPrograms { export const AddOneThree = Bun.file( join(import.meta.dir + '/add-1-3.cps'), ).text(); + export const StringEquality = Bun.file( + join(import.meta.dir + '/string-equal.cps'), + ).text(); + export const StringInEquality = Bun.file( + join(import.meta.dir + '/string-unequal.cps'), + ).text(); } diff --git a/test/programs/string-equal.cps b/test/programs/string-equal.cps new file mode 100644 index 0000000..ea49b22 --- /dev/null +++ b/test/programs/string-equal.cps @@ -0,0 +1 @@ +PRIMOP(==, ["asdf", "asdf"], [result], []) \ No newline at end of file diff --git a/test/programs/string-unequal.cps b/test/programs/string-unequal.cps new file mode 100644 index 0000000..ccd278e --- /dev/null +++ b/test/programs/string-unequal.cps @@ -0,0 +1 @@ +PRIMOP(==, ["asdfasdf", "asdf"], [result], []) diff --git a/test/signature_match.spec.ts b/test/signature_match.spec.ts new file mode 100644 index 0000000..af17a93 --- /dev/null +++ b/test/signature_match.spec.ts @@ -0,0 +1,41 @@ +import { expect, test } from 'bun:test'; +import { TestPrograms } from './programs'; +import { peggyParse } from '@/parser'; +import { + evaluate, + type DenotableFunctionSignature, + matchSignature, +} from '@/interpreter'; +import { testingLogger } from './logger'; + +test('matches simple signatures', async () => { + const simpleSignature: DenotableFunctionSignature[] = [ + { + arguments: ['int'], + return: 'int', + }, + ]; + + expect(matchSignature(['int'], simpleSignature)).toEqual(simpleSignature[0]); +}); + +test('finds first match', async () => { + const simpleSignature: DenotableFunctionSignature[] = [ + { + arguments: ['int', 'int'], + return: 'int', + }, + { + arguments: [['int', 'real'], 'int'], + return: 'real', + }, + ]; + + expect(matchSignature(['int', 'int'], simpleSignature)).toEqual( + simpleSignature[0], + ); + + expect(matchSignature(['real', 'int'], simpleSignature)).toEqual( + simpleSignature[1], + ); +});