added modified ILOC environment

This commit is contained in:
User 2017-07-22 15:52:00 +02:00
parent f9110893c6
commit e9b91f2152
22 changed files with 2991 additions and 1 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
bin/
src/pp/iloc
src/pp/iloc/sample/

398
src/pp/iloc/Assembler.java Normal file
View File

@ -0,0 +1,398 @@
package pp.iloc;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeProperty;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.antlr.v4.runtime.tree.TerminalNode;
import pp.iloc.model.Instr;
import pp.iloc.model.Label;
import pp.iloc.model.Num;
import pp.iloc.model.Op;
import pp.iloc.model.OpCode;
import pp.iloc.model.OpList;
import pp.iloc.model.Operand;
import pp.iloc.model.Operand.Type;
import pp.iloc.model.Program;
import pp.iloc.model.Reg;
import pp.iloc.model.Str;
import pp.iloc.parse.ErrorListener;
import pp.iloc.parse.FormatException;
import pp.iloc.parse.ILOCBaseListener;
import pp.iloc.parse.ILOCLexer;
import pp.iloc.parse.ILOCParser;
import pp.iloc.parse.ILOCParser.CommentContext;
import pp.iloc.parse.ILOCParser.DeclContext;
import pp.iloc.parse.ILOCParser.InstrContext;
import pp.iloc.parse.ILOCParser.InstrListContext;
import pp.iloc.parse.ILOCParser.OpCodeContext;
import pp.iloc.parse.ILOCParser.OperandContext;
import pp.iloc.parse.ILOCParser.ProgramContext;
import pp.iloc.parse.ILOCParser.RealOpContext;
import pp.iloc.parse.ILOCParser.SingleInstrContext;
import pp.iloc.parse.ILOCParser.SourcesContext;
import pp.iloc.parse.ILOCParser.TargetsContext;
/** Assembler for the ILOC language. */
public class Assembler {
private final ILOCWalker walker;
/** Constructor for the singleton instance. */
private Assembler() {
this.walker = new ILOCWalker();
}
/** Parses a given ILOC program given as a string,
* and returns the parsed program.
* @throws FormatException if there was an error parsing the program
*/
public Program assemble(String program) throws FormatException {
return assemble(CharStreams.fromString(program));
}
/** Parses a given ILOC program given as a file,
* and returns the parsed program.
* @throws FormatException if there was an error parsing the program
*/
public Program assemble(File file) throws FormatException, IOException {
return assemble(CharStreams.fromPath(file.toPath()));
}
/** Parses a given ILOC program given as a character stream,
* and returns the parsed program.
* @throws FormatException if there was an error parsing the program
*/
public Program assemble(CharStream chars) throws FormatException {
ErrorListener listener = new ErrorListener();
Lexer lexer = new ILOCLexer(chars);
lexer.removeErrorListeners();
lexer.addErrorListener(listener);
TokenStream tokens = new CommonTokenStream(lexer);
ILOCParser parser = new ILOCParser(tokens);
parser.removeErrorListeners();
parser.addErrorListener(listener);
ParseTree tree = parser.program();
if (listener.hasErrors()) {
throw new FormatException(listener.getErrors());
}
return assemble(tree);
}
/** Parses a given ILOC program given as a parse tree,
* and returns the parsed program.
* @throws FormatException if there was an error parsing the program
*/
public Program assemble(ParseTree tree) throws FormatException {
Program result = this.walker.walk(tree);
result.check();
return result;
}
/** Returns the singleton assembler instance. */
public static final Assembler instance() {
return INSTANCE;
}
private static final Assembler INSTANCE = new Assembler();
private static class ILOCWalker extends ILOCBaseListener {
/** The program to be built. */
private Program program;
/** Instructions associated with @{code instr} parse nodes. */
private ParseTreeProperty<Instr> instrs;
/** Operations associated with @{code op} parse nodes. */
private ParseTreeProperty<Op> ops;
/** Operands associated with @{code operand} parse nodes. */
private ParseTreeProperty<Operand> operands;
/** Operand lists associated with @{code sources} parse nodes. */
private ParseTreeProperty<List<Operand>> sources;
/** Operand lists associated with targets parse nodes. */
private ParseTreeProperty<List<Operand>> targets;
/** Mapping of labels to place of declaration. */
private Map<Label, Token> labelMap;
/** Mapping of symbolic constants to place of initialisation. */
private Map<Num, Token> symbolMap;
/** The error listener of this walker. */
private ErrorListener errors;
public Program walk(ParseTree tree) throws FormatException {
// initialise the data structures
this.program = new Program();
this.instrs = new ParseTreeProperty<>();
this.ops = new ParseTreeProperty<>();
this.operands = new ParseTreeProperty<>();
this.sources = new ParseTreeProperty<>();
this.targets = new ParseTreeProperty<>();
this.labelMap = new HashMap<>();
this.symbolMap = new HashMap<>();
this.errors = new ErrorListener();
new ParseTreeWalker().walk(this, tree);
if (this.errors.hasErrors()) {
throw new FormatException(this.errors.getErrors());
}
return this.program;
}
@Override
public void exitDecl(DeclContext ctx) {
Num symbol = new Num(ctx.ID().getText());
if (addSymbol(ctx.getStart(), symbol)) {
this.program.setSymb(symbol,
Integer.parseInt(ctx.NUM().getText()));
}
}
@Override
public void exitInstrList(InstrListContext ctx) {
OpList result = new OpList();
if (ctx.label() != null) {
Label label = new Label(ctx.label().getText());
if (addLabel(ctx.getStart(), label)) {
result.setLabel(label);
}
}
for (ParseTree op : ctx.op()) {
Op instr = getOp(op);
// op may be null if there was a format error
if (instr != null) {
result.addOp(instr);
}
}
addInstr(ctx, result);
}
@Override
public void exitSingleInstr(SingleInstrContext ctx) {
Instr result = getOp(ctx.op());
// op may be null if there was a format error
if (result != null) {
if (ctx.label() != null) {
Label label = new Label(ctx.label().getText());
if (addLabel(ctx.getStart(), label)) {
result.setLabel(label);
}
}
addInstr(ctx, result);
}
}
@Override
public void exitComment(CommentContext ctx) {
Op result = new Op(OpCode.comment);
String comment = ctx.COMMENT().getText();
comment = comment.substring(2).trim();
result.setComment(comment);
addOp(ctx, result);
}
@Override
public void exitRealOp(RealOpContext ctx) {
OpCodeContext opCodeTree = ctx.opCode();
OpCode code = OpCode.parse(opCodeTree.getText());
if (code == null) {
this.errors.visitError(
opCodeTree.getStart(),
"Unrecognized opcode '%s'", opCodeTree.getText());
return;
}
// collect operands
List<Operand> opnds = new ArrayList<>();
try {
List<Operand> sources = getSources(ctx.sources());
opnds.addAll(getOpnds(code, "source", code.getSourceSig(),
sources));
List<Operand> targets = getTargets(ctx.targets());
opnds.addAll(getOpnds(code, "target", code.getTargetSig(),
targets));
} catch (FormatException e) {
this.errors.visitError(opCodeTree.getStart(), e.getMessage());
return;
}
// check for correct arrow symbol
if (!code.getTargetSig().isEmpty()) {
String expected = code.getClaz().getArrow();
TerminalNode arrowToken = ctx.DARROW() == null ? ctx.ARROW() : ctx.DARROW();
String actual = arrowToken.getText();
if (!expected.equals(actual)) {
this.errors.visitError(arrowToken.getSymbol(),
"Expected '%s' but found '%s'", expected, actual);
}
}
Op result = new Op(code, opnds);
if (ctx.COMMENT() != null) {
String comment = ctx.COMMENT().getText();
comment = comment.substring(2).trim();
result.setComment(comment);
}
addOp(ctx, result);
}
private List<Operand> getOpnds(OpCode code, String role,
List<Type> sig, List<Operand> orig) throws FormatException {
List<Operand> result = new ArrayList<>();
if (orig == null) {
orig = Collections.emptyList();
}
if (orig.size() != sig.size()) {
throw new FormatException(
"Opcode '%s' expects %d %s parameters, but has %d",
code.name(), sig.size(), role, orig.size());
}
for (int i = 0; i < orig.size(); i++) {
Operand opnd = orig.get(i);
Type actualType = opnd.getType();
Type expectedType = sig.get(i);
if (expectedType == Type.REG && actualType == Type.LABEL) {
opnd = new Reg(((Label) opnd).getValue());
} else if (expectedType != actualType) {
throw new FormatException(
"Opcode '%s' %s operand %d should be %s but is %s",
code.name(), role, i, expectedType.name(),
actualType.name());
}
result.add(opnd);
}
return result;
}
@Override
public void exitProgram(ProgramContext ctx) {
for (InstrContext instr : ctx.instr()) {
Instr i = getInstr(instr);
if (i != null) {
this.program.addInstr(i);
}
}
}
@Override
public void exitSources(SourcesContext ctx) {
List<Operand> result = new ArrayList<>();
for (ParseTree o : ctx.operand()) {
result.add(getOperand(o));
}
addSources(ctx, result);
}
@Override
public void exitTargets(TargetsContext ctx) {
List<Operand> result = new ArrayList<>();
for (ParseTree o : ctx.operand()) {
result.add(getOperand(o));
}
addTargets(ctx, result);
}
@Override
public void exitOperand(OperandContext ctx) {
Operand result;
if (ctx.STR() != null) {
String str = ctx.STR().getText();
result = new Str(str.substring(1, str.length() - 1).replaceAll(
"\\\"", "\""));
} else if (ctx.NUM() != null) {
result = new Num(Integer.parseInt(ctx.NUM().getText()));
} else if (ctx.SYMB() != null) {
result = new Num(ctx.SYMB().getText().substring(1));
} else if (ctx.LAB() != null) {
result = new Num(
new Label(ctx.LAB().getText().substring(1)));
} else {
result = new Label(ctx.ID().getText());
}
addOperand(ctx, result);
}
/** Sets the instruction attribute of a given node. */
private void addInstr(ParseTree node, Instr instr) {
this.instrs.put(node, instr);
}
/** Returns the instruction attribute of a given node. */
private Instr getInstr(ParseTree node) {
return this.instrs.get(node);
}
/** Sets the operation attribute of a given node. */
private void addOp(ParseTree node, Op op) {
this.ops.put(node, op);
}
/** Returns the operation attribute of a given node. */
private Op getOp(ParseTree node) {
return this.ops.get(node);
}
/** Sets the operand attribute of a given node. */
private void addOperand(ParseTree node, Operand operand) {
this.operands.put(node, operand);
}
/** Returns the operand attribute of a given node. */
private Operand getOperand(ParseTree node) {
return this.operands.get(node);
}
/** Sets the source operand list attribute of a given node. */
private void addSources(ParseTree node, List<Operand> sources) {
this.sources.put(node, sources);
}
/** Returns the source operand list attribute of a given node. */
private List<Operand> getSources(ParseTree node) {
return this.sources.get(node);
}
/** Sets the target operand list attribute of a given node. */
private void addTargets(ParseTree node, List<Operand> targets) {
this.targets.put(node, targets);
}
/** Returns the target operand list attribute of a given node. */
private List<Operand> getTargets(ParseTree node) {
return this.targets.get(node);
}
/** Registers label definition. Signals an error if the label
* was already defined.
* @return {@code true} if the label was new
*/
private boolean addLabel(Token token, Label label) {
Token oldToken = this.labelMap.put(label, token);
if (oldToken != null) {
this.errors.visitError(token,
"Label '%s' already defined at line %d", label,
oldToken.getLine());
}
return oldToken == null;
}
/** Registers a symbol initialisation. Signals an error if the
* symbol was already initialised.
* @return {@code true} if the symbol was new
*/
private boolean addSymbol(Token token, Num symbol) {
Token oldToken = this.symbolMap.put(symbol, token);
if (oldToken != null) {
this.errors.visitError(token,
"Symbolic constant '%s' already defined at line %d",
symbol, oldToken.getLine());
}
return oldToken == null;
}
}
}

478
src/pp/iloc/Simulator.java Normal file
View File

@ -0,0 +1,478 @@
package pp.iloc;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Scanner;
import pp.iloc.eval.Machine;
import pp.iloc.model.Label;
import pp.iloc.model.Num;
import pp.iloc.model.Op;
import pp.iloc.model.OpClaz;
import pp.iloc.model.OpCode;
import pp.iloc.model.Program;
import pp.iloc.parse.FormatException;
/**
* ILOC program simulator
* @author Arend Rensink
*/
public class Simulator {
/** Representation of <code>true</code>. */
public static final int TRUE = -1;
/** Representation of <code>false</code>. */
public static final int FALSE = 0;
/** Flag controlling debug mode. */
public static boolean DEBUG = false;
static public void main(String[] args) {
if (args.length == 0) {
System.err.println("Usage: filename.iloc");
return;
}
try {
Program prog = Assembler.instance().assemble(new File(args[0]));
new Simulator(prog).run();
} catch (FormatException | IOException exc) {
exc.printStackTrace();
}
}
/** The simulated program. */
private final Program prg;
/** The virtual machine on which the program is run. */
private final Machine vm;
/** Flag signifying that the input is from stdin. */
private boolean stdIn;
/** The reader used for the in-operations. */
private Scanner in;
/** The print writer used for the out-operations. */
private PrintStream out;
/** Constructs a simulator for a given program and VM. */
public Simulator(Program program, Machine vm) {
assert program != null;
assert vm != null;
this.prg = program;
this.vm = vm;
this.out = System.out;
this.in = new Scanner(System.in);
this.stdIn = true;
}
/** Constructs a simulator for a given program and a fresh VM. */
public Simulator(Program program) {
this(program, new Machine());
}
/** Returns the program wrapped in this simulator. */
public Program getProgram() {
return this.prg;
}
/** Returns the Virtual Machine wrapped in this simulator. */
public Machine getVM() {
return this.vm;
}
/** Changes the input stream for the {@link OpCode#in}-operations. */
public void setIn(InputStream in) {
this.in = new Scanner(in);
this.stdIn = false;
}
/** Changes the output stream for the {@link OpCode#out}-operations. */
public void setOut(OutputStream out) {
this.out = new PrintStream(out);
}
/** Runs the program. */
public void run() {
while (this.vm.getPC() < this.prg.size() && this.vm.getInterrupt() == 0) {
step();
}
}
/** Executes one operation of the program. */
public void step() {
Op o = this.prg.getOpAt(this.vm.getPC());
OPContext c = new OPContext(o);
Machine vm = this.vm;
if (DEBUG) {
System.out.printf("Op %d: %s%n", vm.getPC(), o);
System.out.println(vm);
}
switch (o.getOpCode()) {
case nop:
// do nothing
break;
case add:
c.setReg(2, c.reg(0) + c.reg(1));
break;
case sub:
c.setReg(2, c.reg(0) - c.reg(1));
break;
case mult:
c.setReg(2, c.reg(0) * c.reg(1));
break;
case div:
c.setReg(2, c.reg(0) / c.reg(1));
break;
case addI:
c.setReg(2, c.reg(0) + c.num(1));
break;
case subI:
c.setReg(2, c.reg(0) - c.num(1));
break;
case rsubI:
c.setReg(2, c.num(1) - c.reg(0));
break;
case multI:
c.setReg(2, c.reg(0) * c.num(1));
break;
case divI:
c.setReg(2, c.reg(0) / c.num(1));
break;
case rdivI:
c.setReg(2, c.num(1) / c.reg(0));
break;
case lshift:
c.setReg(2, c.num(0) << c.reg(1));
break;
case lshiftI:
c.setReg(2, c.num(0) << c.num(1));
break;
case rshift:
c.setReg(2, c.num(0) >>> c.reg(1));
break;
case rshiftI:
c.setReg(2, c.num(0) >>> c.num(1));
break;
case and:
c.setReg(2, -1 * c.reg(0) * c.reg(1));
break;
case andI:
c.setReg(2, -1 * c.reg(0) * c.num(1));
break;
case or:
c.setReg(2, Math.max(-1, c.reg(0) + c.reg(1)));
break;
case orI:
c.setReg(2, Math.max(-1, c.reg(0) + c.num(1)));
break;
case xor:
c.setReg(2, Math.max(-1, c.reg(0) ^ c.reg(1)));
break;
case xorI:
c.setReg(2, Math.max(-1, c.reg(0) ^ c.num(1)));
break;
case load:
c.setReg(1, vm.load(c.reg(0)));
break;
case loadI:
c.setReg(1, c.num(0));
break;
case loadAI:
c.setReg(2, vm.load(c.reg(0) + c.num(1)));
break;
case loadAO:
c.setReg(2, vm.load(c.reg(0) + c.reg(1)));
break;
case store:
vm.store(c.reg(0), c.reg(1));
break;
case storeAI:
vm.store(c.reg(0), c.reg(1) + c.num(2));
break;
case storeAO:
vm.store(c.reg(0), c.reg(1) + c.reg(2));
break;
case cload:
c.setReg(1, vm.loadC(c.reg(0)));
break;
case cloadAI:
c.setReg(2, vm.loadC(c.reg(0) + c.num(1)));
break;
case cloadAO:
c.setReg(2, vm.loadC(c.reg(0) + c.reg(1)));
break;
case cstore:
vm.storeC(c.reg(0), c.reg(1));
break;
case cstoreAI:
vm.storeC(c.reg(0), c.reg(1) + c.num(2));
break;
case cstoreAO:
vm.storeC(c.reg(0), c.reg(1) + c.reg(2));
break;
case i2i:
c.setReg(1, c.reg(0));
break;
case i2c:
c.setReg(1, (byte) c.reg(0));
break;
case c2i:
c.setReg(1, (byte) c.reg(0));
break;
case c2c:
c.setReg(1, (byte) c.reg(0));
break;
case cmp_LT:
c.setReg(2, c.reg(0) < c.reg(1));
break;
case cmp_LE:
c.setReg(2, c.reg(0) <= c.reg(1));
break;
case cmp_EQ:
c.setReg(2, c.reg(0) == c.reg(1));
break;
case cmp_GE:
c.setReg(2, c.reg(0) >= c.reg(1));
break;
case cmp_GT:
c.setReg(2, c.reg(0) > c.reg(1));
break;
case cmp_NE:
c.setReg(2, c.reg(0) != c.reg(1));
break;
case cbr:
if (c.reg(0) == 0) {
this.vm.setPC(c.label(2));
} else {
this.vm.setPC(c.label(1));
}
break;
case jumpI:
this.vm.setPC(c.label(0));
break;
case jump:
this.vm.setPC(c.reg(0));
break;
case tbl:
// do nothing
break;
case push:
push(c.reg(0));
break;
case pop:
c.setReg(0, pop());
break;
case cpush:
pushC(c.reg(0));
break;
case cpop:
c.setReg(0, popC());
break;
case in:
String message = o.str(0).getText();
if (this.stdIn) {
this.out.print(message);
}
String in = this.in.nextLine();
int val = Integer.MAX_VALUE;
do {
try {
val = Integer.parseInt(in);
} catch (NumberFormatException e) {
// try again
if (this.stdIn) {
this.out.printf("Input '%s' should be a number%n", in);
this.out.print(message);
in = this.in.nextLine();
} else {
throw new IllegalArgumentException(String.format(
"Input '%s' should be a number%n", in));
}
}
} while (val == Integer.MAX_VALUE);
c.setReg(1, val);
break;
case out:
this.out.print(o.str(0).getText());
this.out.println(c.reg(1));
break;
case cin:
message = o.str(0).getText();
if (this.stdIn) {
this.out.print(message);
}
in = this.in.nextLine();
pushString(in);
break;
case cout:
this.out.print(o.str(0).getText());
this.out.println(popString());
break;
case comment:
// do nothing
break;
case halt:
this.vm.setInterrupt(c.reg(0));
System.err.printf(
"Program halted with code %d at line %d.%n",
c.num(0), o.getLine());
break;
case haltI:
this.vm.setInterrupt(c.num(0));
System.err.printf(
"Program halted with code %d at line %d.%n",
c.num(0), o.getLine());
break;
default:
System.err.printf(
"Error at line %d: Operation %s not yet implemented%n",
o.getLine(), o.getOpCode());
this.vm.incPC();
return;
}
if (o.getClaz() != OpClaz.CONTROL) {
this.vm.incPC();
}
}
/** Pushes a 4-byte integer onto the stack. */
private void push(int val) {
int sp = this.vm.getReg(Machine.SP) - 4;
if (sp < this.vm.getReg(Machine.BRK))
throw new RuntimeException("Stack overflow");
this.vm.setReg(Machine.SP, sp);
this.vm.store(val, sp);
}
/** Pushes a character onto the stack. */
private void pushC(int val) {
int sp = this.vm.getReg(Machine.SP) - this.vm.getCharSize();
if (sp < this.vm.getReg(Machine.BRK))
throw new RuntimeException("Stack overflow");
this.vm.setReg(Machine.SP, sp);
this.vm.storeC(val, sp);
}
/** Pushes a string onto the stack.
* The string is represented by an integer length followed
* by the chars of the string, with first char on top .
*/
private void pushString(String text) {
for (int i = text.length() - 1; i >= 0; i--) {
pushC(text.charAt(i));
}
push(text.length());
}
/** Pops a 4-byte integer from the stack. */
private int pop() {
int sp = this.vm.getReg(Machine.SP);
int result = this.vm.load(sp);
if (DEBUG)
this.vm.store(0, sp);
this.vm.setReg(Machine.SP, sp + 4);
return result;
}
/** Pops a character from the stack. */
private int popC() {
int sp = this.vm.getReg(Machine.SP);
int result = this.vm.loadC(sp);
if (DEBUG)
this.vm.storeC(0, sp);
this.vm.setReg(Machine.SP, sp + this.vm.getCharSize());
return result;
}
/** Pops a string from the stack.
* The string should be represented by an integer length followed
* by the chars of the string, with first char on top.
* This method coerces all values to (Java) chars,
* so if {@link Machine#setCharSize(int)} has been set to 4 then
* all more significant bytes are lost
*/
private String popString() {
StringBuilder result = new StringBuilder();
int len = pop();
for (int i = 0; i < len; i++) {
result.append((char) popC());
}
return result.toString();
}
/** Operation context.
* This is a helper class for easy access to the underlying VM,
* given a particular operation.
*/
private class OPContext {
private final Op op;
OPContext(Op o) {
this.op = o;
}
/** Sets a register of the processor to a given boolean value.
* The boolean is converted to {@link #TRUE} of {@link #FALSE}.
* @param regIx operand index of the register to be set
* @param val the boolean value
*/
public void setReg(int regIx, boolean val) {
getVM().setReg(this.op.reg(regIx), val ? TRUE : FALSE);
}
/** Sets a register of the VM to a given integer value.
* @param regIx operand index of the register to be set
* @param val the value
*/
public void setReg(int regIx, int val) {
getVM().setReg(this.op.reg(regIx), val);
}
/** Returns the value of a register of the VM.
* @param regIx operand index of the register to be returned
*/
public int reg(int regIx) {
return getVM().getReg(this.op.reg(regIx));
}
/** Returns the value assigned to a numeric constant.
* @param numIx operand index of the num to be returned
*/
public int num(int numIx) {
Num num = this.op.num(numIx);
switch (num.getKind()) {
case LAB:
Label label = num.getLabel();
int line = getProgram().getLine(label);
if (line < 0) {
System.err.println("Label '" + label
+ "' does not occur in program");
}
return line;
case LIT:
return num.getValue();
case SYMB:
Integer result = getVM().getNum(num);
if (result == null) {
result = getProgram().getSymb(num);
if (result == null) {
System.err.println("Symbolic constant '"
+ num.getName()
+ "' not initialised in VM or program");
}
}
return result;
default:
assert false;
return 0;
}
}
/** Returns the instruction number associated with a given label. */
public int label(int ix) {
return getProgram().getLine(this.op.label(ix));
}
}
}

View File

@ -0,0 +1,327 @@
package pp.iloc.eval;
import java.util.HashMap;
import java.util.Map;
import pp.iloc.model.Num;
import pp.iloc.model.Reg;
/**
* Virtual machine for ILOC program evaluation.
* @author Arend Rensink
*/
public class Machine {
/** Size of an integer values (in bytes). */
public static final int INT_SIZE = 4;
/** Default size of a char value (in bytes).
* The actual size used in a particular Machine instance
* can be set using {@link #setCharSize(int)}
*/
public static final int DEFAULT_CHAR_SIZE = 1;
/** Size of an integer values (in bits). */
public static final int BYTE_SIZE = 8;
/** Name of the allocation pointer register.
* This is initialised to start at address 0.
*/
public static final String ARP = "r_arp";
/** The allocation pointer register (see {@link #ARP}). */
public static final Reg ARP_REG = new Reg(ARP);
/** Name of the stack pointer register.
* This is initialised to start at the top of memory.
*/
public static final String SP = "sp";
/** The stack pointer register (see {@link #SP}). */
public static final Reg SP_REG = new Reg(SP);
/** Name of the program break register.
* This is initialised to halfway across the memory.
*/
public static final String BRK = "brk";
/** The program break register (see {@link #BRK}). */
public static final Reg BRK_REG = new Reg(BRK);
/** The actual size of a char value in this machine.
* Typically, either 1 (as per {@link #DEFAULT_CHAR_SIZE})
* or 4 (the same as {@link #INT_SIZE}).
* This affects the implementation of {@link #loadC} and
* {@link #storeC}.
*/
private int charSize;
/** Mapping from register names to register numbers. */
private final Map<String, Integer> registers;
/** Mapping from symbolic constants to actual values. */
private final Map<Num, Integer> symbMap;
/** Memory of the machine. */
private final Memory memory;
/** Counter of reserved memory cells. */
private int reserved;
/** Program counter. */
private int pc;
/** Value of interrupt. */
private int interrupt;
/** Constructs a new, initially empty machine
* with default-sized memory. */
public Machine() {
this.symbMap = new HashMap<>();
this.memory = new Memory();
this.registers = new HashMap<>();
this.charSize = DEFAULT_CHAR_SIZE;
clear();
}
/** Reinitialises the machine memory to a certain size (in bytes).
* This also resets the stack pointer (to the top of the memory).
* @param size
*/
public void setSize(int size) {
this.memory.setSize(size);
setReg(SP_REG, size);
setReg(BRK_REG, size/2);
}
/** Sets the size used to store a char value.
* @param charSize the number of bytes used to store a char
* value; between 1 and 4 (inclusive)
*/
public void setCharSize(int charSize) {
if (charSize < 1 || charSize > 4) {
throw new IllegalArgumentException("Illegal character size: "
+ charSize);
}
this.charSize = charSize;
}
/** Returns the size used to store a char value.
* This is equal to {@link #DEFAULT_CHAR_SIZE} unless
* changed by a call to {@link #setCharSize} */
public int getCharSize() {
return this.charSize;
}
/** Reserves a memory segment of given length.
* @param length (in bytes) of the segment to be reserved
* @return the base address of the allocated block
* @see #alloc(String, int)
*/
public int alloc(int length) {
int result = this.reserved;
for (int i = 0; i < length; i++) {
this.memory.set(result + i, (byte) 0);
}
this.reserved += length;
return result;
}
/** Reserves a memory segment of a given length (in bytes),
* assigns the base address to a symbolic constant,
* and returns the base address.
* The reserved memory is guaranteed to be unshared and
* initialized to 0.
* @param cst the name for the start address of the allocated memory
* @param length length (in bytes) of the segment to be reserved
* @return the allocated start address
* @throws IllegalArgumentException if the symbolic name is known
*/
public int alloc(String cst, int length) {
if (this.symbMap.get(new Num(cst)) != null) {
throw new IllegalArgumentException("Duplicate symbolic name '"
+ cst + "'");
}
int result = alloc(length);
setNum(cst, result);
return result;
}
/** Initializes a memory segment of a length sufficient to
* accommodate a given list of initial values,
* assigns the start address to a symbolic name,
* and returns the start address.
* The reserved memory is guaranteed to be unshared.
* @param cst the name for the start address of the allocated memory
* @param vals the initial values
* @return the allocated start address
* @throws IllegalArgumentException if the symbolic name is known
*/
public int init(String cst, int... vals) {
if (this.symbMap.get(new Num(cst)) != null) {
throw new IllegalArgumentException("Duplicate symbolic name '"
+ cst + "'");
}
int result = alloc(vals.length * INT_SIZE);
setNum(cst, result);
for (int i = 0; i < vals.length; i++) {
store(vals[i], result + INT_SIZE * i);
}
return result;
}
/** Declares a register with a given name, and sets its value to 0. */
public void declare(String reg) {
setReg(reg, 0);
}
/** Sets the value of a register with a given name to a given number. */
public void setReg(String reg, int val) {
this.registers.put(reg, val);
}
/** Sets the value of a given register to a given number. */
public void setReg(Reg reg, int val) {
this.registers.put(reg.getName(), val);
}
/** Returns the current value in a register with a given name.
* @throws IllegalArgumentException if no such register exists */
public int getReg(String name) {
Integer result = this.registers.get(name);
if (result == null) {
throw new IllegalArgumentException("Unknown register '" + name
+ "'");
}
return result;
}
/** Returns the current value in a given register. */
public int getReg(Reg reg) {
return getReg(reg.getName());
}
/** Sets the value of a given named symbolic constant.
* @param name symbolic name, without '@'-prefix
* @throws IllegalArgumentException if the name is already declared
*/
public void setNum(String name, int val) {
Num symbol = new Num(name);
Integer oldVal = this.symbMap.put(symbol, val);
if (oldVal != null) {
throw new IllegalArgumentException("Duplicate symbol '" + symbol
+ "': values " + oldVal + " and " + val);
}
}
/** Returns the value of a given symbolic numeric value.
* @return the corresponding value, or <code>null</code> if
* the symbol is not defined.
*/
public Integer getNum(Num symb) {
return this.symbMap.get(symb);
}
/** Convenience method to returns the value of a given symbolic constant.
* @param name symbolic name without '@'-prefix
* @throws IllegalArgumentException if the name is not declared
* @see #getNum(Num)
*/
public Integer getNum(String name) {
return getNum(new Num(name));
}
/** Returns the integer value starting at a given memory location.
* The value is computed from the four successive bytes starting
* at that location (most significant first).
*/
public int load(int loc) {
return load(loc, INT_SIZE);
}
/** Returns the char value at a given memory location.
* This consists of a number of successive bytes determined
* by #getCharSize().
*/
public int loadC(int loc) {
return 0xFF & load(loc, getCharSize());
}
/** Returns the integer value starting at a given memory location,
* consisting of 1 through 4 consecutive bytes.
* The value is computed from the successive bytes starting
* at that location (most significant first).
*/
private int load(int loc, int size) {
int result = 0;
for (int i = 0; i < size; i++) {
result <<= BYTE_SIZE;
result += 0xFF & this.memory.get(loc + i);
}
return result;
}
/** Stores an integer value in memory, starting at a given location.
* The value is stored at the four successive bytes starting
* at that location (most significant first).
*/
public void store(int val, int loc) {
store(val, loc, INT_SIZE);
}
/** Stores a character in memory,
* at a given location.
* The number of bytes used is determined by {@link #getCharSize()}.
*/
public void storeC(int val, int loc) {
store((char) val, loc, getCharSize());
}
/** Stores an integer value in memory, starting at a given location.
* The value is stored at the 1-4 successive bytes starting
* at that location (most significant first).
*/
private void store(int val, int loc, int len) {
for (int i = len - 1; i >= 0; i--) {
this.memory.set(loc + i, (byte) val);
val >>= BYTE_SIZE;
}
}
/** Returns the current program counter value. */
public int getPC() {
return this.pc;
}
/** Increases the current program counter value. */
public void incPC() {
this.pc++;
}
/** sets the program counter to a given line number. */
public void setPC(int line) {
if (line < 0) {
throw new IllegalArgumentException("Trying to jump to line " + line);
}
this.pc = line;
}
/**
* Sets the interrupt value of the machine.
* @param interruptCode the new interrupt value
*/
public void setInterrupt(int interruptCode) {
this.interrupt = interruptCode;
}
/**
* Returns the current interrupt value.
* @return the interrupt value
*/
public int getInterrupt() {
return interrupt;
}
/** Clears the registers, constants, memory, PC and interrupt. */
public void clear() {
this.registers.clear();
this.symbMap.clear();
this.memory.clear();
this.pc = 0;
this.interrupt = 0;
setReg(ARP_REG, 0);
setReg(SP_REG, this.memory.size());
setReg(BRK_REG, this.memory.size()/2);
}
@Override
public String toString() {
return String.format("Registers: %s%nConstants: %s%nMemory: %s%n",
this.registers, this.symbMap, this.memory);
}
}

View File

@ -0,0 +1,56 @@
package pp.iloc.eval;
import java.util.Arrays;
/** Simulated memory. */
public class Memory {
/** The default size of the memory, in number of bytes. */
public final static int DEFAULT_SIZE = 10000;
/** The memory array. */
private byte[] mem = new byte[DEFAULT_SIZE];
/** Reinitialises the memory to a certain size. */
public void setSize(int size) {
this.mem = new byte[size];
}
/** Sets a location in memory to a given value. */
public void set(int loc, byte value) {
this.mem[loc] = value;
}
/**
* Returns the value at a given memory location.
* The value is 0 if the location was never accessed before.
*/
public byte get(int loc) {
return this.mem[loc];
}
/** Returns the size of the used memory. */
public int size() {
return this.mem.length;
}
/** Removes all values from the memory. */
public void clear() {
Arrays.fill(this.mem, (byte) 0);
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
for (int i = 0; i < size(); i++) {
if (get(i) == 0) {
continue;
}
if (result.length() > 0) {
result.append(", ");
}
result.append(i);
result.append(":");
result.append(String.format("%02X", get(i) & 0xFF));
}
return result.toString();
}
}

View File

@ -0,0 +1,122 @@
package pp.iloc.model;
import java.util.Iterator;
/**
* ILOC instruction
* @author Arend Rensink
*/
public abstract class Instr implements Iterable<Op> {
/** The line number of this instruction. */
private int line = -1;
/** The label of this instruction. */
private Label label;
/** The program in which this instruction occurs. */
private Program prog;
/** Returns the number of operations in this instruction. */
public abstract int size();
/** Returns an iterator over the operations in this instruction. */
@Override
public abstract Iterator<Op> iterator();
/** Indicates if the line number of this instruction has been set. */
boolean hasLine() {
return getLine() >= 0;
}
/** Returns the line number of this instruction.
* @return the line number; {@code -1} if the line number has not been set.
*/
public int getLine() {
return this.line;
}
/** Sets the line number of this instruction. */
void setLine(int line) {
assert this.line < 0 && line >= 0;
this.line = line;
}
/** Indicates if this instruction has a (non-<code>null</code>) label. */
public boolean hasLabel() {
return getLabel() != null;
}
/** Returns the optional label of this instruction. */
public Label getLabel() {
return this.label;
}
/** Sets the optional label of this instruction.
*/
public void setLabel(Label label) {
if (label == null) {
throw new IllegalArgumentException("Label may not be null");
}
if (!(this.label == null || this.label.equals(label))) {
throw new IllegalArgumentException("Conflicting labels '"
+ this.label + "' and '" + label + "'");
}
assert label != null && this.label == null || label.equals(this.label);
if (this.label == null) {
this.label = label;
if (this.prog != null) {
this.prog.registerLabel(this);
}
}
}
/** Sets the program in which this instruction occurs.
*/
void setProgram(Program prog) {
assert this.prog == null & prog != null;
this.prog = prog;
}
/** Returns the string representation of the optional label. */
String toLabelString() {
if (hasLabel()) {
return getLabel() + LABEL_SEP;
} else {
return "";
}
}
/** Returns a string of the form
* {@code label? opCode sources (arrow targets)? comment?}
* where the widths of the label, sources and targets parts
* are predetermined.
* @param labelSize width of the {@code label} part
* @param sourceSize width of the {@code sources} part
* @param targetSize width of the {@code targets} part
*/
abstract public String prettyPrint(int labelSize, int sourceSize,
int targetSize);
@Override
public int hashCode() {
return (this.label == null) ? 0 : this.label.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof Instr)) {
return false;
}
Instr other = (Instr) obj;
if (this.label == null) {
if (other.label != null) {
return false;
}
} else if (!this.label.equals(other.label)) {
return false;
}
return true;
}
/** Label separator. */
private final static String LABEL_SEP = ": ";
}

View File

@ -0,0 +1,66 @@
package pp.iloc.model;
/** Label operand.
* @author Arend Rensink
*/
public class Label extends Operand {
/** Constructs a label object with a given label text. */
public Label(String value) {
super(Type.LABEL);
assert wellformed(value) : String.format(
"Label '%s' is not well-formed", value);
this.value = value;
}
/** Returns the value of this label. */
public String getValue() {
return this.value;
}
private final String value;
@Override
public String toString() {
return this.value;
}
@Override
public int hashCode() {
return getValue().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Label)) {
return false;
}
Label other = (Label) obj;
if (!getValue().equals(other.getValue())) {
return false;
}
return true;
}
/** Tests if a string value is a well-formed label. */
private boolean wellformed(String value) {
if (value == null) {
return false;
}
if (value.isEmpty()) {
return false;
}
if (!Character.isLetter(value.charAt(0))) {
return false;
}
for (int i = 1; i < value.length(); i++) {
char c = value.charAt(i);
if (!(Character.isLetterOrDigit(c) || c == '-' || c == '_')) {
return false;
}
}
return true;
}
}

154
src/pp/iloc/model/Num.java Normal file
View File

