added modified ILOC environment
This commit is contained in:
parent
f9110893c6
commit
e9b91f2152
|
@ -1,2 +1,2 @@
|
||||||
bin/
|
bin/
|
||||||
src/pp/iloc
|
src/pp/iloc/sample/
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 = ": ";
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 = "]";
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 = "\"";
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
/ILOC.tokens
|
||||||
|
/ILOCLexer.tokens
|
||||||
|
/ILOCBaseListener.java
|
||||||
|
/ILOCLexer.java
|
||||||
|
/ILOCListener.java
|
||||||
|
/ILOCParser.java
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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]+;
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue