From 21dca82f4a98d6a8ae967e844a84fef5d0f7caf9 Mon Sep 17 00:00:00 2001 From: User <> Date: Fri, 12 May 2017 18:28:57 +0200 Subject: [PATCH] basic checker and checker test --- src/pp/s1184725/boppi/Annotations.java | 19 ++ src/pp/s1184725/boppi/BoppiBasicChecker.java | 270 ++++++++++++++++++ src/pp/s1184725/boppi/CachingSymbolTable.java | 91 ++++++ src/pp/s1184725/boppi/SimpleType.java | 21 ++ src/pp/s1184725/boppi/Variable.java | 24 ++ src/pp/s1184725/boppi/test/CheckerTest.java | 135 +++++++++ 6 files changed, 560 insertions(+) create mode 100644 src/pp/s1184725/boppi/Annotations.java create mode 100644 src/pp/s1184725/boppi/BoppiBasicChecker.java create mode 100644 src/pp/s1184725/boppi/CachingSymbolTable.java create mode 100644 src/pp/s1184725/boppi/SimpleType.java create mode 100644 src/pp/s1184725/boppi/Variable.java create mode 100644 src/pp/s1184725/boppi/test/CheckerTest.java diff --git a/src/pp/s1184725/boppi/Annotations.java b/src/pp/s1184725/boppi/Annotations.java new file mode 100644 index 0000000..c705e92 --- /dev/null +++ b/src/pp/s1184725/boppi/Annotations.java @@ -0,0 +1,19 @@ +package pp.s1184725.boppi; + +import org.antlr.v4.runtime.tree.ParseTreeProperty; + +import pp.iloc.model.Reg; + +public class Annotations { + public ParseTreeProperty registers; + public CachingSymbolTable symbols; + public ParseTreeProperty types; + public ParseTreeProperty> variables; + + public Annotations() { + registers = new ParseTreeProperty<>(); + symbols = new CachingSymbolTable<>(); + types = new ParseTreeProperty<>(); + variables = new ParseTreeProperty<>(); + } +} diff --git a/src/pp/s1184725/boppi/BoppiBasicChecker.java b/src/pp/s1184725/boppi/BoppiBasicChecker.java new file mode 100644 index 0000000..c49200e --- /dev/null +++ b/src/pp/s1184725/boppi/BoppiBasicChecker.java @@ -0,0 +1,270 @@ +package pp.s1184725.boppi; + +import java.util.EmptyStackException; +import java.util.function.Supplier; +import java.util.logging.Logger; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ParseTree; + +import pp.s1184725.boppi.BasicParser.*; + +public class BoppiBasicChecker extends BasicBaseVisitor { + private Annotations an; + private Logger log; + + public BoppiBasicChecker(Logger logger, Annotations annotations) { + an = annotations; + log = logger; + } + + @Override + public SimpleType visit(ParseTree tree) { + SimpleType type = super.visit(tree); + an.types.put(tree, type); + return type; + } + + @Override + public SimpleType visitAssign(AssignContext ctx) { + SimpleType expr = visit(ctx.singleExpr()); + SimpleType var = visit(ctx.variable()); + checkConstraint(expr, var, ctx); + return expr; + } + + @Override + public SimpleType visitBool(BoolContext ctx) { + return SimpleType.BOOL; + } + + @Override + public SimpleType visitBlock(BlockContext ctx) { + return inScope(() -> visit(ctx.expr())); + } + + @Override + public SimpleType visitChar(CharContext ctx) { + return SimpleType.CHAR; + } + + @Override + public SimpleType visitExpr(ExprContext ctx) { + if (ctx.singleExpr(ctx.singleExpr().size() - 1) instanceof DeclareContext) + log.severe(getError(ctx, "Compound expression ends with declaration.")); + + return ctx.singleExpr().stream().map(this::visit).reduce((__, snd) -> snd).get(); + } + + @Override + public SimpleType visitDeclare(DeclareContext ctx) { + try { + Variable var = an.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.")); + } catch (Exception e) { + log.severe(getError(ctx, e.getMessage())); + } + return SimpleType.VOID; + } + + @Override + public SimpleType visitIf(IfContext ctx) { + return inScope(() -> { + visit(ctx.cond); + if (ctx.onFalse != null) { + SimpleType trueType = inScope(() -> visit(ctx.onTrue)); + SimpleType falseType = inScope(() -> visit(ctx.onFalse)); + + return (trueType.equals(falseType)) ? trueType : SimpleType.VOID; + } else { + inScope(() -> visit(ctx.onTrue)); + return SimpleType.VOID; + } + }); + } + + @Override + public SimpleType visitInfix1(Infix1Context ctx) { + checkConstraint(visit(ctx.lhs), SimpleType.INT, ctx.lhs); + checkConstraint(visit(ctx.rhs), SimpleType.INT, ctx.rhs); + + return SimpleType.INT; + } + + @Override + public SimpleType visitInfix2(Infix2Context ctx) { + checkConstraint(visit(ctx.lhs), SimpleType.INT, ctx.lhs); + checkConstraint(visit(ctx.rhs), SimpleType.INT, ctx.rhs); + + return SimpleType.INT; + } + + @Override + public SimpleType visitInfix3(Infix3Context ctx) { + SimpleType lht = visit(ctx.lhs); + SimpleType rht = visit(ctx.rhs); + checkConstraint(lht, rht, ctx); + + switch (ctx.op.getType()) { + case BasicLexer.LT: + case BasicLexer.LEQ: + case BasicLexer.GTE: + case BasicLexer.GT: + checkConstraint(lht, SimpleType.INT, ctx); + } + + return SimpleType.BOOL; + } + + @Override + public SimpleType visitInfix4(Infix4Context ctx) { + checkConstraint(visit(ctx.lhs), SimpleType.BOOL, ctx.lhs); + checkConstraint(visit(ctx.rhs), SimpleType.BOOL, ctx.rhs); + + return SimpleType.BOOL; + } + + @Override + public SimpleType visitInfix5(Infix5Context ctx) { + checkConstraint(visit(ctx.lhs), SimpleType.BOOL, ctx.lhs); + checkConstraint(visit(ctx.rhs), SimpleType.BOOL, ctx.rhs); + + return SimpleType.BOOL; + } + + @Override + public SimpleType visitNumber(NumberContext ctx) { + return SimpleType.INT; + } + + @Override + public SimpleType visitParens(ParensContext ctx) { + return visit(ctx.expr()); + } + + @Override + public SimpleType visitPrefix1(Prefix1Context ctx) { + SimpleType type = visit(ctx.singleExpr()); + + switch (ctx.op.getType()) { + case BasicLexer.NOT: + checkConstraint(type, SimpleType.BOOL, ctx.singleExpr()); + break; + + case BasicLexer.PLUS: + case BasicLexer.MINUS: + checkConstraint(type, SimpleType.INT, ctx.singleExpr()); + break; + } + + return type; + } + + @Override + public SimpleType visitProgram(ProgramContext ctx) { + inScope(() -> super.visitProgram(ctx)); + return null; + } + + @Override + public SimpleType visitRead(ReadContext ctx) { + if (ctx.variable().size() == 1) { + return visit(ctx.variable(0)); + } else { + ctx.variable().forEach(this::visit); + return SimpleType.VOID; + } + } + + @Override + public SimpleType visitType(TypeContext ctx) { + if (ctx.variable() != null) + return visit(ctx.variable()); + else + return SimpleType.fromToken(ctx.staticType.getType()); + } + + @Override + public SimpleType visitVar(VarContext ctx) { + return visit(ctx.variable()); + } + + @Override + public SimpleType visitVariable(VariableContext ctx) { + try { + Variable var = an.symbols.get(ctx.getText()); + an.variables.put(ctx, var); + return var.getType(); + } catch (EmptyStackException e) { + log.severe(getError(ctx, "Using variable outside a program.")); + } catch (Exception e) { + log.severe(getError(ctx, e.getMessage())); + } + + return SimpleType.VOID; + } + + @Override + public SimpleType visitWhile(WhileContext ctx) { + return inScope(() -> { + checkConstraint(visit(ctx.cond), SimpleType.BOOL, ctx.cond); + inScope(() -> visit(ctx.onTrue)); + return SimpleType.VOID; + }); + } + + @Override + public SimpleType visitWrite(WriteContext ctx) { + if (ctx.expr().size() == 1) { + 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) -> { + if (SimpleType.VOID.equals(type)) + log.severe(getError(ctx, "Cannot print argument of type %s.", type)); + }); + return SimpleType.VOID; + } + } + + 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 inScope(Supplier code) { + an.symbols.openScope(); + T result = code.get(); + an.symbols.closeScope(); + return result; + } + + /** + * Returns an error message for a given parse tree node. + * + * @param ctx + * the parse tree node at which the error occurred + * @param message + * the error message + * @param args + * arguments for the message, see {@link String#format} + */ + protected 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)); + } +} diff --git a/src/pp/s1184725/boppi/CachingSymbolTable.java b/src/pp/s1184725/boppi/CachingSymbolTable.java new file mode 100644 index 0000000..b212bea --- /dev/null +++ b/src/pp/s1184725/boppi/CachingSymbolTable.java @@ -0,0 +1,91 @@ +package pp.s1184725.boppi; + +import java.util.*; + +import pp.iloc.eval.Machine; + +public class CachingSymbolTable { + protected Stack>> symbolMapStack; + protected Stack offsets; + protected Map> symbolCache; + protected int offset; + + /** + * Creates an empty symbol table with no open scope. + */ + public CachingSymbolTable() { + symbolMapStack = new Stack<>(); + symbolCache = new HashMap<>(); + offsets = new Stack<>(); + offset = 0; + } + + /** + * Opens a lexical scope. + */ + public void openScope() { + symbolMapStack.push(new HashMap<>()); + offsets.push(offset); + } + + /** + * Closes a lexical scope, removing all definitions within this scope. + * @throws EmptyStackException if no scope is open + */ + public void closeScope() throws EmptyStackException { + Map> deletions = symbolMapStack.pop(); + for (String identifier : deletions.keySet()) { + Optional> 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 + * @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 + */ + public Variable 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 var = new Variable<>(type, offset); + offset += Machine.INT_SIZE; + 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 + * @return true if the identifier has a defined type + * @throws EmptyStackException if no scope is open + */ + public boolean has(String id) throws EmptyStackException { + if (symbolMapStack.isEmpty()) + throw new EmptyStackException(); + + return symbolCache.containsKey(id); + } + + /** + * Retrieves the type of an identifier, if any. + * @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 + */ + public Variable get(String id) throws Exception, EmptyStackException { + if (!this.has(id)) + throw new Exception(String.format("Identifier '%s' is undefined.", id)); + + return symbolCache.get(id); + } +} diff --git a/src/pp/s1184725/boppi/SimpleType.java b/src/pp/s1184725/boppi/SimpleType.java new file mode 100644 index 0000000..4a818ec --- /dev/null +++ b/src/pp/s1184725/boppi/SimpleType.java @@ -0,0 +1,21 @@ +package pp.s1184725.boppi; + +public enum SimpleType { + INT, CHAR, BOOL, VOID; + + public static SimpleType fromToken(int token) { + switch (token) { + case BasicLexer.INTTYPE: + return SimpleType.INT; + + case BasicLexer.CHARTYPE: + return SimpleType.CHAR; + + case BasicLexer.BOOLTYPE: + return SimpleType.BOOL; + + default: + return SimpleType.VOID; + } + } +} diff --git a/src/pp/s1184725/boppi/Variable.java b/src/pp/s1184725/boppi/Variable.java new file mode 100644 index 0000000..e401b26 --- /dev/null +++ b/src/pp/s1184725/boppi/Variable.java @@ -0,0 +1,24 @@ +package pp.s1184725.boppi; + +public class Variable { + private final T type; + private final int offset; + + public Variable(T type, int offset) { + this.type = type; + this.offset = offset; + } + + public T getType() { + return type; + } + + public int getOffset() { + return offset; + } + + @Override + public String toString() { + return type.toString()+" @ "+offset; + } +} diff --git a/src/pp/s1184725/boppi/test/CheckerTest.java b/src/pp/s1184725/boppi/test/CheckerTest.java new file mode 100644 index 0000000..50befe7 --- /dev/null +++ b/src/pp/s1184725/boppi/test/CheckerTest.java @@ -0,0 +1,135 @@ +package pp.s1184725.boppi.test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import org.apache.commons.lang3.tuple.Pair; +import org.junit.Test; + +import pp.s1184725.boppi.Annotations; +import pp.s1184725.boppi.BasicParser; +import pp.s1184725.boppi.BasicParserHelper; +import pp.s1184725.boppi.BoppiBasicChecker; + +public class CheckerTest { + static final Path directory = Paths.get("src/pp/s1184725/boppi/test/parsing/"); + private List log; + + static private List checkAndGetLog(String code) { + Logger logger = Logger.getAnonymousLogger(); + Pair> pair = BasicParserHelper.getParserWithLog(code, logger); + new BoppiBasicChecker(logger, new Annotations()).visit(pair.getLeft().program()); + return pair.getRight(); + } + + static private List checkAndGetLog(Path code) throws IOException { + Logger logger = Logger.getAnonymousLogger(); + Pair> pair = BasicParserHelper.getParserWithLog(code, logger); + new BoppiBasicChecker(logger, new Annotations()).visit(pair.getLeft().program()); + return pair.getRight(); + } + + protected void debug() { + log.forEach(entry -> System.out.println(entry.getMessage())); + } + + + + @Test + public void correctExpressionTest() throws Exception { + log = checkAndGetLog(directory.resolve("simpleExpression.boppi")); + assertThat(log, empty()); + } + + @Test + public void wrongExpressionTest() { + log = checkAndGetLog("+true"); + assertThat(log, hasSize(1)); + assertThat(log.get(0).getLevel(), is(Level.SEVERE)); + + log = checkAndGetLog("5 || true"); + assertThat(log, hasSize(1)); + assertThat(log.get(0).getLevel(), is(Level.SEVERE)); + + log = checkAndGetLog("6 + 'c'"); + assertThat(log, hasSize(1)); + assertThat(log.get(0).getLevel(), is(Level.SEVERE)); + + log = checkAndGetLog("4 + print(5, 6)"); + assertThat(log, hasSize(1)); + assertThat(log.get(0).getLevel(), is(Level.SEVERE)); + + log = checkAndGetLog("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()); + } + + @Test + public void wrongVariableTest() { + log = checkAndGetLog("var bool name; name := 5"); + assertThat(log, hasSize(1)); + assertThat(log.get(0).getLevel(), is(Level.SEVERE)); + + log = checkAndGetLog("undefinedName"); + assertThat(log, hasSize(1)); + assertThat(log.get(0).getLevel(), is(Level.SEVERE)); + + log = checkAndGetLog("var undefinedType name; 1"); + assertThat(log, hasSize(1)); + assertThat(log.get(0).getLevel(), is(Level.SEVERE)); + + log = checkAndGetLog("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' "); + 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()); + } + + @Test + public void wrongScopeTest() { + log = checkAndGetLog("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"); + assertThat(log, hasSize(1)); + assertThat(log.get(0).getLevel(), is(Level.SEVERE)); + + log = checkAndGetLog("{ 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()); + + log = checkAndGetLog(directory.resolve("while.boppi")); + assertThat(log, empty()); + } +}