basic checker and checker test
This commit is contained in:
parent
c4f5c77fc4
commit
21dca82f4a
|
@ -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<>();
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue