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 LogLevel,
|
||||||
type TracingLogger,
|
type TracingLogger,
|
||||||
} from '@/utils';
|
} from '@/utils';
|
||||||
import { evaluate } from '@/interpreter';
|
import { doRepl } from './repl';
|
||||||
|
|
||||||
const LOG_LEVELS: LogLevel[] = ['info', 'warn', 'error'];
|
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) => {
|
export const main = async (args: Args) => {
|
||||||
if (args.devMode) {
|
if (args.devMode) {
|
||||||
LOG_LEVELS.push('debug');
|
LOG_LEVELS.push('debug');
|
||||||
@ -55,9 +43,9 @@ export const main = async (args: Args) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (args.repl) {
|
if (args.repl) {
|
||||||
logger.info('Starting REPL...');
|
await doRepl(logger);
|
||||||
logger.info('Welcome to the CPS interpreter!');
|
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
main(args);
|
main(args).then(code => process.exit(code));
|
||||||
|
@ -202,6 +202,51 @@ const addBinaryArithmeticOperationsTo = (env: Environment) => {
|
|||||||
return env;
|
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) => {
|
export const putBuiltinsOnEnvironemtn = (env: Environment) => {
|
||||||
return [
|
return [
|
||||||
addBinaryArithmeticOperationsTo,
|
addBinaryArithmeticOperationsTo,
|
||||||
@ -210,5 +255,6 @@ export const putBuiltinsOnEnvironemtn = (env: Environment) => {
|
|||||||
addNumberComparisonOperationsTo,
|
addNumberComparisonOperationsTo,
|
||||||
addBooleanAlgebraOperationsTo,
|
addBooleanAlgebraOperationsTo,
|
||||||
addEqualityOperationsTo,
|
addEqualityOperationsTo,
|
||||||
|
addIdentityFunctionTo,
|
||||||
].reduce((acc, builtinsAdder) => builtinsAdder(acc), env);
|
].reduce((acc, builtinsAdder) => builtinsAdder(acc), env);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
type ContinuationExpression,
|
type ContinuationExpression,
|
||||||
type PrimitiveOperationExpression,
|
type PrimitiveOperationExpression,
|
||||||
|
type ApplicationExpression,
|
||||||
type Program,
|
type Program,
|
||||||
type Value,
|
type Value,
|
||||||
} from '@/parser';
|
} from '@/parser';
|
||||||
@ -16,7 +17,7 @@ import { putBuiltinsOnEnvironemtn } from './builtins';
|
|||||||
const evaluateValue = (
|
const evaluateValue = (
|
||||||
value: Value,
|
value: Value,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
logger: TracingLogger,
|
_logger: TracingLogger,
|
||||||
): Denotable => {
|
): Denotable => {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
return { type: 'string', value };
|
return { type: 'string', value };
|
||||||
@ -35,6 +36,18 @@ const evaluateValue = (
|
|||||||
throw new InvalidStateError(`Invalid value: ${value}`);
|
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 = (
|
const evaluatePrimitiveOperation = (
|
||||||
{ primitiveOperation }: PrimitiveOperationExpression,
|
{ primitiveOperation }: PrimitiveOperationExpression,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
@ -61,6 +74,7 @@ const evaluatePrimitiveOperation = (
|
|||||||
logger.warn(
|
logger.warn(
|
||||||
`Expected 2 continuations for boolean result, got ContinuationLength=(${continuations.length})`,
|
`Expected 2 continuations for boolean result, got ContinuationLength=(${continuations.length})`,
|
||||||
);
|
);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [trueContinuation, falseContinuation] = continuations;
|
const [trueContinuation, falseContinuation] = continuations;
|
||||||
@ -79,7 +93,7 @@ const evaluatePrimitiveOperation = (
|
|||||||
);
|
);
|
||||||
} else if (continuations.length === 0) {
|
} else if (continuations.length === 0) {
|
||||||
logger.warn(
|
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;
|
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) {
|
if ('record' in expr) {
|
||||||
throw new NotImplementedError('Continuation records are not supported yet');
|
throw new NotImplementedError('Continuation records are not supported yet');
|
||||||
}
|
}
|
||||||
@ -116,11 +139,6 @@ const evaluteContinuationExpression = (
|
|||||||
if ('offset' in expr) {
|
if ('offset' in expr) {
|
||||||
throw new NotImplementedError('Continuation offset is not supported yet');
|
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) {
|
if ('switch' in expr) {
|
||||||
throw new NotImplementedError('Continuation switch is not supported yet');
|
throw new NotImplementedError('Continuation switch is not supported yet');
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ SwitchExpression
|
|||||||
RPAREN { return { switch: { switchIndex, continuations } }; }
|
RPAREN { return { switch: { switchIndex, continuations } }; }
|
||||||
|
|
||||||
ApplicationExpression
|
ApplicationExpression
|
||||||
= APP _? LPAREN _? fn:Value _? COMMA _? args:ValueList _? RPAREN {
|
= APP _? LPAREN _? fn:(LabelStatement / VarStatement) _? COMMA _? args:ValueList _? RPAREN {
|
||||||
return { application: { fn, args } };
|
return { application: { fn, args } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1716,7 +1716,12 @@ peg$parseApplicationExpression() {
|
|||||||
s4 = null;
|
s4 = null;
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
s5 = peg$parseValue();
|
s5 = peg$parseLabelStatement();
|
||||||
|
// @ts-ignore
|
||||||
|
if (s5 === peg$FAILED) {
|
||||||
|
// @ts-ignore
|
||||||
|
s5 = peg$parseVarStatement();
|
||||||
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (s5 !== peg$FAILED) {
|
if (s5 !== peg$FAILED) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -5337,7 +5342,7 @@ export type SwitchExpression = {
|
|||||||
switch: { switchIndex: Value; continuations: ContinuationList };
|
switch: { switchIndex: Value; continuations: ContinuationList };
|
||||||
};
|
};
|
||||||
export type ApplicationExpression = {
|
export type ApplicationExpression = {
|
||||||
application: { fn: Value; args: ValueList };
|
application: { fn: LabelStatement | VarStatement; args: ValueList };
|
||||||
};
|
};
|
||||||
export type FixBinding = [
|
export type FixBinding = [
|
||||||
LPAREN,
|
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 });
|
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);
|
||||||
|
|
||||||
const result = await evaluate(ast, testingLogger);
|
const result = await evaluate(ast, testingLogger);
|
||||||
expect(result).toEqual({ type: 'int', value: 1 });
|
expect(result).toEqual({ type: 'bool', value: 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('String inequality', async () => {
|
test('String inequality', async () => {
|
||||||
const ast = peggyParse(await TestPrograms.StringInEquality);
|
const ast = peggyParse(await TestPrograms.StringInEquality);
|
||||||
|
|
||||||
const result = await evaluate(ast, testingLogger);
|
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 namespace TestPrograms {
|
||||||
export const AddOneThree = Bun.file(
|
export const AddOneThree = Bun.file(
|
||||||
join(import.meta.dir + '/add-1-3.cps'),
|
join(import.meta.dir, 'add-1-3.cps'),
|
||||||
).text();
|
).text();
|
||||||
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(
|
export const Branching = Bun.file(
|
||||||
join(import.meta.dir + '/branching.cps'),
|
join(import.meta.dir, 'branching.cps'),
|
||||||
).text();
|
).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();
|
||||||
export const StringInEquality = Bun.file(
|
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();
|
).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