diff --git a/src/main/java/net/simon987/mar/server/assembly/Assembler.java b/src/main/java/net/simon987/mar/server/assembly/Assembler.java index f82fd80..acc4470 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Assembler.java +++ b/src/main/java/net/simon987/mar/server/assembly/Assembler.java @@ -79,7 +79,7 @@ public class Assembler { String[] tokens = line.trim().split("\\s+"); String mnemonic = tokens[0]; - if (mnemonic.toUpperCase().equals("ORG")) { + if (mnemonic.equalsIgnoreCase("ORG")) { if (tokens.length > 1) { try { result.origin = (Integer.decode(tokens[1])); @@ -124,6 +124,9 @@ public class Assembler { return line.replaceAll("\\s+", "").isEmpty(); } + + private static final Pattern DUP_PATTERN = Pattern.compile("(.+)\\s+DUP(.+)"); + private static final Pattern STRING_PATTERN = Pattern.compile("\"(.*)\"$"); /** * Parse the DW instruction (Define word). Handles DUP operator * @@ -132,7 +135,7 @@ public class Assembler { * @param labels Map of labels * @return Encoded instruction, null if the line is not a DW instruction */ - private static byte[] parseDWInstruction(String line, HashMap labels, int currentLine) + private byte[] parseDWInstruction(String line, HashMap labels, int currentLine) throws InvalidOperandException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -140,7 +143,7 @@ public class Assembler { //System.out.println(line); - if (line.length() >= 2 && line.substring(0, 2).toUpperCase().equals("DW")) { + if (line.length() >= 2 && line.substring(0, 2).equalsIgnoreCase("DW")) { try { @@ -150,18 +153,10 @@ public class Assembler { for (String value : values) { value = value.trim(); - - String[] valueTokens = value.split("\\s+"); - - //Handle DUP operator - if (valueTokens.length == 2 && valueTokens[1].toUpperCase().contains("DUP(")) { - out.write(parseDUPOperator16(valueTokens, labels, currentLine)); - } else if (value.startsWith("\"") && value.endsWith("\"")) { - //Handle string - - //Unescape the string - String string = value.substring(1, value.length() - 1); - + Matcher m = STRING_PATTERN.matcher(value); + if (m.lookingAt()) { + // Parse string + String string = m.group(1); try { string = StringEscapeUtils.unescapeJava(string); } catch (IllegalArgumentException e) { @@ -169,39 +164,39 @@ public class Assembler { "Invalid string operand \"" + string + "\": " + e.getMessage(), currentLine); } - out.write(string.getBytes(StandardCharsets.UTF_16BE)); - } else if (labels != null && labels.containsKey(value)) { - //Handle label - out.writeChar(labels.get(value)); - - } else { - //Handle integer value + continue; + } + int factor; + if (m.usePattern(DUP_PATTERN).lookingAt()) { + // Get DUP factor + TokenParser parser = new TokenParser(m.group(1), currentLine, new HashMap<>()); try { - out.writeChar(Integer.decode(value)); - - } catch (NumberFormatException e) { - //Handle assumed label - if (labels == null) { - - // Write placeholder word - out.writeChar(0); - - } else { - - //Integer.decode failed, try binary - if (value.startsWith("0b")) { - try { - out.writeChar(Integer.parseInt(value.substring(2), 2)); - } catch (NumberFormatException e2) { - throw new InvalidOperandException("Invalid operand \"" + value + '"', currentLine); - } - } else { - throw new InvalidOperandException("Invalid operand \"" + value + '"', currentLine); - - } + if (TokenParser.TokenType.Constant != + parser.getNextToken(true, TokenParser.ParseContext.Value)) { + throw new InvalidOperandException("Invalid DUP factor " + m.group(1), currentLine); } + } catch (AssemblyException ae) { + throw new InvalidOperandException("Couldn't parse DUP factor " + m.group(1), currentLine); } + factor = parser.lastInt; + if (factor > MEM_SIZE) { + throw new InvalidOperandException( + "Factor '" + factor + "' exceeds total memory size", currentLine); + } + + value = m.group(2); + } + else { + // Parse as single number + factor = 1; + } + + Operand operand = new Operand(value, labels, registerSet, currentLine); + char s = (char)operand.getData(); + for (int i = 0; i < factor; i++) { + out.write(Util.getHigherByte(s)); + out.write(Util.getLowerByte(s)); } } @@ -224,61 +219,10 @@ public class Assembler { * @param currentLine Current line number * @return Encoded instruction, null if the line is not a DW instruction */ - private static byte[] parseDWInstruction(String line, int currentLine) throws AssemblyException { + private byte[] parseDWInstruction(String line, int currentLine) throws AssemblyException { return parseDWInstruction(line, null, currentLine); } - /** - * Parse the dup operator - * - * @param valueTokens Value tokens e.g. {"8", "DUP(12)"} - * @param labels Map of labels - * @return The encoded instruction - */ - private static byte[] parseDUPOperator16(String[] valueTokens, HashMap labels, int currentLine) - throws InvalidOperandException { - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - try { - - int factor = Integer.decode(valueTokens[0]); - - if (factor > MEM_SIZE) { - throw new InvalidOperandException("Factor '" + factor + "' exceeds total memory size", currentLine); - } - - String value = valueTokens[1].substring(4, valueTokens[1].lastIndexOf(')')); - - //Handle label - if (labels != null && labels.containsKey(value)) { - //Label value is casted to byte - for (int i = 0; i < factor; i++) { - char s = labels.get(value); - - out.write(Util.getHigherByte(s)); - out.write(Util.getLowerByte(s)); - } - - } else { - //Handle integer value - char s = (char) (int) Integer.decode(value); - - for (int i = 0; i < factor; i++) { - out.write(Util.getHigherByte(s)); - out.write(Util.getLowerByte(s)); - } - } - - - } catch (NumberFormatException e) { - throw new InvalidOperandException("Usage: DUP()", currentLine); - } - - return out.toByteArray(); - - } - /** * Check for and handle section declarations (.text and .data) * @@ -289,13 +233,13 @@ public class Assembler { String[] tokens = line.split("\\s+"); - if (tokens[0].toUpperCase().equals(".TEXT")) { + if (tokens[0].equalsIgnoreCase(".TEXT")) { result.defineSection(Section.TEXT, currentLine, currentOffset); result.disassemblyLines.add(".text"); throw new PseudoInstructionException(currentLine); - } else if (tokens[0].toUpperCase().equals(".DATA")) { + } else if (tokens[0].equalsIgnoreCase(".DATA")) { LogManager.LOGGER.fine("DEBUG: .data @" + currentLine); @@ -304,9 +248,8 @@ public class Assembler { } } - private static final Pattern EQU_PATTERN = Pattern.compile("([\\w]+)[^\\S\\n]+EQU[^\\S\\n]+(.+)", - Pattern.CASE_INSENSITIVE - ); + private static final Pattern EQU_PATTERN = Pattern.compile("([\\w]+)\\s+EQU\\s+(.+)", + Pattern.CASE_INSENSITIVE); /** * Check for and handle the EQU instruction @@ -316,7 +259,7 @@ public class Assembler { * @param currentLine Current line number */ private static void checkForEQUInstruction(String line, HashMap labels, - int currentLine, RegisterSet registers) + int currentLine) throws AssemblyException { /* the EQU pseudo instruction is equivalent to the #define compiler directive in C/C++ * usage: constant_name EQU @@ -347,9 +290,6 @@ public class Assembler { * the errors, if any. */ public AssemblyResult parse(String text) { - - int currentLine; - //Split in lines AssemblyResult result = new AssemblyResult(config); String[] lines = text.split("\n"); @@ -451,7 +391,7 @@ public class Assembler { //Check for pseudo instructions checkForSectionDeclaration(line, result, currentLine, currentOffset); - checkForEQUInstruction(line, result.labels, currentLine, registerSet); + checkForEQUInstruction(line, result.labels, currentLine); checkForORGInstruction(line, result, currentLine); for (String label : result.labels.keySet()) { diff --git a/src/main/java/net/simon987/mar/server/assembly/Operand.java b/src/main/java/net/simon987/mar/server/assembly/Operand.java index 6be8aac..90c9cbd 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Operand.java +++ b/src/main/java/net/simon987/mar/server/assembly/Operand.java @@ -4,7 +4,6 @@ import net.simon987.mar.server.assembly.exception.AssemblyException; import net.simon987.mar.server.assembly.exception.InvalidOperandException; import java.util.HashMap; -import java.util.Stack; /** * Represents an operand of an instruction. An operand can refer to a @@ -36,14 +35,14 @@ public class Operand { * Data of the operand. This will be appended after the instruction. * For example, "[AX+3]" value={index of AX] + {number of registers}, Data=3 */ - private int data = 0; + private char data = 0; public Operand(OperandType type, int value) { this.type = type; this.value = value; } - public Operand(OperandType type, int value, int data) { + public Operand(OperandType type, int value, char data) { this(type, value); this.data = data; } @@ -90,7 +89,7 @@ public class Operand { value = Operand.IMMEDIATE_VALUE_MEM; return; } - if (!parseRegExpr(registerSet, labels)) { + if (!parseRegExpr(registerSet, labels, line)) { if (labels == null) { type = OperandType.MEMORY_IMM16; data = 0; @@ -127,206 +126,6 @@ public class Operand { } } - /** - * Attempt to parse an integer - * - * @param text Text to parse, can be a label or immediate value (hex or dec) - * @return true if successful, false otherwise - */ - private boolean parseImmediate(String text) { - - text = text.trim(); - - try { - //Try IMM - type = OperandType.IMMEDIATE16; - data = Integer.decode(text); - value = IMMEDIATE_VALUE; - return true; - } catch (NumberFormatException e) { - - //Try Binary number (format 0bXXXX) - if (text.startsWith("0b")) { - try { - data = Integer.parseInt(text.substring(2), 2); - value = IMMEDIATE_VALUE; - return true; - } catch (NumberFormatException e2) { - return false; - } - } else if (text.startsWith("0o")) { - try { - data = Integer.parseInt(text.substring(2), 8); - value = IMMEDIATE_VALUE; - return true; - } catch (NumberFormatException e2) { - return false; - } - } - - return false; - } - } - - /** - * Attempt to parse a user-defined label - * - * @param text Text to parse - * @param labels Map of labels - * @return true if parsing is successful, false otherwise - */ - private boolean parseLabel(String text, HashMap labels) { - - text = text.trim(); - - if (labels == null) { - return false; - } else if (labels.containsKey(text)) { - type = OperandType.IMMEDIATE16; - data = labels.get(text); - value = IMMEDIATE_VALUE; - return true; - } else { - return false; - } - } - - - - /** - * Interface allowing parse states to be manipulated, evaluated, and stacked. - */ - private static class ParseOperator { - public int getPrecedence() { - return 0; - } - public int apply(int other) { - return other; - } - - public final int closeExpect; - - public ParseOperator(int closeExpect) { - this.closeExpect = closeExpect; - } - } - - private static class ParseOperatorUnary extends ParseOperator { - TokenParser.UnaryOperatorType op; - @Override - public int getPrecedence() { - return 0; - } - - @Override - public int apply(int other) { - return op.apply(other); - } - - public ParseOperatorUnary(int closeExpect, TokenParser.UnaryOperatorType op) { - super(closeExpect); - this.op = op; - } - } - - private static class ParseOperatorBinary extends ParseOperator { - private final TokenParser.BinaryOperatorType op; - private final int value; - @Override - public int getPrecedence() { - return op.precedence; - } - - @Override - public int apply(int other) { - return op.apply(value, other); - } - - public ParseOperatorBinary(int closeExpect, TokenParser.BinaryOperatorType op, int value) { - super(closeExpect); - this.op = op; - this.value = value; - } - } - - public static int parseConstExpression(String text, int line, HashMap labels) - throws AssemblyException { - TokenParser parser = new TokenParser(text, line, labels); - Stack parseOps = new Stack<>(); - int closeExpect = -1; // No closing parenthesis expected - TokenParser.ParseContext context = TokenParser.ParseContext.Value; - int lastValue = 0; - while (true) { - TokenParser.TokenType ty = parser.getNextToken(true, context); - if (context == TokenParser.ParseContext.Value) { - // Parse value - if (ty == TokenParser.TokenType.UnaryOperator) { - parseOps.push(new ParseOperatorUnary(closeExpect, parser.lastUnary)); - closeExpect = -1; - } - else if (ty == TokenParser.TokenType.GroupOperator) { - if (parser.lastGroup.end) throw new AssemblyException("Unexpected group close", line); - if (closeExpect != -1) parseOps.push(new ParseOperator(closeExpect)); - closeExpect = parser.lastGroup.groupType; - } else if (ty == TokenParser.TokenType.Constant) { - lastValue = parser.lastInt; - context = TokenParser.ParseContext.TackOn; - } else throw new AssemblyException("Value not found", line); - } else { - // Parse modifier - if (ty == TokenParser.TokenType.EOF || ty == TokenParser.TokenType.GroupOperator) { - if (ty == TokenParser.TokenType.GroupOperator && !parser.lastGroup.end) - throw new AssemblyException("Unexpected group open", line); - if (closeExpect != -1) { - if (ty == TokenParser.TokenType.EOF) - throw new AssemblyException("Unclosed group", line); - else if (closeExpect != parser.lastGroup.groupType) - throw new AssemblyException("Unmatched group ends", line); - else { - closeExpect = -1; - continue; - } - } - - boolean completed = false; - - //Evaluation chain - while (!parseOps.isEmpty()) { - ParseOperator op = parseOps.pop(); - if (op.closeExpect != -1) { - if (ty == TokenParser.TokenType.EOF) throw new AssemblyException("Unclosed group", line); - else if (op.closeExpect != parser.lastGroup.groupType) - throw new AssemblyException("Unmatched group ends", line); - lastValue = op.apply(lastValue); - completed = true; - break; - } - lastValue = op.apply(lastValue); - } - if (!completed) { - if (ty == TokenParser.TokenType.EOF) return lastValue; - else if (parser.lastGroup.groupType != -1) - throw new AssemblyException("Unexpected group close", line); - } - - } - else if (ty == TokenParser.TokenType.BinaryOperator) { - TokenParser.BinaryOperatorType bop = parser.lastBinary; - while (closeExpect == -1 && !parseOps.empty()) { - ParseOperator op = parseOps.peek(); - if (bop.precedence <= op.getPrecedence()) break; - lastValue = op.apply(lastValue); - closeExpect = op.closeExpect; - parseOps.pop(); - } - parseOps.push(new ParseOperatorBinary(closeExpect, bop, lastValue)); - closeExpect = -1; - context = TokenParser.ParseContext.Value; - } - else throw new AssemblyException("Modifier or end not found", line); - } - } - } /** * Attempt to parse a register @@ -354,7 +153,7 @@ public class Operand { * * @return true if successful */ - private boolean parseRegExpr(RegisterSet registerSet, HashMap labels) { + private boolean parseRegExpr(RegisterSet registerSet, HashMap labels, int line) { String expr; @@ -374,7 +173,10 @@ public class Operand { return false; } - if (expr.replaceAll("\\s+", "").isEmpty()) { + //Remove white space + expr = expr.replaceAll("\\s+", ""); + + if (expr.isEmpty()) { //No data specified type = OperandType.MEMORY_REG16; value += registerSet.size(); //refers to memory. @@ -382,64 +184,22 @@ public class Operand { return true; } - //Remove white space - expr = expr.replaceAll("\\s+", ""); - - try { - type = OperandType.MEMORY_REG_DISP16; - - if (labels != null) { - - Character address = labels.get(expr.replaceAll("[^A-Za-z0-9_]", "")); - if (address != null) { - data = (expr.startsWith("-")) ? -address : address; - value += registerSet.size() * 2;//refers to memory with disp - - return true; - } - } - - //label is invalid - data = Integer.decode(expr); - value += registerSet.size() * 2; //refers to memory with disp + if (!expr.startsWith("-") && !expr.startsWith("+")) { + return false; + } + type = OperandType.MEMORY_REG_DISP16; + value += registerSet.size() * 2; + if (labels == null) { + data = 0; return true; - } catch (NumberFormatException e) { - - //Integer.decode failed, try binary - if (expr.startsWith("+0b")) { - try { - data = Integer.parseInt(expr.substring(3), 2); - value += registerSet.size() * 2; //refers to memory with disp - return true; - } catch (NumberFormatException e2) { - return false; - } - } else if (expr.startsWith("-0b")) { - try { - data = -Integer.parseInt(expr.substring(3), 2); - value += registerSet.size() * 2; //refers to memory with disp - return true; - } catch (NumberFormatException e2) { - return false; - } - } else if (expr.startsWith("+0o")) { - try { - data = Integer.parseInt(expr.substring(3), 8); - value += registerSet.size() * 2; //refers to memory with disp - return true; - } catch (NumberFormatException e2) { - return false; - } - } else if (expr.startsWith("-0o")) { - try { - data = -Integer.parseInt(expr.substring(3), 8); - value += registerSet.size() * 2; //refers to memory with disp - return true; - } catch (NumberFormatException e2) { - return false; - } - } + } + expr = "0" + expr; + TokenParser parser = new TokenParser(expr, line, labels); + try { + data = parser.parseConstExpression(); + return true; + } catch (AssemblyException ex) { return false; } } @@ -452,7 +212,7 @@ public class Operand { return value; } - public int getData() { + public char getData() { return data; } diff --git a/src/main/java/net/simon987/mar/server/assembly/TokenParser.java b/src/main/java/net/simon987/mar/server/assembly/TokenParser.java index 66498cb..e2da4a8 100644 --- a/src/main/java/net/simon987/mar/server/assembly/TokenParser.java +++ b/src/main/java/net/simon987/mar/server/assembly/TokenParser.java @@ -7,7 +7,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import net.simon987.mar.server.assembly.exception.AssemblyException; -import org.apache.velocity.runtime.directive.Parse; public class TokenParser { @@ -360,7 +359,12 @@ public class TokenParser { throw new AssemblyException("Invalid token found", line); } - public int parseConstExpression() + /** + * Parses and evaluates a constant expression from the input. + * @return the value of the constant expression + * @throws AssemblyException when a constant expression cannot be parsed + */ + public char parseConstExpression() throws AssemblyException { Stack parseOps = new Stack<>(); int closeExpect = -1; // No closing parenthesis expected @@ -423,7 +427,7 @@ public class TokenParser { } if (!completed) { if (ty == TokenParser.TokenType.EOF) { - return lastValue; + return (char)lastValue; } else if (lastGroup.groupType != -1) { throw new AssemblyException("Unexpected group close", line); } diff --git a/src/test/java/net/simon987/mar/server/assembly/DwDirectiveTest.java b/src/test/java/net/simon987/mar/server/assembly/DwDirectiveTest.java index 876e932..f5f46c6 100644 --- a/src/test/java/net/simon987/mar/server/assembly/DwDirectiveTest.java +++ b/src/test/java/net/simon987/mar/server/assembly/DwDirectiveTest.java @@ -59,4 +59,14 @@ public class DwDirectiveTest { assertTrue(res.ar.exceptions.isEmpty()); assertEquals('"' ,res.memValue(res.ar.origin)); } + + @Test + public void dupTest() { + String code = "DW 10 DUP(4)"; + + TestExecutionResult res = TestHelper.executeCode(code); + + assertTrue(res.ar.exceptions.isEmpty()); + assertEquals(4 ,res.memValue(res.ar.origin)); + } } diff --git a/src/test/java/net/simon987/mar/server/assembly/OperandTest.java b/src/test/java/net/simon987/mar/server/assembly/OperandTest.java index f447714..94a3b78 100644 --- a/src/test/java/net/simon987/mar/server/assembly/OperandTest.java +++ b/src/test/java/net/simon987/mar/server/assembly/OperandTest.java @@ -54,7 +54,7 @@ public class OperandTest { Operand imm2 = new Operand(" -12", labels, registerSet, 0); assertEquals(OperandType.IMMEDIATE16, imm2.getType()); assertEquals(Operand.IMMEDIATE_VALUE, imm2.getValue()); - assertEquals(-12, imm2.getData()); + assertEquals((char)-12, imm2.getData()); Operand imm3 = new Operand(" 0xABCD", labels, registerSet, 0); assertEquals(OperandType.IMMEDIATE16, imm3.getType()); @@ -75,7 +75,7 @@ public class OperandTest { Operand mem2 = new Operand("[-12 ]", labels, registerSet, 0); assertEquals(OperandType.MEMORY_IMM16, mem2.getType()); assertEquals(Operand.IMMEDIATE_VALUE_MEM, mem2.getValue()); - assertEquals(-12, mem2.getData()); + assertEquals((char)-12, mem2.getData()); Operand mem3 = new Operand(" [ 0xABCD]", labels, registerSet, 0); assertEquals(OperandType.MEMORY_IMM16, mem3.getType()); @@ -117,7 +117,7 @@ public class OperandTest { Operand mem10 = new Operand("[ B - label1 ]", labels, registerSet, 0); assertEquals(OperandType.MEMORY_REG_DISP16, mem10.getType()); assertEquals(2 + 2 * registerSet.size(), mem10.getValue()); - assertEquals(-10, mem10.getData()); + assertEquals((char)-10, mem10.getData()); } catch (InvalidOperandException e) { @@ -201,11 +201,13 @@ public class OperandTest { fail(); } catch (InvalidOperandException ignored) { } + /* This should most likely be allowed under constant folding. try { new Operand("[A + -1]", labels, registerSet, 0); fail(); } catch (InvalidOperandException ignored) { } + */ try { new Operand("[A + ]", labels, registerSet, 0); fail();