identity function, repl upgrade

This commit is contained in:
Lizzy Hunt 2024-02-28 15:06:00 -07:00
parent d39cf84965
commit 55c00566b0
No known key found for this signature in database
GPG Key ID: E835BD4B08CCAF96
11 changed files with 183 additions and 37 deletions

View File

@ -7,7 +7,7 @@ import {
type LogLevel,
type TracingLogger,
} from '@/utils';
import { evaluate } from '@/interpreter';
import { doRepl } from './repl';
const LOG_LEVELS: LogLevel[] = ['info', 'warn', 'error'];
@ -30,18 +30,6 @@ const devMode = async (logger: TracingLogger) => {
}
};
const doRepl = async (prompt = '~> ') => {
process.stdout.write(prompt);
for await (const line of console) {
const result = await evaluate(line);
console.log(result);
break;
}
await doRepl(prompt);
};
export const main = async (args: Args) => {
if (args.devMode) {
LOG_LEVELS.push('debug');
@ -55,9 +43,9 @@ export const main = async (args: Args) => {
}
if (args.repl) {
logger.info('Starting REPL...');
logger.info('Welcome to the CPS interpreter!');
await doRepl(logger);
}
return 0;
};
main(args);
main(args).then(code => process.exit(code));

View File

@ -202,6 +202,51 @@ const addBinaryArithmeticOperationsTo = (env: Environment) => {
return env;
};
const addIdentityFunctionTo = (env: Environment) => {
env.set('id', {
type: 'function',
value: {
signatures: [
{
arguments: ['null'],
return: 'null',
},
{
arguments: ['int'],
return: 'int',
},
{
arguments: ['real'],
return: 'real',
},
{
arguments: ['bool'],
return: 'bool',
},
{
arguments: ['string'],
return: 'string',
},
{
arguments: ['bytearray'],
return: 'bytearray',
},
{
arguments: ['function'],
return: 'function',
},
{
arguments: ['reference'],
return: 'reference',
},
],
body: ({ value }: Denotable) => value,
},
});
return env;
};
export const putBuiltinsOnEnvironemtn = (env: Environment) => {
return [
addBinaryArithmeticOperationsTo,
@ -210,5 +255,6 @@ export const putBuiltinsOnEnvironemtn = (env: Environment) => {
addNumberComparisonOperationsTo,
addBooleanAlgebraOperationsTo,
addEqualityOperationsTo,
addIdentityFunctionTo,
].reduce((acc, builtinsAdder) => builtinsAdder(acc), env);
};

View File

@ -1,6 +1,7 @@
import {
type ContinuationExpression,
type PrimitiveOperationExpression,
type ApplicationExpression,
type Program,
type Value,
} from '@/parser';
@ -16,7 +17,7 @@ import { putBuiltinsOnEnvironemtn } from './builtins';
const evaluateValue = (
value: Value,
env: Environment,
logger: TracingLogger,
_logger: TracingLogger,
): Denotable => {
if (typeof value === 'string') {
return { type: 'string', value };
@ -35,6 +36,18 @@ const evaluateValue = (
throw new InvalidStateError(`Invalid value: ${value}`);
};
const evaluateApplicationExpression = (
{ application }: ApplicationExpression,
env: Environment,
logger: TracingLogger,
): Denotable => {
const { fn, args } = application;
const argValues = args.map(arg =>
evaluateValue(arg, env, logger.createChild('evaluateValue')),
);
return env.apply(fn.name, argValues);
};
const evaluatePrimitiveOperation = (
{ primitiveOperation }: PrimitiveOperationExpression,
env: Environment,
@ -61,6 +74,7 @@ const evaluatePrimitiveOperation = (
logger.warn(
`Expected 2 continuations for boolean result, got ContinuationLength=(${continuations.length})`,
);
return result;
}
const [trueContinuation, falseContinuation] = continuations;
@ -79,7 +93,7 @@ const evaluatePrimitiveOperation = (
);
} else if (continuations.length === 0) {
logger.warn(
`!! Expected 1 continuation in continuation list... implicitly returning result but PLEASE NOTE this is technically undefined behavior !!`,
"Expected 1 continuation for non-boolean result, but there wasn't any. Implicitly returning the result", // technically undefined behavior
);
return result;
}
@ -107,6 +121,15 @@ const evaluteContinuationExpression = (
);
}
if ('application' in expr) {
logger.debug('Evaluating function application');
return evaluateApplicationExpression(
expr,
env,
logger.createChild('evaluateApplicationExpression'),
);
}
if ('record' in expr) {
throw new NotImplementedError('Continuation records are not supported yet');
}
@ -116,11 +139,6 @@ const evaluteContinuationExpression = (
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');
}

View File

@ -86,7 +86,7 @@ SwitchExpression
RPAREN { return { switch: { switchIndex, continuations } }; }
ApplicationExpression
= APP _? LPAREN _? fn:Value _? COMMA _? args:ValueList _? RPAREN {
= APP _? LPAREN _? fn:(LabelStatement / VarStatement) _? COMMA _? args:ValueList _? RPAREN {
return { application: { fn, args } };
}

View File

@ -1716,7 +1716,12 @@ peg$parseApplicationExpression() {
s4 = null;
}
// @ts-ignore
s5 = peg$parseValue();
s5 = peg$parseLabelStatement();
// @ts-ignore
if (s5 === peg$FAILED) {
// @ts-ignore
s5 = peg$parseVarStatement();
}
// @ts-ignore
if (s5 !== peg$FAILED) {
// @ts-ignore
@ -5337,7 +5342,7 @@ export type SwitchExpression = {
switch: { switchIndex: Value; continuations: ContinuationList };
};
export type ApplicationExpression = {
application: { fn: Value; args: ValueList };
application: { fn: LabelStatement | VarStatement; args: ValueList };
};
export type FixBinding = [
LPAREN,

80
src/repl.ts Normal file
View File

@ -0,0 +1,80 @@
import type { TracingLogger } from './utils';
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
import { peggyParse } from './parser';
import { evaluate } from './interpreter';
// cool asci logo for CPS
const LOGO = `
_______ ________ ______ _______ __
/ \\ / | / \\ / \\ / |
$$$$$$$ |$$$$$$$$/ /$$$$$$ |$$$$$$$ |$$ |
$$ |__$$ |$$ |__ $$ | $$/ $$ |__$$ |$$ |
$$ $$< $$ | $$ | $$ $$/ $$ |
$$$$$$$ |$$$$$/ $$ | __ $$$$$$$/ $$ |
$$ | $$ |$$ |_____ $$ \\__/ |$$ | $$ |_____
$$ | $$ |$$ |$$ $$/ $$ | $$ |
$$/ $$/ $$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/
`;
const HELP = `
This is the CPS REPL. You can enter CPS programs and see the result of evaluating them.
This REPL supports multi-line input. To end a multi-line input, enter an empty line.
Commands:
help - Show this message
exit - Exit the REPL
About:
Read "Compiling With Continuations" by Andrew W. Appel for more information about
this Intermediate Representation.
Example:
~> PRIMOP(+, [INT 1, INT 2], [result], [
| APP(LABEL id, [VAR result])
| ])
`;
export const doRepl = async (
logger: TracingLogger,
prompt = 0,
rl = readline.createInterface({ input, output }),
): Promise<any> => {
if (prompt === 0) {
logger.info('welcome to recpl (read eval continue print loop) :)' + LOGO);
}
const promptString = `[ ${prompt} ] ~> `;
const lines: string[] = [await rl.question(promptString)];
while (lines.at(-1)) {
const line = lines.at(-1)!;
if (lines.length === 1 && line === 'help') {
logger.info(HELP);
return doRepl(logger, prompt + 1, rl);
}
if (line === 'exit') {
logger.info('Exiting REPL...');
rl.close();
return;
}
lines.push(
await rl.question(`|`.padStart(promptString.length - 1, ' ') + ' '),
);
}
const program = lines.slice(0, -1).join('\n');
try {
const ast = peggyParse(program);
logger.debug('AST: ' + JSON.stringify(ast, null, 2));
const result = await evaluate(ast, logger.createChild('evaluate'));
logger.info('Result: ' + JSON.stringify(result, null, 2) + '\n');
} catch (e) {
logger.error(e!.toString() + '\n');
}
return doRepl(logger, prompt + 1, rl);
};

View File

@ -25,18 +25,23 @@ test('Branching', async () => {
expect(result).toEqual({ type: 'real', value: 2 });
});
/*
test('String equality', async () => {
const ast = peggyParse(await TestPrograms.StringEquality);
const result = await evaluate(ast, testingLogger);
expect(result).toEqual({ type: 'int', value: 1 });
expect(result).toEqual({ type: 'bool', 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 });
expect(result).toEqual({ type: 'bool', value: 0 });
});
test('Application of identity function', async () => {
const ast = peggyParse(await TestPrograms.Application);
const result = await evaluate(ast, testingLogger);
expect(result).toEqual({ type: 'int', value: 3 });
});
*/

View File

@ -0,0 +1 @@
PRIMOP(+, [INT 1, INT 2], [result], [APP(LABEL id, [VAR result])])

View File

@ -2,18 +2,21 @@ import { join } from 'path';
export namespace TestPrograms {
export const AddOneThree = Bun.file(
join(import.meta.dir + '/add-1-3.cps'),
join(import.meta.dir, 'add-1-3.cps'),
).text();
export const PrimopScope = Bun.file(
join(import.meta.dir + '/primop-scope.cps'),
join(import.meta.dir, 'primop-scope.cps'),
).text();
export const Branching = Bun.file(
join(import.meta.dir + '/branching.cps'),
join(import.meta.dir, 'branching.cps'),
).text();
export const StringEquality = Bun.file(
join(import.meta.dir + '/string-equal.cps'),
join(import.meta.dir, 'string-equal.cps'),
).text();
export const StringInEquality = Bun.file(
join(import.meta.dir + '/string-unequal.cps'),
join(import.meta.dir, 'string-unequal.cps'),
).text();
export const Application = Bun.file(
join(import.meta.dir, 'application.cps'),
).text();
}

View File

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

View File

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