improved toolchain and test structure, improved documentation

This commit is contained in:
User 2017-07-12 21:30:52 +02:00
parent 8a05789dde
commit a42c621515
12 changed files with 674 additions and 390 deletions

View File

@ -4,15 +4,29 @@ import org.antlr.v4.runtime.tree.ParseTreeProperty;
import pp.iloc.model.Reg; import pp.iloc.model.Reg;
/**
* This class holds properties of all AST nodes during compilation.
*
*/
public class Annotations { public class Annotations {
/**
* Maps nodes to registers.
*/
public ParseTreeProperty<Reg> registers; public ParseTreeProperty<Reg> registers;
public CachingSymbolTable<SimpleType> symbols; /**
* Maps nodes to {@link SimpleType}s.
*/
public ParseTreeProperty<SimpleType> types; public ParseTreeProperty<SimpleType> types;
/**
* Maps nodes to {@link Variable}s.
*/
public ParseTreeProperty<Variable<SimpleType>> variables; public ParseTreeProperty<Variable<SimpleType>> variables;
/**
* Creates a new annotations object with empty maps.
*/
public Annotations() { public Annotations() {
registers = new ParseTreeProperty<>(); registers = new ParseTreeProperty<>();
symbols = new CachingSymbolTable<>();
types = new ParseTreeProperty<>(); types = new ParseTreeProperty<>();
variables = new ParseTreeProperty<>(); variables = new ParseTreeProperty<>();
} }

View File

@ -1,126 +0,0 @@
package pp.s1184725.boppi;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeListener;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.commons.lang3.tuple.Pair;
public class BasicParserHelper {
public static Pair<BasicParser,List<LogRecord>> getParserWithLog(Path file, Logger logger) throws IOException {
return Pair.of(getParser(CharStreams.fromFileName(file.toString()), logger), makeListLog(logger));
}
public static Pair<BasicParser,List<LogRecord>> getParserWithLog(String code, Logger logger) {
return Pair.of(getParser(CharStreams.fromString(code), logger), makeListLog(logger));
}
public static BasicParser getParser(CharStream stream, Logger logger) {
BasicLexer lexer = new BasicLexer(stream);
lexer.removeErrorListeners();
lexer.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> r, Object oSym, int l, int c, String msg, RecognitionException e) {
logger.severe(""+l+":"+c+" "+msg);
}
});
BasicParser parser = new BasicParser(new CommonTokenStream(lexer));
parser.removeErrorListeners();
parser.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> r, Object oSym, int l, int c, String msg, RecognitionException e) {
logger.severe(""+l+":"+c+" "+msg);
}
});
return parser;
}
public static List<LogRecord> makeListLog(Logger logger) {
List<LogRecord> log = new ArrayList<LogRecord>();
Handler listLogger = new Handler() {
@Override
public void close() throws SecurityException {}
@Override
public void flush() {}
@Override
public void publish(LogRecord record) {
log.add(record);
}
};
for (Handler handler : logger.getHandlers())
logger.removeHandler(handler);
logger.setUseParentHandlers(false);
logger.addHandler(listLogger);
return log;
}
public static String getAnnotatedDOT(ParseTree tree, Annotations annotater) {
StringBuilder sb = new StringBuilder();
sb.append("digraph {\n");
new ParseTreeWalker().walk(new ParseTreeListener() {
private String escape(String str) {
return str.replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
}
@Override
public void visitTerminal(TerminalNode node) {
sb.append("\tn"+node.hashCode()+"[label=<"+escape(node.getText())+">;shape=rect]\n");
}
@Override
public void visitErrorNode(ErrorNode node) {
sb.append("\tn"+node.hashCode()+"[label=<"+escape(node.getText())+">;style=filled;fillcolor=red]\n");
}
@Override
public void exitEveryRule(ParserRuleContext ctx) {
float hue = (ctx.getClass().hashCode() % 65521)/65521.0f;
sb.append("\tn"+ctx.hashCode()+"[label=<");
sb.append("rule: "+escape(ctx.getClass().getSimpleName())+"<br/>");
if (annotater.registers.get(ctx) != null)
sb.append("reg: <b>"+escape(annotater.registers.get(ctx).getName())+"</b><br/>");
if (annotater.variables.get(ctx) != null)
sb.append("var: "+escape(annotater.variables.get(ctx).toString())+"<br/>");
if (annotater.types.get(ctx) != null)
sb.append("type: "+escape(annotater.types.get(ctx).name())+"<br/>");
sb.append(">;style=filled;fillcolor=\""+hue+"+0.1+1\"]\n");
if (ctx.children != null)
for (ParseTree child : ctx.children)
sb.append("\tn"+ctx.hashCode()+" -> n"+child.hashCode()+"\n");
}
@Override
public void enterEveryRule(ParserRuleContext ctx) {}
}, tree);
sb.append("}\n");
return sb.toString();
}
}

View File

