implemented closures with correct cleanup

This commit is contained in:
User 2017-07-27 20:26:31 +02:00
parent 9baff2d080
commit 5a48e93674
21 changed files with 1237 additions and 492 deletions

View File

@ -1,7 +1,5 @@
package pp.s1184725.boppi; package pp.s1184725.boppi;
import java.util.Stack;
import org.antlr.v4.runtime.tree.ParseTreeProperty; import org.antlr.v4.runtime.tree.ParseTreeProperty;
import pp.iloc.model.Reg; import pp.iloc.model.Reg;
@ -28,11 +26,7 @@ public class Annotations {
/** /**
* Maps nodes to their function scope. * Maps nodes to their function scope.
*/ */
public ParseTreeProperty<Variable<Type>> function; public ParseTreeProperty<FunctionScope<Type>> function;
/**
* A stack with the current function scope on top.
*/
public Stack<Variable<Type>> currentFunction;
/** /**
* A symbol table * A symbol table
*/ */
@ -46,7 +40,6 @@ public class Annotations {
types = new ParseTreeProperty<>(); types = new ParseTreeProperty<>();
function = new ParseTreeProperty<>(); function = new ParseTreeProperty<>();
variables = new ParseTreeProperty<>(); variables = new ParseTreeProperty<>();
currentFunction = new Stack<>();
symbols = new CachingSymbolTable<>(); symbols = new CachingSymbolTable<>();
} }
} }

View File

@ -1,6 +1,5 @@
package pp.s1184725.boppi; package pp.s1184725.boppi;
import java.util.*;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors; 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.*;
import pp.s1184725.boppi.antlr.BoppiParser.*; import pp.s1184725.boppi.antlr.BoppiParser.*;
import pp.s1184725.boppi.exception.*;
import pp.s1184725.boppi.type.*; import pp.s1184725.boppi.type.*;
/** /**
@ -77,8 +77,6 @@ public class BoppiChecker extends BoppiBaseVisitor<Type> {
public Type visit(ParseTree tree) { public Type visit(ParseTree tree) {
Type type = super.visit(tree); Type type = super.visit(tree);
an.types.put(tree, type); an.types.put(tree, type);
if (an.function.get(tree) == null)
an.function.put(tree, an.currentFunction.peek());
return type; return type;
} }
@ -129,9 +127,7 @@ public class BoppiChecker extends BoppiBaseVisitor<Type> {
if (!(var.getType() instanceof SimpleType)) if (!(var.getType() instanceof SimpleType))
log.warning("Be careful only to pass pure functions outside their scope."); log.warning("Be careful only to pass pure functions outside their scope.");
} catch (EmptyStackException e) { } catch (SymbolTableException e) {
log.severe(getError(ctx, "Declaring variable outside a program."));
} catch (Exception e) {
log.severe(getError(ctx, e.getMessage())); log.severe(getError(ctx, e.getMessage()));
} }
return SimpleType.VOID; return SimpleType.VOID;
@ -145,23 +141,21 @@ public class BoppiChecker extends BoppiBaseVisitor<Type> {
FunctionType type = new FunctionType(visit(ctx.result), parameterTypes); FunctionType type = new FunctionType(visit(ctx.result), parameterTypes);
Variable<Type> func = an.symbols.put(ctx.name.getText(), type); Variable<Type> func = an.symbols.put(ctx.name.getText(), type);
an.variables.put(ctx, func); 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++) for (int i = 1; i < ctx.type().size(); i++)
try { try {
an.symbols.put(ctx.IDENTIFIER(i).getText(), an.types.get(ctx.type(i))); 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())); log.severe(getError(ctx, e.getMessage()));
} }
checkConstraint(an.symbols.withScope(() -> visit(ctx.body)), an.types.get(ctx.result), ctx); checkConstraint(an.symbols.withScope(() -> visit(ctx.body)), an.types.get(ctx.result), ctx);
})); }));
} catch (EmptyStackException e) { } catch (SymbolTableException e) {
log.severe(getError(ctx, "Declaring function outside a program."));
} catch (Exception e) {
log.severe(getError(ctx, e.getMessage())); log.severe(getError(ctx, e.getMessage()));
} }
an.currentFunction.pop();
return SimpleType.VOID; return SimpleType.VOID;
} }
@ -279,9 +273,7 @@ public class BoppiChecker extends BoppiBaseVisitor<Type> {
@Override @Override
public Type visitProgram(ProgramContext ctx) { public Type visitProgram(ProgramContext ctx) {
FunctionType main = new FunctionType(TupleType.UNIT, TupleType.UNIT); an.function.put(ctx, an.symbols.withFunctionScope(() -> super.visitProgram(ctx)));
an.currentFunction.push(new Variable<Type>(main, 0, 0));
main.setLocalDataSize(an.symbols.withFunctionScope(() -> super.visitProgram(ctx)));
return null; return null;
} }
@ -319,12 +311,17 @@ public class BoppiChecker extends BoppiBaseVisitor<Type> {
public Type visitVariable(VariableContext ctx) { public Type visitVariable(VariableContext ctx) {
try { try {
Variable<Type> var = an.symbols.get(ctx.getText()); 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); an.variables.put(ctx, var);
return var.getType(); return var.getType();
} catch (EmptyStackException e) { } catch (SymbolTableException e) {
log.severe(getError(ctx, "Using variable outside a program.")); log.severe(getError(ctx, e.getMessage()));
} catch (Exception e) {
log.severe(getError(ctx, e.getMessage() != null ? e.getMessage() : "unknown error"));
} }
return SimpleType.VOID; return SimpleType.VOID;

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ package pp.s1184725.boppi;
import java.util.*; import java.util.*;
import java.util.function.Supplier; import java.util.function.Supplier;
import pp.s1184725.boppi.exception.*;
import pp.s1184725.boppi.type.*; import pp.s1184725.boppi.type.*;
/** /**
@ -19,12 +20,8 @@ import pp.s1184725.boppi.type.*;
*/ */
public class CachingSymbolTable<T extends Type> { public class CachingSymbolTable<T extends Type> {
protected Stack<Map<String, Variable<T>>> symbolMapStack; protected Stack<Map<String, Variable<T>>> symbolMapStack;
protected Stack<Integer> offsets;
protected Stack<Integer> functionSizes;
protected Map<String, Variable<T>> symbolCache; protected Map<String, Variable<T>> symbolCache;
protected int offset; protected Stack<FunctionScope<T>> functionScope;
protected int functionDepth;
protected int functionSize;
/** /**
* Creates an empty symbol table with no open scope. * Creates an empty symbol table with no open scope.
@ -32,11 +29,7 @@ public class CachingSymbolTable<T extends Type> {
public CachingSymbolTable() { public CachingSymbolTable() {
symbolMapStack = new Stack<>(); symbolMapStack = new Stack<>();
symbolCache = new HashMap<>(); symbolCache = new HashMap<>();
offsets = new Stack<>(); functionScope = new Stack<>();
functionSizes = new Stack<>();
offset = 0;
functionDepth = 0;
functionSize = 0;
} }
/** /**
@ -44,7 +37,6 @@ public class CachingSymbolTable<T extends Type> {
*/ */
public void openScope() { public void openScope() {
symbolMapStack.push(new HashMap<>()); symbolMapStack.push(new HashMap<>());
offsets.push(offset);
} }
/** /**
@ -53,28 +45,27 @@ public class CachingSymbolTable<T extends Type> {
*/ */
public void openFunctionScope() { public void openFunctionScope() {
openScope(); openScope();
functionSizes.push(functionSize); functionScope.push(new FunctionScope<T>(functionScope.size()));
functionDepth++;
functionSize = 0;
offset = 0;
} }
/** /**
* 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 * if no scope is open
*/ */
public void closeScope() throws EmptyStackException { public void closeScope() throws NoProgramException {
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)) Optional<Variable<T>> maybeType = symbolMapStack.parallelStream()
.map((map) -> map.get(identifier)).reduce((__, snd) -> snd); .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();
} }
/** /**
@ -82,13 +73,14 @@ public class CachingSymbolTable<T extends Type> {
* declarations within this scope, restoring the local offset and decreasing * declarations within this scope, restoring the local offset and decreasing
* the lookup depth. * the lookup depth.
* *
* @throws EmptyStackException * @return function scope details
*
* @throws NoProgramException
* if no scope is open * if no scope is open
*/ */
public void closeFunctionScope() throws EmptyStackException { public FunctionScope<T> closeFunctionScope() throws NoProgramException {
closeScope(); closeScope();
functionSize = functionSizes.pop(); return functionScope.pop();
functionDepth--;
} }
/** /**
@ -106,27 +98,32 @@ public class CachingSymbolTable<T extends Type> {
public <U> U withScope(Supplier<U> code) { public <U> U withScope(Supplier<U> code) {
openScope(); openScope();
U result = code.get(); U result = code.get();
closeScope(); try {
closeScope();
} catch (NoProgramException e) {
}
return result; return result;
} }
/** /**
* Opens a function scope, executes the given code and closes it. This is a * Opens a function scope, executes the given code and closes it. This is a
* helper method to make sure calls to {@link #openFunctionScope()} and * helper method to make sure calls to {@link #openFunctionScope()} and
* {@link #closeFunctionScope()} are balanced and returns the local data * {@link #closeFunctionScope()} are balanced and returns the function scope
* size (in bytes) of the function. {@code code} must take no arguments and * details. {@code code} must take no arguments and must not return a value.
* must not return a value.
* *
* @param code * @param code
* the code to execute within the scope * 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(); openFunctionScope();
code.run(); code.run();
int result = functionSize;
closeFunctionScope(); try {
return result; return closeFunctionScope();
} catch (NoProgramException e) {
return null;
}
} }
/** /**
@ -140,18 +137,19 @@ public class CachingSymbolTable<T extends Type> {
* @param type * @param type
* the type of the identifier * the type of the identifier
* @return 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 * if the identifier is declared in the current scope already
* @throws EmptyStackException * @throws NoProgramException
* if no scope is open * 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 DeclaredException, NoProgramException {
if (symbolMapStack.peek().containsKey(id)) if (symbolMapStack.isEmpty())
throw new Exception(String.format("Identifier '%s' already declared in this scope.", id)); throw new NoProgramException();
Variable<T> var = new Variable<>(type, functionDepth, offset); if (symbolMapStack.peek().containsKey(id))
offset += type.getSize(); throw new DeclaredException(String.format("Identifier '%s' already declared in this scope.", id));
functionSize = Math.max(functionSize, offset);
Variable<T> var = functionScope.peek().addVariable(type);
symbolMapStack.peek().put(id, var); symbolMapStack.peek().put(id, var);
symbolCache.put(id, var); symbolCache.put(id, var);
@ -164,53 +162,46 @@ public class CachingSymbolTable<T extends Type> {
* @param id * @param id
* the name of the identifier * the name of the identifier
* @return true if the identifier has a declared type * @return true if the identifier has a declared type
* @throws EmptyStackException * @throws NoProgramException
* if no scope is open * if no scope is open
*/ */
public boolean has(String id) throws EmptyStackException { public boolean has(String id) throws NoProgramException {
if (symbolMapStack.isEmpty()) if (symbolMapStack.isEmpty())
throw new EmptyStackException(); throw new NoProgramException();
return symbolCache.containsKey(id); return symbolCache.containsKey(id);
} }
/** /**
* Retrieves the type of an identifier, if any. * Returns the type of an identifier, if any.
* *
* @param id * @param id
* the name of the identifier * the name of the identifier
* @return the type of the identifier * @return the type of the identifier
* @throws Exception * @throws UndeclaredException
* if the identifier is not declared * if the identifier is not declared
* @throws EmptyStackException * @throws NoProgramException
* if no scope is open * 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)) 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); return symbolCache.get(id);
} }
/** /**
* Returns the size, in bytes, required to store all the variables declared * Returns the current function scope, if any.
* 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 * @return the current function scope
* function * @throws NoProgramException
* if no scope is open
*/ */
public int getLocalDataSize() { public FunctionScope<T> getFunctionScope() throws NoProgramException {
return functionSize; 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;
}
} }

View File

@ -1,7 +1,6 @@
package pp.s1184725.boppi; package pp.s1184725.boppi;
import java.util.EmptyStackException; import pp.s1184725.boppi.exception.*;
import pp.s1184725.boppi.type.*; import pp.s1184725.boppi.type.*;
/** /**
@ -18,26 +17,26 @@ public class DebugCachingSymbolTable<T extends Type> extends CachingSymbolTable<
@Override @Override
public void openFunctionScope() { public void openFunctionScope() {
super.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 @Override
public void closeFunctionScope() throws EmptyStackException { public FunctionScope<T> closeFunctionScope() throws NoProgramException {
System.out.println(this.getClass().getName() + ": leaving scope depth " + functionDepth); System.out.println(this.getClass().getName() + ": leaving scope depth " + functionScope.size());
super.closeFunctionScope(); return super.closeFunctionScope();
} }
@Override @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 " System.out.println(this.getClass().getName() + ": declaring '" + id + "' (" + type.toString() + ") at scope "
+ functionDepth); + functionScope.size());
return super.put(id, type); return super.put(id, type);
} }
@Override @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 " 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); return super.get(id);
} }

View File

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

View File

@ -2,7 +2,7 @@ package pp.s1184725.boppi;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Path; import java.nio.file.*;
import java.util.*; import java.util.*;
import java.util.logging.*; import java.util.logging.*;
@ -11,6 +11,7 @@ import org.antlr.v4.runtime.tree.*;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import pp.iloc.Simulator; import pp.iloc.Simulator;
import pp.iloc.eval.Machine;
import pp.iloc.model.Program; import pp.iloc.model.Program;
import pp.s1184725.boppi.antlr.*; import pp.s1184725.boppi.antlr.*;
@ -20,6 +21,14 @@ import pp.s1184725.boppi.antlr.*;
* @author Frank Wibbelink * @author Frank Wibbelink
*/ */
public class ToolChain { 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 * 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 * 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 * @param program
* the program to run * the program to run
@ -202,7 +212,27 @@ public class ToolChain {
* the output stream the program can write to * the output stream the program can write to
*/ */
public static void execute(Program program, Logger logger, InputStream in, OutputStream out) { 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.setIn(in);
s.setOut(out); s.setOut(out);
try { try {

View File

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

View File

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

View File

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

View File

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

View File

@ -100,8 +100,8 @@ memfree: loadI 0 => m_0
mf_ynul: haltI 1865442925 mf_ynul: haltI 1865442925
mf_nnul: loadAI m_n,@off_oref => m_1 mf_nnul: loadAI m_n,@off_oref => m_1
subI m_1,1 => m_1 subI m_1,1 => m_1
cmp_GT m_1,m_0 => m_1 cmp_GT m_1,m_0 => m_2
cbr m_1 -> mf_exit,mf_free cbr m_2 -> mf_exit,mf_free
mf_exit: storeAI m_1 => m_n,@off_oref mf_exit: storeAI m_1 => m_n,@off_oref
pop => m_1 // load return address pop => m_1 // load return address

View File

@ -22,7 +22,7 @@ import pp.s1184725.boppi.*;
* @author Frank Wibbelink * @author Frank Wibbelink
*/ */
@RunWith(Suite.class) @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 { public class BoppiTests {
/** /**
* The path for test programs * The path for test programs

View File

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

View File

@ -107,6 +107,7 @@ public class SimpleVariableTest {
@Test @Test
public void correctVariableGeneration() { public void correctVariableGeneration() {
BoppiTests.compileAndRunFile("simpleVariable.boppi"); BoppiTests.compileAndRunFile("simpleVariable.boppi");
BoppiTests.log.forEach((l)->System.out.println(l.getMessage()));
assertThat(BoppiTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
} }

View File

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

View File

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

View File

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

View File

@ -7,9 +7,8 @@ import pp.iloc.eval.Machine;
* *
* @author Frank Wibbelink * @author Frank Wibbelink
*/ */
public class FunctionType implements Type { public class FunctionType implements ReferenceType {
private Type argument, result; private Type argument, result;
private int localDataSize;
/** /**
* Creates a new function type from the given parameter and return types. * 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(); 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. * Returns the result type of this function.
* *

View File

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

View File

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