improved toolchain and test structure, improved documentation
This commit is contained in:
parent
8a05789dde
commit
a42c621515
|
@ -4,15 +4,29 @@ import org.antlr.v4.runtime.tree.ParseTreeProperty;
|
|||
|
||||
import pp.iloc.model.Reg;
|
||||
|
||||
/**
|
||||
* This class holds properties of all AST nodes during compilation.
|
||||
*
|
||||
*/
|
||||
public class Annotations {
|
||||
/**
|
||||
* Maps nodes to registers.
|
||||
*/
|
||||
public ParseTreeProperty<Reg> registers;
|
||||
public CachingSymbolTable<SimpleType> symbols;
|
||||
/**
|
||||
* Maps nodes to {@link SimpleType}s.
|
||||
*/
|
||||
public ParseTreeProperty<SimpleType> types;
|
||||
/**
|
||||
* Maps nodes to {@link Variable}s.
|
||||
*/
|
||||
public ParseTreeProperty<Variable<SimpleType>> variables;
|
||||
|
||||
/**
|
||||
* Creates a new annotations object with empty maps.
|
||||
*/
|
||||
public Annotations() {
|
||||
registers = new ParseTreeProperty<>();
|
||||
symbols = new CachingSymbolTable<>();
|
||||
types = new ParseTreeProperty<>();
|
||||
variables = new ParseTreeProperty<>();
|
||||
}
|
||||
|
|
|
@ -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("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package pp.s1184725.boppi;
|
||||
|
||||
import java.util.EmptyStackException;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
|
@ -9,15 +8,31 @@ import org.antlr.v4.runtime.tree.ParseTree;
|
|||
|
||||
import pp.s1184725.boppi.BasicParser.*;
|
||||
|
||||
/**
|
||||
* This class performs type checking and variable assignment on a bare parse
|
||||
* tree.
|
||||
*
|
||||
*/
|
||||
public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
|
||||
private CachingSymbolTable<SimpleType> symbols;
|
||||
private Annotations an;
|
||||
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) {
|
||||
symbols = new CachingSymbolTable<>();
|
||||
an = annotations;
|
||||
log = logger;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SimpleType visit(ParseTree tree) {
|
||||
SimpleType type = super.visit(tree);
|
||||
|
@ -40,7 +55,7 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
|
|||
|
||||
@Override
|
||||
public SimpleType visitBlock(BlockContext ctx) {
|
||||
return inScope(() -> visit(ctx.expr()));
|
||||
return symbols.withScope(() -> visit(ctx.expr()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -59,7 +74,7 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
|
|||
@Override
|
||||
public SimpleType visitDeclare(DeclareContext ctx) {
|
||||
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);
|
||||
} catch (EmptyStackException e) {
|
||||
log.severe(getError(ctx, "Declaring variable outside a program."));
|
||||
|
@ -71,15 +86,15 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
|
|||
|
||||
@Override
|
||||
public SimpleType visitIf(IfContext ctx) {
|
||||
return inScope(() -> {
|
||||
return symbols.withScope(() -> {
|
||||
visit(ctx.cond);
|
||||
if (ctx.onFalse != null) {
|
||||
SimpleType trueType = inScope(() -> visit(ctx.onTrue));
|
||||
SimpleType falseType = inScope(() -> visit(ctx.onFalse));
|
||||
SimpleType trueType = symbols.withScope(() -> visit(ctx.onTrue));
|
||||
SimpleType falseType = symbols.withScope(() -> visit(ctx.onFalse));
|
||||
|
||||
return (trueType.equals(falseType)) ? trueType : SimpleType.VOID;
|
||||
} else {
|
||||
inScope(() -> visit(ctx.onTrue));
|
||||
symbols.withScope(() -> visit(ctx.onTrue));
|
||||
return SimpleType.VOID;
|
||||
}
|
||||
});
|
||||
|
@ -164,7 +179,7 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
|
|||
|
||||
@Override
|
||||
public SimpleType visitProgram(ProgramContext ctx) {
|
||||
inScope(() -> super.visitProgram(ctx));
|
||||
symbols.withScope(() -> super.visitProgram(ctx));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -194,7 +209,7 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
|
|||
@Override
|
||||
public SimpleType visitVariable(VariableContext ctx) {
|
||||
try {
|
||||
Variable<SimpleType> var = an.symbols.get(ctx.getText());
|
||||
Variable<SimpleType> var = symbols.get(ctx.getText());
|
||||
an.variables.put(ctx, var);
|
||||
return var.getType();
|
||||
} catch (EmptyStackException e) {
|
||||
|
@ -208,9 +223,9 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
|
|||
|
||||
@Override
|
||||
public SimpleType visitWhile(WhileContext ctx) {
|
||||
return inScope(() -> {
|
||||
return symbols.withScope(() -> {
|
||||
checkConstraint(visit(ctx.cond), SimpleType.BOOL, ctx.cond);
|
||||
inScope(() -> visit(ctx.onTrue));
|
||||
symbols.withScope(() -> visit(ctx.onTrue));
|
||||
return SimpleType.VOID;
|
||||
});
|
||||
}
|
||||
|
@ -221,7 +236,7 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
|
|||
SimpleType type = visit(ctx.expr(0));
|
||||
if (SimpleType.VOID.equals(type))
|
||||
log.severe(getError(ctx, "Cannot print argument of type %s.", type));
|
||||
|
||||
|
||||
return type;
|
||||
} else {
|
||||
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) {
|
||||
if (!type2.equals(type1))
|
||||
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.
|
||||
*
|
||||
|
@ -262,7 +272,7 @@ public class BoppiBasicChecker extends BasicBaseVisitor<SimpleType> {
|
|||
* @param args
|
||||
* 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 column = node.getStart().getCharPositionInLine();
|
||||
return String.format("Line %d:%d - %s", line, column, String.format(message, args));
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package pp.s1184725.boppi;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import org.antlr.v4.runtime.RuleContext;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.RuleNode;
|
||||
import org.antlr.v4.runtime.tree.*;
|
||||
|
||||
import pp.iloc.model.*;
|
||||
import pp.s1184725.boppi.BasicParser.*;
|
||||
|
@ -16,8 +14,12 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
|
|||
private int regNum;
|
||||
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;
|
||||
|
||||
{
|
||||
put(BasicLexer.AND, OpCode.and);
|
||||
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) {
|
||||
an = annotations;
|
||||
prog = new Program();
|
||||
|
@ -42,6 +50,13 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
|
|||
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) {
|
||||
Reg reg = an.registers.get(parseTree);
|
||||
if (reg == null) {
|
||||
|
@ -51,6 +66,16 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
|
|||
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) {
|
||||
Reg reg = an.registers.get(source);
|
||||
an.registers.put(destination, reg);
|
||||
|
@ -92,7 +117,7 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
|
|||
public Void visitAssign(AssignContext ctx) {
|
||||
visit(ctx.singleExpr());
|
||||
copyReg(ctx.singleExpr(), ctx);
|
||||
|
||||
|
||||
switch (an.types.get(ctx)) {
|
||||
case CHAR:
|
||||
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));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Void visitRead(ReadContext ctx) {
|
||||
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()));
|
||||
break;
|
||||
case CHAR:
|
||||
//Get input until at least 1 character
|
||||
Label getTarget = new Label("lcin_l"+ctx.hashCode());
|
||||
Label continueTarget = new Label("lcin_e"+ctx.hashCode());
|
||||
// Get input until at least 1 character
|
||||
Label getTarget = new Label("lcin_l" + ctx.hashCode());
|
||||
Label continueTarget = new Label("lcin_e" + ctx.hashCode());
|
||||
Reg countReg = new Reg("tempReg");
|
||||
emit(getTarget, OpCode.cin, new Str(""));
|
||||
emit(OpCode.pop, countReg);
|
||||
emit(OpCode.cbr, countReg, continueTarget, getTarget);
|
||||
|
||||
//Get character
|
||||
|
||||
// Get character
|
||||
emit(continueTarget, OpCode.cpop, getReg(ctx));
|
||||
emit(OpCode.cstoreAI, getReg(ctx), zero, new Num(an.variables.get(expr).getOffset()));
|
||||
|
||||
//Pop all remaining characters
|
||||
Label loopTarget = new Label("lcpop_l"+ctx.hashCode());
|
||||
Label iterTarget = new Label("lcpop_c"+ctx.hashCode());
|
||||
Label stopTarget = new Label("lcpop_e"+ctx.hashCode());
|
||||
|
||||
// Pop all remaining characters
|
||||
Label loopTarget = new Label("lcpop_l" + ctx.hashCode());
|
||||
Label iterTarget = new Label("lcpop_c" + ctx.hashCode());
|
||||
Label stopTarget = new Label("lcpop_e" + ctx.hashCode());
|
||||
Reg tempReg2 = new Reg("tempReg2");
|
||||
emit(loopTarget, OpCode.subI, countReg, new Num(1), countReg);
|
||||
emit(OpCode.cbr, countReg, iterTarget, stopTarget);
|
||||
|
@ -220,46 +245,45 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Void visitIf(IfContext ctx) {
|
||||
Label toTrue = new Label("if_t"+ctx.hashCode());
|
||||
Label toFalse = new Label("if_f"+ctx.hashCode());
|
||||
Label toEnd = new Label("if_e"+ctx.hashCode());
|
||||
|
||||
Label toTrue = new Label("if_t" + ctx.hashCode());
|
||||
Label toFalse = new Label("if_f" + ctx.hashCode());
|
||||
Label toEnd = new Label("if_e" + ctx.hashCode());
|
||||
|
||||
visit(ctx.cond);
|
||||
if (ctx.onFalse == null) {
|
||||
emit(OpCode.cbr, getReg(ctx.cond), toTrue, toEnd);
|
||||
|
||||
|
||||
emit(toTrue, OpCode.nop);
|
||||
visit(ctx.onTrue);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
emit(OpCode.cbr, getReg(ctx.cond), toTrue, toFalse);
|
||||
|
||||
|
||||
emit(toTrue, OpCode.nop);
|
||||
visit(ctx.onTrue);
|
||||
if (an.types.get(ctx) != SimpleType.VOID)
|
||||
emit(OpCode.i2i, getReg(ctx.onTrue), getReg(ctx));
|
||||
emit(OpCode.jumpI, toEnd);
|
||||
|
||||
|
||||
emit(toFalse, OpCode.nop);
|
||||
visit(ctx.onFalse);
|
||||
if (an.types.get(ctx) != SimpleType.VOID)
|
||||
emit(OpCode.i2i, getReg(ctx.onFalse), getReg(ctx));
|
||||
|
||||
|
||||
}
|
||||
emit(toEnd, OpCode.nop);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Void visitWhile(WhileContext ctx) {
|
||||
Label toLoop = new Label("while_t"+ctx.hashCode());
|
||||
Label toCond = new Label("while_f"+ctx.hashCode());
|
||||
Label toEnd = new Label("while_e"+ctx.hashCode());
|
||||
|
||||
Label toLoop = new Label("while_t" + ctx.hashCode());
|
||||
Label toCond = new Label("while_f" + ctx.hashCode());
|
||||
Label toEnd = new Label("while_e" + ctx.hashCode());
|
||||
|
||||
emit(OpCode.jumpI, toCond);
|
||||
emit(toLoop, OpCode.nop);
|
||||
visit(ctx.onTrue);
|
||||
|
@ -267,7 +291,7 @@ public class BoppiBasicGenerator extends BasicBaseVisitor<Void> {
|
|||
visit(ctx.cond);
|
||||
emit(OpCode.cbr, getReg(ctx.cond), toLoop, toEnd);
|
||||
emit(toEnd, OpCode.nop);
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
package pp.s1184725.boppi;
|
||||
|
||||
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> {
|
||||
protected Stack<Map<String,Variable<T>>> symbolMapStack;
|
||||
protected Stack<Map<String, Variable<T>>> symbolMapStack;
|
||||
protected Stack<Integer> offsets;
|
||||
protected Map<String,Variable<T>> symbolCache;
|
||||
protected Map<String, Variable<T>> symbolCache;
|
||||
protected int offset;
|
||||
|
||||
/**
|
||||
|
@ -17,7 +28,7 @@ public class CachingSymbolTable<T> {
|
|||
offsets = new Stack<>();
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Opens a lexical scope.
|
||||
*/
|
||||
|
@ -25,47 +36,79 @@ public class CachingSymbolTable<T> {
|
|||
symbolMapStack.push(new HashMap<>());
|
||||
offsets.push(offset);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
Map<String,Variable<T>> deletions = symbolMapStack.pop();
|
||||
Map<String, Variable<T>> deletions = symbolMapStack.pop();
|
||||
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));
|
||||
if (!maybeType.isPresent())
|
||||
symbolCache.remove(identifier);
|
||||
}
|
||||
offset = offsets.pop();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Associates an identifier with a certain type in the current lexical scope.
|
||||
* @param id the name of the identifier
|
||||
* @param type the type of the identifier
|
||||
* Opens a scope, executes the given code and closes the scope. This is a
|
||||
* helper method to make sure {@link #openScope()} and {@link #closeScope()}
|
||||
* 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
|
||||
* @throws Exception if the identifier is declared in the current scope already
|
||||
* @throws EmptyStackException if no scope is open
|
||||
* @throws Exception
|
||||
* 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 {
|
||||
if (symbolMapStack.peek().containsKey(id))
|
||||
throw new Exception(String.format("Identifier '%s' already declared in this scope.", id));
|
||||
|
||||
|
||||
Variable<T> var = new Variable<>(type, offset);
|
||||
// TODO refactor to get type size
|
||||
offset += ((SimpleType)type).getSize();
|
||||
offset += ((SimpleType) type).getSize();
|
||||
symbolMapStack.peek().put(id, var);
|
||||
symbolCache.put(id, var);
|
||||
return var;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @throws EmptyStackException if no scope is open
|
||||
* @throws EmptyStackException
|
||||
* if no scope is open
|
||||
*/
|
||||
public boolean has(String id) throws EmptyStackException {
|
||||
if (symbolMapStack.isEmpty())
|
||||
|
@ -73,13 +116,17 @@ public class CachingSymbolTable<T> {
|
|||
|
||||
return symbolCache.containsKey(id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @throws Exception if the identifier is not defined
|
||||
* @throws EmptyStackException if no scope is open
|
||||
* @throws Exception
|
||||
* if the identifier is not defined
|
||||
* @throws EmptyStackException
|
||||
* if no scope is open
|
||||
*/
|
||||
public Variable<T> get(String id) throws Exception, EmptyStackException {
|
||||
if (!this.has(id))
|
||||
|
|
|
@ -1,21 +1,44 @@
|
|||
package pp.s1184725.boppi;
|
||||
|
||||
import static pp.s1184725.boppi.BasicLexer.*;
|
||||
|
||||
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 {
|
||||
INT(Machine.INT_SIZE), CHAR(Machine.DEFAULT_CHAR_SIZE), BOOL(Machine.INT_SIZE), VOID(0);
|
||||
|
||||
|
||||
private final int size;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the size of this data type.
|
||||
*
|
||||
* @return the size (in bytes) of this type
|
||||
*/
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
private SimpleType(int 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) {
|
||||
switch (token) {
|
||||
case INTTYPE:
|
||||
|
|
|
@ -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("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
|
@ -1,24 +1,49 @@
|
|||
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> {
|
||||
private final T type;
|
||||
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) {
|
||||
this.type = type;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the type of this variable instance.
|
||||
*
|
||||
* @return the type
|
||||
*/
|
||||
public T getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the memory offset of this variable instance.
|
||||
*
|
||||
* @return the offset
|
||||
*/
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type.toString()+" @ "+offset;
|
||||
return String.format("%s:%X@%d", type.toString(), hashCode(), offset);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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/");
|
||||
|
||||
}
|
|
@ -3,123 +3,116 @@ package pp.s1184725.boppi.test;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
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.logging.*;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import pp.s1184725.boppi.*;
|
||||
import pp.s1184725.boppi.ToolChain;
|
||||
|
||||
public class CheckerTest {
|
||||
static final Path directory = Paths.get("src/pp/s1184725/boppi/test/parsing/");
|
||||
private List<LogRecord> log;
|
||||
|
||||
static private List<LogRecord> checkAndGetLog(String code) {
|
||||
Logger logger = Logger.getAnonymousLogger();
|
||||
Pair<BasicParser, List<LogRecord>> pair = BasicParserHelper.getParserWithLog(code, logger);
|
||||
new BoppiBasicChecker(logger, new Annotations()).visit(pair.getLeft().program());
|
||||
return pair.getRight();
|
||||
private void checkString(String code) {
|
||||
check(ToolChain.getCharStream(code));
|
||||
}
|
||||
|
||||
static private List<LogRecord> checkAndGetLog(Path code) throws IOException {
|
||||
Logger logger = Logger.getAnonymousLogger();
|
||||
Pair<BasicParser, List<LogRecord>> pair = BasicParserHelper.getParserWithLog(code, logger);
|
||||
new BoppiBasicChecker(logger, new Annotations()).visit(pair.getLeft().program());
|
||||
return pair.getRight();
|
||||
private void checkFile(String file) {
|
||||
check(ToolChain.getCharStream(AllTests.directory.resolve(file)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
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
|
||||
public void correctExpressionTest() throws Exception {
|
||||
log = checkAndGetLog(directory.resolve("simpleExpression.boppi"));
|
||||
assertThat(log, empty());
|
||||
public void correctExpressionTest() {
|
||||
checkFile("simpleExpression.boppi");
|
||||
assertThat(log, is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrongExpressionTest() {
|
||||
log = checkAndGetLog("+true");
|
||||
checkString("+true");
|
||||
assertThat(log, hasSize(1));
|
||||
assertThat(log.get(0).getLevel(), is(Level.SEVERE));
|
||||
|
||||
log = checkAndGetLog("5 || true");
|
||||
checkString("5 || true");
|
||||
assertThat(log, hasSize(1));
|
||||
assertThat(log.get(0).getLevel(), is(Level.SEVERE));
|
||||
|
||||
log = checkAndGetLog("6 + 'c'");
|
||||
checkString("6 + 'c'");
|
||||
assertThat(log, hasSize(1));
|
||||
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.get(0).getLevel(), is(Level.SEVERE));
|
||||
|
||||
log = checkAndGetLog("print(print(3, 5))");
|
||||
checkString("print(print(3, 5))");
|
||||
assertThat(log, hasSize(1));
|
||||
assertThat(log.get(0).getLevel(), is(Level.SEVERE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctVariableTest() throws Exception {
|
||||
log = checkAndGetLog(directory.resolve("simpleVariable.boppi"));
|
||||
assertThat(log, empty());
|
||||
public void correctVariableTest() {
|
||||
checkFile("simpleVariable.boppi");
|
||||
assertThat(log, is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrongVariableTest() {
|
||||
log = checkAndGetLog("var bool name; name := 5");
|
||||
checkString("var bool name; name := 5");
|
||||
assertThat(log, hasSize(1));
|
||||
assertThat(log.get(0).getLevel(), is(Level.SEVERE));
|
||||
|
||||
log = checkAndGetLog("undefinedName");
|
||||
checkString("undefinedName");
|
||||
assertThat(log, hasSize(1));
|
||||
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.get(0).getLevel(), is(Level.SEVERE));
|
||||
|
||||
log = checkAndGetLog("var bool endsWithDeclaration;");
|
||||
checkString("var bool endsWithDeclaration;");
|
||||
assertThat(log, hasSize(1));
|
||||
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.get(0).getLevel(), is(Level.SEVERE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctScopeTest() throws Exception {
|
||||
log = checkAndGetLog(directory.resolve("simpleScope.boppi"));
|
||||
assertThat(log, empty());
|
||||
public void correctScopeTest() {
|
||||
checkFile("simpleScope.boppi");
|
||||
assertThat(log, is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
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.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.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.get(0).getLevel(), is(Level.SEVERE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctConditionalTest() throws Exception {
|
||||
log = checkAndGetLog(directory.resolve("if.boppi"));
|
||||
assertThat(log, empty());
|
||||
public void correctConditionalTest() {
|
||||
checkFile("if.boppi");
|
||||
assertThat(log, is(empty()));
|
||||
|
||||
log = checkAndGetLog(directory.resolve("while.boppi"));
|
||||
assertThat(log, empty());
|
||||
checkFile("while.boppi");
|
||||
assertThat(log, is(empty()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,111 +1,83 @@
|
|||
package pp.s1184725.boppi.test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
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.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.logging.*;
|
||||
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.junit.Test;
|
||||
|
||||
import pp.iloc.Simulator;
|
||||
import pp.s1184725.boppi.Annotations;
|
||||
import pp.s1184725.boppi.BasicParser;
|
||||
import pp.s1184725.boppi.BasicParserHelper;
|
||||
import pp.s1184725.boppi.BoppiBasicChecker;
|
||||
import pp.s1184725.boppi.BoppiBasicGenerator;
|
||||
import pp.iloc.model.Program;
|
||||
import pp.s1184725.boppi.*;
|
||||
|
||||
public class GeneratorTest {
|
||||
static final Path directory = Paths.get("src/pp/s1184725/boppi/test/parsing/");
|
||||
private Pair<String, List<LogRecord>> result;
|
||||
private List<LogRecord> log;
|
||||
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();
|
||||
Pair<BasicParser, List<LogRecord>> pair = BasicParserHelper.getParserWithLog(code, logger);
|
||||
return derp(logger, pair, input);
|
||||
log = ToolChain.makeListLog(logger);
|
||||
|
||||
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
|
||||
public void correctExpressionTest() throws IOException {
|
||||
result = generateAndGetLog(directory.resolve("simpleExpression.boppi"), "");
|
||||
assertThat(result.getRight(), empty());
|
||||
public void correctExpressionTest() {
|
||||
compileAndRunFile("simpleExpression.boppi");
|
||||
assertThat(log, is(empty()));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void wrongExpressionTest() {
|
||||
result = generateAndGetLog("1/0", "");
|
||||
assertThat(result.getRight(), hasSize(1));
|
||||
assertThat(result.getRight().get(0).getMessage(), containsString("zero"));
|
||||
compileAndRunString("1/0");
|
||||
assertThat(log, hasSize(1));
|
||||
assertThat(log.get(0).getMessage(), containsString("zero"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctVariableTest() throws IOException {
|
||||
result = generateAndGetLog(directory.resolve("simpleVariable.boppi"), "");
|
||||
assertThat(result.getRight(), empty());
|
||||
public void correctVariableTest() {
|
||||
compileAndRunFile("simpleVariable.boppi");
|
||||
assertThat(log, is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicPrograms() throws IOException {
|
||||
result = generateAndGetLog("print(5*3)", "");
|
||||
assertThat(result.getRight(), empty());
|
||||
assertThat(result.getLeft(), is("15\n"));
|
||||
|
||||
result = generateAndGetLog("print('T', 'e', 's', 't', '!')", "");
|
||||
assertThat(result.getRight(), empty());
|
||||
assertThat(result.getLeft(), is("T\ne\ns\nt\n!\n"));
|
||||
|
||||
result = generateAndGetLog("var int x; var int y; x := 3*(y := 4); print(x,y)", "");
|
||||
assertThat(result.getRight(), empty());
|
||||
assertThat(result.getLeft(), is("12\n4\n"));
|
||||
|
||||
result = generateAndGetLog(directory.resolve("basicProgram1.boppi"), "1\nT\n");
|
||||
assertThat(result.getRight(), empty());
|
||||
assertThat(result.getLeft(), is("T\nT\n"));
|
||||
|
||||
result = generateAndGetLog(directory.resolve("fibonacciIterative.boppi"), "6\n");
|
||||
assertThat(result.getRight(), empty());
|
||||
assertThat(result.getLeft(), is("13\n"));
|
||||
public void basicPrograms() {
|
||||
compileAndRunString("print(5*3)");
|
||||
assertThat(log, is(empty()));
|
||||
assertThat(out, is(arrayContaining("15")));
|
||||
|
||||
compileAndRunString("print({var int x; x := 8; print(x)})");
|
||||
assertThat(log, is(empty()));
|
||||
assertThat(out, is(arrayContaining("8", "8")));
|
||||
|
||||
compileAndRunString("print('T', 'e', 's', 't', '!')");
|
||||
assertThat(log, is(empty()));
|
||||
assertThat(out, is(arrayContaining("T", "e", "s", "t", "!")));
|
||||
|
||||
compileAndRunString("var int x; var int y; x := 3*(y := 4); print(x,y)");
|
||||
assertThat(log, is(empty()));
|
||||
assertThat(out, is(arrayContaining("12", "4")));
|
||||
|
||||
compileAndRunFile("basicProgram1.boppi", "1", "T");
|
||||
assertThat(log, is(empty()));
|
||||
assertThat(out, is(arrayContaining("T", "T")));
|
||||
|
||||
compileAndRunFile("fibonacciIterative.boppi", "6");
|
||||
assertThat(log, is(empty()));
|
||||
assertThat(out, is(arrayContaining("13")));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,100 +1,98 @@
|
|||
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.logging.*;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.junit.Test;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import pp.s1184725.boppi.*;
|
||||
|
||||
public class ParserTest {
|
||||
static final Path directory = Paths.get("src/pp/s1184725/boppi/test/parsing/");
|
||||
private List<LogRecord> log;
|
||||
|
||||
static private List<LogRecord> parseAndGetLog(String code) {
|
||||
Pair<BasicParser, List<LogRecord>> pair = BasicParserHelper.getParserWithLog(code, Logger.getAnonymousLogger());
|
||||
pair.getLeft().program();
|
||||
return pair.getRight();
|
||||
|
||||
private void parseString(String code) {
|
||||
parse(ToolChain.getCharStream(code));
|
||||
}
|
||||
|
||||
static private List<LogRecord> parseAndGetLog(Path code) throws IOException {
|
||||
Pair<BasicParser, List<LogRecord>> pair = BasicParserHelper.getParserWithLog(code, Logger.getAnonymousLogger());
|
||||
pair.getLeft().program();
|
||||
return pair.getRight();
|
||||
|
||||
private void parseFile(String file) {
|
||||
parse(ToolChain.getCharStream(AllTests.directory.resolve(file)));
|
||||
}
|
||||
|
||||
|
||||
private void parse(CharStream stream) {
|
||||
Logger logger = Logger.getAnonymousLogger();
|
||||
log = ToolChain.makeListLog(logger);
|
||||
ToolChain.getParser(ToolChain.getLexer(stream, logger), logger).program();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctExpressionTest() throws Exception {
|
||||
log = parseAndGetLog(directory.resolve("simpleExpression.boppi"));
|
||||
public void correctExpressionTest() {
|
||||
parseFile("simpleExpression.boppi");
|
||||
assertThat(log, empty());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void wrongExpressionTest() {
|
||||
log = parseAndGetLog("");
|
||||
parseString("");
|
||||
assertThat(log, not(empty()));
|
||||
assertThat(log.get(0).getLevel(), is(Level.SEVERE));
|
||||
|
||||
log = parseAndGetLog("~");
|
||||
parseString("~");
|
||||
assertThat(log, not(empty()));
|
||||
assertThat(log.get(0).getLevel(), is(Level.SEVERE));
|
||||
|
||||
log = parseAndGetLog("0A");
|
||||
parseString("0A");
|
||||
assertThat(log, hasSize(1));
|
||||
assertThat(log.get(0).getLevel(), is(Level.SEVERE));
|
||||
|
||||
log = parseAndGetLog("do");
|
||||
parseString("do");
|
||||
assertThat(log, hasSize(1));
|
||||
assertThat(log.get(0).getLevel(), is(Level.SEVERE));
|
||||
|
||||
log = parseAndGetLog("true true");
|
||||
parseString("true true");
|
||||
assertThat(log, hasSize(1));
|
||||
assertThat(log.get(0).getLevel(), is(Level.SEVERE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctVariableTest() throws Exception {
|
||||
log = parseAndGetLog(directory.resolve("simpleVariable.boppi"));
|
||||
public void correctVariableTest() {
|
||||
parseFile("simpleVariable.boppi");
|
||||
assertThat(log, empty());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void wrongVariableTest() {
|
||||
log = parseAndGetLog("var");
|
||||
parseString("var");
|
||||
assertThat(log, hasSize(1));
|
||||
assertThat(log.get(0).getLevel(), is(Level.SEVERE));
|
||||
|
||||
log = parseAndGetLog("var bool 5");
|
||||
parseString("var bool 5");
|
||||
assertThat(log, hasSize(1));
|
||||
assertThat(log.get(0).getLevel(), is(Level.SEVERE));
|
||||
|
||||
log = parseAndGetLog("var 'c' varname");
|
||||
parseString("var 'c' varname");
|
||||
assertThat(log, not(empty()));
|
||||
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.get(0).getLevel(), is(Level.SEVERE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctScopeTest() throws Exception {
|
||||
log = parseAndGetLog(directory.resolve("simpleScope.boppi"));
|
||||
public void correctScopeTest() {
|
||||
parseFile("simpleScope.boppi");
|
||||
assertThat(log, empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctConditionalTest() throws Exception {
|
||||
log = parseAndGetLog(directory.resolve("if.boppi"));
|
||||
public void correctConditionalTest() {
|
||||
parseFile("if.boppi");
|
||||
assertThat(log, empty());
|
||||
|
||||
log = parseAndGetLog(directory.resolve("while.boppi"));
|
||||
parseFile("while.boppi");
|
||||
assertThat(log, empty());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue