initial parser

This commit is contained in:
Elizabeth Hunt 2024-02-23 16:46:10 -07:00
commit d0d6aae1e5
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
16 changed files with 5991 additions and 0 deletions

175
.gitignore vendored Normal file
View File

@ -0,0 +1,175 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

18
.prettierrc Normal file
View File

@ -0,0 +1,18 @@
{
"arrowParens": "avoid",
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": true,
"printWidth": 80,
"proseWrap": "always",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false,
"plugins": ["prettier-plugin-pegjs"]
}

2
README.md Normal file
View File

@ -0,0 +1,2 @@
An interpreter for the CPS intermediate representation as
suggested in "Compiling with Continuations" by Appel.

BIN
bun.lockb Executable file

Binary file not shown.

18
package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "cps-interpreter",
"module": "index.ts",
"type": "module",
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"minimist": "^1.2.8",
"peggy": "^4.0.0",
"prettier": "^3.2.5",
"prettier-plugin-pegjs": "^2.0.2",
"ts-pegjs": "^4.2.1"
}
}

11
src/args.ts Normal file
View File

@ -0,0 +1,11 @@
const argv = require('minimist')(process.argv.slice(2));
export type Args = {
devMode: boolean;
repl: boolean;
};
export const args: Args = {
repl: argv.repl ?? false,
devMode: argv.dev ?? false,
};

63
src/index.ts Normal file
View File

@ -0,0 +1,63 @@
import { args, type Args } from '@/args';
import { join } from 'path';
import { watch } from 'fs/promises';
import { generateParser, GRAMMAR_FILE, GENERATED_PARSER } from '@/parser';
import {
ConsoleTracingLogger,
type LogLevel,
type TracingLogger,
} from '@/utils';
import { evaluate } from '@/interpreter';
const LOG_LEVELS: LogLevel[] = ['info', 'warn', 'error'];
const devMode = async (logger: TracingLogger) => {
logger.info('Watching for changes in parser...');
const watcher = watch(import.meta.dir, { recursive: true });
for await (const event of watcher) {
if (event.filename?.endsWith(GRAMMAR_FILE)) {
const grammarFile = join(import.meta.dir, event.filename);
const outputFile = join(
import.meta.dir,
event.filename.replace(GRAMMAR_FILE, GENERATED_PARSER),
);
logger.info(
`Generating parser at Location=(${grammarFile}) to Source=(${outputFile})...`,
);
generateParser(grammarFile, outputFile);
}
}
};
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');
}
const logger = new ConsoleTracingLogger('main', LOG_LEVELS);
if (args.devMode) {
logger.info('Starting in dev mode...');
await devMode(logger);
}
if (args.repl) {
logger.info('Starting REPL...');
logger.info('Welcome to the CPS interpreter!');
}
};
main(args);

1
src/interpreter/index.ts Normal file
View File

@ -0,0 +1 @@
export const evaluate = async (ast: Program) => {};

17
src/parser/generate.ts Normal file
View File

@ -0,0 +1,17 @@
import peggy from 'peggy';
const tspegjs: any = require('ts-pegjs');
export const GRAMMAR_FILE = 'grammar.pegjs';
export const GENERATED_PARSER = 'parser.ts';
export const generateParser = async (file: string, output: string) => {
const grammar = await Bun.file(file).text();
const parserSrc = peggy.generate(grammar, {
format: 'commonjs',
plugins: [tspegjs],
output: 'source',
});
await Bun.write(output, parserSrc);
};

364
src/parser/grammar.pegjs Normal file
View File

@ -0,0 +1,364 @@
Program
= exprs:(ContinuationExpression / _)* {
return exprs.filter(x => !Array.isArray(x));
}
ContinuationExpression
= RecordExpression
/ SelectExpression
/ OffsetExpression
/ ApplicationExpression
/ FixExpression
/ SwitchExpression
/ PrimitiveOperationExpression
SelectExpression
= SELECT
_?
LPAREN
_?
select:Integer
_?
COMMA
_?
val:Value
_?
COMMA
_?
bind:Identifier
_?
continuation:ContinuationExpression
_?
RPAREN { return { select, val, bind, continuation }; }
OffsetExpression
= OFFSET
_?
LPAREN
_?
offset:Integer
_?
COMMA
_?
val:Value
_?
COMMA
_?
bind:Identifier
_?
continuation:ContinuationExpression
_?
RPAREN { return { offset, val, bind, continuation }; }
IdentifierList
= LBRACKET
_?
identifiers:(ident:Identifier _? COMMA _?)*
_?
lastIdent:Identifier?
_?
RBRACKET {
return identifiers.length || lastIdent
? [...identifiers.map(x => x.ident), lastIdent]
: [];
}
ValueList
= LBRACKET
_?
values:(value:Value _? COMMA _?)*
_?
lastValue:Value?
_?
RBRACKET {
return values.length || lastValue
? [...values.map(x => x.value), lastValue]
: [];
}
SwitchExpression
= SWITCH
_?
LPAREN
_?
switchIndex:Value
_?
COMMA
_?
continuations:ContinuationList
_?
RPAREN { return { switchIndex, continuations }; }
ApplicationExpression
= APP _? LPAREN _? fn:Value _? COMMA _? args:ValueList _? RPAREN {
return { fn, args };
}
FixBinding
= LPAREN
_?
fn:Identifier
_?
COMMA
_?
args:IdentifierList
_?
COMMA
_?
continuation:ContinuationExpression
_?
RPAREN
FixBindingList
= LBRACKET
_
bindings:(binding:FixBinding _? COMMA _?)*
_?
lastBinding:FixBinding?
_?
RBRACKET {
return bindings.length || lastBinding
? [...bindings.map(x => x.binding), lastBinding]
: [];
}
FixExpression
= FIX
_?
LPAREN
_?
fixBindings:FixBindingList
_?
COMMA
_?
continuation:ContinuationExpression
_?
RPAREN { return { fixBindings, continuation }; }
ContinuationList
= LBRACKET
_?
continuations:(continuation:ContinuationExpression _? COMMA _?)*
_?
lastContinuation:ContinuationExpression?
_?
RBRACKET {
return lastContinuation || continuations.length
? [...continuations.map(x => x.continuation), lastContinuation]
: [];
}
PrimitiveOperationExpression
= PRIMOP
_?
LPAREN
_?
opr:PrimitiveOperation
_?
COMMA
_?
operands:ValueList
_?
COMMA
_?
resultBindings:IdentifierList
_?
COMMA
_?
continuations:ContinuationList
_?
RPAREN { return { opr, operands, resultBindings, continuations }; }
RecordExpressionTuple
= LPAREN
_?
variable:VarStatement
_?
COMMA
_?
offset:OffsetStatement
_?
RPAREN { return { variable, offset }; }
RecordExpressionTupleList
= LBRACKET
_?
records:(record:RecordExpressionTuple _? COMMA _?)*
_?
lastRecord:RecordExpressionTuple?
_?
RBRACKET {
return records.length || lastRecord
? [...records.map(x => x.record), lastRecord]
: [];
}
RecordExpression
= RECORD
_?
LPAREN
_?
records:RecordExpressionTupleList
_?
COMMA
_?
address:Literal
_?
COMMA
_?
body:ContinuationExpression
_?
RPAREN {
return {
records,
address,
body,
};
}
Value
= VarStatement
/ LabelStatement
/ IntStatement
/ RealStatement
/ StringStatement
VarStatement = VAR _ ident:Identifier { return ident; }
LabelStatement = LABEL _ ident:Identifier { return ident; }
IntStatement = INT _ int:Integer { return int; }
RealStatement = REAL _ real:Real { return real; }
StringStatement = STRING _ string:QuotedString { return string; }
AccessStatement
= OffsetStatement
/ SelectStatement
OffsetStatement = OFFP _ offset:Integer { return offset; }
SelectStatement = SELP _ offset:Integer { return offset; }
Identifier
= name:([A-Za-z] (LETTER / DIGIT / SAFE_SYMBOL)*) {
return { name: name[0] + name[1].join('') };
}
PrimitiveOperation
= ArithmeticOperation
/ ComparisonOperation
/ BitOperation
/ StoreOperation
StoreOperation
= STORE
/ UPDATE
/ MAKEREF
/ MAKEREFUNBOXED
/ UNBOXED_UPDATE
/ BOXED
/ SUBSCRIPT
ArithmeticOperation
= "+"
/ "-"
/ "/"
/ "*"
/ "**"
BitOperation
= ">>"
/ "<<"
/ "~"
/ "^"
ComparisonOperation
= "=="
/ "<="
/ ">="
/ "!="
/ "!"
/ ">"
/ "<"
Integer = digits:[0-9]+ !"." { return parseInt(digits.join(''), 10); }
QuotedString
= "'" content:[^']* "'" { return content.join(''); }
/ "\"" content:[^"]* "\"" { return content.join(''); }
Real
= value:("-"? [0-9]+ "." [0-9]+) {
return parseFloat(
value.map(x => (Array.isArray(x) ? x.join('') : x)).join(''),
);
}
Literal
= Real
/ QuotedString
/ Integer
OFFSET = "OFFSET"
OFFP = "OFFp"
SELP = "SELp"
VAR = "VAR"
INT = "INT"
REAL = "REAL"
STRING = "STRING"
APP = "APP"
RECORD = "RECORD"
SELECT = "SELECT"
FIX = "FIX"
SWITCH = "SWITCH"
PRIMOP = "PRIMOP"
LABEL = "LABEL"
STORE = "store"
UPDATE = "update"
MAKEREF = "makeref"
MAKEREFUNBOXED = "makerefunboxed"
UNBOXED_UPDATE = "unboxedupdate"
SUBSCRIPT = "subscript"
BOXED = "boxed"
LETTER = [A-Za-z]
SAFE_SYMBOL = "_"
DIGIT = [0-9]
LBRACKET = "["
RBRACKET = "]"
COMMA = ","
EQUALS = "="
LPAREN = "("
RPAREN = ")"
_ = (" " / "\n" / "\t" / "\r\n")+

6
src/parser/index.ts Normal file
View File

@ -0,0 +1,6 @@
export * from './generate';
export * from './parser';
import * as peggy from './parser';
export const peggyParse = (source: string): peggy.FunctionDefinition[] =>
peggy.parse(source);

5235
src/parser/parser.ts Normal file

File diff suppressed because it is too large Load Diff

1
src/utils/exception.ts Normal file
View File

@ -0,0 +1 @@
export class NotImplementedException extends Error {}

2
src/utils/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './logger';
export * from './exception';

51
src/utils/logger.ts Normal file
View File

@ -0,0 +1,51 @@
export interface TracingLogger {
info(log: string): void;
warn(log: string): void;
error(log: string): void;
debug(log: string): void;
createChild(prefix: string): TracingLogger;
}
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
export class ConsoleTracingLogger implements TracingLogger {
protected prefix: string;
private levels: LogLevel[];
constructor(prefix: string, levels: LogLevel[] = ['warn', 'error']) {
this.prefix = prefix;
this.levels = levels;
}
private makePrefix(level: LogLevel): string {
return `[${new Date().toISOString()}] ${level} (${this.prefix}) > `;
}
public info(log: string) {
if (this.levels.includes('info'))
console.log(this.makePrefix('info') + log);
}
public warn(log: string) {
if (this.levels.includes('warn'))
console.warn(this.makePrefix('warn') + log);
}
public error(log: string) {
if (this.levels.includes('error'))
console.error(this.makePrefix('error') + log);
}
public debug(log: string) {
if (this.levels.includes('debug'))
console.debug(this.makePrefix('debug') + log);
}
public createChild(prefix: string, levels?: LogLevel[]) {
return new ConsoleTracingLogger(
`${this.prefix} -> ${prefix}`,
levels ?? this.levels,
);
}
}

27
tsconfig.json Normal file
View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
/* Linting */
"skipLibCheck": true,
"strict": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"@/*": ["./src/*"],
"@t/*": ["./test/*"]
}
}
}