branching

This commit is contained in:
Lizzy Hunt 2024-02-28 13:41:53 -07:00
parent 7cc3ef5fa1
commit d39cf84965
No known key found for this signature in database
GPG Key ID: E835BD4B08CCAF96
9 changed files with 544 additions and 275 deletions

View File

@ -28,6 +28,117 @@ const addUnaryIntegerOperationsTo = (env: Environment) => {
return env;
};
const addNumberComparisonOperationsTo = (env: Environment) => {
const comparisonOperationsSignatures: DenotableFunctionSignature[] = [
{
arguments: [
['real', 'int'],
['real', 'int'],
],
return: 'bool',
},
];
for (const { name, fn } of [
{ 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: comparisonOperationsSignatures,
body: ({ value: a }: Denotable, { value: b }: Denotable) =>
fn(a as number, b as number),
},
});
}
return env;
};
const addEqualityOperationsTo = (env: Environment) => {
const equalityOperationSignatures: DenotableFunctionSignature[] = [
{
arguments: ['int', 'int'],
return: 'bool',
},
{
arguments: ['real', 'real'],
return: 'bool',
},
{
arguments: ['bool', 'bool'],
return: 'bool',
},
{
arguments: ['string', 'string'],
return: 'bool',
},
];
for (const { name, fn } of [
{
name: '==',
fn: (a: number | string, b: number | string) => (a === b ? 1 : 0),
},
{
name: '!=',
fn: (a: number | string, b: number | string) => (a !== b ? 1 : 0),
},
]) {
env.set(name, {
type: 'function',
value: {
signatures: equalityOperationSignatures,
body: ({ value: a }: Denotable, { value: b }: Denotable) =>
fn(a as number | string, b as number | string),
},
});
}
return env;
};
const addBooleanAlgebraOperationsTo = (env: Environment) => {
const binaryBooleanOps: DenotableFunctionSignature[] = [
{
arguments: ['bool', 'bool'],
return: 'bool',
},
];
for (const { name, fn } of [
{ name: '||', fn: (a: number, b: number) => (a === 1 || b === 1 ? 1 : 0) },
{ name: '&&', fn: (a: number, b: number) => (a === 1 && b === 1 ? 1 : 0) },
]) {
env.set(name, {
type: 'function',
value: {
signatures: binaryBooleanOps,
body: ({ value: a }: Denotable, { value: b }: Denotable) =>
fn(a as number, b as number),
},
});
}
env.set('!', {
type: 'function',
value: {
signatures: [
{
arguments: ['bool'],
return: 'bool',
},
],
body: ({ value }: Denotable) => (value === 1 ? 0 : 1),
},
});
return env;
};
const addBinaryIntegerOperationsTo = (env: Environment) => {
const binaryIntegerOperationSignatures: DenotableFunctionSignature[] = [
{
@ -42,12 +153,6 @@ const addBinaryIntegerOperationsTo = (env: Environment) => {
{ 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) },
]) {
env.set(name, {
type: 'function',
@ -102,5 +207,8 @@ export const putBuiltinsOnEnvironemtn = (env: Environment) => {
addBinaryArithmeticOperationsTo,
addBinaryIntegerOperationsTo,
addUnaryIntegerOperationsTo,
addNumberComparisonOperationsTo,
addBooleanAlgebraOperationsTo,
addEqualityOperationsTo,
].reduce((acc, builtinsAdder) => builtinsAdder(acc), env);
};

View File

@ -21,6 +21,7 @@ export type DenotableType =
| 'null'
| 'int'
| 'real'
| 'bool'
| 'string'
| 'bytearray'
| 'function'

View File

@ -29,7 +29,6 @@ const evaluateValue = (
return { type: 'int', value: value.int };
}
if ('name' in value) {
logger.debug(`Evaluating variable: ${value.name}`);
return env.get(value.name);
}
@ -42,10 +41,6 @@ const evaluatePrimitiveOperation = (
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')),
);
@ -56,15 +51,46 @@ const evaluatePrimitiveOperation = (
continuationEnvironment.set(name, result);
}
// return the result of the last continuation
return continuations.reduce((_, continuation, i) => {
const childLogger = logger.createChild(`continuation[${i}]`);
if (result.type === 'bool') {
if (continuations.length > 2) {
throw new BadArgumentError(
`Expected <= 2 continuations for boolean result, got ${continuations.length}`,
);
}
if (continuations.length !== 2) {
logger.warn(
`Expected 2 continuations for boolean result, got ContinuationLength=(${continuations.length})`,
);
}
const [trueContinuation, falseContinuation] = continuations;
const childLogger = logger.createChild('continuation[true]');
const continuation = result.value ? trueContinuation : falseContinuation;
return evaluteContinuationExpression(
continuation,
continuationEnvironment,
childLogger,
);
}
if (continuations.length > 1) {
throw new BadArgumentError(
`Expected <= 1 continuations for non-boolean result, got ${continuations.length}`,
);
} else if (continuations.length === 0) {
logger.warn(
`!! Expected 1 continuation in continuation list... implicitly returning result but PLEASE NOTE this is technically undefined behavior !!`,
);
return result;
}
const [continuation] = continuations;
const childLogger = logger.createChild('continuation');
return evaluteContinuationExpression(
continuation,
continuationEnvironment,
childLogger,
);
}, result);
};
const evaluteContinuationExpression = (

View File

@ -223,6 +223,7 @@ Value
/ LabelStatement
/ IntStatement
/ RealStatement
/ BoolStatement
/ StringStatement
VarStatement = VAR _ ident:Identifier { return ident; }
@ -233,6 +234,8 @@ IntStatement = INT _ int:Integer { return int; }
RealStatement = REAL _ real:Real { return real; }
BoolStatement = BOOL _ bool:Integer { return bool; }
StringStatement = STRING _ string:QuotedString { return string; }
AccessStatement
@ -286,6 +289,7 @@ ComparisonOperation
/ ">"
/ "<"
/ "||"
/ "&&"
Integer = digits:("-"? [0-9]+) !"." { return { int: parseInt(digits.join(''), 10) }; }
@ -317,6 +321,8 @@ INT = "INT"
REAL = "REAL"
BOOL = "BOOL"
STRING = "STRING"
APP = "APP"

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,13 @@ test('Add (1 real) and (3 int) -> result => (real 1 - result) = -3 done with cor
expect(result).toEqual({ type: 'real', value: -3 });
});
test('Branching', async () => {
const ast = peggyParse(await TestPrograms.Branching);
const result = await evaluate(ast, testingLogger);
expect(result).toEqual({ type: 'real', value: 2 });
});
/*
test('String equality', async () => {
const ast = peggyParse(await TestPrograms.StringEquality);

View File

@ -0,0 +1,10 @@
PRIMOP(>=, [REAL 0, REAL 1], [resultFalse], [
PRIMOP(+, [REAL 2, REAL 4], [result], []),
PRIMOP(<=, [INT 1, REAL 1], [resultTrue], [
PRIMOP(&&, [VAR resultTrue, VAR resultFalse], [fin], [
PRIMOP(-, [REAL 1, REAL 1], [result], []),
PRIMOP(+, [REAL 1, REAL 1], [twoWhenFinIsFalse], [])
]),
PRIMOP(-, [REAL 1, REAL 1], [result], [])
])
])

View File

@ -7,6 +7,9 @@ export namespace TestPrograms {
export const PrimopScope = Bun.file(
join(import.meta.dir + '/primop-scope.cps'),
).text();
export const Branching = Bun.file(
join(import.meta.dir + '/branching.cps'),
).text();
export const StringEquality = Bun.file(
join(import.meta.dir + '/string-equal.cps'),
).text();

View File

@ -1,13 +1,9 @@
import { expect, test } from 'bun:test';
import { TestPrograms } from './programs';
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);