simple interpreter
This commit is contained in:
parent
c8336ee487
commit
ea7a11844a
106
src/interpreter/builtin_signatures.ts
Normal file
106
src/interpreter/builtin_signatures.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
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;
|
||||||
|
};
|
44
src/interpreter/environment.ts
Normal file
44
src/interpreter/environment.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { UnknownSymbolError } from '@/utils';
|
||||||
|
import type { DenotableValue } from '.';
|
||||||
|
|
||||||
|
export class Environment {
|
||||||
|
private scope: Map<string, DenotableValue>;
|
||||||
|
private parent: Environment | null;
|
||||||
|
|
||||||
|
constructor(parent: Environment | null = null) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.scope = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
public set(name: string, value: DenotableValue) {
|
||||||
|
this.scope.set(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(name: string): DenotableValue {
|
||||||
|
if (this.scope.has(name)) {
|
||||||
|
return this.scope.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.parent) {
|
||||||
|
return this.parent.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnknownSymbolError(`Undefined variable: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public has(name: string): boolean {
|
||||||
|
if (this.scope.has(name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.parent) {
|
||||||
|
return this.parent.has(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public createChild(): Environment {
|
||||||
|
return new Environment(this);
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,11 @@
|
|||||||
export const evaluate = async (ast: Program) => {};
|
export type DenotableValueType =
|
||||||
|
| 'null'
|
||||||
|
| 'int'
|
||||||
|
| 'real'
|
||||||
|
| 'string'
|
||||||
|
| 'function'
|
||||||
|
| 'reference';
|
||||||
|
export type DenotableValue = { type: DenotableValueType; value: any };
|
||||||
|
|
||||||
|
export * from './environment';
|
||||||
|
export * from './interpreter';
|
||||||
|
160
src/interpreter/interpreter.ts
Normal file
160
src/interpreter/interpreter.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import {
|
||||||
|
type ContinuationExpression,
|
||||||
|
type PrimitiveOperationExpression,
|
||||||
|
type Program,
|
||||||
|
type Value,
|
||||||
|
} from '@/parser';
|
||||||
|
import { Environment, type DenotableValue } from '.';
|
||||||
|
import {
|
||||||
|
BadArgumentError,
|
||||||
|
InvalidStateError,
|
||||||
|
NotImplementedError,
|
||||||
|
type TracingLogger,
|
||||||
|
} from '@/utils';
|
||||||
|
|
||||||
|
const evaluateValue = (
|
||||||
|
value: Value,
|
||||||
|
env: Environment,
|
||||||
|
logger: TracingLogger,
|
||||||
|
): DenotableValue => {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return { type: 'string', value };
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('real' in value) {
|
||||||
|
return { type: 'real', value: value.real };
|
||||||
|
}
|
||||||
|
if ('int' in value) {
|
||||||
|
return { type: 'int', value: value.int };
|
||||||
|
}
|
||||||
|
if ('name' in value) {
|
||||||
|
logger.debug(`Evaluating variable: ${value.name}`);
|
||||||
|
return env.get(value.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidStateError(`Invalid value: ${value}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const evaluatePrimitiveOperation = (
|
||||||
|
{ primitiveOperation }: PrimitiveOperationExpression,
|
||||||
|
env: Environment,
|
||||||
|
logger: TracingLogger,
|
||||||
|
) => {
|
||||||
|
const { opr, operands, resultBindings, continuations } = primitiveOperation;
|
||||||
|
if (operands.length !== 2) {
|
||||||
|
throw new BadArgumentError('Primitive operations must have 2 operands');
|
||||||
|
}
|
||||||
|
|
||||||
|
const operandValues = operands.map(operand =>
|
||||||
|
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 };
|
||||||
|
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) => {
|
||||||
|
const childLogger = logger.createChild(`continuation[${i}]`);
|
||||||
|
return evaluteContinuationExpression(
|
||||||
|
continuation,
|
||||||
|
continuationEnvironment,
|
||||||
|
childLogger,
|
||||||
|
);
|
||||||
|
}, result);
|
||||||
|
};
|
||||||
|
|
||||||
|
const evaluteContinuationExpression = (
|
||||||
|
expr: ContinuationExpression,
|
||||||
|
env: Environment,
|
||||||
|
logger: TracingLogger,
|
||||||
|
): DenotableValue => {
|
||||||
|
if ('primitiveOperation' in expr) {
|
||||||
|
logger.debug('Evaluating primitive operation');
|
||||||
|
return evaluatePrimitiveOperation(
|
||||||
|
expr,
|
||||||
|
env,
|
||||||
|
logger.createChild('evaluatePrimitiveOperation'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('record' in expr) {
|
||||||
|
throw new NotImplementedError('Continuation records are not supported yet');
|
||||||
|
}
|
||||||
|
if ('select' in expr) {
|
||||||
|
throw new NotImplementedError('Continuation select is not supported yet');
|
||||||
|
}
|
||||||
|
if ('offset' in expr) {
|
||||||
|
throw new NotImplementedError('Continuation offset is not supported yet');
|
||||||
|
}
|
||||||
|
if ('application' in expr) {
|
||||||
|
throw new NotImplementedError(
|
||||||
|
'Continuation application is not supported yet',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ('switch' in expr) {
|
||||||
|
throw new NotImplementedError('Continuation switch is not supported yet');
|
||||||
|
}
|
||||||
|
if ('fix' in expr) {
|
||||||
|
throw new NotImplementedError('Continuation fix is not supported yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidStateError(`Invalid continuation expression: ${expr}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const evaluate = async (
|
||||||
|
ast: Program,
|
||||||
|
logger: TracingLogger,
|
||||||
|
): Promise<DenotableValue> => {
|
||||||
|
const environment = new Environment();
|
||||||
|
|
||||||
|
return ast.reduce((_, continuation, i) => {
|
||||||
|
const exprLogger = logger.createChild(`statement[${i}]`);
|
||||||
|
return evaluteContinuationExpression(
|
||||||
|
continuation as ContinuationExpression,
|
||||||
|
environment,
|
||||||
|
exprLogger,
|
||||||
|
);
|
||||||
|
}, null);
|
||||||
|
};
|
@ -285,6 +285,7 @@ ComparisonOperation
|
|||||||
/ "!"
|
/ "!"
|
||||||
/ ">"
|
/ ">"
|
||||||
/ "<"
|
/ "<"
|
||||||
|
/ "||"
|
||||||
|
|
||||||
Integer = digits:("-"? [0-9]+) !"." { return { int: parseInt(digits.join(''), 10) }; }
|
Integer = digits:("-"? [0-9]+) !"." { return { int: parseInt(digits.join(''), 10) }; }
|
||||||
|
|
||||||
|
10584
src/parser/parser.ts
10584
src/parser/parser.ts
File diff suppressed because it is too large
Load Diff
@ -1 +1,9 @@
|
|||||||
export class NotImplementedException extends Error {}
|
export class NotImplementedError extends Error {}
|
||||||
|
|
||||||
|
export class UnknownSymbolError extends Error {}
|
||||||
|
|
||||||
|
export class InvalidStateError extends Error {}
|
||||||
|
|
||||||
|
export class BadArgumentError extends Error {}
|
||||||
|
|
||||||
|
export class TypeError extends Error {}
|
||||||
|
12
test/interpreter.spec.ts
Normal file
12
test/interpreter.spec.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { expect, test } from 'bun:test';
|
||||||
|
import { TestPrograms } from './programs';
|
||||||
|
import { peggyParse } from '@/parser';
|
||||||
|
import { evaluate } from '@/interpreter';
|
||||||
|
import { testingLogger } from './logger';
|
||||||
|
|
||||||
|
test('Add (1 real) and (3 int) => (4 real)', async () => {
|
||||||
|
const ast = peggyParse(await TestPrograms.AddOneThree);
|
||||||
|
|
||||||
|
const result = await evaluate(ast, testingLogger);
|
||||||
|
expect(result).toEqual({ type: 'real', value: 4 });
|
||||||
|
});
|
@ -3,5 +3,13 @@ import { TestPrograms } from './programs';
|
|||||||
import { peggyParse } from '@/parser';
|
import { peggyParse } from '@/parser';
|
||||||
|
|
||||||
test('Primitive Operations', async () => {
|
test('Primitive Operations', async () => {
|
||||||
const ast = peggyParse(await TestPrograms.AddOneThree);
|
const [operation] = peggyParse(await TestPrograms.AddOneThree);
|
||||||
|
const { primitiveOperation } = operation;
|
||||||
|
|
||||||
|
expect(primitiveOperation).toEqual({
|
||||||
|
opr: '+',
|
||||||
|
operands: [{ real: 1 }, { int: 3 }],
|
||||||
|
resultBindings: [{ name: 'result' }],
|
||||||
|
continuations: [],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,2 +1 @@
|
|||||||
PRIMOP(+, [INT 1, INT 2], [u],
|
PRIMOP(+, [REAL 1, INT 3], [result], [])
|
||||||
[APP(LABEL identity, [VAR u])])
|
|
Loading…
Reference in New Issue
Block a user