identity function, repl upgrade
This commit is contained in:
parent
d39cf84965
commit
55c00566b0
20
src/index.ts
20
src/index.ts
@ -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));
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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 } };
|
||||
}
|
||||
|
||||
|
@ -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
80
src/repl.ts
Normal 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);
|
||||
};
|
@ -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 });
|
||||
});
|
||||
*/
|
||||
|
1
test/programs/application.cps
Normal file
1
test/programs/application.cps
Normal file
@ -0,0 +1 @@
|
||||
PRIMOP(+, [INT 1, INT 2], [result], [APP(LABEL id, [VAR result])])
|
@ -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();
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
PRIMOP(==, ["asdf", "asdf"], [result], [])
|
||||
PRIMOP(==, [STRING "asdf", STRING "asdf"], [result], [])
|
@ -1 +1 @@
|
||||
PRIMOP(==, ["asdfasdf", "asdf"], [result], [])
|
||||
PRIMOP(==, [STRING "asdfasdf", STRING "asdf"], [result], [])
|
||||
|
Loading…
Reference in New Issue
Block a user