@ -0,0 +1,154 @@
package pp.iloc.model;
/** Numeric operand.
* A numeric operand can be a literal or a (symbolic) constant.
* @author Arend Rensink
*/
public class Num extends Operand {
/** Value of the numeric operand, if it is a literal. */
private final int value;
/** Name of the numeric operand, if it is a symbolic constant. */
private final String name;
/** Label wrapped in the numeric operand, if it is label-based. */
private final Label label;
/** The kind of numeric operand. */
private final NumKind kind;
/** Constructs a literal numeric operand. */
public Num(int value) {
super(Type.NUM);
this.kind = NumKind.LIT;
this.value = value;
this.name = null;
this.label = null;
}
/** Constructs a symbolic numeric operand.
* @name name symbolic name, without '@'-prefix
*/
public Num(String name) {
super(Type.NUM);
this.kind = NumKind.SYMB;
assert wellformed(name);
this.name = name;
this.value = -1;
this.label = null;
}
/** Constructs a label-based numeric operand. */
public Num(Label label) {
super(Type.NUM);
this.kind = NumKind.LAB;
this.label = label;
this.value = -1;
this.name = null;
}
/** Returns the kind of this numeric operand. */
public NumKind getKind() {
return this.kind;
}
/** Returns the label on which this operand is based,
* if it is label-based. */
public Label getLabel() {
return this.label;
}
/** Returns the value of this numeric operand, if it is a literal. */
public int getValue() {
return this.value;
}
/** Returns the name of this numeric operand, if it is a constant. */
public String getName() {
return this.name;
}
@Override
public String toString() {
switch (getKind()) {
case LAB:
return "#" + getLabel();
case LIT:
return "" + getValue();
case SYMB:
return '@' + getName();
default:
assert false;
return null;
}
}
@Override
public int hashCode() {
int prime = 31;
int result = prime * getKind().hashCode();
switch (getKind()) {
case LAB:
result += getLabel().hashCode();
break;
case LIT:
result += getValue();
break;
case SYMB:
result += getName().hashCode();
}
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Num)) {
return false;
}
Num other = (Num) obj;
if (getKind() != other.getKind()) {
return false;
}
switch (getKind()) {
case LAB:
return getLabel().equals(other.getLabel());
case LIT:
return getValue() == other.getValue();
case SYMB:
return getName().equals(other.getName());
default:
assert false;
return false;
}
}
/** Tests if a string value is a well-formed name. */
private boolean wellformed(String value) {
if (value == null) {
return false;
}
if (value.isEmpty()) {
return false;
}
if (!Character.isLetter(value.charAt(0))) {
return false;
}
for (int i = 1; i < value.length(); i++) {
char c = value.charAt(i);
if (!(Character.isLetterOrDigit(c) || c == '-' || c == '_')) {
return false;
}
}
return true;
}
/** Type class for numeric operands. */
public static enum NumKind {
/** Literal constant. */
LIT,
/** Symbolic name. */
SYMB,
/** Label-based constant. */
LAB;
}
}

277
src/pp/iloc/model/Op.java Normal file
View File

@ -0,0 +1,277 @@
package pp.iloc.model;
import static pp.iloc.model.OpClaz.COMMENT;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import pp.iloc.model.Operand.Type;
/**
* ILOC operation
* @author Arend Rensink
*/
public class Op extends Instr {
/** Comment separator. */
private final static String COMMENT_SEP = "// ";
/** Operand separator. */
private final static String OP_SEP = ",";
/** The operation code. */
private final OpCode opCode;
/** The list of arguments of this operation. */
private final List<Operand> args;
/** The optional comment for this operation. */
private String comment;
/** Constructs an unlabelled operation with a given opcode and arguments. */
public Op(OpCode opCode, Operand... args) {
this(null, opCode, args);
}
/** Constructs an unlabelled operation with a given opcode and arguments. */
public Op(OpCode opCode, List<Operand> args) {
this(null, opCode, args);
}
/** Constructs a labelled operation with a given opcode and arguments. */
public Op(Label label, OpCode opCode, Operand... args) {
this(label, opCode, Arrays.asList(args));
}
/** Constructs a labelled operation with a given opcode and arguments.
* @throws IllegalArgumentException if one of the arguments
* is not of the expected type
*/
public Op(Label label, OpCode opCode, List<Operand> args)
throws IllegalArgumentException {
if (label != null) {
super.setLabel(label);
}
this.opCode = opCode;
int argsCount = opCode.getSigSize();
if (args.size() != argsCount) {
throw new IllegalArgumentException(String.format(
"Operation '%s' expects %d arguments but has %d", opCode,
argsCount, args.size()));
}
for (int i = 0; i < argsCount; i++) {
Operand arg = args.get(i);
Type expected = opCode.getSig().get(i);
if (arg.getType() != expected) {
throw new IllegalArgumentException(
String.format(
"Operation '%s' argument %d should be '%s' but is '%s'",
this.opCode, i, expected, arg.getType()));
}
}
this.args = new ArrayList<>(args);
}
/** Returns the class of operation (normal or control flow). */
public OpClaz getClaz() {
return this.opCode.getClaz();
}
/** Returns the opcode of this operation. */
public OpCode getOpCode() {
return this.opCode;
}
/** Returns the list of all (source + target) arguments. */
public List<Operand> getArgs() {
return this.args;
}
/** Convenience method to retrieve a given argument as {@link Reg}. */
public Reg reg(int i) {
return (Reg) this.args.get(i);
}
/** Convenience method to retrieve a given argument as {@link Str}. */
public Str str(int i) {
return (Str) this.args.get(i);
}
/** Convenience method to retrieve a given argument as {@link Num}. */
public Num num(int i) {
return (Num) this.args.get(i);
}
/** Convenience method to retrieve a given operand as {@link Label}. */
public Label label(int i) {
return (Label) this.args.get(i);
}
/** Indicates if this operation has a comment. */
public boolean hasComment() {
return getComment() != null;
}
/** Returns the optional comment for this operation. */
public String getComment() {
return this.comment;
}
/** Sets a comment for this operation. */
public void setComment(String comment) {
this.comment = comment;
}
@Override
public int size() {
return 1;
}
@Override
public Iterator<Op> iterator() {
return Collections.singleton(this).iterator();
}
@Override
public String prettyPrint(int labelSize, int sourceSize, int targetSize) {
StringBuilder result = new StringBuilder();
if (labelSize > 0) {
result.append(String
.format("%-" + labelSize + "s", toLabelString()));
}
int arrowSize = 4;
if (getClaz() == COMMENT) {
result.append(toCommentString());
}
if (getOpCode() == OpCode.out) {
int size = sourceSize + targetSize + arrowSize;
result.append(String.format("%-8s", getOpCode().name()));
result.append(String.format("%-" + size + "s ", toSourceString()));
result.append(toCommentString());
} else {
result.append(String.format("%-8s", getOpCode().name()));
if (sourceSize > 0) {
result.append(String.format("%-" + sourceSize + "s",
toSourceString()));
}
result.append(String
.format("%-" + arrowSize + "s", toArrowString()));
if (targetSize > 0) {
result.append(String.format("%-" + targetSize + "s ",
toTargetString()));
}
result.append(toCommentString());
}
result.append('\n');
return result.toString();
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append(toLabelString());
if (getClaz() != COMMENT) {
result.append(getOpCode());
if (getOpCode().getSourceCount() > 0) {
result.append(' ');
result.append(toSourceString());
}
if (getOpCode().getTargetCount() > 0) {
result.append(' ');
result.append(getClaz().getArrow());
result.append(' ');
result.append(toTargetString());
}
result.append(' ');
}
result.append(toCommentString());
return result.toString();
}
/** Returns the string representation of the arrow symbol. */
String toArrowString() {
if (getOpCode().getTargetCount() > 0 && getClaz() != COMMENT) {
return ' ' + getClaz().getArrow() + ' ';
} else {
return "";
}
}
/** Returns the string representation of the optional comment. */
String toCommentString() {
if (hasComment()) {
return COMMENT_SEP + getComment();
} else {
return "";
}
}
/** Returns the string representation of the source operands. */
String toSourceString() {
StringBuilder result = new StringBuilder();
boolean first = true;
for (int i = 0; i < getOpCode().getSourceCount(); i++) {
Operand o = getArgs().get(i);
if (first) {
first = false;
} else {
result.append(OP_SEP);
}
result.append(o);
}
return result.toString();
}
/** Returns the string representation of the target operands. */
String toTargetString() {
StringBuilder result = new StringBuilder();
boolean first = true;
for (int i = getOpCode().getSourceCount(); i < getOpCode()
.getSigSize(); i++) {
Operand o = getArgs().get(i);
if (first) {
first = false;
} else {
result.append(OP_SEP);
}
result.append(o);
}
return result.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + super.hashCode();
result = prime * result
+ ((this.comment == null) ? 0 : this.comment.hashCode());
result = prime * result + this.opCode.hashCode();
result = prime * result + this.args.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
Op other = (Op) obj;
if (!hasComment()) {
if (other.hasComment()) {
return false;
}
} else if (!getComment().equals(other.getComment())) {
return false;
}
if (this.opCode != other.opCode) {
return false;
}
if (!this.args.equals(other.args)) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,25 @@
package pp.iloc.model;
/**
* Class of operation: either normal or control flow.
* @author Arend Rensink
*/
public enum OpClaz {
/** A normal (non-control) operation. */
NORMAL("=>"),
/** A control operation, i.e., one that changes the PC. */
CONTROL("->"),
/** Special operation type holding a comment. */
COMMENT("");
private final String arrow;
private OpClaz(String arrow) {
this.arrow = arrow;
}
/** Returns the arrow symbol used for this operation type. */
public String getArrow() {
return this.arrow;
}
}

View File

@ -0,0 +1,252 @@
package pp.iloc.model;
import static pp.iloc.model.OpClaz.COMMENT;
import static pp.iloc.model.OpClaz.CONTROL;
import static pp.iloc.model.OpClaz.NORMAL;
import static pp.iloc.model.Operand.Type.LABEL;
import static pp.iloc.model.Operand.Type.NUM;
import static pp.iloc.model.Operand.Type.REG;
import static pp.iloc.model.Operand.Type.STR;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Code defining the type of a (non-control flow) operation.
* @author Arend Rensink
*/
public enum OpCode {
// Placeholder
/** Placeholder (no operation). */
nop(0),
// Register arithmetic
/** Addition (reg0 + reg1 => reg2). */
add(2, REG, REG, REG),
/** Subtraction (reg0 - reg1 => reg2). */
sub(2, REG, REG, REG),
/** Multiplication (reg0 * reg1 => reg2). */
mult(2, REG, REG, REG),
/** Division (reg0 / reg1 => reg2). */
div(2, REG, REG, REG),
// Immediate arithmetic
/** Addition of immediate value (reg0 + num1 => reg2). */
addI(2, REG, NUM, REG),
/** Subtraction of immediate value (reg0 - num1 => reg2). */
subI(2, REG, NUM, REG),
/** Subtraction from immediate value (num1 - reg0 => reg2). */
rsubI(2, REG, NUM, REG),
/** Multiplication by immediate value (reg0 * num1 => reg2). */
multI(2, REG, NUM, REG),
/** Division by immediate value (reg0 / num1 => reg2). */
divI(2, REG, NUM, REG),
/** Division of immediate value (num1 / reg0 => reg2). */
rdivI(2, REG, NUM, REG),
// Shifts (register + immediate)
/** Left-shift (reg0 << reg1 => reg2). */
lshift(2, REG, REG, REG),
/** Left-shift immediate value (reg0 << num1 => reg2). */
lshiftI(2, REG, NUM, REG),
/** Right-shift (reg0 >> reg1 => reg2). */
rshift(2, REG, REG, REG),
/** Right-shift immediate value (reg0 >> num1 => reg2). */
rshiftI(2, REG, NUM, REG),
// Other bitwise operations
/** Bitwise OR (reg0 | reg1 => reg2). */
or(2, REG, REG, REG),
/** Bitwise OR with immediate value (reg0 | num1 => reg2). */
orI(2, REG, NUM, REG),
/** Bitwise AND (reg0 & reg1 => reg2). */
and(2, REG, REG, REG),
/** Bitwise AND with immediate value (reg0 & num1 => reg2). */
andI(2, REG, NUM, REG),
/** Bitwise XOR (reg0 ^ reg1 => reg2). */
xor(2, REG, REG, REG),
/** Bitwise XOR with immediate value (reg0 ^ num1 => reg2). */
xorI(2, REG, NUM, REG),
// Memory operations
/** Load immediate (num0 => reg1). */
loadI(1, NUM, REG),
/** Load (mem(reg0) => reg1). */
load(1, REG, REG),
/** Load address + immediate (mem(reg0 + num1) => reg2). */
loadAI(2, REG, NUM, REG),
/** Load address + offset (mem(reg0 + reg1) => reg2). */
loadAO(2, REG, REG, REG),
/** Character load (mem(reg0) => reg1). */
cload(1, REG, REG),
/** Character load address + immediate (mem(reg0 + num1) => reg1). */
cloadAI(2, REG, NUM, REG),
/** Character load address + offset (mem(reg0 + reg1) => reg2). */
cloadAO(2, REG, REG, REG),
/** Store (reg0 => mem(reg1)). */
store(1, REG, REG),
/** Store (reg0 => mem(reg1 + num2)). */
storeAI(1, REG, REG, NUM),
/** Store (reg0 => mem(reg1 + reg2)). */
storeAO(1, REG, REG, REG),
/** Character store (reg0 => mem(reg)). */
cstore(1, REG, REG),
/** Character store (reg0 => mem(reg1 + num2)). */
cstoreAI(1, REG, REG, NUM),
/** Character store (reg0 => mem(reg1 + reg2)). */
cstoreAO(1, REG, REG, REG),
// Copy operations
/** Integer-to-integer copy (reg0 => reg1). */
i2i(1, REG, REG),
/** Character-to-character copy (reg0 => reg1). */
c2c(1, REG, REG),
/** Character-to-integer conversion (reg0 => reg1). */
c2i(1, REG, REG),
/** Integer-to-character conversion (reg0 => reg1). */
i2c(1, REG, REG),
// Comparison operations
/** Less-than comparison (reg0 < reg1 => reg2). */
cmp_LT(2, REG, REG, REG),
/** Less-or-equal comparison (reg0 <= reg1 => reg2). */
cmp_LE(2, REG, REG, REG),
/** Equals comparison (reg0 == reg1 => reg2). */
cmp_EQ(2, REG, REG, REG),
/** Greater-or-equal comparison (reg0 >= reg1 => reg2). */
cmp_GE(2, REG, REG, REG),
/** Greater-than comparison (reg0 > reg1 => reg2). */
cmp_GT(2, REG, REG, REG),
/** Not-equals comparison (reg0 != reg1 => reg2). */
cmp_NE(2, REG, REG, REG),
// Jump operations
/** Conditional branch (reg0 != 0 ? #label0 : #label1 => pc). */
cbr(CONTROL, 1, REG, LABEL, LABEL),
/** Immediate jump (#label0 => pc). */
jumpI(CONTROL, 0, LABEL),
/** Register jump (reg0 => pc). */
jump(CONTROL, 0, REG),
/** Pseudo-op to record labels of a register jump. */
tbl(2, REG, LABEL),
// Extra ops for stack manipulation
/** Push the (4-byte integer) value of a register onto the stack.
* Not official ILOC. */
push(1, REG),
/** Pop the (4-byte integer) stack top into a register.
* Not official ILOC. */
pop(0, REG),
/** Push the (1-byte character) value of a register onto the stack.
* Not official ILOC. */
cpush(1, REG),
/** Pop the (1-byte character) stack top into a register.
* Not official ILOC. */
cpop(0, REG),
// Extra ops for simulation and debugging
/** Value input (str0 => stdout and stdin => reg1).
* Not official ILOC. */
in(1, STR, REG),
/** Value output (str0 + reg1 => stdout).
* Not official ILOC. */
out(2, STR, REG),
/** String input (str0 => stdout and stdin => stack).
* The string is represented as length + chars (first char on top).
* Not official ILOC. */
cin(1, STR),
/** Value output (str0 + stack => stdout).
* The string is represented as length + chars (first char on top).
* Not official ILOC. */
cout(1, STR),
/** Stand-alone program comment; effect = nop.
* Not official ILOC. */
comment(COMMENT, 0),
/** Halt the program with immediate status code.
* Not official ILOC. */
halt(1, REG),
/** Halt the program with immediate status code.
* Not official ILOC. */
haltI(1, NUM);
/** The class that this opcode falls into. */
private final OpClaz claz;
/** The source operand types. */
private final List<Operand.Type> sourceSig;
/** The target operand types. */
private final List<Operand.Type> targetSig;
/** The operand types. */
private final List<Operand.Type> sig;
private OpCode(int sourceCount, Operand.Type... sig) {
this(NORMAL, sourceCount, sig);
}
private OpCode(OpClaz claz, int sourceCount, Operand.Type... sig) {
this.claz = claz;
this.sourceSig = new ArrayList<>(sourceCount);
for (int i = 0; i < sourceCount; i++) {
this.sourceSig.add(sig[i]);
}
this.targetSig = new ArrayList<>(sig.length - sourceCount);
for (int i = sourceCount; i < sig.length; i++) {
this.targetSig.add(sig[i]);
}
this.sig = new ArrayList<>(Arrays.asList(sig));
}
/** Returns the class of this opcode (normal or control flow). */
public OpClaz getClaz() {
return this.claz;
}
/** Returns the number of operands. */
public int getSigSize() {
return getSourceCount() + getTargetCount();
}
/** Returns the list of expected operand types. */
public List<Operand.Type> getSig() {
return this.sig;
}
/** Returns the number of source operands. */
public int getSourceCount() {
return getSourceSig().size();
}
/** Returns the list of expected source operand types. */
public List<Operand.Type> getSourceSig() {
return this.sourceSig;
}
/** Returns the number of target operands. */
public int getTargetCount() {
return getTargetSig().size();
}
/** Returns the list of expected target operand types. */
public List<Operand.Type> getTargetSig() {
return this.targetSig;
}
/** Returns the {@link OpCode} for a given string, if any. */
public static OpCode parse(String code) {
return codeMap.get(code);
}
private static final Map<String, OpCode> codeMap = new HashMap<>();
static {
for (OpCode op : values()) {
if (op.getClaz() != OpClaz.COMMENT) {
codeMap.put(op.name(), op);
}
}
}
}

View File

@ -0,0 +1,101 @@
package pp.iloc.model;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/** List of operations forming a single instruction. */
public class OpList extends Instr {
/** The internally stored list of operations. */
private final List<Op> opList;
/** Constructs an operation list with an optional label. */
public OpList() {
this.opList = new ArrayList<>();
}
/** Adds an operation to this list. */
public void addOp(Op op) {
assert !hasLine() : "Line numer set; do not add new operations";
this.opList.add(op);
}
/** Returns the list of operations. */
public List<Op> getOps() {
return this.opList;
}
@Override
public int size() {
return this.opList.size();
}
@Override
public Iterator<Op> iterator() {
return getOps().iterator();
}
@Override
public void setLine(int line) {
super.setLine(line);
// also sets the line numbers of the operations in the list
for (Op op : this) {
op.setLine(line);
line++;
}
}
/** Returns a string consisting of this operation list in a nice layout.
* @param indent the number of columns by which the string should be indented.
* All lines except the first will be indented by this number of spaces.
*/
@Override
public String prettyPrint(int labelSize, int sourceSize, int targetSize) {
StringBuilder result = new StringBuilder();
result.append(String.format("%-" + labelSize + "s", toLabelString()));
result.append(LIST_PREFIX);
for (Op op : getOps()) {
sourceSize = Math.max(sourceSize, op.toSourceString().length());
targetSize = Math.max(targetSize, op.toTargetString().length());
}
int innerLabelSize = 0;
for (Op op : getOps()) {
result.append(op
.prettyPrint(innerLabelSize, sourceSize, targetSize));
innerLabelSize = labelSize + LIST_PREFIX.length();
}
result.append(String.format("%-" + labelSize + "s" + LIST_POSTFIX, ""));
result.append('\n');
return result.toString();
}
@Override
public String toString() {
return toLabelString() + this.opList.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + this.opList.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj)) {
return false;
}
OpList other = (OpList) obj;
if (!this.opList.equals(other.opList)) {
return false;
}
return true;
}
private final static String LIST_PREFIX = "[ ";
private final static String LIST_POSTFIX = "]";
}

View File

@ -0,0 +1,27 @@
package pp.iloc.model;
/** Abstract supertype of all kinds of operands. */
abstract public class Operand {
private final Type type;
protected Operand(Type type) {
this.type = type;
}
/** Returns the type of this operand. */
public Type getType() {
return this.type;
}
/** Enumeration of all available operand types. */
public static enum Type {
/** Register-type operand; class {@link Reg}. */
REG,
/** Numeric operand; class {@link Num} or {@link Symb}. */
NUM,
/** Label operand; class {@link Label}. */
LABEL,
/** Literal string operand; class {@link Str}. */
STR;
}
}

View File

@ -0,0 +1,270 @@
package pp.iloc.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import pp.iloc.model.Num.NumKind;
import pp.iloc.model.Operand.Type;
import pp.iloc.parse.FormatException;
/** ILOC program.
* @author Arend Rensink
*/
public class Program {
/** Indexed list of all instructions in the program. */
private final List<Instr> instrList;
/**
* Indexed list of all operations in the program.
* This is the flattened list of instructions.
*/
private final List<Op> opList;
/** Mapping from labels defined in the program to corresponding
* index locations.
*/
private final Map<Label, Integer> labelMap;
/** (Partial) mapping from symbolic constants used in the program
* to corresponding numeric values. */
private final Map<Num, Integer> symbMap;
/** Creates a program with an initially empty instruction list. */
public Program() {
this.instrList = new ArrayList<>();
this.opList = new ArrayList<>();
this.labelMap = new LinkedHashMap<>();
this.symbMap = new LinkedHashMap<>();
}
/** Adds an instruction to the instruction list of this program.
* @throws IllegalArgumentException if the instruction has a known label
*/
public void addInstr(Instr instr) {
instr.setProgram(this);
instr.setLine(this.opList.size());
if (instr.hasLabel()) {
registerLabel(instr);
}
this.instrList.add(instr);
for (Op op : instr) {
this.opList.add(op);
}
}
/** Registers the label of a given instruction. */
void registerLabel(Instr instr) {
Label label = instr.getLabel();
Integer loc = this.labelMap.get(label);
if (loc != null) {
throw new IllegalArgumentException(String.format(
"Label %s already occurred at location %d", label, loc));
}
this.labelMap.put(label, instr.getLine());
}
/** Returns the current list of instructions of this program. */
public List<Instr> getInstr() {
return Collections.unmodifiableList(this.instrList);
}
/** Returns the flattened list of operations in this program. */
public List<Op> getOps() {
return Collections.unmodifiableList(this.opList);
}
/** Returns the operation at a given line number. */
public Op getOpAt(int line) {
return this.opList.get(line);
}
/** Returns the size of the program, in number of operations. */
public int size() {
return this.opList.size();
}
/**
* Returns the location at which a given label is defined, if any.
* @return the location of an instruction with the label, or {@code -1}
* if the label is undefined
*/
public int getLine(Label label) {
Integer result = this.labelMap.get(label);
return result == null ? -1 : result;
}
/** Assigns a fixed numeric value to a symbolic constant.
* It is an error to assign to the same constant twice.
* @param name constant name, without preceding '@'
*/
public void setSymb(Num symb, int value) {
if (this.symbMap.containsKey(symb)) {
throw new IllegalArgumentException("Constant '" + symb
+ "' already assigned");
}
this.symbMap.put(symb, value);
}
/**
* Returns the value with which a given symbol has been
* initialised, if any.
*/
public Integer getSymb(Num symb) {
return this.symbMap.get(symb);
}
/**
* Returns the value with which a given named symbol has been
* initialised, if any.
* @param name name of the symbol, without '@'-prefix
*/
public Integer getSymb(String name) {
return getSymb(new Num(name));
}
/**
* Checks for internal consistency, in particular whether
* all used labels are defined.
*/
public void check() throws FormatException {
List<String> messages = new ArrayList<>();
for (Instr instr : getInstr()) {
for (Op op : instr) {
messages.addAll(checkOpnds(op.getLine(), op.getArgs()));
}
}
if (!messages.isEmpty()) {
throw new FormatException(messages);
}
}
private List<String> checkOpnds(int loc, List<Operand> opnds) {
List<String> result = new ArrayList<>();
for (Operand opnd : opnds) {
if (opnd instanceof Label) {
if (getLine((Label) opnd) < 0) {
result.add(String.format("Line %d: Undefined label '%s'",
loc, opnd));
}
}
}
return result;
}
/**
* Returns a mapping from registers to line numbers
* in which they appear.
*/
public Map<String, Set<Integer>> getRegLines() {
Map<String, Set<Integer>> result = new LinkedHashMap<>();
for (Op op : this.opList) {
for (Operand opnd : op.getArgs()) {
if (opnd.getType() == Type.REG) {
Set<Integer> ops = result.get(((Reg) opnd).getName());
if (ops == null) {
result.put(((Reg) opnd).getName(),
ops = new LinkedHashSet<>());
}
ops.add(op.getLine());
}
}
}
return result;
}
/**
* Returns a mapping from (symbolic) variables to line numbers
* in which they appear.
*/
public Map<String, Set<Integer>> getSymbLines() {
Map<String, Set<Integer>> result = new LinkedHashMap<>();
for (Op op : this.opList) {
for (Operand opnd : op.getArgs()) {
if (!(opnd instanceof Num)) {
continue;
}
if (((Num) opnd).getKind() != NumKind.SYMB) {
continue;
}
String name = ((Num) opnd).getName();
Set<Integer> lines = result.get(name);
if (lines == null) {
result.put(name, lines = new LinkedHashSet<>());
}
lines.add(op.getLine());
}
}
return result;
}
/** Returns a line-by-line printout of this program. */
@Override
public String toString() {
StringBuilder result = new StringBuilder();
for (Map.Entry<Num, Integer> symbEntry : this.symbMap.entrySet()) {
result.append(String.format("%s <- %d%n", symbEntry.getKey()
.getName(), symbEntry.getValue()));
}
for (Instr instr : getInstr()) {
result.append(instr.toString());
result.append('\n');
}
return result.toString();
}
@Override
public int hashCode() {
return this.instrList.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Program)) {
return false;
}
Program other = (Program) obj;
if (!this.instrList.equals(other.instrList)) {
return false;
}
return true;
}
/** Returns a string consisting of this program in a nice layout.
*/
public String prettyPrint() {
StringBuilder result = new StringBuilder();
// first print the symbolic declaration map
int idSize = 0;
for (Num symb : this.symbMap.keySet()) {
idSize = Math.max(idSize, symb.getName().length());
}
for (Map.Entry<Num, Integer> symbEntry : this.symbMap.entrySet()) {
result.append(String.format("%-" + idSize + "s <- %d%n", symbEntry
.getKey().getName(), symbEntry.getValue()));
}
if (idSize > 0) {
result.append('\n');
}
// then print the instructions
int labelSize = 0;
int sourceSize = 0;
int targetSize = 0;
for (Instr i : getInstr()) {
labelSize = Math.max(labelSize, i.toLabelString().length());
if (i instanceof Op && ((Op) i).getOpCode() != OpCode.out) {
Op op = (Op) i;
sourceSize = Math.max(sourceSize, op.toSourceString().length());
targetSize = Math.max(targetSize, op.toTargetString().length());
}
}
for (Instr i : getInstr()) {
result.append(i.prettyPrint(labelSize, sourceSize, targetSize));
}
return result.toString();
}
}

View File

@ -0,0 +1,45 @@
package pp.iloc.model;
/** Register operand
* @author Arend Rensink
*/
public class Reg extends Operand {
private final String name;
/** Constructs an operand with a given name. */
public Reg(String name) {
super(Type.REG);
assert name != null && name.length() > 1 : "Register names must be non-empty strings";
this.name = name;
}
/** Returns the name of this register. */
public String getName() {
return this.name;
}
@Override
public String toString() {
return this.name;
}
@Override
public int hashCode() {
return getName().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Reg)) {
return false;
}
Reg other = (Reg) obj;
if (!getName().equals(other.getName())) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,48 @@
package pp.iloc.model;
/** Literal string operand.
* This is not part of official ILOC
* @author Arend Rensink
*/
public class Str extends Operand {
private final String text;
/** Constructs a string operand with a given (string) value. */
public Str(String value) {
super(Type.STR);
this.text = value;
}
/** Returns the text of this string. */
public String getText() {
return this.text;
}
@Override
public String toString() {
return DQUOTE + this.text.replaceAll(DQUOTE, BSLASH + DQUOTE) + DQUOTE;
}
@Override
public int hashCode() {
return getText().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Str)) {
return false;
}
Str other = (Str) obj;
if (!getText().equals(other.getText())) {
return false;
}
return true;
}
private final static String BSLASH = "\\";
private final static String DQUOTE = "\"";
}

6
src/pp/iloc/parse/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/ILOC.tokens
/ILOCLexer.tokens
/ILOCBaseListener.java
/ILOCLexer.java
/ILOCListener.java
/ILOCParser.java

View File

@ -0,0 +1,42 @@
package pp.iloc.parse;
import java.util.ArrayList;
import java.util.List;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.Token;
/** Antlr error listener to collect errors rather than send them to stderr. */
public class ErrorListener extends BaseErrorListener {
/** Errors collected by the listener. */
private final List<String> errors = new ArrayList<>();
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol, int line, int charPositionInLine,
String msg, RecognitionException e) {
this.errors.add(String.format("Line %d:%d - %s", line,
charPositionInLine, msg));
}
/** Adds an error message during the tree visit stage. */
public void visitError(Token token, String msg, Object... args) {
int line = token.getLine();
int charPositionInLine = token.getCharPositionInLine();
msg = String.format(msg, args);
msg = String.format("Line %d:%d - %s", line, charPositionInLine, msg);
this.errors.add(msg);
}
/** Indicates if the listener has collected any errors. */
public boolean hasErrors() {
return !this.errors.isEmpty();
}
/** Returns the (possibly empty) list of errors collected by the listener. */
public List<String> getErrors() {
return this.errors;
}
}

View File

@ -0,0 +1,43 @@
package pp.iloc.parse;
import java.util.ArrayList;
import java.util.List;
/** Exception class to collect errors found during
* scanning, parsing and assembly of an ILOC program.
* @author Arend Rensink
*/
public class FormatException extends Exception {
private static final long serialVersionUID = 3908719003113286390L;
/** Constructs an exception without a message. */
public FormatException() {
// empty
}
/** Constructs an exception with a formatted message.
* @param message format string in {@link String#format(String, Object...)} syntax
* @param args arguments for the format string
*/
public FormatException(String message, Object... args) {
super(String.format(message, args));
this.messages.add(getMessage());
}
/** Constructs an exception from a list of messages. */
public FormatException(List<String> messages) {
super(concat(messages));
this.messages.addAll(messages);
}
private final List<String> messages = new ArrayList<>();
static private final String concat(List<String> args) {
StringBuilder result = new StringBuilder();
for (String arg : args) {
result.append(arg);
result.append('\n');
}
return result.toString();
}
}

70
src/pp/iloc/parse/ILOC.g4 Normal file
View File

@ -0,0 +1,70 @@
grammar ILOC;
@header{package pp.iloc.parse;}
fragment LETTER: [a-zA-Z];
fragment DIGIT: [0-9];
/** Full ILOC program. */
program: decl* instr (EOL+ instr)* EOL* EOF;
decl: ID ASS NUM COMMENT? EOL+
;
/** Instruction: single op or []-bracketed non-empty op sequence. */
instr
: (label ':')?
op #singleInstr
| (label ':')?
LSQ
EOL*
op
(EOL+ op)*
EOL*
RSQ #instrList
;
/** Single operation. */
op : COMMENT #comment
| opCode sources ((ARROW|DARROW) targets)?
SEMI?
COMMENT? #realOp
;
sources: (operand (COMMA operand)*)?;
targets: operand (COMMA operand)*;
/** Operation label. */
label: ID;
/** Opcode: not distinguished by the parser. */
opCode: ID;
/** Operand: ID (label or register), number or string. */
operand : ID | NUM | SYMB | LAB | STR;
MINUS: '-';
COMMA: ',';
SEMI: ';';
LSQ: '[';
RSQ: ']';
DARROW: '=>';
ARROW: '->';
ASS: '<-';
/** Identifier. */
ID: LETTER (LETTER|DIGIT|[\-_])*;
/** Symbolic name. */
SYMB: '@' ID;
/** Label used as numeric parameter. */
LAB: '#' ID;
/** Number. */
NUM: MINUS? DIGIT+;
/** String with optional escaped double quotes. */
STR : '"' (~["\n\r] | '\\"')* '"';
/** Java-style comment: // to end of line */
COMMENT: '//' ~[\r\n]*;
/** Whitespace. */
WS : [ \t]+ -> skip;
EOL : [\r\n]+;

View File

@ -0,0 +1,71 @@
package pp.iloc.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import org.junit.Test;
import pp.iloc.Assembler;
import pp.iloc.model.Program;
import pp.iloc.parse.FormatException;
@SuppressWarnings("javadoc")
public class AssemblerTest {
@Test
public void testFig13() {
Program p = parse("fig1-3");
assertEquals(ints(0, 5, 6, 7, 8, 9), p.getRegLines().get("r_a"));
assertEquals(ints(1, 5), p.getRegLines().get("r_2"));
assertEquals(ints(0, 9), p.getSymbLines().get("a"));
}
@Test
public void testFig13Init() {
Program p = parse("fig1-3-init");
assertEquals(ints(0, 5, 6, 7, 8, 9), p.getRegLines().get("r_a"));
assertEquals(ints(1, 5), p.getRegLines().get("r_2"));
assertEquals(ints(0, 9), p.getSymbLines().get("a"));
}
@Test
public void testFig13Stack() {
Program p = parse("fig1-3-stack");
assertEquals(ints(0, 2, 3, 4, 6, 7, 8, 10, 11, 13, 14, 15), p
.getRegLines().get("r_1"));
assertEquals(ints(1, 2, 5, 6, 9, 10, 12, 14), p.getRegLines().get("r_2"));
assertEquals(ints(0, 15), p.getSymbLines().get("a"));
}
private HashSet<Integer> ints(Integer... vals) {
return new HashSet<>(Arrays.asList(vals));
}
Program parse(String filename) {
File file = new File(filename + ".iloc");
if (!file.exists()) {
file = new File(BASE_DIR + filename + ".iloc");
}
try {
Program result = Assembler.instance().assemble(file);
String print = result.prettyPrint();
if (SHOW) {
System.out.println("Program " + file + ":");
System.out.print(print);
}
Program other = Assembler.instance().assemble(print);
assertEquals(result, other);
return result;
} catch (FormatException | IOException e) {
fail(e.getMessage());
return null;
}
}
private final static String BASE_DIR = "pp/iloc/sample/";
private final static boolean SHOW = true;
}

View File

@ -0,0 +1,112 @@
package pp.iloc.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import org.junit.Test;
import pp.iloc.Assembler;
import pp.iloc.Simulator;
import pp.iloc.eval.Machine;
import pp.iloc.model.Program;
import pp.iloc.parse.FormatException;
@SuppressWarnings("javadoc")
public class SimulatorTest {
@Test(timeout = 1000)
public void testFig13() {
Program p = parse("fig1-3");
Machine c = new Machine();
int a = c.init("a", 2);
c.init("b", 3);
c.init("c", 4);
c.init("d", 5);
new Simulator(p, c).run();
if (SHOW) {
System.out.println(c);
}
assertEquals(240, c.load(a));
}
@Test(timeout = 1000)
public void testFig13Stack() {
Program p = parse("fig1-3-stack");
Machine c = new Machine();
int a = c.init("a", 2);
c.init("b", 3);
c.init("c", 4);
c.init("d", 5);
new Simulator(p, c).run();
if (SHOW) {
System.out.println(c);
}
assertEquals(240, c.load(a));
}
@Test(timeout = 1000)
public void testFig13Init() {
Program p = parse("fig1-3-init");
Machine c = new Machine();
c.store(2, p.getSymb("a"));
c.store(3, p.getSymb("b"));
c.store(4, p.getSymb("c"));
c.store(5, p.getSymb("d"));
new Simulator(p, c).run();
if (SHOW) {
System.out.println(c);
System.out.println(p.prettyPrint());
}
assertEquals(240, c.load(p.getSymb("a")));
}
@Test(timeout = 1000)
public void testString() {
Program p = parse("string");
Simulator sim = new Simulator(p);
sim.setIn(new ByteArrayInputStream("abc".getBytes()));
ByteArrayOutputStream out = new ByteArrayOutputStream();
sim.setOut(out);
sim.run();
if (SHOW) {
System.out.println(p.prettyPrint());
}
assertEquals("Doubled: abcabc", out.toString().trim());
}
@Test
//(timeout = 1000)
public void testStringChar4() {
Program p = parse("string4");
Simulator sim = new Simulator(p);
sim.getVM().setCharSize(4);
sim.setIn(new ByteArrayInputStream("abc".getBytes()));
ByteArrayOutputStream out = new ByteArrayOutputStream();
sim.setOut(out);
sim.run();
if (SHOW) {
System.out.println(p.prettyPrint());
}
assertEquals("Doubled: abcabc", out.toString().trim());
}
Program parse(String filename) {
File file = new File(filename + ".iloc");
if (!file.exists()) {
file = new File(BASE_DIR + filename + ".iloc");
}
try {
return Assembler.instance().assemble(file);
} catch (FormatException | IOException e) {
fail(e.getMessage());
return null;
}
}
private final static String BASE_DIR = "pp/iloc/sample/";
private final static boolean SHOW = true;
}