basic checker and checker test

This commit is contained in:
User 2017-05-12 18:28:57 +02:00
parent c4f5c77fc4
commit 21dca82f4a
6 changed files with 560 additions and 0 deletions

View File

@ -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<Reg> registers;
public CachingSymbolTable<SimpleType> symbols;
public ParseTreeProperty<SimpleType> types;
public ParseTreeProperty<Variable<SimpleType>> variables;
public Annotations() {
registers = new ParseTreeProperty<>();
symbols = new CachingSymbolTable<>();
types = new ParseTreeProperty<>();
variables = new ParseTreeProperty<>();
}
}

View File

@ -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<SimpleType> {
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<SimpleType> 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<SimpleType> 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> 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.
*
* @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));
}
}

View File

@ -0,0 +1,91 @@
package pp.s1184725.boppi;
import java.util.*;
import pp.iloc.eval.Machine;
public class CachingSymbolTable<T> {
protected Stack<Map<String,Variable<T>>> symbolMapStack;
protected Stack<Integer> offsets;
protected Map<String,Variable<T>> 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<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);
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<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);
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<T> get(String id) throws Exception, EmptyStackException {
if (!this.has(id))
throw new Exception(String.format("Identifier '%s' is undefined.", id));
return symbolCache.get(id);
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,24 @@
package pp.s1184725.boppi;
public class Variable<T> {
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;
}
}

View File

@ -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<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();
}
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();
}
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());
}
}