added modified ILOC environment
This commit is contained in:
		
							parent
							
								
									f9110893c6
								
							
						
					
					
						commit
						e9b91f2152
					
				| 
						 | 
				
			
			@ -1,2 +1,2 @@
 | 
			
		|||
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