Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
happytomatoe committed Oct 3, 2024
1 parent 1b9a348 commit afbf33b
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 194 deletions.
166 changes: 86 additions & 80 deletions simulator/src/jack/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,100 +9,106 @@ import JackLexer from "./generated/JackLexer.js";
import { CompilationError } from "../languages/base.js";

export function compile(
files: Record<string, string>,
files: Record<string, string>,
): Record<string, string | CompilationError> {
try {
return _compile(files)
} catch (err) {
const result: Record<string, CompilationError> = {};
console.error(err);
const keys = Object.keys(files);
for (const name of keys) {
result[name] = { message: "Something went wrong while compiling files. Please create a bug report" } as CompilationError;
}
return result;
try {
return _compile(files);
} catch (err) {
const result: Record<string, CompilationError> = {};
console.error(err);
const keys = Object.keys(files);
for (const name of keys) {
result[name] = {
message:
"Something went wrong while compiling files. Please create a bug report",
} as CompilationError;
}
return result;
}
}
function _compile(files: Record<string, string>,
function _compile(
files: Record<string, string>,
): Record<string, string | CompilationError> {
if (files instanceof LexerOrParserError) {
throw new Error("Expected tree but got a lexer or parser error");
}
const result: Record<string, string | CompilationError> = {};
for (const name of Object.keys(files)) {
result[name] = "";
if (files instanceof LexerOrParserError) {
throw new Error("Expected tree but got a lexer or parser error");
}
const result: Record<string, string | CompilationError> = {};
for (const name of Object.keys(files)) {
result[name] = "";
}
const trees: Record<string, ProgramContext> = {};
const errors: Record<string, CompilationError> = {};
const compiler = new Compiler();
for (const [name, content] of Object.entries(files)) {
const treeOrErrors = compiler.parserAndBind(content);
if (Array.isArray(treeOrErrors)) {
errors[name] = toCompilerError(treeOrErrors);
}
const trees: Record<string, ProgramContext> = {};
const errors: Record<string, CompilationError> = {};
const compiler = new Compiler();
for (const [name, content] of Object.entries(files)) {
const treeOrErrors = compiler.parserAndBind(content);
if (Array.isArray(treeOrErrors)) {
errors[name] = toCompilerError(treeOrErrors);
}
trees[name] = treeOrErrors as ProgramContext;
}
if (Object.keys(errors).length > 0) {
for (const [name, error] of Object.entries(errors)) {
result[name] = error;
}
return result;
trees[name] = treeOrErrors as ProgramContext;
}
if (Object.keys(errors).length > 0) {
for (const [name, error] of Object.entries(errors)) {
result[name] = error;
}
return result;
}

for (const [name, tree] of Object.entries(trees)) {
const compiledOrErrors = compiler.compile(tree);
if (Array.isArray(compiledOrErrors)) {
result[name] = toCompilerError(compiledOrErrors);
} else {
result[name] = compiledOrErrors;
}
for (const [name, tree] of Object.entries(trees)) {
const compiledOrErrors = compiler.compile(tree);
if (Array.isArray(compiledOrErrors)) {
result[name] = toCompilerError(compiledOrErrors);
} else {
result[name] = compiledOrErrors;
}
return result;
}
return result;
}
function toCompilerError(errors: JackCompilerError[]): CompilationError {
const err = errors[0];
return { message: `Line ${err.span.line}: ${err.msg}`, span: err.span } as CompilationError
const err = errors[0];
return {
message: `Line ${err.span.line}: ${err.msg}`,
span: err.span,
} as CompilationError;
}


export class Compiler {
private binder = new BinderListener();
private errorListener = new CustomErrorListener();
compile(tree: ProgramContext): string | JackCompilerError[] {
if (Object.keys(this.binder.globalSymbolTable).length == 0) {
throw new Error(
"Please populate global symbol table using parserAndBind method",
);
}
const validator = new ValidatorListener(this.binder.globalSymbolTable);
ParseTreeWalker.DEFAULT.walk(validator, tree);
if (validator.errors.length > 0) {
console.log("Errors in validator " + JSON.stringify(validator.errors));
return validator.errors;
}
const vmWriter = new VMWriter(this.binder.globalSymbolTable);
ParseTreeWalker.DEFAULT.walk(vmWriter, tree);
return vmWriter.result;
private binder = new BinderListener();
private errorListener = new CustomErrorListener();
compile(tree: ProgramContext): string | JackCompilerError[] {
if (Object.keys(this.binder.globalSymbolTable).length == 0) {
throw new Error(
"Please populate global symbol table using parserAndBind method",
);
}
const validator = new ValidatorListener(this.binder.globalSymbolTable);
ParseTreeWalker.DEFAULT.walk(validator, tree);
if (validator.errors.length > 0) {
console.log("Errors in validator " + JSON.stringify(validator.errors));
return validator.errors;
}
const vmWriter = new VMWriter(this.binder.globalSymbolTable);
ParseTreeWalker.DEFAULT.walk(vmWriter, tree);
return vmWriter.result;
}

parserAndBind(src: string): ProgramContext | JackCompilerError[] {
const lexer = new JackLexer(CharStreams.fromString(src));
lexer.removeErrorListeners();
lexer.addErrorListener(this.errorListener);
const tokenStream = new CommonTokenStream(lexer);
const parser = new JackParser(tokenStream);
parser.removeErrorListeners();
parser.addErrorListener(this.errorListener);
const tree = parser.program();
if (this.errorListener.errors.length > 0) {
console.log("Errors when parsing or lexing");
return this.errorListener.errors;
}
ParseTreeWalker.DEFAULT.walk(this.binder, tree);
if (this.binder.errors.length > 0) {
console.log("Errors in binder");
return this.binder.errors;
}
return tree;
parserAndBind(src: string): ProgramContext | JackCompilerError[] {
const lexer = new JackLexer(CharStreams.fromString(src));
lexer.removeErrorListeners();
lexer.addErrorListener(this.errorListener);
const tokenStream = new CommonTokenStream(lexer);
const parser = new JackParser(tokenStream);
parser.removeErrorListeners();
parser.addErrorListener(this.errorListener);
const tree = parser.program();
if (this.errorListener.errors.length > 0) {
console.log("Errors when parsing or lexing");
return this.errorListener.errors;
}
ParseTreeWalker.DEFAULT.walk(this.binder, tree);
if (this.binder.errors.length > 0) {
console.log("Errors in binder");
return this.binder.errors;
}
return tree;
}
}
2 changes: 1 addition & 1 deletion simulator/src/jack/generated/JackLexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Lexer,
LexerATNSimulator,
PredictionContextCache,
Token
Token,
} from "antlr4";
export default class JackLexer extends Lexer {
public static readonly CLASS = 1;
Expand Down
30 changes: 15 additions & 15 deletions simulator/src/jack/generated/JackParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@
// noinspection ES6UnusedImports,JSUnusedGlobalSymbols,JSUnusedLocalSymbols

import {
ATN,
ATNDeserializer,
DecisionState,
DFA,
FailedPredicateException,
NoViableAltException,
Parser,
ParserATNSimulator,
ParserRuleContext,
PredictionContextCache,
RecognitionException,
RuleContext,
TerminalNode,
Token,
TokenStream
ATN,
ATNDeserializer,
DecisionState,
DFA,
FailedPredicateException,
NoViableAltException,
Parser,
ParserATNSimulator,
ParserRuleContext,
PredictionContextCache,
RecognitionException,
RuleContext,
TerminalNode,
Token,
TokenStream,
} from "antlr4";
import JackParserListener from "./JackParserListener.js";
// for running tests with parameters, TODO: discuss strategy for typed parameters in CI
Expand Down
4 changes: 2 additions & 2 deletions simulator/src/jack/listener/error.listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ export class CustomErrorListener extends ErrorListener<any> {
e: RecognitionException | undefined,
) => {
const t = offendingSymbol as Token;
this.errors.push(new LexerOrParserError(line, t.start, t.stop+1, msg));
this.errors.push(new LexerOrParserError(line, t.start, t.stop + 1, msg));
};
}
}
4 changes: 2 additions & 2 deletions simulator/src/jack/listener/validator.listener.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -806,8 +806,8 @@ function testValidator<T extends { name: string }>(
} catch (e) {
throw new Error(
`Expected error ${expectedError.name} but got '` +
validator.errors.join(",") +
"'",
validator.errors.join(",") +
"'",
);
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion simulator/src/jack/listener/validator.listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ class BinaryTreeNode {
public parent?: BinaryTreeNode,
public left?: BinaryTreeNode,
public right?: BinaryTreeNode,
) { }
) {}

public get returns(): boolean {
if (this._returns) {
Expand Down
80 changes: 40 additions & 40 deletions simulator/src/jack/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,59 @@ import fs from "fs";

import path from "path";
import {
getTestResourcePath,
listenToTheTree,
parseJackFile,
parseJackText,
testResourceDirs,
getTestResourcePath,
listenToTheTree,
parseJackFile,
parseJackText,
testResourceDirs,
} from "./test.helper";
import { BinderListener } from "./listener/binder.listener";

describe("Parser", () => {
const jestConsole = console;
beforeEach(() => {
global.console = require("console");
});
const jestConsole = console;
beforeEach(() => {
global.console = require("console");
});

afterEach(() => {
global.console = jestConsole;
});
afterEach(() => {
global.console = jestConsole;
});

test.each(testResourceDirs)("%s", (dir: string) => {
testJackDir(getTestResourcePath(dir));
});
test("expected EOF", () => {
try {
parseJackText(`
test.each(testResourceDirs)("%s", (dir: string) => {
testJackDir(getTestResourcePath(dir));
});
test("expected EOF", () => {
try {
parseJackText(`
class A{
}
var a;
`);
} catch (e) {
if (e instanceof Error) {
expect(e.message).toContain("expecting <EOF>");
return;
} else {
fail("Expected Error");
}
}
} catch (e) {
if (e instanceof Error) {
expect(e.message).toContain("expecting <EOF>");
return;
} else {
fail("Expected Error");
});
}
}
fail("Expected Error");
});
});

function testJackDir(testFolder: string): void {
const files = fs
.readdirSync(testFolder)
.filter((file) => file.endsWith(".jack"))
.map((file) => path.join(testFolder, file));
for (const filePath of files) {
const tree = parseJackFile(filePath);
const globalSymbolsListener = listenToTheTree(tree, new BinderListener());
const symbolsErrors = globalSymbolsListener.errors.join("\n");
try {
expect(globalSymbolsListener.errors.length).toBe(0);
} catch (e) {
throw new Error(symbolsErrors);
}
const files = fs
.readdirSync(testFolder)
.filter((file) => file.endsWith(".jack"))
.map((file) => path.join(testFolder, file));
for (const filePath of files) {
const tree = parseJackFile(filePath);
const globalSymbolsListener = listenToTheTree(tree, new BinderListener());
const symbolsErrors = globalSymbolsListener.errors.join("\n");
try {
expect(globalSymbolsListener.errors.length).toBe(0);
} catch (e) {
throw new Error(symbolsErrors);
}
}
}
Loading

0 comments on commit afbf33b

Please sign in to comment.