@ -1,7 +1,6 @@
package pp.s1184725.boppi; package pp.s1184725.boppi;
import java.util.EmptyStackException; import java.util.EmptyStackException;
import java.util.function.Supplier;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.ParserRuleContext;
@ -9,15 +8,31 @@ import org.antlr.v4.runtime.tree.ParseTree;
import pp.s1184725.boppi.BasicParser.*; import pp.s1184725.boppi.BasicParser.*;
/**
* This class performs type checking and variable assignment on a bare parse
* tree.
*
*/
public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> { public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
private CachingSymbolTable<SimpleType> symbols;
private Annotations an; private Annotations an;
private Logger log; private Logger log;
/**
* Creates a new checker with a logger and an object to link types and
* variables to parse tree nodes.
*
* @param logger
* the logger object to write warnings and errors to
* @param annotations
* the annotations object
*/
public BoppiBasicChecker(Logger logger, Annotations annotations) { public BoppiBasicChecker(Logger logger, Annotations annotations) {
symbols = new CachingSymbolTable<>();
an = annotations; an = annotations;
log = logger; log = logger;
} }
@Override @Override
public SimpleType visit(ParseTree tree) { public SimpleType visit(ParseTree tree) {
SimpleType type = super.visit(tree); SimpleType type = super.visit(tree);
@ -40,7 +55,7 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
@Override @Override
public SimpleType visitBlock(BlockContext ctx) { public SimpleType visitBlock(BlockContext ctx) {
return inScope(() -> visit(ctx.expr())); return symbols.withScope(() -> visit(ctx.expr()));
} }
@Override @Override
@ -59,7 +74,7 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
@Override @Override
public SimpleType visitDeclare(DeclareContext ctx) { public SimpleType visitDeclare(DeclareContext ctx) {
try { try {
Variable<SimpleType> var = an.symbols.put(ctx.IDENTIFIER().getText(), visit(ctx.type())); Variable<SimpleType> var = symbols.put(ctx.IDENTIFIER().getText(), visit(ctx.type()));
an.variables.put(ctx, var); an.variables.put(ctx, var);
} catch (EmptyStackException e) { } catch (EmptyStackException e) {
log.severe(getError(ctx, "Declaring variable outside a program.")); log.severe(getError(ctx, "Declaring variable outside a program."));
@ -71,15 +86,15 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
@Override @Override
public SimpleType visitIf(IfContext ctx) { public SimpleType visitIf(IfContext ctx) {
return inScope(() -> { return symbols.withScope(() -> {
visit(ctx.cond); visit(ctx.cond);
if (ctx.onFalse != null) { if (ctx.onFalse != null) {
SimpleType trueType = inScope(() -> visit(ctx.onTrue)); SimpleType trueType = symbols.withScope(() -> visit(ctx.onTrue));
SimpleType falseType = inScope(() -> visit(ctx.onFalse)); SimpleType falseType = symbols.withScope(() -> visit(ctx.onFalse));
return (trueType.equals(falseType)) ? trueType : SimpleType.VOID; return (trueType.equals(falseType)) ? trueType : SimpleType.VOID;
} else { } else {
inScope(() -> visit(ctx.onTrue)); symbols.withScope(() -> visit(ctx.onTrue));
return SimpleType.VOID; return SimpleType.VOID;
} }
}); });
@ -164,7 +179,7 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
@Override @Override
public SimpleType visitProgram(ProgramContext ctx) { public SimpleType visitProgram(ProgramContext ctx) {
inScope(() -> super.visitProgram(ctx)); symbols.withScope(() -> super.visitProgram(ctx));
return null; return null;
} }
@ -194,7 +209,7 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
@Override @Override
public SimpleType visitVariable(VariableContext ctx) { public SimpleType visitVariable(VariableContext ctx) {
try { try {
Variable<SimpleType> var = an.symbols.get(ctx.getText()); Variable<SimpleType> var = symbols.get(ctx.getText());
an.variables.put(ctx, var); an.variables.put(ctx, var);
return var.getType(); return var.getType();
} catch (EmptyStackException e) { } catch (EmptyStackException e) {
@ -208,9 +223,9 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
@Override @Override
public SimpleType visitWhile(WhileContext ctx) { public SimpleType visitWhile(WhileContext ctx) {
return inScope(() -> { return symbols.withScope(() -> {
checkConstraint(visit(ctx.cond), SimpleType.BOOL, ctx.cond); checkConstraint(visit(ctx.cond), SimpleType.BOOL, ctx.cond);
inScope(() -> visit(ctx.onTrue)); symbols.withScope(() -> visit(ctx.onTrue));
return SimpleType.VOID; return SimpleType.VOID;
}); });
} }
@ -221,7 +236,7 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
SimpleType type = visit(ctx.expr(0)); SimpleType type = visit(ctx.expr(0));
if (SimpleType.VOID.equals(type)) if (SimpleType.VOID.equals(type))
log.severe(getError(ctx, "Cannot print argument of type %s.", type)); log.severe(getError(ctx, "Cannot print argument of type %s.", type));
return type; return type;
} else { } else {
ctx.expr().stream().map(this::visit).forEach((type) -> { ctx.expr().stream().map(this::visit).forEach((type) -> {
@ -232,26 +247,21 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
} }
} }
/**
* Checks whether two types are compatible and log an error if they do not.
*
* @param type1
* the left hand or actual type
* @param type2
* the right hand or desired type
* @param node
* the context used for logging
*/
private void checkConstraint(SimpleType type1, SimpleType type2, ParserRuleContext node) { private void checkConstraint(SimpleType type1, SimpleType type2, ParserRuleContext node) {
if (!type2.equals(type1)) if (!type2.equals(type1))
log.severe(getError(node, "Could not match type %s with type %s.", type1.toString(), type2.toString())); log.severe(getError(node, "Could not match type %s with type %s.", type1.toString(), type2.toString()));
} }
/**
* Helper method to correctly open a scope, run some code and close the
* scope. {@code} must take no arguments and must return a value.
*
* @param code
* the code to execute within the scope
* @return the result of {@code}
*/
protected <T> T inScope(Supplier<T> code) {
an.symbols.openScope();
T result = code.get();
an.symbols.closeScope();
return result;
}
/** /**
* Returns an error message for a given parse tree node. * Returns an error message for a given parse tree node.
* *
@ -262,7 +272,7 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
* @param args * @param args
* arguments for the message, see {@link String#format} * arguments for the message, see {@link String#format}
*/ */
protected String getError(ParserRuleContext node, String message, Object... args) { private String getError(ParserRuleContext node, String message, Object... args) {
int line = node.getStart().getLine(); int line = node.getStart().getLine();
int column = node.getStart().getCharPositionInLine(); int column = node.getStart().getCharPositionInLine();
return String.format("Line %d:%d - %s", line, column, String.format(message, args)); return String.format("Line %d:%d - %s", line, column, String.format(message, args));

View File

@ -1,11 +1,9 @@
package pp.s1184725.boppi; package pp.s1184725.boppi;
import java.util.HashMap; import java.util.*;
import java.util.Map;
import org.antlr.v4.runtime.RuleContext; import org.antlr.v4.runtime.RuleContext;
import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.*;
import org.antlr.v4.runtime.tree.RuleNode;
import pp.iloc.model.*; import pp.iloc.model.*;
import pp.s1184725.boppi.BasicParser.*; import pp.s1184725.boppi.BasicParser.*;
@ -16,8 +14,12 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
private int regNum; private int regNum;
private final Reg zero = new Reg("zero"); private final Reg zero = new Reg("zero");
static Map<Integer, OpCode> ops = new HashMap<Integer, OpCode>() { /**
* Maps operator tokens to ILOC opcodes.
*/
private static Map<Integer, OpCode> ops = new HashMap<Integer, OpCode>() {
private static final long serialVersionUID = 8979722313842633807L; private static final long serialVersionUID = 8979722313842633807L;
{ {
put(BasicLexer.AND, OpCode.and); put(BasicLexer.AND, OpCode.and);
put(BasicLexer.DIVIDE, OpCode.div); put(BasicLexer.DIVIDE, OpCode.div);
@ -34,6 +36,12 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
} }
}; };
/**
* Creates a new generator with the given variable annotations.
*
* @param annotations
* the annotation object to use during generation
*/
public BoppiBasicGenerator(Annotations annotations) { public BoppiBasicGenerator(Annotations annotations) {
an = annotations; an = annotations;
prog = new Program(); prog = new Program();
@ -42,6 +50,13 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
emit(OpCode.loadI, new Num(0), zero); emit(OpCode.loadI, new Num(0), zero);
} }
/**
* Retrieves a register for a given tree node.
*
* @param parseTree
* the tree node
* @return a register for the tree node
*/
private Reg getReg(ParseTree parseTree) { private Reg getReg(ParseTree parseTree) {
Reg reg = an.registers.get(parseTree); Reg reg = an.registers.get(parseTree);
if (reg == null) { if (reg == null) {
@ -51,6 +66,16 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
return reg; return reg;
} }
/**
* Attaches the register used in the {@code source} tree node to the
* {@link destination} node.
*
* @param source
* the node to retrieve the register from
* @param destination
* the node to copy the register to
* @return the register that was copied
*/
private Reg copyReg(ParseTree source, ParseTree destination) { private Reg copyReg(ParseTree source, ParseTree destination) {
Reg reg = an.registers.get(source); Reg reg = an.registers.get(source);
an.registers.put(destination, reg); an.registers.put(destination, reg);
@ -92,7 +117,7 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
public Void visitAssign(AssignContext ctx) { public Void visitAssign(AssignContext ctx) {
visit(ctx.singleExpr()); visit(ctx.singleExpr());
copyReg(ctx.singleExpr(), ctx); copyReg(ctx.singleExpr(), ctx);
switch (an.types.get(ctx)) { switch (an.types.get(ctx)) {
case CHAR: case CHAR:
emit(OpCode.cstoreAI, getReg(ctx), zero, new Num(an.variables.get(ctx.variable()).getOffset())); emit(OpCode.cstoreAI, getReg(ctx), zero, new Num(an.variables.get(ctx.variable()).getOffset()));
@ -179,7 +204,7 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
emit(OpCode.i2c, getReg(ctx), getReg(ctx)); emit(OpCode.i2c, getReg(ctx), getReg(ctx));
return null; return null;
} }
@Override @Override
public Void visitRead(ReadContext ctx) { public Void visitRead(ReadContext ctx) {
for (VariableContext expr : ctx.variable()) { for (VariableContext expr : ctx.variable()) {
@ -191,22 +216,22 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
emit(OpCode.storeAI, getReg(ctx), zero, new Num(an.variables.get(expr).getOffset())); emit(OpCode.storeAI, getReg(ctx), zero, new Num(an.variables.get(expr).getOffset()));
break; break;
case CHAR: case CHAR:
//Get input until at least 1 character // Get input until at least 1 character
Label getTarget = new Label("lcin_l"+ctx.hashCode()); Label getTarget = new Label("lcin_l" + ctx.hashCode());
Label continueTarget = new Label("lcin_e"+ctx.hashCode()); Label continueTarget = new Label("lcin_e" + ctx.hashCode());
Reg countReg = new Reg("tempReg"); Reg countReg = new Reg("tempReg");
emit(getTarget, OpCode.cin, new Str("")); emit(getTarget, OpCode.cin, new Str(""));
emit(OpCode.pop, countReg); emit(OpCode.pop, countReg);
emit(OpCode.cbr, countReg, continueTarget, getTarget); emit(OpCode.cbr, countReg, continueTarget, getTarget);
//Get character // Get character
emit(continueTarget, OpCode.cpop, getReg(ctx)); emit(continueTarget, OpCode.cpop, getReg(ctx));
emit(OpCode.cstoreAI, getReg(ctx), zero, new Num(an.variables.get(expr).getOffset())); emit(OpCode.cstoreAI, getReg(ctx), zero, new Num(an.variables.get(expr).getOffset()));
//Pop all remaining characters // Pop all remaining characters
Label loopTarget = new Label("lcpop_l"+ctx.hashCode()); Label loopTarget = new Label("lcpop_l" + ctx.hashCode());
Label iterTarget = new Label("lcpop_c"+ctx.hashCode()); Label iterTarget = new Label("lcpop_c" + ctx.hashCode());
Label stopTarget = new Label("lcpop_e"+ctx.hashCode()); Label stopTarget = new Label("lcpop_e" + ctx.hashCode());
Reg tempReg2 = new Reg("tempReg2"); Reg tempReg2 = new Reg("tempReg2");
emit(loopTarget, OpCode.subI, countReg, new Num(1), countReg); emit(loopTarget, OpCode.subI, countReg, new Num(1), countReg);
emit(OpCode.cbr, countReg, iterTarget, stopTarget); emit(OpCode.cbr, countReg, iterTarget, stopTarget);
@ -220,46 +245,45 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
} }
return null; return null;
} }
@Override @Override
public Void visitIf(IfContext ctx) { public Void visitIf(IfContext ctx) {
Label toTrue = new Label("if_t"+ctx.hashCode()); Label toTrue = new Label("if_t" + ctx.hashCode());
Label toFalse = new Label("if_f"+ctx.hashCode()); Label toFalse = new Label("if_f" + ctx.hashCode());
Label toEnd = new Label("if_e"+ctx.hashCode()); Label toEnd = new Label("if_e" + ctx.hashCode());
visit(ctx.cond); visit(ctx.cond);
if (ctx.onFalse == null) { if (ctx.onFalse == null) {
emit(OpCode.cbr, getReg(ctx.cond), toTrue, toEnd); emit(OpCode.cbr, getReg(ctx.cond), toTrue, toEnd);
emit(toTrue, OpCode.nop); emit(toTrue, OpCode.nop);
visit(ctx.onTrue); visit(ctx.onTrue);
} } else {
else {
emit(OpCode.cbr, getReg(ctx.cond), toTrue, toFalse); emit(OpCode.cbr, getReg(ctx.cond), toTrue, toFalse);
emit(toTrue, OpCode.nop); emit(toTrue, OpCode.nop);
visit(ctx.onTrue); visit(ctx.onTrue);
if (an.types.get(ctx) != SimpleType.VOID) if (an.types.get(ctx) != SimpleType.VOID)
emit(OpCode.i2i, getReg(ctx.onTrue), getReg(ctx)); emit(OpCode.i2i, getReg(ctx.onTrue), getReg(ctx));
emit(OpCode.jumpI, toEnd); emit(OpCode.jumpI, toEnd);
emit(toFalse, OpCode.nop); emit(toFalse, OpCode.nop);
visit(ctx.onFalse); visit(ctx.onFalse);
if (an.types.get(ctx) != SimpleType.VOID) if (an.types.get(ctx) != SimpleType.VOID)
emit(OpCode.i2i, getReg(ctx.onFalse), getReg(ctx)); emit(OpCode.i2i, getReg(ctx.onFalse), getReg(ctx));
} }
emit(toEnd, OpCode.nop); emit(toEnd, OpCode.nop);
return null; return null;
} }
@Override @Override
public Void visitWhile(WhileContext ctx) { public Void visitWhile(WhileContext ctx) {
Label toLoop = new Label("while_t"+ctx.hashCode()); Label toLoop = new Label("while_t" + ctx.hashCode());
Label toCond = new Label("while_f"+ctx.hashCode()); Label toCond = new Label("while_f" + ctx.hashCode());
Label toEnd = new Label("while_e"+ctx.hashCode()); Label toEnd = new Label("while_e" + ctx.hashCode());
emit(OpCode.jumpI, toCond); emit(OpCode.jumpI, toCond);
emit(toLoop, OpCode.nop); emit(toLoop, OpCode.nop);
visit(ctx.onTrue); visit(ctx.onTrue);
@ -267,7 +291,7 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
visit(ctx.cond); visit(ctx.cond);
emit(OpCode.cbr, getReg(ctx.cond), toLoop, toEnd); emit(OpCode.cbr, getReg(ctx.cond), toLoop, toEnd);
emit(toEnd, OpCode.nop); emit(toEnd, OpCode.nop);
return null; return null;
} }

View File

@ -1,11 +1,22 @@
package pp.s1184725.boppi; package pp.s1184725.boppi;
import java.util.*; import java.util.*;
import java.util.function.Supplier;
/**
* This class maps string identifiers to {@link Variable} instances. It takes
* care of lexical scope, memory offsets and variable lifetime. This
* implementation has a theoretical lookup time of O(1), scope opening time of
* O(1) and scope closing time of O(k log k log n) with k the number of
* identifiers going out of scope. It is agnostic to the type system used.
*
* @param <T>
* the typing class
*/
public class CachingSymbolTable<T> { public class CachingSymbolTable<T> {
protected Stack<Map<String,Variable<T>>> symbolMapStack; protected Stack<Map<String, Variable<T>>> symbolMapStack;
protected Stack<Integer> offsets; protected Stack<Integer> offsets;
protected Map<String,Variable<T>> symbolCache; protected Map<String, Variable<T>> symbolCache;
protected int offset; protected int offset;
/** /**
@ -17,7 +28,7 @@ public class CachingSymbolTable<T> {
offsets = new Stack<>(); offsets = new Stack<>();
offset = 0; offset = 0;
} }
/** /**
* Opens a lexical scope. * Opens a lexical scope.
*/ */
@ -25,47 +36,79 @@ public class CachingSymbolTable<T> {
symbolMapStack.push(new HashMap<>()); symbolMapStack.push(new HashMap<>());
offsets.push(offset); offsets.push(offset);
} }
/** /**
* Closes a lexical scope, removing all definitions within this scope. * Closes a lexical scope, removing all definitions within this scope.
* @throws EmptyStackException if no scope is open *
* @throws EmptyStackException
* if no scope is open
*/ */
public void closeScope() throws EmptyStackException { public void closeScope() throws EmptyStackException {
Map<String,Variable<T>> deletions = symbolMapStack.pop(); Map<String, Variable<T>> deletions = symbolMapStack.pop();
for (String identifier : deletions.keySet()) { for (String identifier : deletions.keySet()) {
Optional<Variable<T>> maybeType = symbolMapStack.stream().filter((map) -> map.containsKey(identifier)).map((map) -> map.get(identifier)).reduce((__, snd) -> snd); Optional<Variable<T>> maybeType = symbolMapStack.stream().filter((map) -> map.containsKey(identifier))
.map((map) -> map.get(identifier)).reduce((__, snd) -> snd);
maybeType.ifPresent((var) -> symbolCache.replace(identifier, var)); maybeType.ifPresent((var) -> symbolCache.replace(identifier, var));
if (!maybeType.isPresent()) if (!maybeType.isPresent())
symbolCache.remove(identifier); symbolCache.remove(identifier);
} }
offset = offsets.pop(); offset = offsets.pop();
} }
/** /**
* Associates an identifier with a certain type in the current lexical scope. * Opens a scope, executes the given code and closes the scope. This is a
* @param id the name of the identifier * helper method to make sure {@link #openScope()} and {@link #closeScope()}
* @param type the type of the identifier * calls are balanced. {@code code} must take no arguments and must return a
* value.
*
* @param <U>
* the type of the code's return value
* @param code
* the code to execute within the scope
* @return the return value of the code
*/
public <U> U withScope(Supplier<U> code) {
openScope();
U result = code.get();
closeScope();
return result;
}
/**
* Associates an identifier with a certain type in the current lexical scope
* and returns the newly made variable instance that belongs to this
* identifier. Throws an exception if the
*
* @param id
* the name of the identifier
* @param type
* the type of the identifier
* @return the type of the identifier * @return the type of the identifier
* @throws Exception if the identifier is declared in the current scope already * @throws Exception
* @throws EmptyStackException if no scope is open * if the identifier is declared in the current scope already
* @throws EmptyStackException
* if no scope is open
*/ */
public Variable<T> put(String id, T type) throws Exception, EmptyStackException { public Variable<T> put(String id, T type) throws Exception, EmptyStackException {
if (symbolMapStack.peek().containsKey(id)) if (symbolMapStack.peek().containsKey(id))
throw new Exception(String.format("Identifier '%s' already declared in this scope.", id)); throw new Exception(String.format("Identifier '%s' already declared in this scope.", id));
Variable<T> var = new Variable<>(type, offset); Variable<T> var = new Variable<>(type, offset);
// TODO refactor to get type size // TODO refactor to get type size
offset += ((SimpleType)type).getSize(); offset += ((SimpleType) type).getSize();
symbolMapStack.peek().put(id, var); symbolMapStack.peek().put(id, var);
symbolCache.put(id, var); symbolCache.put(id, var);
return var; return var;
} }
/** /**
* Returns whether the given identifier is defined. * Returns whether the given identifier is defined.
* @param id the name of the identifier *
* @param id
* the name of the identifier
* @return true if the identifier has a defined type * @return true if the identifier has a defined type
* @throws EmptyStackException if no scope is open * @throws EmptyStackException
* if no scope is open
*/ */
public boolean has(String id) throws EmptyStackException { public boolean has(String id) throws EmptyStackException {
if (symbolMapStack.isEmpty()) if (symbolMapStack.isEmpty())
@ -73,13 +116,17 @@ public class CachingSymbolTable<T> {
return symbolCache.containsKey(id); return symbolCache.containsKey(id);
} }
/** /**
* Retrieves the type of an identifier, if any. * Retrieves the type of an identifier, if any.
* @param id the name of the identifier *
* @param id
* the name of the identifier
* @return the type of the identifier * @return the type of the identifier
* @throws Exception if the identifier is not defined * @throws Exception
* @throws EmptyStackException if no scope is open * if the identifier is not defined
* @throws EmptyStackException
* if no scope is open
*/ */
public Variable<T> get(String id) throws Exception, EmptyStackException { public Variable<T> get(String id) throws Exception, EmptyStackException {
if (!this.has(id)) if (!this.has(id))

View File

@ -1,21 +1,44 @@
package pp.s1184725.boppi; package pp.s1184725.boppi;
import static pp.s1184725.boppi.BasicLexer.*; import static pp.s1184725.boppi.BasicLexer.*;
import pp.iloc.eval.Machine; import pp.iloc.eval.Machine;
/**
* This class provides 4 basic data types:
* <ul>
* <li>character (sized {@link Machine#DEFAULT_CHAR_SIZE})</li>
* <li>integer (sized {@link Machine#INT_SIZE})</li>
* <li>boolean (sized {@link Machine#INT_SIZE})</li>
* <li>void (empty/unit type)</li>
* </ul>
*/
public enum SimpleType { public enum SimpleType {
INT(Machine.INT_SIZE), CHAR(Machine.DEFAULT_CHAR_SIZE), BOOL(Machine.INT_SIZE), VOID(0); INT(Machine.INT_SIZE), CHAR(Machine.DEFAULT_CHAR_SIZE), BOOL(Machine.INT_SIZE), VOID(0);
private final int size; private final int size;
/**
* Gets the size of this data type.
*
* @return the size (in bytes) of this type
*/
public int getSize() { public int getSize() {
return size; return size;
} }
private SimpleType(int typeSize) { private SimpleType(int typeSize) {
size = typeSize; size = typeSize;
} }
/**
* Maps a token lexed by {@link BasicLexer} to a data type.
*
* @param token
* the token to parse
* @return the data type of the token, {@link SimpleType#VOID} if
* unrecognized
*/
public static SimpleType fromToken(int token) { public static SimpleType fromToken(int token) {
switch (token) { switch (token) {
case INTTYPE: case INTTYPE:

View File

@ -0,0 +1,290 @@
package pp.s1184725.boppi;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.*;
import java.util.logging.*;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import org.apache.commons.lang3.tuple.Pair;
import pp.iloc.Simulator;
import pp.iloc.model.Program;
/**
* This class provides methods for all steps in the Boppi tool chain.
*
*/
public class ToolChain {
/**
* Opens a file for reading and returns its charstream. Throws an unhandled
* exception if the file could not be read.
*
* @param file
* the file to read
* @return a {@link CharStream} to be used in the lexer phase
*/
public static CharStream getCharStream(Path file) {
try {
return CharStreams.fromFileName(file.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Reads a string as a charstream.
*
* @param code
* the string to read
* @return a {@link CharStream} to be used in the lexer phase
*/
public static CharStream getCharStream(String code) {
return CharStreams.fromString(code);
}
/**
* Creates and initialises a lexer for a given character {@code stream} with
* the given {@code logger} attached to it.
*
* @param stream
* the character stream to read from
* @param logger
* the logger to attach
* @return the initialised lexer
*/
public static BasicLexer getLexer(CharStream stream, Logger logger) {
BasicLexer lexer = new BasicLexer(stream);
lexer.removeErrorListeners();
lexer.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> r, Object oSym, int l, int c, String msg, RecognitionException e) {
logger.severe("" + l + ":" + c + " " + msg);
}
});
return lexer;
}
/**
* Creates and initialises a parser for a given {@code lexer} with the given
* {@code logger} attached to it.
*
* @param lexer
* the lexer to read from
* @param logger
* the logger to attach
* @return the initialised parser
*/
public static BasicParser getParser(BasicLexer lexer, Logger logger) {
BasicParser parser = new BasicParser(new CommonTokenStream(lexer));
parser.removeErrorListeners();
parser.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> r, Object oSym, int l, int c, String msg, RecognitionException e) {
logger.severe("" + l + ":" + c + " " + msg);
}
});
return parser;
}
/**
* Checks a (sub)program with the {@link BoppiBasicChecker} and returns the
* annotations.
*
* @param program
* the parse tree to check
* @param logger
* the logger to write checker messages to
* @return the annotations made by the checker
*/
public static Annotations getAnnotations(ParseTree program, Logger logger) {
Annotations annotations = new Annotations();
new BoppiBasicChecker(logger, annotations).visit(program);
return annotations;
}
/**
* Generates ILOC code for an annotated program.
*
* @param program
* the parse tree to convert to ILOC
* @param annotations
* the annotations object provided by the checking phase
* @return an ILOC program
*/
public static Program getILOC(ParseTree program, Annotations annotations) {
BoppiBasicGenerator generator = new BoppiBasicGenerator(annotations);
generator.visit(program);
return generator.prog;
}
/**
* Replaces all handlers for the given {@code logger} with a handler that
* appends to the list that is returned.
*
* @param logger
* the logger to change
* @return the list to which messages are logged
*/
public static List<LogRecord> makeListLog(Logger logger) {
List<LogRecord> log = new ArrayList<LogRecord>();
Handler listLogger = new Handler() {
@Override
public void close() throws SecurityException {
}
@Override
public void flush() {
}
@Override
public void publish(LogRecord record) {
log.add(record);
}
};
for (Handler handler : logger.getHandlers())
logger.removeHandler(handler);
logger.setUseParentHandlers(false);
logger.addHandler(listLogger);
return log;
}
/**
* Shorthand for the compiler chain from {@link CharStream} to
* {@link Program}.
*
* @param stream
* the charstream to compile
* @return a pair of the ILOC program and the logger
*/
public static Pair<Program, List<LogRecord>> compile(CharStream stream) {
Logger logger = Logger.getAnonymousLogger();
List<LogRecord> logs = makeListLog(logger);
ParseTree ast = getParser(getLexer(stream, logger), logger).program();
return Pair.of(getILOC(ast, getAnnotations(ast, logger)), logs);
}
/**
* Shorthand for the compiler chain from {@link CharStream} to
* {@link Program}.
*
* @param stream
* the charstream to compile
* @param logger
* the logger to report compiler messages to
* @return a pair of the ILOC program and the logger
*/
public static Program compile(CharStream stream, Logger logger) {
ParseTree ast = getParser(getLexer(stream, logger), logger).program();
return getILOC(ast, getAnnotations(ast, logger));
}
/**
* Runs a program using the given input and output streams. Run errors will
* be logged as {@link Level#SEVERE}.
*
* @param program
* the program to run
* @param logger
* the logger to write runtime errors to
* @param in
* the input stream the program can read from
* @param out
* the output stream the program can write to
*/
public static void execute(Program program, Logger logger, InputStream in, OutputStream out) {
Simulator s = new Simulator(program);
s.setIn(in);
s.setOut(out);
try {
s.run();
} catch (Exception e) {
logger.severe(e.getMessage());
}
}
/**
* Runs a program with a given input string and returns all output as a
* string. Run errors will be logged as {@link Level#SEVERE} in the logger.
*
* @param program
* the program to run
* @param logger
* the logger to write runtime messages to
* @param input
* the string to use as input for the program
* @return the output of the program
*/
public static String execute(Program program, Logger logger, String input) {
InputStream in = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));
ByteArrayOutputStream out = new ByteArrayOutputStream();
execute(program, logger, in, out);
return out.toString().replaceAll("\r\n|\r", "\n");
}
/**
* Converts an annotated {@link ParseTree} to a colourful DOT graph.
*
* @param tree
* the tree to build
* @param annotater
* the objct containing annotations, if any
* @return a string containing the DOT graph
*/
public static String getAnnotatedDOT(ParseTree tree, Annotations annotater) {
StringBuilder sb = new StringBuilder();
sb.append("digraph {\n");
new ParseTreeWalker().walk(new ParseTreeListener() {
private String escape(String str) {
return str.replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
}
@Override
public void visitTerminal(TerminalNode node) {
sb.append("\tn" + node.hashCode() + "[label=<" + escape(node.getText()) + ">;shape=rect]\n");
}
@Override
public void visitErrorNode(ErrorNode node) {
sb.append("\tn" + node.hashCode() + "[label=<" + escape(node.getText())
+ ">;style=filled;fillcolor=red]\n");
}
@Override
public void exitEveryRule(ParserRuleContext ctx) {
float hue = (ctx.getClass().hashCode() % 65521) / 65521.0f;
sb.append("\tn" + ctx.hashCode() + "[label=<");
sb.append("rule: " + escape(ctx.getClass().getSimpleName()) + "<br/>");
if (annotater.registers.get(ctx) != null)
sb.append("reg: <b>" + escape(annotater.registers.get(ctx).getName()) + "</b><br/>");
if (annotater.variables.get(ctx) != null)
sb.append("var: " + escape(annotater.variables.get(ctx).toString()) + "<br/>");
if (annotater.types.get(ctx) != null)
sb.append("type: " + escape(annotater.types.get(ctx).name()) + "<br/>");
sb.append(">;style=filled;fillcolor=\"" + hue + "+0.1+1\"]\n");
if (ctx.children != null)
for (ParseTree child : ctx.children)
sb.append("\tn" + ctx.hashCode() + " -> n" + child.hashCode() + "\n");
}
@Override
public void enterEveryRule(ParserRuleContext ctx) {
}
}, tree);
sb.append("}\n");
return sb.toString();
}
}

View File

@ -1,24 +1,49 @@
package pp.s1184725.boppi; package pp.s1184725.boppi;
/**
* This class tracks a variable's properties. It is agnostic of the type system
* used. The type reference and offset are immutable.
*
* @param <T>
* the typing class
*/
public class Variable<T> { public class Variable<T> {
private final T type; private final T type;
private final int offset; private final int offset;
/**
* Creates a variable with the given type instance and memory offset.
*
* @param type
* the type of the variable
* @param offset
* the memory offset for this variable
*/
public Variable(T type, int offset) { public Variable(T type, int offset) {
this.type = type; this.type = type;
this.offset = offset; this.offset = offset;
} }
/**
* Gets the type of this variable instance.
*
* @return the type
*/
public T getType() { public T getType() {
return type; return type;
} }
/**
* Gets the memory offset of this variable instance.
*
* @return the offset
*/
public int getOffset() { public int getOffset() {
return offset; return offset;
} }
@Override @Override
public String toString() { public String toString() {
return type.toString()+" @ "+offset; return String.format("%s:%X@%d", type.toString(), hashCode(), offset);
} }
} }

View File

@ -0,0 +1,14 @@
package pp.s1184725.boppi.test;
import java.nio.file.*;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({ CheckerTest.class, GeneratorTest.class, ParserTest.class })
public class AllTests {
static final Path directory = Paths.get("src/pp/s1184725/boppi/test/parsing/");
}

View File

@ -3,123 +3,116 @@ package pp.s1184725.boppi.test;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List; import java.util.List;
import java.util.logging.*; import java.util.logging.*;
import org.apache.commons.lang3.tuple.Pair; import org.antlr.v4.runtime.CharStream;
import org.junit.Test; import org.junit.Test;
import pp.s1184725.boppi.*; import pp.s1184725.boppi.ToolChain;
public class CheckerTest { public class CheckerTest {
static final Path directory = Paths.get("src/pp/s1184725/boppi/test/parsing/");
private List<LogRecord> log; private List<LogRecord> log;
static private List<LogRecord> checkAndGetLog(String code) { private void checkString(String code) {
Logger logger = Logger.getAnonymousLogger(); check(ToolChain.getCharStream(code));
Pair<BasicParser, List<LogRecord>> pair = BasicParserHelper.getParserWithLog(code, logger);
new BoppiBasicChecker(logger, new Annotations()).visit(pair.getLeft().program());
return pair.getRight();
} }
static private List<LogRecord> checkAndGetLog(Path code) throws IOException { private void checkFile(String file) {
Logger logger = Logger.getAnonymousLogger(); check(ToolChain.getCharStream(AllTests.directory.resolve(file)));
Pair<BasicParser, List<LogRecord>> pair = BasicParserHelper.getParserWithLog(code, logger);
new BoppiBasicChecker(logger, new Annotations()).visit(pair.getLeft().program());
return pair.getRight();
} }
private void check(CharStream stream) {
Logger logger = Logger.getAnonymousLogger();
log = ToolChain.makeListLog(logger);
ToolChain.getAnnotations(ToolChain.getParser(ToolChain.getLexer(stream, logger), logger).program(), logger);
}
@Test @Test
public void correctExpressionTest() throws Exception { public void correctExpressionTest() {
log = checkAndGetLog(directory.resolve("simpleExpression.boppi")); checkFile("simpleExpression.boppi");
assertThat(log, empty()); assertThat(log, is(empty()));
} }
@Test @Test
public void wrongExpressionTest() { public void wrongExpressionTest() {
log = checkAndGetLog("+true"); checkString("+true");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = checkAndGetLog("5 || true"); checkString("5 || true");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = checkAndGetLog("6 + 'c'"); checkString("6 + 'c'");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = checkAndGetLog("4 + print(5, 6)"); checkString("4 + print(5, 6)");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = checkAndGetLog("print(print(3, 5))"); checkString("print(print(3, 5))");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
} }
@Test @Test
public void correctVariableTest() throws Exception { public void correctVariableTest() {
log = checkAndGetLog(directory.resolve("simpleVariable.boppi")); checkFile("simpleVariable.boppi");
assertThat(log, empty()); assertThat(log, is(empty()));
} }
@Test @Test
public void wrongVariableTest() { public void wrongVariableTest() {
log = checkAndGetLog("var bool name; name := 5"); checkString("var bool name; name := 5");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = checkAndGetLog("undefinedName"); checkString("undefinedName");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = checkAndGetLog("var undefinedType name; 1"); checkString("var undefinedType name; 1");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = checkAndGetLog("var bool endsWithDeclaration;"); checkString("var bool endsWithDeclaration;");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = checkAndGetLog("var bool var1; var var1 var2; var2 := 'c' "); checkString("var bool var1; var var1 var2; var2 := 'c' ");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
} }
@Test @Test
public void correctScopeTest() throws Exception { public void correctScopeTest() {
log = checkAndGetLog(directory.resolve("simpleScope.boppi")); checkFile("simpleScope.boppi");
assertThat(log, empty()); assertThat(log, is(empty()));
} }
@Test @Test
public void wrongScopeTest() { public void wrongScopeTest() {
log = checkAndGetLog("var bool var1; var bool var1; 1"); checkString("var bool var1; var bool var1; 1");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = checkAndGetLog("var bool var1; var char var1; 1"); checkString("var bool var1; var char var1; 1");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = checkAndGetLog("{ var int var1; var1 := 4}; var int var2; var1"); checkString("{ var int var1; var1 := 4}; var int var2; var1");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
} }
@Test @Test
public void correctConditionalTest() throws Exception { public void correctConditionalTest() {
log = checkAndGetLog(directory.resolve("if.boppi")); checkFile("if.boppi");
assertThat(log, empty()); assertThat(log, is(empty()));
log = checkAndGetLog(directory.resolve("while.boppi")); checkFile("while.boppi");
assertThat(log, empty()); assertThat(log, is(empty()));
} }
} }

View File

@ -1,111 +1,83 @@
package pp.s1184725.boppi.test; package pp.s1184725.boppi.test;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List; import java.util.List;
import java.util.logging.LogRecord; import java.util.logging.*;
import java.util.logging.Logger;
import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.CharStream;
import org.apache.commons.lang3.tuple.Pair; import org.antlr.v4.runtime.tree.ParseTree;
import org.junit.Test; import org.junit.Test;
import pp.iloc.Simulator; import pp.iloc.model.Program;
import pp.s1184725.boppi.Annotations; import pp.s1184725.boppi.*;
import pp.s1184725.boppi.BasicParser;
import pp.s1184725.boppi.BasicParserHelper;
import pp.s1184725.boppi.BoppiBasicChecker;
import pp.s1184725.boppi.BoppiBasicGenerator;
public class GeneratorTest { public class GeneratorTest {
static final Path directory = Paths.get("src/pp/s1184725/boppi/test/parsing/"); private List<LogRecord> log;
private Pair<String, List<LogRecord>> result; private String[] out;
static private Pair<String, List<LogRecord>> generateAndGetLog(String code, String input) { private void compileAndRunString(String code, String... input) {
compileAndRun(ToolChain.getCharStream(code), String.join("\n", input) + "\n");
}
private void compileAndRunFile(String file, String... input) {
compileAndRun(ToolChain.getCharStream(AllTests.directory.resolve(file)), String.join("\n", input) + "\n");
}
private void compileAndRun(CharStream stream, String input) {
Logger logger = Logger.getAnonymousLogger(); Logger logger = Logger.getAnonymousLogger();
Pair<BasicParser, List<LogRecord>> pair = BasicParserHelper.getParserWithLog(code, logger); log = ToolChain.makeListLog(logger);
return derp(logger, pair, input);
ParseTree ast = ToolChain.getParser(ToolChain.getLexer(stream, logger), logger).program();
Annotations annotations = ToolChain.getAnnotations(ast, logger);
Program program = ToolChain.getILOC(ast, annotations);
out = ToolChain.execute(program, logger, input).split("\n");
} }
static private Pair<String, List<LogRecord>> generateAndGetLog(Path code, String input) throws IOException {
Logger logger = Logger.getAnonymousLogger();
Pair<BasicParser, List<LogRecord>> pair = BasicParserHelper.getParserWithLog(code, logger);
return derp(logger, pair, input);
}
static private Pair<String, List<LogRecord>> derp(Logger logger, Pair<BasicParser, List<LogRecord>> pair, String input) {
Annotations annotater = new Annotations();
ParserRuleContext tree = pair.getLeft().program();
new BoppiBasicChecker(logger, annotater).visit(tree);
BoppiBasicGenerator generator = new BoppiBasicGenerator(annotater);
generator.visit(tree);
Simulator s = new Simulator(generator.prog);
InputStream in = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8));
ByteArrayOutputStream out = new ByteArrayOutputStream();
s.setIn(in);
s.setOut(out);
try {
s.run();
}
catch (Exception e) {
logger.severe(e.getMessage());
}
return Pair.of(out.toString().replaceAll("\r\n", "\n"), pair.getRight());
}
@Test @Test
public void correctExpressionTest() throws IOException { public void correctExpressionTest() {
result = generateAndGetLog(directory.resolve("simpleExpression.boppi"), ""); compileAndRunFile("simpleExpression.boppi");
assertThat(result.getRight(), empty()); assertThat(log, is(empty()));
} }
@Test @Test
public void wrongExpressionTest() { public void wrongExpressionTest() {
result = generateAndGetLog("1/0", ""); compileAndRunString("1/0");
assertThat(result.getRight(), hasSize(1)); assertThat(log, hasSize(1));
assertThat(result.getRight().get(0).getMessage(), containsString("zero")); assertThat(log.get(0).getMessage(), containsString("zero"));
} }
@Test @Test
public void correctVariableTest() throws IOException { public void correctVariableTest() {
result = generateAndGetLog(directory.resolve("simpleVariable.boppi"), ""); compileAndRunFile("simpleVariable.boppi");
assertThat(result.getRight(), empty()); assertThat(log, is(empty()));
} }
@Test @Test
public void basicPrograms() throws IOException { public void basicPrograms() {
result = generateAndGetLog("print(5*3)", ""); compileAndRunString("print(5*3)");
assertThat(result.getRight(), empty()); assertThat(log, is(empty()));
assertThat(result.getLeft(), is("15\n")); assertThat(out, is(arrayContaining("15")));
result = generateAndGetLog("print('T', 'e', 's', 't', '!')", ""); compileAndRunString("print({var int x; x := 8; print(x)})");
assertThat(result.getRight(), empty()); assertThat(log, is(empty()));
assertThat(result.getLeft(), is("T\ne\ns\nt\n!\n")); assertThat(out, is(arrayContaining("8", "8")));
result = generateAndGetLog("var int x; var int y; x := 3*(y := 4); print(x,y)", ""); compileAndRunString("print('T', 'e', 's', 't', '!')");
assertThat(result.getRight(), empty()); assertThat(log, is(empty()));
assertThat(result.getLeft(), is("12\n4\n")); assertThat(out, is(arrayContaining("T", "e", "s", "t", "!")));
result = generateAndGetLog(directory.resolve("basicProgram1.boppi"), "1\nT\n"); compileAndRunString("var int x; var int y; x := 3*(y := 4); print(x,y)");
assertThat(result.getRight(), empty()); assertThat(log, is(empty()));
assertThat(result.getLeft(), is("T\nT\n")); assertThat(out, is(arrayContaining("12", "4")));
result = generateAndGetLog(directory.resolve("fibonacciIterative.boppi"), "6\n"); compileAndRunFile("basicProgram1.boppi", "1", "T");
assertThat(result.getRight(), empty()); assertThat(log, is(empty()));
assertThat(result.getLeft(), is("13\n")); assertThat(out, is(arrayContaining("T", "T")));
compileAndRunFile("fibonacciIterative.boppi", "6");
assertThat(log, is(empty()));
assertThat(out, is(arrayContaining("13")));
} }
} }

View File

@ -1,100 +1,98 @@
package pp.s1184725.boppi.test; package pp.s1184725.boppi.test;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List; import java.util.List;
import java.util.logging.*; import java.util.logging.*;
import org.apache.commons.lang3.tuple.Pair; import org.antlr.v4.runtime.CharStream;
import org.junit.Test; import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import pp.s1184725.boppi.*; import pp.s1184725.boppi.*;
public class ParserTest { public class ParserTest {
static final Path directory = Paths.get("src/pp/s1184725/boppi/test/parsing/");
private List<LogRecord> log; private List<LogRecord> log;
static private List<LogRecord> parseAndGetLog(String code) { private void parseString(String code) {
Pair<BasicParser, List<LogRecord>> pair = BasicParserHelper.getParserWithLog(code, Logger.getAnonymousLogger()); parse(ToolChain.getCharStream(code));
pair.getLeft().program();
return pair.getRight();
} }
static private List<LogRecord> parseAndGetLog(Path code) throws IOException { private void parseFile(String file) {
Pair<BasicParser, List<LogRecord>> pair = BasicParserHelper.getParserWithLog(code, Logger.getAnonymousLogger()); parse(ToolChain.getCharStream(AllTests.directory.resolve(file)));
pair.getLeft().program();
return pair.getRight();
} }
private void parse(CharStream stream) {
Logger logger = Logger.getAnonymousLogger();
log = ToolChain.makeListLog(logger);
ToolChain.getParser(ToolChain.getLexer(stream, logger), logger).program();
}
@Test @Test
public void correctExpressionTest() throws Exception { public void correctExpressionTest() {
log = parseAndGetLog(directory.resolve("simpleExpression.boppi")); parseFile("simpleExpression.boppi");
assertThat(log, empty()); assertThat(log, empty());
} }
@Test @Test
public void wrongExpressionTest() { public void wrongExpressionTest() {
log = parseAndGetLog(""); parseString("");
assertThat(log, not(empty())); assertThat(log, not(empty()));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = parseAndGetLog("~"); parseString("~");
assertThat(log, not(empty())); assertThat(log, not(empty()));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = parseAndGetLog("0A"); parseString("0A");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = parseAndGetLog("do"); parseString("do");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = parseAndGetLog("true true"); parseString("true true");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
} }
@Test @Test
public void correctVariableTest() throws Exception { public void correctVariableTest() {
log = parseAndGetLog(directory.resolve("simpleVariable.boppi")); parseFile("simpleVariable.boppi");
assertThat(log, empty()); assertThat(log, empty());
} }
@Test @Test
public void wrongVariableTest() { public void wrongVariableTest() {
log = parseAndGetLog("var"); parseString("var");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = parseAndGetLog("var bool 5"); parseString("var bool 5");
assertThat(log, hasSize(1)); assertThat(log, hasSize(1));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = parseAndGetLog("var 'c' varname"); parseString("var 'c' varname");
assertThat(log, not(empty())); assertThat(log, not(empty()));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
log = parseAndGetLog("var bool; true true;"); parseString("var bool; true true;");
assertThat(log, hasSize(2)); assertThat(log, hasSize(2));
assertThat(log.get(0).getLevel(), is(Level.SEVERE)); assertThat(log.get(0).getLevel(), is(Level.SEVERE));
} }
@Test @Test
public void correctScopeTest() throws Exception { public void correctScopeTest() {
log = parseAndGetLog(directory.resolve("simpleScope.boppi")); parseFile("simpleScope.boppi");
assertThat(log, empty()); assertThat(log, empty());
} }
@Test @Test
public void correctConditionalTest() throws Exception { public void correctConditionalTest() {
log = parseAndGetLog(directory.resolve("if.boppi")); parseFile("if.boppi");
assertThat(log, empty()); assertThat(log, empty());
log = parseAndGetLog(directory.resolve("while.boppi")); parseFile("while.boppi");
assertThat(log, empty()); assertThat(log, empty());
} }
} }