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 36c8ae3..9369fcb 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Assembler.java +++ b/src/main/java/net/simon987/mar/server/assembly/Assembler.java @@ -541,19 +541,15 @@ public class Assembler { //Check for DW instruction try { - if (assumeLabels) { - byte[] bytes = parseDWInstruction(line, currentLine); - if (bytes != null) { - out.write(bytes); - return out.toByteArray(); - } - } else { - byte[] bytes = parseDWInstruction(line, labels, currentLine); - if (bytes != null) { - out.write(bytes); - return out.toByteArray(); - } + byte[] bytes; + if (assumeLabels) bytes = parseDWInstruction(line, currentLine); + else bytes = parseDWInstruction(line, labels, currentLine); + + if (bytes != null) { + out.write(bytes); + return out.toByteArray(); } + } catch (IOException e) { e.printStackTrace(); } 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 a2cc669..d411fdf 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Operand.java +++ b/src/main/java/net/simon987/mar/server/assembly/Operand.java @@ -1,8 +1,10 @@ package net.simon987.mar.server.assembly; +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 @@ -170,6 +172,127 @@ public class Operand { } } + + + /** + * 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; + } + } + + private 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) throw new AssemblyException("Found empty group", line); + + //Evaluation chain + while (!parseOps.isEmpty()) { + ParseOperator op = parseOps.peek(); + 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); + parseOps.pop(); + break; + } + lastValue = op.apply(lastValue); + parseOps.pop(); + } + if (parseOps.isEmpty() && ty == TokenParser.TokenType.EOF) return lastValue; + } + 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; + } + else throw new AssemblyException("Modifier or end not found", line); + } + } + } + /** * Attempt to parse a register * diff --git a/src/main/java/net/simon987/mar/server/assembly/TokenParser.java b/src/main/java/net/simon987/mar/server/assembly/TokenParser.java new file mode 100644 index 0000000..668e79c --- /dev/null +++ b/src/main/java/net/simon987/mar/server/assembly/TokenParser.java @@ -0,0 +1,280 @@ +package net.simon987.mar.server.assembly; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.simon987.mar.server.assembly.exception.AssemblyException; + +public class TokenParser { + + public enum TokenType { + Constant, BinaryOperator, UnaryOperator, GroupOperator, Space, EOF + } + + public enum BinaryOperatorType { + Add("+", 40) { + @Override + public int apply(int a, int b) { + return (a + b) & 0xFFFF; + } + },Sub("-", 40) { + @Override + public int apply(int a, int b) { + return (a - b) & 0xFFFF; + } + }, Mul("*", 30) { + @Override + public int apply(int a, int b) { + return (a * b) & 0xFFFF; + } + }, Div("/", 30) { + @Override + public int apply(int a, int b) { + return (a / b) & 0xFFFF; + } + }, Rem("%", 30) { + @Override + public int apply(int a, int b) { + return (a / b) & 0xFFFF; + } + }, Shl("<<", 50) { + @Override + public int apply(int a, int b) { + if (b >= 16) return 0; + return (a << b) & 0xFFFF; + } + }, Shr(">>", 50) { + @Override + public int apply(int a, int b) { + if (b >= 16) return 0; + return (a >>> b) & 0xFFFF; + } + }, Rol("<", 50) { + @Override + public int apply(int a, int b) { + b &= 0x11; + return ((a >>> (16-b)) | (a << b)) & 0xFFFF; + } + }, Ror(">", 50) { + @Override + public int apply(int a, int b) { + b &= 0x11; + return ((a >>> b) | (a << (16-b))) & 0xFFFF; + } + }, Or("|", 80) { + @Override + public int apply(int a, int b) { + return (a | b) & 0xFFFF; + } + }, And("&", 60) { + @Override + public int apply(int a, int b) { + return (a & b) & 0xFFFF; + } + }, Xor("^", 70) { + @Override + public int apply(int a, int b) { + return (a ^ b) & 0xFFFF; + } + }; + + final public static Map stringMap = new HashMap<>(); + + static { + for (BinaryOperatorType op : values()) stringMap.put(op.symbol, op); + } + + public final String symbol; + public final int precedence; + + BinaryOperatorType(final String symbol, final int precedence) { + this.symbol = symbol; + this.precedence = precedence; + } + + public abstract int apply(int a, int b); + + } + + public enum UnaryOperatorType { + Neg("-") { + @Override + public int apply(int a) { + return -a; + } + }, Not("~") { + @Override + public int apply(int a) { + return ~a; + } + }; + + public static final Map stringMap = new HashMap<>(); + + static { + for (UnaryOperatorType op : values()) stringMap.put(op.symbol, op); + } + + public final String symbol; + + UnaryOperatorType(final String symbol) { + this.symbol = symbol; + } + public abstract int apply(int a); + } + + public enum GroupOperatorType { + GroupStart("(", false, 0), + GroupEnd(")", true, 0); + + public static final Map stringMap = new HashMap<>(); + + static { + for (GroupOperatorType op : values()) stringMap.put(op.symbol, op); + } + + public final String symbol; + public final boolean end; + public final int groupType; + + GroupOperatorType(final String symbol, boolean end, int groupType) { + this.symbol = symbol; + this.end = end; + this.groupType = groupType; + } + } + + public enum ParseContext { + TackOn, Value + } + + private static final Pattern BINARY_OPERATOR_PATTERN = Pattern.compile("(<<|>>|[\\-+*/<>&|^])"); + private static final Pattern UNARY_OPERATOR_PATTERN = Pattern.compile("([\\-!])"); + private static final Pattern GROUP_OPERATOR_PATTERN = Pattern.compile("([()])"); + private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("([\\w])"); + private static final Pattern NUMBER_PATTERN_16 = Pattern.compile("0[xX]([\\da-fA-F]+)(?![\\w\\d])"); + private static final Pattern NUMBER_PATTERN_8 = Pattern.compile("0[oO]([0-7]+)(?![\\w\\d])"); + private static final Pattern NUMBER_PATTERN_2 = Pattern.compile("0[bB]([01]+)(?![\\w\\d])"); + private static final Pattern NUMBER_PATTERN_10 = Pattern.compile("(\\d+)(?![\\w\\d])"); + private static final Pattern NUMBER_PATTERN_START = Pattern.compile("(\\d)"); + private static final Pattern SPACE_PATTERN = Pattern.compile("[^\\S\\n]+"); + + + /** + * @param sequence The characters to parse + * @param start The index of the first character to be parsed + * @param end The index of the character after the last character to be parsed + */ + + public TokenParser(CharSequence sequence, int line, Map labels, int start, int end) { + matcher = SPACE_PATTERN.matcher(sequence); + this.line = line; + this.start = start; + this.end = end; + this.labels = labels; + } + + public TokenParser(CharSequence sequence, int line, Map labels) { + this(sequence, line, labels,0, sequence.length()); + } + + private int line, start, end; + + private Map labels; + + private Matcher matcher; + + public int lastInt; + + public BinaryOperatorType lastBinary; + + public UnaryOperatorType lastUnary; + + public GroupOperatorType lastGroup; + + /** + * Reads the next token. + * + * @param eatSpace whether the parser should ignore leading spaces + * @param context the current class of tokens expected + * @return The token that was found + * @throws AssemblyException if an unrecognized token is found, + * or if the found token is not supported in the current context. + */ + public TokenType GetNextToken(boolean eatSpace, ParseContext context) throws AssemblyException { + if (start >= end) return TokenType.EOF; + matcher.region(start, end); + if (matcher.usePattern(SPACE_PATTERN).lookingAt()) { + start = matcher.end(); + if (!eatSpace) return TokenType.Space; + if (start >= end) return TokenType.EOF; + matcher.region(start, end); + } + matcher.usePattern(GROUP_OPERATOR_PATTERN); + if (matcher.lookingAt()) { + start = matcher.end(); + String symbol = matcher.group(1); + lastGroup = GroupOperatorType.stringMap.get(symbol); + + // Should never happen unless the regex does not agree with GroupOperatorType. + if (lastGroup == null) throw new AssemblyException("Group operator not supported", line); + return TokenType.GroupOperator; + } + if (context == ParseContext.TackOn) { + if (matcher.usePattern(BINARY_OPERATOR_PATTERN).lookingAt()) { + start = matcher.end(); + String symbol = matcher.group(1); + lastBinary = BinaryOperatorType.stringMap.get(symbol); + + // Should never happen unless the regex does not agree with BinaryOperatorType. + if (lastBinary == null) throw new AssemblyException("Binary operator not supported", line); + + return TokenType.BinaryOperator; + } + } + else { + if (matcher.usePattern(NUMBER_PATTERN_START).lookingAt()) { + try { + if (matcher.usePattern(NUMBER_PATTERN_10).lookingAt()) + lastInt = Integer.parseInt(matcher.group(1), 10); + else if (matcher.usePattern(NUMBER_PATTERN_16).lookingAt()) + lastInt = Integer.parseInt( matcher.group(1), 16); + else if (matcher.usePattern(NUMBER_PATTERN_2).lookingAt()) + lastInt = Integer.parseInt(matcher.group(1), 2); + else if (matcher.usePattern(NUMBER_PATTERN_8).lookingAt()) + lastInt = Integer.parseInt(matcher.group(1), 8); + else throw new AssemblyException("Invalid number found.", line); + } catch (NumberFormatException ex) { + throw new AssemblyException("Number parsing failed", line); + } + start = matcher.end(1); + lastInt &= 0xFFFF; + return TokenType.Constant; + } + if (matcher.usePattern(IDENTIFIER_PATTERN).lookingAt()) { + start = matcher.end(); + String identifier = matcher.group(1); + Character val = labels.get(identifier); + + if (val == null) throw new AssemblyException("Unknown label found", line); + + return TokenType.Constant; + } + matcher.usePattern(UNARY_OPERATOR_PATTERN); + if (matcher.lookingAt()) { + start = matcher.end(); + String symbol = matcher.group(1); + lastUnary = UnaryOperatorType.stringMap.get(symbol); + + // Should never happen unless the regex does not agree with UnaryOperatorType. + if (lastUnary == null) throw new AssemblyException("Unary operator not supported", line); + return TokenType.BinaryOperator; + } + } + throw new AssemblyException("Invalid token found", line); + } + + +} diff --git a/src/test/java/net/simon987/mar/server/assembly/TokenTest.java b/src/test/java/net/simon987/mar/server/assembly/TokenTest.java new file mode 100644 index 0000000..7fc7754 --- /dev/null +++ b/src/test/java/net/simon987/mar/server/assembly/TokenTest.java @@ -0,0 +1,25 @@ +package net.simon987.mar.server.assembly; + +import org.junit.Test; + +import java.util.HashMap; + +import static org.junit.Assert.assertEquals; + +public class TokenTest { + @Test + public void numberTokenParsing() throws Exception { + TokenParser parser = new TokenParser("27 0x27 0o27 0b11011", 0, new HashMap<>()); + assertEquals(TokenParser.TokenType.Constant, parser.GetNextToken(true, TokenParser.ParseContext.Value)); + assertEquals(27, parser.lastInt); + + assertEquals(TokenParser.TokenType.Constant, parser.GetNextToken(true, TokenParser.ParseContext.Value)); + assertEquals(39, parser.lastInt); + + assertEquals(TokenParser.TokenType.Constant, parser.GetNextToken(true, TokenParser.ParseContext.Value)); + assertEquals(23, parser.lastInt); + + assertEquals(TokenParser.TokenType.Constant, parser.GetNextToken(true, TokenParser.ParseContext.Value)); + assertEquals(27, parser.lastInt); + } +}