implemented closures with correct cleanup
This commit is contained in:
parent
9baff2d080
commit
5a48e93674
|
@ -1,7 +1,5 @@
|
|||
package pp.s1184725.boppi;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import org.antlr.v4.runtime.tree.ParseTreeProperty;
|
||||
|
||||
import pp.iloc.model.Reg;
|
||||
|
@ -28,11 +26,7 @@ public class Annotations {
|
|||
/**
|
||||
* Maps nodes to their function scope.
|
||||
*/
|
||||
public ParseTreeProperty<Variable<Type>> function;
|
||||
/**
|
||||
* A stack with the current function scope on top.
|
||||
*/
|
||||
public Stack<Variable<Type>> currentFunction;
|
||||
public ParseTreeProperty<FunctionScope<Type>> function;
|
||||
/**
|
||||
* A symbol table
|
||||
*/
|
||||
|
@ -46,7 +40,6 @@ public class Annotations {
|
|||
types = new ParseTreeProperty<>();
|
||||
function = new ParseTreeProperty<>();
|
||||
variables = new ParseTreeProperty<>();
|
||||
currentFunction = new Stack<>();
|
||||
symbols = new CachingSymbolTable<>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package pp.s1184725.boppi;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -9,6 +8,7 @@ import org.antlr.v4.runtime.tree.ParseTree;
|
|||
|
||||
import pp.s1184725.boppi.antlr.*;
|
||||
import pp.s1184725.boppi.antlr.BoppiParser.*;
|
||||
import pp.s1184725.boppi.exception.*;
|
||||
import pp.s1184725.boppi.type.*;
|
||||
|
||||
/**
|
||||
|
@ -77,8 +77,6 @@ public class BoppiChecker extends BoppiBaseVisitor<Type> {
|
|||
public Type visit(ParseTree tree) {
|
||||
Type type = super.visit(tree);
|
||||
an.types.put(tree, type);
|
||||
if (an.function.get(tree) == null)
|
||||
an.function.put(tree, an.currentFunction.peek());
|
||||
return type;
|
||||
}
|
||||
|
||||
|
@ -129,9 +127,7 @@ public class BoppiChecker extends BoppiBaseVisitor<Type> {
|
|||
|
||||
if (!(var.getType() instanceof SimpleType))
|
||||
log.warning("Be careful only to pass pure functions outside their scope.");
|
||||
} catch (EmptyStackException e) {
|
||||
log.severe(getError(ctx, "Declaring variable outside a program."));
|
||||
} catch (Exception e) {
|
||||
} catch (SymbolTableException e) {
|
||||
log.severe(getError(ctx, e.getMessage()));
|
||||
}
|
||||
return SimpleType.VOID;
|
||||
|
@ -145,23 +141,21 @@ public class BoppiChecker extends BoppiBaseVisitor<Type> {
|
|||
FunctionType type = new FunctionType(visit(ctx.result), parameterTypes);
|
||||
Variable<Type> func = an.symbols.put(ctx.name.getText(), type);
|
||||
an.variables.put(ctx, func);
|
||||
an.currentFunction.push(func);
|
||||
|
||||
type.setLocalDataSize(an.symbols.withFunctionScope(() -> {
|
||||
an.function.put(ctx, an.symbols.withFunctionScope(() -> {
|
||||
for (int i = 1; i < ctx.type().size(); i++)
|
||||
try {
|
||||
an.symbols.put(ctx.IDENTIFIER(i).getText(), an.types.get(ctx.type(i)));
|
||||
} catch (Exception e) {
|
||||
} catch (SymbolTableException e) {
|
||||
log.severe(getError(ctx, e.getMessage()));
|
||||
}
|
||||
|
||||
checkConstraint(an.symbols.withScope(() -> visit(ctx.body)), an.types.get(ctx.result), ctx);
|
||||
}));
|
||||
} catch (EmptyStackException e) {
|
||||
log.severe(getError(ctx, "Declaring function outside a program."));
|
||||
} catch (Exception e) {
|
||||
} catch (SymbolTableException e) {
|
||||
log.severe(getError(ctx, e.getMessage()));
|
||||
}
|
||||
an.currentFunction.pop();
|
||||
|
||||
return SimpleType.VOID;
|
||||
}
|
||||
|
||||
|
@ -279,9 +273,7 @@ public class BoppiChecker extends BoppiBaseVisitor<Type> {
|
|||
|
||||
@Override
|
||||
public Type visitProgram(ProgramContext ctx) {
|
||||
FunctionType main = new FunctionType(TupleType.UNIT, TupleType.UNIT);
|
||||
an.currentFunction.push(new Variable<Type>(main, 0, 0));
|
||||
main.setLocalDataSize(an.symbols.withFunctionScope(() -> super.visitProgram(ctx)));
|
||||
an.function.put(ctx, an.symbols.withFunctionScope(() -> super.visitProgram(ctx)));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -319,12 +311,17 @@ public class BoppiChecker extends BoppiBaseVisitor<Type> {
|
|||
public Type visitVariable(VariableContext ctx) {
|
||||
try {
|
||||
Variable<Type> var = an.symbols.get(ctx.getText());
|
||||
|
||||
try {
|
||||
an.function.put(ctx, an.symbols.getFunctionScope());
|
||||
} catch (NoProgramException e) {
|
||||
log.severe(getError(ctx, e.getMessage()));
|
||||
}
|
||||
|
||||
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() != null ? e.getMessage() : "unknown error"));
|
||||
} catch (SymbolTableException e) {
|
||||
log.severe(getError(ctx, e.getMessage()));
|
||||
}
|
||||
|
||||
return SimpleType.VOID;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,6 +3,7 @@ package pp.s1184725.boppi;
|
|||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import pp.s1184725.boppi.exception.*;
|
||||
import pp.s1184725.boppi.type.*;
|
||||
|
||||
/**
|
||||
|
@ -19,12 +20,8 @@ import pp.s1184725.boppi.type.*;
|
|||
*/
|
||||
public class CachingSymbolTable<T extends Type> {
|
||||
protected Stack<Map<String, Variable<T>>> symbolMapStack;
|
||||
protected Stack<Integer> offsets;
|
||||
protected Stack<Integer> functionSizes;
|
||||
protected Map<String, Variable<T>> symbolCache;
|
||||
protected int offset;
|
||||
protected int functionDepth;
|
||||
protected int functionSize;
|
||||
protected Stack<FunctionScope<T>> functionScope;
|
||||
|
||||
/**
|
||||
* Creates an empty symbol table with no open scope.
|
||||
|
@ -32,11 +29,7 @@ public class CachingSymbolTable<T extends Type> {
|
|||
public CachingSymbolTable() {
|
||||
symbolMapStack = new Stack<>();
|
||||
symbolCache = new HashMap<>();
|
||||
offsets = new Stack<>();
|
||||
functionSizes = new Stack<>();
|
||||
offset = 0;
|
||||
functionDepth = 0;
|
||||
functionSize = 0;
|
||||
functionScope = new Stack<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,7 +37,6 @@ public class CachingSymbolTable<T extends Type> {
|
|||
*/
|
||||
public void openScope() {
|
||||
symbolMapStack.push(new HashMap<>());
|
||||
offsets.push(offset);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,28 +45,27 @@ public class CachingSymbolTable<T extends Type> {
|
|||
*/
|
||||
public void openFunctionScope() {
|
||||
openScope();
|
||||
functionSizes.push(functionSize);
|
||||
functionDepth++;
|
||||
functionSize = 0;
|
||||
offset = 0;
|
||||
functionScope.push(new FunctionScope<T>(functionScope.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a lexical scope, removing all declarations within this scope.
|
||||
* Closes a lexical scope, removing all declarations within this scope. Runs
|
||||
* in {@code O(k log k log n)} time, with {@code k} the number of
|
||||
* identifiers going out of scope, when the stream is parallelized.
|
||||
*
|
||||
* @throws EmptyStackException
|
||||
* @throws NoProgramException
|
||||
* if no scope is open
|
||||
*/
|
||||
public void closeScope() throws EmptyStackException {
|
||||
public void closeScope() throws NoProgramException {
|
||||
Map<String, Variable<T>> deletions = symbolMapStack.pop();
|
||||
for (String identifier : deletions.keySet()) {
|
||||
Optional<Variable<T>> maybeType = symbolMapStack.stream().filter((map) -> map.containsKey(identifier))
|
||||
.map((map) -> map.get(identifier)).reduce((__, snd) -> snd);
|
||||
Optional<Variable<T>> maybeType = symbolMapStack.parallelStream()
|
||||
.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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,13 +73,14 @@ public class CachingSymbolTable<T extends Type> {
|
|||
* declarations within this scope, restoring the local offset and decreasing
|
||||
* the lookup depth.
|
||||
*
|
||||
* @throws EmptyStackException
|
||||
* @return function scope details
|
||||
*
|
||||
* @throws NoProgramException
|
||||
* if no scope is open
|
||||
*/
|
||||
public void closeFunctionScope() throws EmptyStackException {
|
||||
public FunctionScope<T> closeFunctionScope() throws NoProgramException {
|
||||
closeScope();
|
||||
functionSize = functionSizes.pop();
|
||||
functionDepth--;
|
||||
return functionScope.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,27 +98,32 @@ public class CachingSymbolTable<T extends Type> {
|
|||
public <U> U withScope(Supplier<U> code) {
|
||||
openScope();
|
||||
U result = code.get();
|
||||
try {
|
||||
closeScope();
|
||||
} catch (NoProgramException e) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a function scope, executes the given code and closes it. This is a
|
||||
* helper method to make sure calls to {@link #openFunctionScope()} and
|
||||
* {@link #closeFunctionScope()} are balanced and returns the local data
|
||||
* size (in bytes) of the function. {@code code} must take no arguments and
|
||||
* must not return a value.
|
||||
* {@link #closeFunctionScope()} are balanced and returns the function scope
|
||||
* details. {@code code} must take no arguments and must not return a value.
|
||||
*
|
||||
* @param code
|
||||
* the code to execute within the scope
|
||||
* @return the return value of the code
|
||||
* @return the function scope details
|
||||
*/
|
||||
public int withFunctionScope(Runnable code) {
|
||||
public FunctionScope<T> withFunctionScope(Runnable code) {
|
||||
openFunctionScope();
|
||||
code.run();
|
||||
int result = functionSize;
|
||||
closeFunctionScope();
|
||||
return result;
|
||||
|
||||
try {
|
||||
return closeFunctionScope();
|
||||
} catch (NoProgramException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,18 +137,19 @@ public class CachingSymbolTable<T extends Type> {
|
|||
* @param type
|
||||
* the type of the identifier
|
||||
* @return the type of the identifier
|
||||
* @throws Exception
|
||||
* @throws DeclaredException
|
||||
* if the identifier is declared in the current scope already
|
||||
* @throws EmptyStackException
|
||||
* @throws NoProgramException
|
||||
* 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));
|
||||
public Variable<T> put(String id, T type) throws DeclaredException, NoProgramException {
|
||||
if (symbolMapStack.isEmpty())
|
||||
throw new NoProgramException();
|
||||
|
||||
Variable<T> var = new Variable<>(type, functionDepth, offset);
|
||||
offset += type.getSize();
|
||||
functionSize = Math.max(functionSize, offset);
|
||||
if (symbolMapStack.peek().containsKey(id))
|
||||
throw new DeclaredException(String.format("Identifier '%s' already declared in this scope.", id));
|
||||
|
||||
Variable<T> var = functionScope.peek().addVariable(type);
|
||||
|
||||
symbolMapStack.peek().put(id, var);
|
||||
symbolCache.put(id, var);
|
||||
|
@ -164,53 +162,46 @@ public class CachingSymbolTable<T extends Type> {
|
|||
* @param id
|
||||
* the name of the identifier
|
||||
* @return true if the identifier has a declared type
|
||||
* @throws EmptyStackException
|
||||
* @throws NoProgramException
|
||||
* if no scope is open
|
||||
*/
|
||||
public boolean has(String id) throws EmptyStackException {
|
||||
public boolean has(String id) throws NoProgramException {
|
||||
if (symbolMapStack.isEmpty())
|
||||
throw new EmptyStackException();
|
||||
throw new NoProgramException();
|
||||
|
||||
return symbolCache.containsKey(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the type of an identifier, if any.
|
||||
* Returns the type of an identifier, if any.
|
||||
*
|
||||
* @param id
|
||||
* the name of the identifier
|
||||
* @return the type of the identifier
|
||||
* @throws Exception
|
||||
* @throws UndeclaredException
|
||||
* if the identifier is not declared
|
||||
* @throws EmptyStackException
|
||||
* @throws NoProgramException
|
||||
* if no scope is open
|
||||
*/
|
||||
public Variable<T> get(String id) throws Exception, EmptyStackException {
|
||||
public Variable<T> get(String id) throws UndeclaredException, NoProgramException {
|
||||
if (!this.has(id))
|
||||
throw new Exception(String.format("Identifier '%s' is undeclared.", id));
|
||||
throw new UndeclaredException(String.format("Identifier '%s' is undeclared.", id));
|
||||
|
||||
return symbolCache.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size, in bytes, required to store all the variables declared
|
||||
* in the current function scope. Note that this is the size calculated up
|
||||
* and including the last variable declaration, so make sure to call it
|
||||
* right before leaving a function scope.
|
||||
* Returns the current function scope, if any.
|
||||
*
|
||||
* @return the number of bytes required to store all variables local to a
|
||||
* function
|
||||
* @return the current function scope
|
||||
* @throws NoProgramException
|
||||
* if no scope is open
|
||||
*/
|
||||
public int getLocalDataSize() {
|
||||
return functionSize;
|
||||
public FunctionScope<T> getFunctionScope() throws NoProgramException {
|
||||
if (symbolMapStack.isEmpty())
|
||||
throw new NoProgramException();
|
||||
|
||||
return functionScope.peek();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current lexical scope/function depth.
|
||||
*
|
||||
* @return the function depth
|
||||
*/
|
||||
public int getFunctionDepth() {
|
||||
return functionDepth;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package pp.s1184725.boppi;
|
||||
|
||||
import java.util.EmptyStackException;
|
||||
|
||||
import pp.s1184725.boppi.exception.*;
|
||||
import pp.s1184725.boppi.type.*;
|
||||
|
||||
/**
|
||||
|
@ -18,26 +17,26 @@ public class DebugCachingSymbolTable<T extends Type> extends CachingSymbolTable<
|
|||
@Override
|
||||
public void openFunctionScope() {
|
||||
super.openFunctionScope();
|
||||
System.out.println(this.getClass().getName() + ": entering scope depth " + functionDepth);
|
||||
System.out.println(this.getClass().getName() + ": entering scope depth " + functionScope.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeFunctionScope() throws EmptyStackException {
|
||||
System.out.println(this.getClass().getName() + ": leaving scope depth " + functionDepth);
|
||||
super.closeFunctionScope();
|
||||
public FunctionScope<T> closeFunctionScope() throws NoProgramException {
|
||||
System.out.println(this.getClass().getName() + ": leaving scope depth " + functionScope.size());
|
||||
return super.closeFunctionScope();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Variable<T> put(String id, T type) throws Exception, EmptyStackException {
|
||||
public Variable<T> put(String id, T type) throws DeclaredException, NoProgramException {
|
||||
System.out.println(this.getClass().getName() + ": declaring '" + id + "' (" + type.toString() + ") at scope "
|
||||
+ functionDepth);
|
||||
+ functionScope.size());
|
||||
return super.put(id, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Variable<T> get(String id) throws Exception, EmptyStackException {
|
||||
public Variable<T> get(String id) throws UndeclaredException, NoProgramException {
|
||||
System.out.println(this.getClass().getName() + ": retrieving '" + id + "' (depth "
|
||||
+ symbolCache.get(id).getDepth() + ") at scope " + functionDepth);
|
||||
+ symbolCache.get(id).getDepth() + ") at scope " + functionScope.size());
|
||||
return super.get(id);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package pp.s1184725.boppi;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import pp.s1184725.boppi.type.Type;
|
||||
|
||||
/**
|
||||
* Keeps track of local data for functions.
|
||||
*
|
||||
* @author Frank Wibbelink
|
||||
*
|
||||
* @param <T>
|
||||
* the type system to use
|
||||
*/
|
||||
public class FunctionScope<T extends Type> {
|
||||
private final int scopeDepth;
|
||||
private List<Variable<T>> localVars;
|
||||
|
||||
/**
|
||||
* Creates a function scope with the given depth.
|
||||
*
|
||||
* @param depth
|
||||
* the lexical depth of this function
|
||||
*/
|
||||
public FunctionScope(int depth) {
|
||||
scopeDepth = depth;
|
||||
localVars = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new variable in this function scope and returns it.
|
||||
*
|
||||
* @param type
|
||||
* the type of variable to create
|
||||
* @return the variable created
|
||||
*/
|
||||
public Variable<T> addVariable(T type) {
|
||||
Variable<T> var = new Variable<T>(type, scopeDepth, getLocalDataSize());
|
||||
localVars.add(var);
|
||||
|
||||
return var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size, in bytes, required to store all the variables declared
|
||||
* in the current function scope. Note that this is the size calculated up
|
||||
* and including the last variable declaration, so make sure to call it
|
||||
* right before leaving a function scope.
|
||||
*
|
||||
* @return the number of bytes required to store all variables local to a
|
||||
* function
|
||||
*/
|
||||
public int getLocalDataSize() {
|
||||
return localVars.stream().map(Variable::getType).collect(Collectors.summingInt(Type::getSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current lexical scope/function depth.
|
||||
*
|
||||
* @return the function depth
|
||||
*/
|
||||
public int getFunctionDepth() {
|
||||
return scopeDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the variables local to this function scope.
|
||||
*
|
||||
* @return a list of variables
|
||||
*/
|
||||
public List<Variable<T>> getVars() {
|
||||
return localVars;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,7 +2,7 @@ package pp.s1184725.boppi;
|
|||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.logging.*;
|
||||
|
||||
|
@ -11,6 +11,7 @@ import org.antlr.v4.runtime.tree.*;
|
|||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import pp.iloc.Simulator;
|
||||
import pp.iloc.eval.Machine;
|
||||
import pp.iloc.model.Program;
|
||||
import pp.s1184725.boppi.antlr.*;
|
||||
|
||||
|
@ -20,6 +21,14 @@ import pp.s1184725.boppi.antlr.*;
|
|||
* @author Frank Wibbelink
|
||||
*/
|
||||
public class ToolChain {
|
||||
/**
|
||||
* The file system path of this class
|
||||
*/
|
||||
public static final Path PATH = Paths.get("src/" + ToolChain.class.getPackage().getName().replaceAll("\\.", "/"));
|
||||
/**
|
||||
* The last virtual machine used for executing a program
|
||||
*/
|
||||
public static Machine machine;
|
||||
|
||||
/**
|
||||
* Opens a file for reading and returns its charstream. Throws an unhandled
|
||||
|
@ -190,7 +199,8 @@ public class ToolChain {
|
|||
|
||||
/**
|
||||
* Runs a program using the given input and output streams. Run errors will
|
||||
* be logged as {@link Level#SEVERE}.
|
||||
* be logged as {@link Level#SEVERE}. Instantiates a fresh machine and
|
||||
* simulator.
|
||||
*
|
||||
* @param program
|
||||
* the program to run
|
||||
|
@ -202,7 +212,27 @@ public class ToolChain {
|
|||
* 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);
|
||||
execute(program, logger, in, out, new Machine());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param vm
|
||||
* the virtual machine to use
|
||||
*/
|
||||
public static void execute(Program program, Logger logger, InputStream in, OutputStream out, Machine vm) {
|
||||
machine = vm;
|
||||
Simulator s = new Simulator(program, machine);
|
||||
s.setIn(in);
|
||||
s.setOut(out);
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package pp.s1184725.boppi.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when declaring a variable a second time in the same scope.
|
||||
*
|
||||
* @author Frank Wibbelink
|
||||
*/
|
||||
public class DeclaredException extends SymbolTableException {
|
||||
private static final long serialVersionUID = 5769561320967096410L;
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message. The cause
|
||||
* is not initialized, and may subsequently be initialized by a call to
|
||||
* {@link #initCause}.
|
||||
*
|
||||
* @param message
|
||||
* the detail message. The detail message is saved for later
|
||||
* retrieval by the {@link #getMessage()} method.
|
||||
*/
|
||||
public DeclaredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package pp.s1184725.boppi.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when trying to use a SymbolTable while no scope is open.
|
||||
*
|
||||
* @author Frank Wibbelink
|
||||
*/
|
||||
public class NoProgramException extends SymbolTableException {
|
||||
private static final long serialVersionUID = 214588214046135357L;
|
||||
|
||||
/**
|
||||
* Constructs a new exception with {@code "No scope open."} as its detail
|
||||
* message. The cause is not initialized, and may subsequently be
|
||||
* initialized by a call to {@link #initCause}.
|
||||
*/
|
||||
public NoProgramException() {
|
||||
super("No scope open.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package pp.s1184725.boppi.exception;
|
||||
|
||||
import pp.s1184725.boppi.CachingSymbolTable;
|
||||
|
||||
/**
|
||||
* Exceptions that may be thrown by the {@link CachingSymbolTable}.
|
||||
*/
|
||||
public abstract class SymbolTableException extends Exception {
|
||||
private static final long serialVersionUID = 2390361610733507914L;
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message. The cause
|
||||
* is not initialized, and may subsequently be initialized by a call to
|
||||
* {@link #initCause}.
|
||||
*
|
||||
* @param message
|
||||
* the detail message. The detail message is saved for later
|
||||
* retrieval by the {@link #getMessage()} method.
|
||||
*/
|
||||
public SymbolTableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package pp.s1184725.boppi.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when declaring a variable a second time in the same scope.
|
||||
*
|
||||
* @author Frank Wibbelink
|
||||
*/
|
||||
public class UndeclaredException extends SymbolTableException {
|
||||
private static final long serialVersionUID = -5333137316309067383L;
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message. The cause
|
||||
* is not initialized, and may subsequently be initialized by a call to
|
||||
* {@link #initCause}.
|
||||
*
|
||||
* @param message
|
||||
* the detail message. The detail message is saved for later
|
||||
* retrieval by the {@link #getMessage()} method.
|
||||
*/
|
||||
public UndeclaredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -100,8 +100,8 @@ memfree: loadI 0 => m_0
|
|||
mf_ynul: haltI 1865442925
|
||||
mf_nnul: loadAI m_n,@off_oref => m_1
|
||||
subI m_1,1 => m_1
|
||||
cmp_GT m_1,m_0 => m_1
|
||||
cbr m_1 -> mf_exit,mf_free
|
||||
cmp_GT m_1,m_0 => m_2
|
||||
cbr m_2 -> mf_exit,mf_free
|
||||
|
||||
mf_exit: storeAI m_1 => m_n,@off_oref
|
||||
pop => m_1 // load return address
|
||||
|
|
|
@ -22,7 +22,7 @@ import pp.s1184725.boppi.*;
|
|||
* @author Frank Wibbelink
|
||||
*/
|
||||
@RunWith(Suite.class)
|
||||
@SuiteClasses({ ExpressionTest.class, SimpleVariableTest.class, ConditionalTest.class, SimpleFunctionTest.class })
|
||||
@SuiteClasses({ ExpressionTest.class, SimpleVariableTest.class, ConditionalTest.class, SimpleFunctionTest.class, ClosureTest.class })
|
||||
public class BoppiTests {
|
||||
/**
|
||||
* The path for test programs
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package pp.s1184725.boppi.test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import pp.s1184725.boppi.ToolChain;
|
||||
|
||||
/**
|
||||
* Tests for function closures, mostly testing runtime correctness and garbage collection.
|
||||
*
|
||||
* @author Frank Wibbelink
|
||||
*/
|
||||
public class ClosureTest {
|
||||
|
||||
/**
|
||||
* Correct closure parsing
|
||||
*/
|
||||
@Test
|
||||
public void correctClosureParsing() {
|
||||
BoppiTests.parseFile("closure1.boppi");
|
||||
assertThat(BoppiTests.log, is(empty()));
|
||||
|
||||
BoppiTests.parseFile("closure2.boppi");
|
||||
assertThat(BoppiTests.log, is(empty()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct closure checking
|
||||
*/
|
||||
@Test
|
||||
public void correctClosureChecking() {
|
||||
BoppiTests.checkFile("closure1.boppi");
|
||||
assertThat(BoppiTests.log, is(empty()));
|
||||
|
||||
BoppiTests.checkFile("closure2.boppi");
|
||||
assertThat(BoppiTests.log, is(empty()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct closure use
|
||||
*/
|
||||
@Test
|
||||
public void correctClosureGeneration() {
|
||||
BoppiTests.compileAndRunFile("closure1.boppi");
|
||||
assertThat(ToolChain.machine.getInterrupt(), is(0));
|
||||
assertThat(BoppiTests.log, is(empty()));
|
||||
assertThat(BoppiTests.out, is(arrayContaining("8", "9")));
|
||||
|
||||
BoppiTests.compileAndRunFile("closure2.boppi");
|
||||
assertThat(ToolChain.machine.getInterrupt(), is(0));
|
||||
assertThat(BoppiTests.log, is(empty()));
|
||||
assertThat(BoppiTests.out, is(arrayContaining("8", "7", "15", "2")));
|
||||
|
||||
BoppiTests.compileAndRunFile("closure3.boppi");
|
||||
assertThat(ToolChain.machine.getInterrupt(), is(0));
|
||||
assertThat(BoppiTests.log, is(empty()));
|
||||
assertThat(BoppiTests.out, is(arrayContaining("7", "15", "2")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test garbago collection. Nothing must be allocated at the end of the program.
|
||||
*/
|
||||
@Test
|
||||
public void correctClosureCleanup() {
|
||||
BoppiTests.compileAndRunFile("closure1.boppi");
|
||||
assertThat(ToolChain.machine.load(0), is(4));
|
||||
|
||||
BoppiTests.compileAndRunFile("closure2.boppi");
|
||||
assertThat(ToolChain.machine.load(0), is(4));
|
||||
|
||||
BoppiTests.compileAndRunFile("closure3.boppi");
|
||||
assertThat(ToolChain.machine.load(0), is(4));
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -107,6 +107,7 @@ public class SimpleVariableTest {
|
|||
@Test
|
||||
public void correctVariableGeneration() {
|
||||
BoppiTests.compileAndRunFile("simpleVariable.boppi");
|
||||
BoppiTests.log.forEach((l)->System.out.println(l.getMessage()));
|
||||
assertThat(BoppiTests.log, is(empty()));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
function (int)->int bindAdd(int a) {
|
||||
var int left;
|
||||
left := a;
|
||||
function int return(int right) left+right;
|
||||
return
|
||||
};
|
||||
|
||||
var (int)->int add5;
|
||||
add5 := bindAdd(5);
|
||||
|
||||
print(add5(3), add5(4));
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
function (int)->int bindInt((int,int)->int f, int first) {
|
||||
function int return(int second) f(first,second);
|
||||
return
|
||||
};
|
||||
|
||||
function int add(int a, int b) a+b;
|
||||
|
||||
var (int)->int add5;
|
||||
add5 := bindInt(add, 5);
|
||||
|
||||
print(add5(3));
|
||||
|
||||
|
||||
var ()->int getCalls;
|
||||
|
||||
function (int,int)->int logCalls((int,int)->int f) {
|
||||
var int calls;
|
||||
calls := 0;
|
||||
function int getCalls2() calls;
|
||||
getCalls := getCalls2;
|
||||
function int return(int first, int second) {
|
||||
calls := calls + 1;
|
||||
f(first,second)
|
||||
};
|
||||
return
|
||||
};
|
||||
|
||||
add := logCalls(add);
|
||||
|
||||
print(add(1,6), add(10,5));
|
||||
print(getCalls());
|
|
@ -0,0 +1,26 @@
|
|||
function int main1() {
|
||||
function int add(int a, int b) a+b;
|
||||
|
||||
var (int)->int add5;
|
||||
|
||||
var ()->int getCalls;
|
||||
|
||||
function (int,int)->int logCalls((int,int)->int f) {
|
||||
var int calls;
|
||||
calls := 0;
|
||||
function int getCalls2() calls;
|
||||
getCalls := getCalls2;
|
||||
function int return(int first, int second) {
|
||||
calls := calls + 1;
|
||||
f(first,second)
|
||||
};
|
||||
return
|
||||
};
|
||||
|
||||
add := logCalls(add);
|
||||
|
||||
print(add(1,6), add(10,5));
|
||||
print(getCalls());
|
||||
};
|
||||
|
||||
main1();
|
|
@ -7,9 +7,8 @@ import pp.iloc.eval.Machine;
|
|||
*
|
||||
* @author Frank Wibbelink
|
||||
*/
|
||||
public class FunctionType implements Type {
|
||||
public class FunctionType implements ReferenceType {
|
||||
private Type argument, result;
|
||||
private int localDataSize;
|
||||
|
||||
/**
|
||||
* Creates a new function type from the given parameter and return types.
|
||||
|
@ -43,25 +42,6 @@ public class FunctionType implements Type {
|
|||
return argument.toString() + "->" + result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size (in bytes) of the local data segment for this function.
|
||||
*
|
||||
* @param newSize
|
||||
* the size (in bytes) of the local data segment
|
||||
*/
|
||||
public void setLocalDataSize(int newSize) {
|
||||
localDataSize = newSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size (in bytes) of the local data segment for this function.
|
||||
*
|
||||
* @return the size (in bytes) of the local data segment
|
||||
*/
|
||||
public int getLocalDataSize() {
|
||||
return localDataSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result type of this function.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package pp.s1184725.boppi.type;
|
||||
|
||||
/**
|
||||
* An interface for types that are stored in dynamic memory.
|
||||
*
|
||||
* @author Frank Wibbelink
|
||||
*/
|
||||
public interface ReferenceType extends Type {
|
||||
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
package pp.s1184725.boppi.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import pp.iloc.model.Reg;
|
||||
|
||||
/**
|
||||
* Simple class to manage registers.
|
||||
*/
|
||||
public class RegisterPool {
|
||||
/**
|
||||
* Recommended number of registers to use before throwing a warning.
|
||||
*/
|
||||
public static int RECOMMENDED_REGISTER_COUNT = 10;
|
||||
|
||||
private Logger logger;
|
||||
private List<Reg> regFree, regInUse;
|
||||
|
||||
/**
|
||||
* Creates a new register pool
|
||||
*
|
||||
* @param logger
|
||||
* the logger to use
|
||||
*/
|
||||
public RegisterPool(Logger logger) {
|
||||
regFree = new ArrayList<>();
|
||||
regInUse = new ArrayList<>();
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
private Reg makeReg() {
|
||||
return makeRegThatIsNot(null);
|
||||
}
|
||||
|
||||
private Reg makeRegThatIsNot(Reg r1) {
|
||||
Reg reg = regFree.stream().filter((r2) -> !r2.equals(r1)).findAny().orElseGet(() -> {
|
||||
if (regInUse.size() == RECOMMENDED_REGISTER_COUNT + 1)
|
||||
logger.warning(String.format("Using more than %d registers. Consider rebalancing your expressions.",
|
||||
RECOMMENDED_REGISTER_COUNT));
|
||||
|
||||
return new Reg("__" + (regInUse.size() + 1));
|
||||
});
|
||||
|
||||
regFree.remove(reg);
|
||||
regInUse.add(reg);
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
private void freeReg(Reg reg) {
|
||||
if (reg == null) {
|
||||
logger.severe("INTERNAL: cannot free null register.");
|
||||
} else if (!regInUse.contains(reg)) {
|
||||
logger.severe(String.format("INTERNAL: cannot free register %s because it is not in use.", reg.getName()));
|
||||
} else {
|
||||
regInUse.remove(reg);
|
||||
regFree.add(reg);
|
||||
}
|
||||
}
|
||||
|
||||
private void blockReg(Reg reg) {
|
||||
if (reg == null)
|
||||
logger.severe("INTERNAL: cannot reserve null register.");
|
||||
else if (!regFree.contains(reg)) {
|
||||
logger.severe(String.format("INTERNAL: cannot reserve register %s because it is in use.", reg.getName()));
|
||||
} else {
|
||||
regFree.remove(reg);
|
||||
regInUse.add(reg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of registers currently in use.
|
||||
*
|
||||
* @return the list of registers in use
|
||||
*/
|
||||
public List<Reg> getInUse() {
|
||||
return regInUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs some code with a single register allocated.
|
||||
*
|
||||
* @param <T>
|
||||
* the return type of the code
|
||||
* @param code
|
||||
* the code to run
|
||||
* @return the return value of the code
|
||||
*/
|
||||
public <T> T withReg(Function<Reg, T> code) {
|
||||
Reg r = makeReg();
|
||||
T result = code.apply(r);
|
||||
freeReg(r);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs some code with a single register allocated.
|
||||
*
|
||||
* @param code
|
||||
* the code to run
|
||||
* @return the register used in the code
|
||||
*/
|
||||
public Reg withReg(Consumer<Reg> code) {
|
||||
Reg r = makeReg();
|
||||
code.accept(r);
|
||||
freeReg(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs some code with two registers allocated.
|
||||
*
|
||||
* @param code
|
||||
* the code to run
|
||||
* @return the second register used in the code
|
||||
*/
|
||||
public Reg withReg(BiConsumer<Reg, Reg> code) {
|
||||
Reg r1 = makeReg();
|
||||
Reg r2 = makeReg();
|
||||
code.accept(r1, r2);
|
||||
freeReg(r1);
|
||||
freeReg(r2);
|
||||
return r2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs some code with one register given and one allocated.
|
||||
*
|
||||
* @param r1
|
||||
* the first register to use
|
||||
* @param code
|
||||
* the code to run
|
||||
* @return the register used in the code
|
||||
*/
|
||||
public Reg withReg(Reg r1, Consumer<Reg> code) {
|
||||
Reg r = makeRegThatIsNot(r1);
|
||||
code.accept(r);
|
||||
freeReg(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs some code with one register given and two allocated.
|
||||
*
|
||||
* @param r1
|
||||
* the first register to use
|
||||
* @param code
|
||||
* the code to run
|
||||
* @return the second register used in the code
|
||||
*/
|
||||
public Reg withReg(Reg r1, BiConsumer<Reg, Reg> code) {
|
||||
Reg r2 = makeRegThatIsNot(r1);
|
||||
Reg r3 = makeRegThatIsNot(r1);
|
||||
code.accept(r2, r3);
|
||||
freeReg(r2);
|
||||
freeReg(r3);
|
||||
return r3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks a register while running some code.
|
||||
*
|
||||
* @param <T>
|
||||
* the return type of the code
|
||||
* @param r1
|
||||
* the register to reserve
|
||||
* @param code
|
||||
* the code to run
|
||||
* @return the return value of the code
|
||||
*/
|
||||
public <T> T blockReg(Reg r1, Supplier<T> code) {
|
||||
blockReg(r1);
|
||||
T result = code.get();
|
||||
freeReg(r1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks a register while running some code.
|
||||
*
|
||||
* @param r1
|
||||
* the register to reserve
|
||||
* @param code
|
||||
* the code to run
|
||||
*/
|
||||
public void blockReg(Reg r1, Runnable code) {
|
||||
blockReg(r1);
|
||||
code.run();
|
||||
freeReg(r1);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue