added memory allocator with tests

This commit is contained in:
User 2017-07-22 15:46:54 +02:00
parent b617136564
commit 87b859a70c
9 changed files with 723 additions and 330 deletions

View File

@ -167,7 +167,8 @@ public class BoppiChecker extends BoppiBaseVisitor<Type> {
@Override @Override
public Type visitExpr(ExprContext ctx) { public Type visitExpr(ExprContext ctx) {
if (ctx.singleExpr(ctx.singleExpr().size() - 1) instanceof DeclareContext) ParserRuleContext lastExpr = ctx.singleExpr(ctx.singleExpr().size() - 1);
if (lastExpr instanceof DeclareContext || lastExpr instanceof DeclareFunctionContext)
log.severe(getError(ctx, "Compound expression ends with declaration.")); log.severe(getError(ctx, "Compound expression ends with declaration."));
return ctx.singleExpr().stream().map(this::visit).reduce((__, snd) -> snd).get(); return ctx.singleExpr().stream().map(this::visit).reduce((__, snd) -> snd).get();

View File

@ -0,0 +1,162 @@
fst_ptr <- 28
slot1 <- 32
header <- 8
off_oref <- -8
off_osize <- -4
off_next <- 0
off_size <- 4
// memlib - simple memory allocator for ILOC
//
// This library maintains a single-linked list (of monotonically increasing addresses) of free
// slots in memory and a reference count for occupied slots. Each slot contains a tuple `(next
// free slot address,size)` followed by `size` free bytes. The memory is initiliased with a
// pointer to the first free slot at `#fst_ptr` and one free slot at `#slot1` of size
// `brk-slot1-header`. When a piece of memory is requested for allocation, a tuple
// `(reference count, size)` followed by `size` bytes for the object is formed. The calling
// convention of the functions in this library consists of storing the return address at #0,
// arguments at #4, #8 and #12 and return values (if any) at #16, #20 and #24.
//
// Requires a register `brk` for the highest address available for heap allocation.
// Requires an instruction `halt` when memory runs out
//
// @author Frank Wibbelink
// initialise
memlib_: loadI 0 => m_0
loadI @slot1 => m_1
subI brk,@slot1 => m_2
subI m_2,@header => m_2
storeAI m_1 => m_0,@fst_ptr // pointer to first slot
storeAI m_0 => m_1,@off_next // next slot is null
storeAI m_2 => m_1,@off_size // first slot size
jumpI -> ememlib_
// allocate memory
// searches through the free slots for either a slot size of ~~exactly #4+8 or a size of~~ at
// least #4+16
// memory: [return address, object size] -> [object address]
memalloc: loadI 0 => m_0
loadAI m_0,4 => m_s // load size from #4
loadI @fst_ptr => m_p // load previous address (base pointer)
loadAI m_p,@off_next => m_c // load current address (first slot)
ma_loop: cmp_EQ m_0,m_c => m_1 // check if address pointer is null
cbr m_1 -> ma_null,ma_cont // if null, go to end and return null pointer
ma_cont: loadAI m_c,@off_size => m_1 // load slot size
cmp_EQ m_1,m_s => m_2 // check if request fits exactly
cbr m_2 -> ma_yxct,ma_nxct
ma_nxct: subI m_1,@header => m_1 // subtract free slot size
cmp_GE m_1,m_s => m_1 // check if request fits
cbr m_1 -> ma_found,ma_next
ma_next: i2i m_c => m_p
loadAI m_p,@off_next => m_c
jumpI -> ma_loop
ma_yxct: loadAI m_c,@off_next => m_n // location of next free slot
jumpI -> ma_final
ma_found: addI m_s,@header => m_1
add m_1,m_c => m_n // location of new free slot
loadAI m_c,@off_size => m_1
subI m_1,@header => m_1
sub m_1,m_s => m_1 // size of new free slot
storeAI m_1 => m_n,@off_size
loadAI m_c,@off_next => m_1 // location of next free slot
storeAI m_1 => m_n,@off_next
ma_final: storeAI m_n => m_p,@off_next // link previous free slot to new
addI m_c,@header => m_c // move to object location
loadI 1 => m_1
storeAI m_1 => m_c,@off_oref // set reference count to 1
storeAI m_s => m_c,@off_osize // set object size
ma_null: storeAI m_c => m_0,16 // store object address at #16
loadAI m_0,0 => m_r // load return address from #0
jump -> m_r
// increase reference count of object
// memory: [return address, object address] -> []
memaddref: loadI 0 => m_0
loadAI m_0,4 => m_n // load object address from #4
cmp_EQ m_0,m_n => m_1
cbr m_1 -> mr_ynul,mr_nnul // check if null pointer
mr_ynul: haltI 1865445997
mr_nnul: loadAI m_n,@off_oref => m_1
addI m_1,1 => m_1
storeAI m_1 => m_n,@off_oref
loadAI m_0,0 => m_r // load return address from #0
jump -> m_r
// decrease reference count of object
// frees memory if count goes to zero
// memory: [return address, object address] -> []
memfree: loadI 0 => m_0
loadAI m_0,4 => m_n // load object from #4
cmp_EQ m_0,m_n => m_1
cbr m_1 -> mf_ynul,mf_nnul // check if null pointer
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
mf_exit: storeAI m_1 => m_n,@off_oref
loadAI m_0,0 => m_r // load return address from #0
jump -> m_r
mf_free: subI m_n,@header => m_n
loadI @fst_ptr => m_p
loadAI m_p,@off_next => m_c
mf_loop: cmp_EQ m_0,m_c => m_1 // check if address pointer is null
cbr m_1 -> mf_halt,mf_cont
mf_halt: haltI 1882220141 // halt program; object beyond last free slot (or memory corrupted)
mf_cont: cmp_EQ m_c,m_n => m_1
cbr m_1 -> mf_hal2,mf_con2
mf_hal2: haltI 1717855853 // halt program; object is free slot
mf_con2: cmp_LE m_c,m_n => m_1
cbr m_1 -> mf_next, mf_done
mf_next: i2i m_c => m_p
loadAI m_p,@off_next => m_c
jumpI -> mf_loop
mf_done: loadAI m_p,@off_size => m_1
addI m_1,@header => m_1
add m_1,m_p => m_2
cmp_EQ m_2,m_n => m_2
cbr m_2 -> mf_yprv,mf_nprv
mf_yprv: loadAI m_n,@off_size => m_2 // merge with previous free slot
add m_1,m_2 => m_1 // new size of previous free slot
storeAI m_1 => m_p,@off_size
i2i m_p => m_n
jumpI -> mf_dprv
mf_nprv: storeAI m_n => m_p,@off_next // link previous free slot with new
mf_dprv: loadAI m_n,@off_size => m_1
addI m_1,@header => m_1
add m_1,m_n => m_2
cmp_EQ m_2,m_c => m_2
cbr m_2 -> mf_ynxt,mf_nnxt
mf_ynxt: loadAI m_c,@off_size => m_2 // merge with next free slot
add m_1,m_2 => m_1 // new size of next free slot
storeAI m_1 => m_n,@off_size
loadAI m_c,@off_next => m_1
storeAI m_1 => m_n,@off_next // move link of next's next to new free slot
jumpI -> mf_exit
mf_nnxt: storeAI m_c => m_n,@off_next // link new free slot with next
loadAI m_0,0 => m_r // load return address from #0
jump -> m_r
// copy object to location
// memory: [return address, object address, destination] -> []
memcopy: loadI 0 => m_0
haltI 1835626101 // unimplemented
loadAI m_0,0 => m_r // load return address from #0
jump -> m_r
ememlib_: nop
// end of memlib

View File

@ -1,191 +1,16 @@
package pp.s1184725.boppi.test; package pp.s1184725.boppi.test;
import java.nio.file.*;
import java.util.List;
import java.util.logging.*;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Suite; import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses; import org.junit.runners.Suite.SuiteClasses;
import pp.iloc.Simulator;
import pp.iloc.model.Program;
import pp.s1184725.boppi.*;
/** /**
* Test suite for all Boppi language features. This suite contains helper * Test suite for all tests, including the Boppi language and utilities.
* functions and attributes to assist test cases. Each test case should consider
* a single language feature across all compiler stages.
* *
* @author Frank Wibbelink * @author Frank Wibbelink
*/ */
@RunWith(Suite.class) @RunWith(Suite.class)
@SuiteClasses({ ExpressionTest.class, SimpleVariableTest.class, ConditionalTest.class, SimpleFunctionTest.class }) @SuiteClasses({ BoppiTests.class, ILOCAllocatorTest.class })
public class AllTests { public class AllTests {
/**
* The path for test programs
*/
public static final Path TEST_PROGRAM_LOCATION = Paths
.get("src/" + AllTests.class.getPackage().getName().replaceAll("\\.", "/") + "/programs/");
/**
* The default logging level
*/
public static final Level DEFAULT_LOG_LEVEL = Level.SEVERE;
/**
* Log records of last test
*/
public static List<LogRecord> log;
/**
* Output of last program split by line breaks
*/
public static String[] out;
/**
* The level of error reporting to use for the tests. Can be changed to
* include finer tests.
*/
private static Level logLevel = DEFAULT_LOG_LEVEL;
/**
* Runs a test with warnings enabled.
*
* @param test
* the test to run
*/
public static void withWarnings(Runnable test) {
logLevel = Level.WARNING;
test.run();
logLevel = DEFAULT_LOG_LEVEL;
}
/**
* Parses a string of code, logging to {@link #log}.
*
* @param code
* the code to parse
*/
public static void parseString(String code) {
parse(ToolChain.getCharStream(code));
}
/**
* Parses code from a file, logging to {@link #log}.
*
* @param file
* the file name
*/
public static void parseFile(String file) {
parse(ToolChain.getCharStream(TEST_PROGRAM_LOCATION.resolve(file)));
}
private static void parse(CharStream stream) {
Logger logger = Logger.getAnonymousLogger();
log = ToolChain.makeListLog(logger);
logger.setLevel(logLevel);
ToolChain.getParser(ToolChain.getLexer(stream, logger), logger).program();
}
/**
* Parses and type checks a string of code, logging to {@link #log}.
*
* @param code
* the code to type check
*/
public static void checkString(String code) {
check(ToolChain.getCharStream(code));
}
/**
* Parses and type checks code from a file, logging to {@link #log}.
*
* @param file
* the file name
*/
public static void checkFile(String file) {
check(ToolChain.getCharStream(TEST_PROGRAM_LOCATION.resolve(file)));
}
private static void check(CharStream stream) {
Logger logger = Logger.getAnonymousLogger();
log = ToolChain.makeListLog(logger);
logger.setLevel(logLevel);
ToolChain.getAnnotations(ToolChain.getParser(ToolChain.getLexer(stream, logger), logger).program(), logger);
}
/**
* Compiles and runs a program from a string, logging to {@link #log} and
* writing lines of output to {@link #out}.
*
* @param code
* the code to type check
* @param input
* lines of input for the program
*/
public static void compileAndRunString(String code, String... input) {
compileAndRun(ToolChain.getCharStream(code), String.join("\n", input) + "\n");
}
/**
* Compiles and runs a program from a file, logging to {@link #log} and
* writing lines of output to {@link #out}.
*
* @param file
* the file name
* @param input
* lines of input for the program
*/
public static void compileAndRunFile(String file, String... input) {
compileAndRun(ToolChain.getCharStream(TEST_PROGRAM_LOCATION.resolve(file)), String.join("\n", input) + "\n");
}
private static void compileAndRun(CharStream stream, String input) {
Logger logger = Logger.getAnonymousLogger();
log = ToolChain.makeListLog(logger);
logger.setLevel(logLevel);
ParseTree ast = ToolChain.getParser(ToolChain.getLexer(stream, logger), logger).program();
Annotations annotations = ToolChain.getAnnotations(ast, logger);
Program program = ToolChain.getILOC(ast, logger, annotations);
if (Simulator.DEBUG)
System.out.println(program.prettyPrint());
out = ToolChain.execute(program, logger, input).split("\n");
}
/**
* Compiles and runs a program in debug mode, printing the generated
* program, a simulation of the program and all log messages.
*
* @param stream
* a character stream with the program code
* @param input
* lines of input for the program
*/
public static void debugRun(CharStream stream, String... input) {
Simulator.DEBUG = true;
String in = String.join("\n", input) + "\n";
Logger logger = Logger.getAnonymousLogger();
log = ToolChain.makeListLog(logger);
logger.setLevel(Level.FINEST);
ParseTree ast = ToolChain.getParser(ToolChain.getLexer(stream, logger), logger).program();
Annotations annotations = ToolChain.getAnnotations(ast, logger);
Program program = ToolChain.getILOC(ast, logger, annotations);
System.out.println(program.prettyPrint());
out = ToolChain.execute(program, logger, in).split("\n");
log.forEach((entry) -> System.out.println(entry.getMessage()));
Simulator.DEBUG = false;
}
} }

View File

@ -0,0 +1,191 @@
package pp.s1184725.boppi.test;
import java.nio.file.*;
import java.util.List;
import java.util.logging.*;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import pp.iloc.Simulator;
import pp.iloc.model.Program;
import pp.s1184725.boppi.*;
/**
* Test suite for all Boppi language features. This suite contains helper
* functions and attributes to assist test cases. Each test case should consider
* a single language feature across all compiler stages.
*
* @author Frank Wibbelink
*/
@RunWith(Suite.class)
@SuiteClasses({ ExpressionTest.class, SimpleVariableTest.class, ConditionalTest.class, SimpleFunctionTest.class })
public class BoppiTests {
/**
* The path for test programs
*/
public static final Path TEST_PROGRAM_LOCATION = Paths
.get("src/" + BoppiTests.class.getPackage().getName().replaceAll("\\.", "/") + "/programs/");
/**
* The default logging level
*/
public static final Level DEFAULT_LOG_LEVEL = Level.SEVERE;
/**
* Log records of last test
*/
public static List<LogRecord> log;
/**
* Output of last program split by line breaks
*/
public static String[] out;
/**
* The level of error reporting to use for the tests. Can be changed to
* include finer tests.
*/
private static Level logLevel = DEFAULT_LOG_LEVEL;
/**
* Runs a test with warnings enabled.
*
* @param test
* the test to run
*/
public static void withWarnings(Runnable test) {
logLevel = Level.WARNING;
test.run();
logLevel = DEFAULT_LOG_LEVEL;
}
/**
* Parses a string of code, logging to {@link #log}.
*
* @param code
* the code to parse
*/
public static void parseString(String code) {
parse(ToolChain.getCharStream(code));
}
/**
* Parses code from a file, logging to {@link #log}.
*
* @param file
* the file name
*/
public static void parseFile(String file) {
parse(ToolChain.getCharStream(TEST_PROGRAM_LOCATION.resolve(file)));
}
private static void parse(CharStream stream) {
Logger logger = Logger.getAnonymousLogger();
log = ToolChain.makeListLog(logger);
logger.setLevel(logLevel);
ToolChain.getParser(ToolChain.getLexer(stream, logger), logger).program();
}
/**
* Parses and type checks a string of code, logging to {@link #log}.
*
* @param code
* the code to type check
*/
public static void checkString(String code) {
check(ToolChain.getCharStream(code));
}
/**
* Parses and type checks code from a file, logging to {@link #log}.
*
* @param file
* the file name
*/
public static void checkFile(String file) {
check(ToolChain.getCharStream(TEST_PROGRAM_LOCATION.resolve(file)));
}
private static void check(CharStream stream) {
Logger logger = Logger.getAnonymousLogger();
log = ToolChain.makeListLog(logger);
logger.setLevel(logLevel);
ToolChain.getAnnotations(ToolChain.getParser(ToolChain.getLexer(stream, logger), logger).program(), logger);
}
/**
* Compiles and runs a program from a string, logging to {@link #log} and
* writing lines of output to {@link #out}.
*
* @param code
* the code to type check
* @param input
* lines of input for the program
*/
public static void compileAndRunString(String code, String... input) {
compileAndRun(ToolChain.getCharStream(code), String.join("\n", input) + "\n");
}
/**
* Compiles and runs a program from a file, logging to {@link #log} and
* writing lines of output to {@link #out}.
*
* @param file
* the file name
* @param input
* lines of input for the program
*/
public static void compileAndRunFile(String file, String... input) {
compileAndRun(ToolChain.getCharStream(TEST_PROGRAM_LOCATION.resolve(file)), String.join("\n", input) + "\n");
}
private static void compileAndRun(CharStream stream, String input) {
Logger logger = Logger.getAnonymousLogger();
log = ToolChain.makeListLog(logger);
logger.setLevel(logLevel);
ParseTree ast = ToolChain.getParser(ToolChain.getLexer(stream, logger), logger).program();
Annotations annotations = ToolChain.getAnnotations(ast, logger);
Program program = ToolChain.getILOC(ast, logger, annotations);
if (Simulator.DEBUG)
System.out.println(program.prettyPrint());
out = ToolChain.execute(program, logger, input).split("\n");
}
/**
* Compiles and runs a program in debug mode, printing the generated
* program, a simulation of the program and all log messages.
*
* @param stream
* a character stream with the program code
* @param input
* lines of input for the program
*/
public static void debugRun(CharStream stream, String... input) {
Simulator.DEBUG = true;
String in = String.join("\n", input) + "\n";
Logger logger = Logger.getAnonymousLogger();
log = ToolChain.makeListLog(logger);
logger.setLevel(Level.FINEST);
ParseTree ast = ToolChain.getParser(ToolChain.getLexer(stream, logger), logger).program();
Annotations annotations = ToolChain.getAnnotations(ast, logger);
Program program = ToolChain.getILOC(ast, logger, annotations);
System.out.println(program.prettyPrint());
out = ToolChain.execute(program, logger, in).split("\n");
log.forEach((entry) -> System.out.println(entry.getMessage()));
Simulator.DEBUG = false;
}
}

View File

@ -16,11 +16,11 @@ public class ConditionalTest {
*/ */
@Test @Test
public void correctConditionalParsing() { public void correctConditionalParsing() {
AllTests.parseFile("if.boppi"); BoppiTests.parseFile("if.boppi");
assertThat(AllTests.log, empty()); assertThat(BoppiTests.log, empty());
AllTests.parseFile("while.boppi"); BoppiTests.parseFile("while.boppi");
assertThat(AllTests.log, empty()); assertThat(BoppiTests.log, empty());
} }
/** /**
@ -28,11 +28,11 @@ public class ConditionalTest {
*/ */
@Test @Test
public void correctConditionalChecking() { public void correctConditionalChecking() {
AllTests.checkFile("if.boppi"); BoppiTests.checkFile("if.boppi");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.checkFile("while.boppi"); BoppiTests.checkFile("while.boppi");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
} }
/** /**
@ -40,29 +40,29 @@ public class ConditionalTest {
*/ */
@Test @Test
public void basicPrograms() { public void basicPrograms() {
AllTests.compileAndRunString("print(5*3)"); BoppiTests.compileAndRunString("print(5*3)");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
assertThat(AllTests.out, is(arrayContaining("15"))); assertThat(BoppiTests.out, is(arrayContaining("15")));
AllTests.compileAndRunString("print({var int x; x := 8; print(x)})"); BoppiTests.compileAndRunString("print({var int x; x := 8; print(x)})");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
assertThat(AllTests.out, is(arrayContaining("8", "8"))); assertThat(BoppiTests.out, is(arrayContaining("8", "8")));
AllTests.compileAndRunString("print('T', 'e', 's', 't', '!')"); BoppiTests.compileAndRunString("print('T', 'e', 's', 't', '!')");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
assertThat(AllTests.out, is(arrayContaining("T", "e", "s", "t", "!"))); assertThat(BoppiTests.out, is(arrayContaining("T", "e", "s", "t", "!")));
AllTests.compileAndRunString("var int x; var int y; x := 3*(y := 4); print(x,y)"); BoppiTests.compileAndRunString("var int x; var int y; x := 3*(y := 4); print(x,y)");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
assertThat(AllTests.out, is(arrayContaining("12", "4"))); assertThat(BoppiTests.out, is(arrayContaining("12", "4")));
AllTests.compileAndRunFile("basicProgram1.boppi", "1", "T"); BoppiTests.compileAndRunFile("basicProgram1.boppi", "1", "T");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
assertThat(AllTests.out, is(arrayContaining("T", "T"))); assertThat(BoppiTests.out, is(arrayContaining("T", "T")));
AllTests.compileAndRunFile("fibonacciIterative.boppi", "6"); BoppiTests.compileAndRunFile("fibonacciIterative.boppi", "6");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
assertThat(AllTests.out, is(arrayContaining("13"))); assertThat(BoppiTests.out, is(arrayContaining("13")));
} }
} }

View File

@ -20,8 +20,8 @@ public class ExpressionTest {
*/ */
@Test @Test
public void correctExpressionParsing() { public void correctExpressionParsing() {
AllTests.parseFile("simpleExpression.boppi"); BoppiTests.parseFile("simpleExpression.boppi");
assertThat(AllTests.log, empty()); assertThat(BoppiTests.log, empty());
} }
/** /**
@ -29,20 +29,20 @@ public class ExpressionTest {
*/ */
@Test @Test
public void wrongExpressionParsing() { public void wrongExpressionParsing() {
AllTests.parseString(""); BoppiTests.parseString("");
assertThat(AllTests.log, not(empty())); assertThat(BoppiTests.log, not(empty()));
AllTests.parseString("~"); BoppiTests.parseString("~");
assertThat(AllTests.log, not(empty())); assertThat(BoppiTests.log, not(empty()));
AllTests.parseString("0A"); BoppiTests.parseString("0A");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.parseString("do"); BoppiTests.parseString("do");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.parseString("true true"); BoppiTests.parseString("true true");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
} }
/** /**
@ -50,8 +50,8 @@ public class ExpressionTest {
*/ */
@Test @Test
public void correctExpressionChecking() { public void correctExpressionChecking() {
AllTests.checkFile("simpleExpression.boppi"); BoppiTests.checkFile("simpleExpression.boppi");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
} }
/** /**
@ -59,20 +59,20 @@ public class ExpressionTest {
*/ */
@Test @Test
public void wrongExpressionChecking() { public void wrongExpressionChecking() {
AllTests.checkString("+true"); BoppiTests.checkString("+true");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.checkString("5 || true"); BoppiTests.checkString("5 || true");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.checkString("6 + 'c'"); BoppiTests.checkString("6 + 'c'");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.checkString("4 + print(5, 6)"); BoppiTests.checkString("4 + print(5, 6)");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.checkString("print(print(3, 5))"); BoppiTests.checkString("print(print(3, 5))");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
} }
/** /**
@ -80,16 +80,16 @@ public class ExpressionTest {
*/ */
@Test @Test
public void correctExpressionGeneration() { public void correctExpressionGeneration() {
AllTests.compileAndRunFile("simpleExpression.boppi"); BoppiTests.compileAndRunFile("simpleExpression.boppi");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
int n = 12; int n = 12;
String expression = StringUtils.repeat("1+(", n) + "1" + StringUtils.repeat(")", n); String expression = StringUtils.repeat("1+(", n) + "1" + StringUtils.repeat(")", n);
AllTests.withWarnings(() -> { BoppiTests.withWarnings(() -> {
AllTests.compileAndRunString("print(" + expression + ")", "" + n); BoppiTests.compileAndRunString("print(" + expression + ")", "" + n);
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
assertThat(AllTests.log.get(0).getLevel(), is(Level.WARNING)); assertThat(BoppiTests.log.get(0).getLevel(), is(Level.WARNING));
}); });
} }
@ -98,9 +98,9 @@ public class ExpressionTest {
*/ */
@Test @Test
public void wrongExpressionGeneration() { public void wrongExpressionGeneration() {
AllTests.compileAndRunString("1/0"); BoppiTests.compileAndRunString("1/0");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
assertThat(AllTests.log.get(0).getMessage(), containsString("zero")); assertThat(BoppiTests.log.get(0).getMessage(), containsString("zero"));
} }
} }

View File

@ -0,0 +1,214 @@
package pp.s1184725.boppi.test;
import java.nio.file.Files;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import org.junit.Test;
import pp.iloc.*;
import pp.iloc.eval.Machine;
import pp.iloc.model.Program;
/**
* Tests for the ILOC simple allocator (memlib.iloc).
*
* @author Frank Wibbelink
*/
public class ILOCAllocatorTest {
/**
* Default free memory (free slot size must report this value when nothing
* is allocated).
*/
private static final int FREEMEM = 0XFF;
private static final int HEADER = 8;
private static final int FIRSTPTR = 28;
private static final int FIRSTSLOT = 32;
private static final int SIZEOFFSET = 4;
private static final int REFOFFSET = -8;
private static final String obj1 = "r_obj1", obj2 = "r_obj2", obj3 = "r_obj3";
private int returnAddress = 0;
private Machine vm;
private String malloc(int size, String reg) {
returnAddress++;
String s = "";
s += "loadI 0 => r0\n";
s += "loadI " + size + " => r1\n";
s += "storeAI r1 => r0, 4\n";
s += "loadI #end" + returnAddress + " => r2\n";
s += "storeAI r2 => r0, 0\n";
s += "jumpI -> memalloc\n";
s += "end" + returnAddress + ": loadAI r0,16 => " + reg + "\n";
if (size >= 4) {
s += "loadI " + 0xDADACAFE + " => r2\n";
s += "storeAI r2 => " + reg + ",0\n";
}
s += "\n";
return s;
}
private String incRef(String reg) {
returnAddress++;
String s = "";
s += "loadI 0 => r0\n";
s += "storeAI " + reg + " => r0, 4\n";
s += "loadI #end" + returnAddress + " => r2\n";
s += "storeAI r2 => r0, 0\n";
s += "jumpI -> memaddref\n";
s += "end" + returnAddress + ": nop\n";
s += "\n";
return s;
}
private String free(String reg) {
returnAddress++;
String s = "";
s += "loadI 0 => r0\n";
s += "storeAI " + reg + " => r0, 4\n";
s += "loadI #end" + returnAddress + " => r2\n";
s += "storeAI r2 => r0, 0\n";
s += "jumpI -> memfree\n";
s += "end" + returnAddress + ": nop\n";
s += "\n";
return s;
}
private void sim(String code) {
try {
String memlib = new String(
Files.readAllBytes(BoppiTests.TEST_PROGRAM_LOCATION.resolve("../../memlib.iloc")));
Program program = Assembler.instance().assemble(memlib + "\n" + code);
vm = new Machine();
vm.setSize((FREEMEM + FIRSTSLOT + HEADER) * 2);
Simulator sim = new Simulator(program, vm);
sim.run();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Tests whether memlib correctly compiles and initialises.
*/
@Test
public void compileAndRun() {
sim("");
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(FIRSTPTR), is(FIRSTSLOT));
assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM));
}
/**
* Tests whether memlib correctly allocates and deallocates at the end of
* used memory Tests whether memlib correctly merges deallocated memory with
* the free slot at the end.
*/
@Test
public void simpleMallocFree() {
sim(malloc(11, obj1));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(FIRSTPTR), is(not(FIRSTSLOT)));
assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM - (HEADER + 11)));
sim(malloc(11, obj1) + malloc(7, obj2));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(FIRSTPTR), is(not(FIRSTSLOT)));
assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM - (HEADER + 11) - (HEADER + 7)));
sim(malloc(11, obj1) + free(obj1));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM));
sim(malloc(11, obj1) + malloc(7, obj2) + free(obj2));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
sim(malloc(11, obj1) + malloc(7, obj2) + free(obj2) + free(obj1));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
}
/**
* Tests whether memlib correctly halts the machine if freeing wrong
* addresses or using null pointers.
*/
@Test
public void incorrectTests() {
sim(malloc(11, obj1) + free(obj1) + free(obj1));
assertThat(vm.getInterrupt(), is(not(0)));
sim(malloc(11, obj1) + incRef("r0"));
assertThat(vm.getInterrupt(), is(not(0)));
sim(malloc(11, obj1) + free("r0"));
assertThat(vm.getInterrupt(), is(not(0)));
}
/**
* Tests whether memlib correctly allocates and deallocates memory in the
* middle of used memory. Tests whether memlib correctly merges deallocated
* memory with previous and next slots. Tests whether memlib correctly uses
* free slots that are either exactly the requested size or fit at least a
* new free slot.
*/
@Test
public void discontinuousMemory() {
sim(malloc(11, obj1) + malloc(7, obj2) + free(obj1));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(vm.load(FIRSTPTR)), is(not(0)));
sim(malloc(11, obj1) + malloc(7, obj2) + free(obj1) + free(obj2));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM));
sim(malloc(11, obj1) + malloc(7, obj2) + free(obj1) + malloc(11, obj3));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM - (HEADER + 11) - (HEADER + 7)));
sim(malloc(11, obj1) + malloc(7, obj2) + free(obj1) + malloc(10, obj3));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(vm.load(FIRSTPTR)), is(not(0)));
assertThat(vm.load(vm.load(vm.load(FIRSTPTR)) + SIZEOFFSET),
is(FREEMEM - (HEADER + 11) - (HEADER + 7) - (HEADER + 10)));
sim(malloc(11, obj1) + malloc(7, obj2) + free(obj1) + malloc(1, obj3) + malloc(2, obj1));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(FIRSTPTR), is(not(FIRSTSLOT)));
assertThat(vm.load(vm.load(FIRSTPTR)), is(0));
assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM - (HEADER + 1) - (HEADER + 2) - (HEADER + 7)));
}
/**
* Tests whether memlib correctly tracks reference counts.
*/
@Test
public void referenceCounting() {
sim(malloc(11, obj1));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(vm.getReg(obj1) + REFOFFSET), is(1));
sim(malloc(11, obj1) + incRef(obj1));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(vm.getReg(obj1) + REFOFFSET), is(2));
sim(malloc(11, obj1) + incRef(obj1) + free(obj1));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(FIRSTPTR), is(not(FIRSTSLOT)));
assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(not(FREEMEM)));
sim(malloc(11, obj1) + incRef(obj1) + free(obj1) + free(obj1));
assertThat(vm.getInterrupt(), is(0));
assertThat(vm.load(FIRSTPTR), is(FIRSTSLOT));
assertThat(vm.load(vm.load(FIRSTPTR) + SIZEOFFSET), is(FREEMEM));
}
}

View File

@ -16,23 +16,23 @@ public class SimpleFunctionTest {
*/ */
@Test @Test
public void correctFunctionParsing() { public void correctFunctionParsing() {
AllTests.parseString("function int id(int a) a; 1"); BoppiTests.parseString("function int id(int a) a; 1");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.parseString("function int id(int a) a; id(1)"); BoppiTests.parseString("function int id(int a) a; id(1)");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.parseString("var int a; a := 1; function int const() a; const()"); BoppiTests.parseString("var int a; a := 1; function int const() a; const()");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.parseString("function int add(int a, int b) a+b; print(add(4, 5)+1)"); BoppiTests.parseString("function int add(int a, int b) a+b; print(add(4, 5)+1)");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.parseString("function char const(char a, char b) a; print(const('A', 'T'))"); BoppiTests.parseString("function char const(char a, char b) a; print(const('A', 'T'))");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.parseString("function int factorial(int n) {if (n > 1) then n*factorial(n-1) else 1 fi}"); BoppiTests.parseString("function int factorial(int n) {if (n > 1) then n*factorial(n-1) else 1 fi}");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
} }
/** /**
@ -40,14 +40,14 @@ public class SimpleFunctionTest {
*/ */
@Test @Test
public void wrongFunctionParsing() { public void wrongFunctionParsing() {
AllTests.parseString("function doNothing(int a, char b) a+b; print(add(4, 'T')+1)"); BoppiTests.parseString("function doNothing(int a, char b) a+b; print(add(4, 'T')+1)");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.parseString("function char add(int a, int b);"); BoppiTests.parseString("function char add(int a, int b);");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.parseString("function int add(int a, int b) a+b; add(4,)"); BoppiTests.parseString("function int add(int a, int b) a+b; add(4,)");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
} }
/** /**
@ -55,23 +55,23 @@ public class SimpleFunctionTest {
*/ */
@Test @Test
public void correctFunctionChecking() { public void correctFunctionChecking() {
AllTests.checkString("function int id(int a) a; 1"); BoppiTests.checkString("function int id(int a) a; 1");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.checkString("function int id(int a) a; id(1); 1"); BoppiTests.checkString("function int id(int a) a; id(1); 1");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.checkString("var int a; function int const() a; 1"); BoppiTests.checkString("var int a; function int const() a; 1");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.checkString("function int add(int a, int b) a+b; print(add(4, 5)+1)"); BoppiTests.checkString("function int add(int a, int b) a+b; print(add(4, 5)+1)");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.checkString("function char const(char a, char b) a; print(const('A', 'T'))"); BoppiTests.checkString("function char const(char a, char b) a; print(const('A', 'T'))");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.checkString("function int factorial(int n) {if (n > 1) then n*factorial(n-1) else 1 fi}"); BoppiTests.checkString("function int factorial(int n) {if (n > 1) then n*factorial(n-1) else 1 fi}; 0");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
} }
/** /**
@ -79,14 +79,14 @@ public class SimpleFunctionTest {
*/ */
@Test @Test
public void wrongFunctionChecking() { public void wrongFunctionChecking() {
AllTests.checkString("function int add(int a, char b) a+b; print(add(4, 'T')+1)"); BoppiTests.checkString("function int add(int a, char b) a+b; print(add(4, 'T')+1)");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.checkString("function char add(int a, int b) a+b; print(add(4, 5))"); BoppiTests.checkString("function char add(int a, int b) a+b; print(add(4, 5))");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.checkString("function int add(int a, int b) a+b; print(add(4, 'T')+1)"); BoppiTests.checkString("function int add(int a, int b) a+b; print(add(4, 'T')+1)");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
} }
/** /**
@ -94,26 +94,26 @@ public class SimpleFunctionTest {
*/ */
@Test @Test
public void correctSimpleFunctionGeneration() { public void correctSimpleFunctionGeneration() {
AllTests.compileAndRunString("function int id(int a) a; 1"); BoppiTests.compileAndRunString("function int id(int a) a; 1");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.compileAndRunString("function int id(int a) a; id(1); 1"); BoppiTests.compileAndRunString("function int id(int a) a; id(1); 1");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.compileAndRunString("var int a; function int const(int b) a; 1"); BoppiTests.compileAndRunString("var int a; function int const(int b) a; 1");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.compileAndRunString("var int a; a := 1; function int const(int b) a; print(const(4))"); BoppiTests.compileAndRunString("var int a; a := 1; function int const(int b) a; print(const(4))");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
assertThat(AllTests.out, is(arrayContaining("1"))); assertThat(BoppiTests.out, is(arrayContaining("1")));
AllTests.compileAndRunString("function int add(int a, int b) a+b; print(add(4, 5)+1)"); BoppiTests.compileAndRunString("function int add(int a, int b) a+b; print(add(4, 5)+1)");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
assertThat(AllTests.out, is(arrayContaining("10"))); assertThat(BoppiTests.out, is(arrayContaining("10")));
AllTests.compileAndRunString("function char const(char a, char b) a; print(const('A', 'T'))"); BoppiTests.compileAndRunString("function char const(char a, char b) a; print(const('A', 'T'))");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
assertThat(AllTests.out, is(arrayContaining("A"))); assertThat(BoppiTests.out, is(arrayContaining("A")));
} }
/** /**
@ -122,16 +122,16 @@ public class SimpleFunctionTest {
*/ */
@Test @Test
public void correctComplexFunctionGeneration() { public void correctComplexFunctionGeneration() {
AllTests.compileAndRunString("function int factorial(int n) {if (n > 1) then n*factorial(n-1) else 1 fi}"); BoppiTests.compileAndRunString("function int factorial(int n) {if (n > 1) then n*factorial(n-1) else 1 fi}; 0");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
AllTests.compileAndRunFile("recursiveFactorial.boppi", "5"); BoppiTests.compileAndRunFile("recursiveFactorial.boppi", "5");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
assertThat(AllTests.out, is(arrayContaining("120"))); assertThat(BoppiTests.out, is(arrayContaining("120")));
AllTests.compileAndRunFile("simpleFunctionPassing.boppi"); BoppiTests.compileAndRunFile("simpleFunctionPassing.boppi");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
assertThat(AllTests.out, is(arrayContaining("40", "104", "1", "2", "8"))); assertThat(BoppiTests.out, is(arrayContaining("40", "104", "1", "2", "8")));
} }
} }

View File

@ -16,8 +16,8 @@ public class SimpleVariableTest {
*/ */
@Test @Test
public void correctVariableParsing() { public void correctVariableParsing() {
AllTests.parseFile("simpleVariable.boppi"); BoppiTests.parseFile("simpleVariable.boppi");
assertThat(AllTests.log, empty()); assertThat(BoppiTests.log, empty());
} }
/** /**
@ -25,17 +25,17 @@ public class SimpleVariableTest {
*/ */
@Test @Test
public void wrongVariableParsing() { public void wrongVariableParsing() {
AllTests.parseString("var"); BoppiTests.parseString("var");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.parseString("var bool 5"); BoppiTests.parseString("var bool 5");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.parseString("var 'c' varname"); BoppiTests.parseString("var 'c' varname");
assertThat(AllTests.log, not(empty())); assertThat(BoppiTests.log, not(empty()));
AllTests.parseString("var bool; true true;"); BoppiTests.parseString("var bool; true true;");
assertThat(AllTests.log, hasSize(2)); assertThat(BoppiTests.log, hasSize(2));
} }
/** /**
@ -43,8 +43,8 @@ public class SimpleVariableTest {
*/ */
@Test @Test
public void correctScopeParsing() { public void correctScopeParsing() {
AllTests.parseFile("simpleScope.boppi"); BoppiTests.parseFile("simpleScope.boppi");
assertThat(AllTests.log, empty()); assertThat(BoppiTests.log, empty());
} }
/** /**
@ -52,8 +52,8 @@ public class SimpleVariableTest {
*/ */
@Test @Test
public void correctVariableChecking() { public void correctVariableChecking() {
AllTests.checkFile("simpleVariable.boppi"); BoppiTests.checkFile("simpleVariable.boppi");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
} }
/** /**
@ -61,20 +61,20 @@ public class SimpleVariableTest {
*/ */
@Test @Test
public void wrongVariableChecking() { public void wrongVariableChecking() {
AllTests.checkString("var bool name; name := 5"); BoppiTests.checkString("var bool name; name := 5");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.checkString("undefinedName"); BoppiTests.checkString("undefinedName");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.checkString("var undefinedType name; 1"); BoppiTests.checkString("var undefinedType name; 1");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.checkString("var bool endsWithDeclaration;"); BoppiTests.checkString("var bool endsWithDeclaration;");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.checkString("var bool var1; var var1 var2; var2 := 'c' "); BoppiTests.checkString("var bool var1; var var1 var2; var2 := 'c' ");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
} }
/** /**
@ -82,8 +82,8 @@ public class SimpleVariableTest {
*/ */
@Test @Test
public void correctScopeChecking() { public void correctScopeChecking() {
AllTests.checkFile("simpleScope.boppi"); BoppiTests.checkFile("simpleScope.boppi");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
} }
/** /**
@ -91,14 +91,14 @@ public class SimpleVariableTest {
*/ */
@Test @Test
public void wrongScopeChecking() { public void wrongScopeChecking() {
AllTests.checkString("var bool var1; var bool var1; 1"); BoppiTests.checkString("var bool var1; var bool var1; 1");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.checkString("var bool var1; var char var1; 1"); BoppiTests.checkString("var bool var1; var char var1; 1");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
AllTests.checkString("{ var int var1; var1 := 4}; var int var2; var1"); BoppiTests.checkString("{ var int var1; var1 := 4}; var int var2; var1");
assertThat(AllTests.log, hasSize(1)); assertThat(BoppiTests.log, hasSize(1));
} }
/** /**
@ -106,8 +106,8 @@ public class SimpleVariableTest {
*/ */
@Test @Test
public void correctVariableGeneration() { public void correctVariableGeneration() {
AllTests.compileAndRunFile("simpleVariable.boppi"); BoppiTests.compileAndRunFile("simpleVariable.boppi");
assertThat(AllTests.log, is(empty())); assertThat(BoppiTests.log, is(empty()));
} }
} }