checkpoint

This commit is contained in:
Elizabeth Hunt 2024-02-28 10:52:40 -07:00
parent ea7a11844a
commit f5faa68baf
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
12 changed files with 258 additions and 169 deletions

View File

@ -1,106 +0,0 @@
import { BadArgumentError } from '@/utils';
import type { DenotableValueType } from '.';
export type Signature = Array<Array<DenotableValueType | Signature>>;
export const primitiveOperationSignatures: Record<string, Signature> = {
'+': [
['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;
};

View File

@ -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);
};

View File

@ -0,0 +1,71 @@
import type { Identifier } from '@/parser';
export type UnionDenotableType =
| Array<DenotableType | DenotableFunctionSignature>
| DenotableType
| DenotableFunctionSignature;
export type DenotableFunctionSignature = {
arguments: Array<UnionDenotableType>;
return: UnionDenotableType;
};
export type DenotableFunction = {
signatures: Array<DenotableFunctionSignature>;
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<DenotableFunction | DenotableType>,
signatures: Array<DenotableFunctionSignature>,
): DenotableFunctionSignature | undefined => {
return signatures.find(signature => {
if (args.length !== signature.arguments.length) {
return false;
}
});
};

View File

@ -1,8 +1,14 @@
import { UnknownSymbolError } from '@/utils'; import { UnknownSymbolError, InvalidType } from '@/utils';
import type { DenotableValue } from '.'; import type {
Denotable,
DenotableFunction,
DenotableFunctionSignature,
DenotableType,
DenotableValue,
} from '.';
export class Environment { export class Environment {
private scope: Map<string, DenotableValue>; private scope: Map<string, Denotable>;
private parent: Environment | null; private parent: Environment | null;
constructor(parent: Environment | null = null) { constructor(parent: Environment | null = null) {
@ -10,13 +16,13 @@ export class Environment {
this.scope = new Map(); this.scope = new Map();
} }
public set(name: string, value: DenotableValue) { public set(name: string, value: Denotable) {
this.scope.set(name, value); this.scope.set(name, value);
} }
public get(name: string): DenotableValue { public get(name: string): Denotable {
if (this.scope.has(name)) { if (this.scope.has(name)) {
return this.scope.get(name); return this.scope.get(name)!;
} }
if (this.parent) { if (this.parent) {
@ -41,4 +47,13 @@ export class Environment {
public createChild(): Environment { public createChild(): Environment {
return new Environment(this); 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 };
}
} }

View File

@ -1,11 +1,3 @@
export type DenotableValueType = export * from './denotable';
| 'null'
| 'int'
| 'real'
| 'string'
| 'function'
| 'reference';
export type DenotableValue = { type: DenotableValueType; value: any };
export * from './environment'; export * from './environment';
export * from './interpreter'; export * from './interpreter';

View File

@ -4,7 +4,7 @@ import {
type Program, type Program,
type Value, type Value,
} from '@/parser'; } from '@/parser';
import { Environment, type DenotableValue } from '.'; import { Environment, type Denotable } from '.';
import { import {
BadArgumentError, BadArgumentError,
InvalidStateError, InvalidStateError,
@ -16,7 +16,7 @@ const evaluateValue = (
value: Value, value: Value,
env: Environment, env: Environment,
logger: TracingLogger, logger: TracingLogger,
): DenotableValue => { ): Denotable => {
if (typeof value === 'string') { if (typeof value === 'string') {
return { type: 'string', value }; return { type: 'string', value };
} }
@ -49,50 +49,8 @@ const evaluatePrimitiveOperation = (
evaluateValue(operand, env, logger.createChild('evaluteValue')), evaluateValue(operand, env, logger.createChild('evaluteValue')),
); );
const rightReducingOperations = { let result: Denotable = { type: 'null', value: null };
'+': (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 };
const continuationEnvironment = env.createChild(); 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 the result of the last continuation
return continuations.reduce((_, continuation, i) => { return continuations.reduce((_, continuation, i) => {
@ -109,7 +67,7 @@ const evaluteContinuationExpression = (
expr: ContinuationExpression, expr: ContinuationExpression,
env: Environment, env: Environment,
logger: TracingLogger, logger: TracingLogger,
): DenotableValue => { ): Denotable => {
if ('primitiveOperation' in expr) { if ('primitiveOperation' in expr) {
logger.debug('Evaluating primitive operation'); logger.debug('Evaluating primitive operation');
return evaluatePrimitiveOperation( return evaluatePrimitiveOperation(
@ -146,7 +104,7 @@ const evaluteContinuationExpression = (
export const evaluate = async ( export const evaluate = async (
ast: Program, ast: Program,
logger: TracingLogger, logger: TracingLogger,
): Promise<DenotableValue> => { ): Promise<Denotable> => {
const environment = new Environment(); const environment = new Environment();
return ast.reduce((_, continuation, i) => { return ast.reduce((_, continuation, i) => {

View File

@ -6,4 +6,4 @@ export class InvalidStateError extends Error {}
export class BadArgumentError extends Error {} export class BadArgumentError extends Error {}
export class TypeError extends Error {} export class InvalidType extends Error {}

View File

@ -10,3 +10,19 @@ test('Add (1 real) and (3 int) => (4 real)', async () => {
const result = await evaluate(ast, testingLogger); const result = await evaluate(ast, testingLogger);
expect(result).toEqual({ type: 'real', value: 4 }); 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 });
});
*/

View File

@ -4,4 +4,10 @@ export namespace TestPrograms {
export const AddOneThree = Bun.file( export const AddOneThree = Bun.file(
join(import.meta.dir + '/add-1-3.cps'), join(import.meta.dir + '/add-1-3.cps'),
).text(); ).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();
} }

View File

@ -0,0 +1 @@
PRIMOP(==, ["asdf", "asdf"], [result], [])

View File

@ -0,0 +1 @@
PRIMOP(==, ["asdfasdf", "asdf"], [result], [])

View File

@ -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],
);
});