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; 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 addBinaryIntegerOperationsTo = (env: Environment) => {
const binaryIntegerOperationSignatures: DenotableFunctionSignature[] = [ 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 },
{ 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, { env.set(name, {
type: 'function', type: 'function',
@ -102,5 +207,8 @@ export const putBuiltinsOnEnvironemtn = (env: Environment) => {
addBinaryArithmeticOperationsTo, addBinaryArithmeticOperationsTo,
addBinaryIntegerOperationsTo, addBinaryIntegerOperationsTo,
addUnaryIntegerOperationsTo, addUnaryIntegerOperationsTo,
addNumberComparisonOperationsTo,
addBooleanAlgebraOperationsTo,
addEqualityOperationsTo,
].reduce((acc, builtinsAdder) => builtinsAdder(acc), env); ].reduce((acc, builtinsAdder) => builtinsAdder(acc), env);
}; };

View File

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

View File

@ -29,7 +29,6 @@ const evaluateValue = (
return { type: 'int', value: value.int }; return { type: 'int', value: value.int };
} }
if ('name' in value) { if ('name' in value) {
logger.debug(`Evaluating variable: ${value.name}`);
return env.get(value.name); return env.get(value.name);
} }
@ -42,10 +41,6 @@ const evaluatePrimitiveOperation = (
logger: TracingLogger, logger: TracingLogger,
) => { ) => {
const { opr, operands, resultBindings, continuations } = primitiveOperation; const { opr, operands, resultBindings, continuations } = primitiveOperation;
if (operands.length !== 2) {
throw new BadArgumentError('Primitive operations must have 2 operands');
}
const operandValues = operands.map(operand => const operandValues = operands.map(operand =>
evaluateValue(operand, env, logger.createChild('evaluteValue')), evaluateValue(operand, env, logger.createChild('evaluteValue')),
); );
@ -56,15 +51,46 @@ const evaluatePrimitiveOperation = (
continuationEnvironment.set(name, result); continuationEnvironment.set(name, result);
} }
// return the result of the last continuation if (result.type === 'bool') {
return continuations.reduce((_, continuation, i) => { if (continuations.length > 2) {
const childLogger = logger.createChild(`continuation[${i}]`); 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( return evaluteContinuationExpression(
continuation, continuation,
continuationEnvironment, continuationEnvironment,
childLogger, childLogger,
); );
}, result);
}; };
const evaluteContinuationExpression = ( const evaluteContinuationExpression = (

View File

@ -223,6 +223,7 @@ Value
/ LabelStatement / LabelStatement
/ IntStatement / IntStatement
/ RealStatement / RealStatement
/ BoolStatement
/ StringStatement / StringStatement
VarStatement = VAR _ ident:Identifier { return ident; } VarStatement = VAR _ ident:Identifier { return ident; }
@ -233,6 +234,8 @@ IntStatement = INT _ int:Integer { return int; }
RealStatement = REAL _ real:Real { return real; } RealStatement = REAL _ real:Real { return real; }
BoolStatement = BOOL _ bool:Integer { return bool; }
StringStatement = STRING _ string:QuotedString { return string; } StringStatement = STRING _ string:QuotedString { return string; }
AccessStatement AccessStatement
@ -286,6 +289,7 @@ ComparisonOperation
/ ">" / ">"
/ "<" / "<"
/ "||" / "||"
/ "&&"
Integer = digits:("-"? [0-9]+) !"." { return { int: parseInt(digits.join(''), 10) }; } Integer = digits:("-"? [0-9]+) !"." { return { int: parseInt(digits.join(''), 10) }; }
@ -317,6 +321,8 @@ INT = "INT"
REAL = "REAL" REAL = "REAL"
BOOL = "BOOL"
STRING = "STRING" STRING = "STRING"
APP = "APP" 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 }); 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 () => { test('String equality', async () => {
const ast = peggyParse(await TestPrograms.StringEquality); 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( export const PrimopScope = Bun.file(
join(import.meta.dir + '/primop-scope.cps'), join(import.meta.dir + '/primop-scope.cps'),
).text(); ).text();
export const Branching = Bun.file(
join(import.meta.dir + '/branching.cps'),
).text();
export const StringEquality = Bun.file( export const StringEquality = Bun.file(
join(import.meta.dir + '/string-equal.cps'), join(import.meta.dir + '/string-equal.cps'),
).text(); ).text();

View File

@ -1,13 +1,9 @@
import { expect, test } from 'bun:test'; import { expect, test } from 'bun:test';
import { TestPrograms } from './programs';
import { peggyParse } from '@/parser';
import { import {
evaluate,
type DenotableFunctionSignature, type DenotableFunctionSignature,
denotableTypesEquivalent, denotableTypesEquivalent,
matchSignature, matchSignature,
} from '@/interpreter'; } from '@/interpreter';
import { testingLogger } from './logger';
test('simple denotable types are equivalent', () => { test('simple denotable types are equivalent', () => {
expect(denotableTypesEquivalent('int', 'int')).toBe(true); expect(denotableTypesEquivalent('int', 'int')).toBe(true);