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;
|
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<>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
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));
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue