package com.github.charmoniumq.assembler.backend; import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.misc.ParseCancellationException; import org.antlr.v4.runtime.tree.ParseTree; import com.github.charmoniumq.assembler.Utils; import com.github.charmoniumq.assembler.grammar.MainBaseVisitor; import com.github.charmoniumq.assembler.grammar.MainLexer; import com.github.charmoniumq.assembler.grammar.MainParser; import java.util.Map; import java.util.HashMap; public class Compiler extends MainBaseVisitor<String> { /* This function calls `visit` on its children. Eventually the `visit` call bubbles down to a token that can be translated to a String. Then it returns that String which bubbles back up to larger tokens, which know how to assemble Strings */ @Override public String visitSource(MainParser.SourceContext ctx) { StringBuilder program = new StringBuilder("v2.0 raw\n"); for (ParseTree child : ctx.children) { String currentByte = visit(child); if (! currentByte.isEmpty()) { // empty string doesn't effect program output if (currentByte.matches("^[01]{8}$")) { int value = Integer.parseInt(currentByte, 2); program.append(toBase(value, 16, 2) + " "); bytes++; // running count of bytes is important for visitAssignLabel } else { throw new ParseCancellationException(new com.github.charmoniumq.assembler.backend.InternalError(String.format("Invalid byte produced: %s", currentByte))); } } } return program.toString(); } @Override public String visitSetLabel(MainParser.SetLabelContext ctx) { String labelName = ctx.label().word().getText(); labels.put(labelName, toBase(bytes, 2, 5)); return ""; // empty string doesn't effect program output } @Override public String visitSetPointer(MainParser.SetPointerContext ctx) { String pointerName = ctx.pointer().word().getText(); String pointerAddr = visit(ctx.literal4()); pointers.put(pointerName, pointerAddr); return ""; // empty string doesn't effect program output } @Override public String visitCommand8Execute(MainParser.Command8ExecuteContext ctx) { return visit(ctx.command8Name()); } @Override public String visitCommand8Name(MainParser.Command8NameContext ctx) { return ctx.code; } @Override public String visitCommand4Execute(MainParser.Command4ExecuteContext ctx) { return visit(ctx.address4()) + visit(ctx.command4Name()); } @Override public String visitCommand4Name(MainParser.Command4NameContext ctx) { return ctx.code; } @Override public String visitCommand3Execute(MainParser.Command3ExecuteContext ctx) { return visit(ctx.address5()) + visit(ctx.command3Name()); } @Override public String visitCommand3Name(MainParser.Command3NameContext ctx) { return ctx.code; } @Override public String visitGetPointer(MainParser.GetPointerContext ctx) { String pointerName = ctx.pointer().word().getText(); if (pointers.containsKey(pointerName)) { String pointerValue = pointers.get(pointerName); return pointerValue; } else { throw new ParseCancellationException(new PostProcessError( String.format("Pointer \"%s\" is undefined", pointerName), "Pointer lookup", ctx)); } } @Override public String visitGetLabel(MainParser.GetLabelContext ctx) { String labelName = ctx.label().word().getText(); if (labels.containsKey(labelName)) { String labelValue = labels.get(labelName); return labelValue; } else { throw new ParseCancellationException(new PostProcessError( String.format("Label \"%s\" is undefined", labelName), "Label lookup", ctx)); } } @Override public String visitLiteral8(MainParser.Literal8Context ctx) { return interpretLiteral(ctx.literal(), 8); } @Override public String visitLiteral4(MainParser.Literal4Context ctx) { return interpretLiteral(ctx.literal(), 4); } @Override public String visitLiteral5(MainParser.Literal5Context ctx) { return interpretLiteral(ctx.literal(), 5); } @Override public String visitEof(MainParser.EofContext ctx) { return ""; } private String interpretLiteral(MainParser.LiteralContext ctx, int binaryWidth) { int num; int base = 0; String literal = ctx.getText(); if (literal.startsWith("0b")) { base = 2; } else if (literal.startsWith("0x")) { base = 16; } else if (literal.startsWith("0d")) { base = 10; } else { base = 2; literal = "0b" + literal; } num = Integer.parseInt(literal.substring(2).toUpperCase(), base); String output = ""; try { output = toBase(num, 2, binaryWidth); } catch (IllegalArgumentException e) { throw new ParseCancellationException(new PostProcessError( String.format("The number \"%s\" (base %d) is too big to be a %d-bit binary number", literal, base, binaryWidth), String.format("%d-bit address", binaryWidth), ctx)); } return output; } public static String toBase(int num, int base, int width) { if (num < Math.pow(base, width)) { return (String.format(String.format("%%%ds", width), Integer.toString(num, base)).replace(' ', '0')); } else { throw new IllegalArgumentException(); } } public String run(String inputString) throws Throwable { ANTLRInputStream input = new ANTLRInputStream(inputString); MainLexer lexer = new MainLexer(input); CommonTokenStream tokens = new CommonTokenStream(lexer); MainParser parser = new MainParser(tokens); parser.removeErrorListeners(); parser.setErrorHandler(new BailErrorStrategy()); ParseTree tree = null; String output = ""; StringBuilder error = new StringBuilder(); try { tree = parser.source(); output = this.visit(tree); } catch (ParseCancellationException pce) { Throwable cause = pce.getCause(); if (cause instanceof RecognitionException) { RecognitionException re = (RecognitionException) cause; error.append(com.github.charmoniumq.assembler.backend.ErrorMessages.recognitionException(re)); } else if (cause instanceof PostProcessError) { PostProcessError us = (PostProcessError) cause; error.append(com.github.charmoniumq.assembler.backend.ErrorMessages.undefinedSymbol(us)); } else if (cause instanceof com.github.charmoniumq.assembler.backend.InternalError) { error.append("Internal error\nContact the maintainer.\nPresent your source code, assembler version, and the following message:\n"); error.append(cause.getMessage() + "\n"); // no stack trace necessary, since the location of an error can be found by the message text } else { error.append( "Unknown error\nContact the maintainer.\nPresent your source code, assembler version, and the following message:\n"); error.append(Utils.stackTraceString(pce)); error.append(Utils.stackTraceString(cause)); } Throwable monkeyPoop = new Throwable(error.toString()); throw monkeyPoop; } return output; } public int getBytes() { return bytes; } Map<String, String> labels = new HashMap<String, String>(); Map<String, String> pointers = new HashMap<String, String>(); int bytes = 0; String output = ""; }