From 36edfc57dcc74bb14f40c093a358a8b3789a4243 Mon Sep 17 00:00:00 2001 From: chromapid <67613147+chromapid@users.noreply.github.com> Date: Sun, 19 Dec 2021 14:22:42 -0700 Subject: [PATCH 1/8] Began work on compile time math expressions. --- .../mar/server/assembly/Assembler.java | 20 +- .../simon987/mar/server/assembly/Operand.java | 123 ++++++++ .../mar/server/assembly/TokenParser.java | 280 ++++++++++++++++++ .../mar/server/assembly/TokenTest.java | 25 ++ 4 files changed, 436 insertions(+), 12 deletions(-) create mode 100644 src/main/java/net/simon987/mar/server/assembly/TokenParser.java create mode 100644 src/test/java/net/simon987/mar/server/assembly/TokenTest.java 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); + } +} From 23ddff6eb24d968080242d2c5329463e131ac85e Mon Sep 17 00:00:00 2001 From: chromapid <67613147+chromapid@users.noreply.github.com> Date: Sun, 19 Dec 2021 23:48:59 -0700 Subject: [PATCH 2/8] Tests, fixes for constant folding --- .../simon987/mar/server/assembly/Operand.java | 30 ++- .../mar/server/assembly/TokenParser.java | 31 ++- .../mar/server/assembly/TokenTest.java | 185 ++++++++++++++++-- 3 files changed, 218 insertions(+), 28 deletions(-) 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 d411fdf..7029e78 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Operand.java +++ b/src/main/java/net/simon987/mar/server/assembly/Operand.java @@ -230,7 +230,7 @@ public class Operand { } } - private int parseConstExpression(String text, int line, HashMap labels) + public static int parseConstExpression(String text, int line, HashMap labels) throws AssemblyException { TokenParser parser = new TokenParser(text, line, labels); Stack parseOps = new Stack<>(); @@ -238,7 +238,7 @@ public class Operand { TokenParser.ParseContext context = TokenParser.ParseContext.Value; int lastValue = 0; while (true) { - TokenParser.TokenType ty = parser.GetNextToken(true, context); + TokenParser.TokenType ty = parser.getNextToken(true, context); if (context == TokenParser.ParseContext.Value) { // Parse value if (ty == TokenParser.TokenType.UnaryOperator) { @@ -258,23 +258,38 @@ public class Operand { 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); + 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.peek(); + 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); - parseOps.pop(); + completed = true; break; } lastValue = op.apply(lastValue); - parseOps.pop(); } - if (parseOps.isEmpty() && ty == TokenParser.TokenType.EOF) return 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; @@ -287,6 +302,7 @@ public class Operand { } parseOps.push(new ParseOperatorBinary(closeExpect, bop, lastValue)); closeExpect = -1; + context = TokenParser.ParseContext.Value; } else throw new AssemblyException("Modifier or end not found", line); } 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 668e79c..2423542 100644 --- a/src/main/java/net/simon987/mar/server/assembly/TokenParser.java +++ b/src/main/java/net/simon987/mar/server/assembly/TokenParser.java @@ -6,6 +6,7 @@ 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 { @@ -151,17 +152,17 @@ public class TokenParser { } private static final Pattern BINARY_OPERATOR_PATTERN = Pattern.compile("(<<|>>|[\\-+*/<>&|^])"); - private static final Pattern UNARY_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 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 RESET_PATTERN = Pattern.compile("[^\\w\\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 @@ -203,7 +204,7 @@ public class TokenParser { * @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 { + 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()) { @@ -214,12 +215,13 @@ public class TokenParser { } matcher.usePattern(GROUP_OPERATOR_PATTERN); if (matcher.lookingAt()) { - start = matcher.end(); + start = matcher.end(1); 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) { @@ -236,17 +238,23 @@ public class TokenParser { } 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); + 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); + else { + if (matcher.usePattern(RESET_PATTERN).find()) start = matcher.start(); + else start = end; + throw new AssemblyException("Invalid number found.", line); + } } catch (NumberFormatException ex) { + start = matcher.end(1); throw new AssemblyException("Number parsing failed", line); } start = matcher.end(1); @@ -254,25 +262,28 @@ public class TokenParser { return TokenType.Constant; } if (matcher.usePattern(IDENTIFIER_PATTERN).lookingAt()) { - start = matcher.end(); + start = matcher.end(1); String identifier = matcher.group(1); Character val = labels.get(identifier); if (val == null) throw new AssemblyException("Unknown label found", line); + lastInt = val; return TokenType.Constant; } matcher.usePattern(UNARY_OPERATOR_PATTERN); if (matcher.lookingAt()) { - start = matcher.end(); + start = matcher.end(1); 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; + return TokenType.UnaryOperator; } } + if (matcher.usePattern(RESET_PATTERN).find()) start = matcher.end(); + else start = end; 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 index 7fc7754..c55ee34 100644 --- a/src/test/java/net/simon987/mar/server/assembly/TokenTest.java +++ b/src/test/java/net/simon987/mar/server/assembly/TokenTest.java @@ -1,25 +1,188 @@ package net.simon987.mar.server.assembly; +import net.simon987.mar.server.assembly.exception.AssemblyException; import org.junit.Test; import java.util.HashMap; +import java.util.Map; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; 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); + public void constantTokenParsing() throws Exception { + Map labels = new HashMap<>(); + labels.put("alpha", (char)29); + labels.put("beta", (char)28); + labels.put("gamma", (char)-5); + labels.put("epsilon", (char)0); + TokenParser parser = new TokenParser( + " 27 0x27 0o27 0b11011 alpha beta gamma delta epsilon 0 1a 0xG 0o8 0b2", + 0, labels); - assertEquals(TokenParser.TokenType.Constant, parser.GetNextToken(true, TokenParser.ParseContext.Value)); - assertEquals(39, parser.lastInt); + assertEquals(TokenParser.TokenType.Space, parser.getNextToken(false, TokenParser.ParseContext.Value)); + assertEquals(TokenParser.TokenType.Constant, parser.getNextToken(false, TokenParser.ParseContext.Value)); + assertEquals((char)27, parser.lastInt); + assertEquals(TokenParser.TokenType.Space, parser.getNextToken(false, TokenParser.ParseContext.TackOn)); + assertEquals(TokenParser.TokenType.Constant, parser.getNextToken(true, TokenParser.ParseContext.Value)); + assertEquals((char)39, parser.lastInt); + assertEquals(TokenParser.TokenType.Constant, parser.getNextToken(true, TokenParser.ParseContext.Value)); + assertEquals((char)23, parser.lastInt); + assertEquals(TokenParser.TokenType.Constant, parser.getNextToken(true, TokenParser.ParseContext.Value)); + assertEquals((char)27, parser.lastInt); - assertEquals(TokenParser.TokenType.Constant, parser.GetNextToken(true, TokenParser.ParseContext.Value)); - assertEquals(23, parser.lastInt); + // Labels + assertEquals(TokenParser.TokenType.Constant, parser.getNextToken(true, TokenParser.ParseContext.Value)); + assertEquals((char)29, parser.lastInt); + assertEquals(TokenParser.TokenType.Constant, parser.getNextToken(true, TokenParser.ParseContext.Value)); + assertEquals((char)28, parser.lastInt); + assertEquals(TokenParser.TokenType.Constant, parser.getNextToken(true, TokenParser.ParseContext.Value)); + assertEquals((char)-5, parser.lastInt); + try { + parser.getNextToken(true, TokenParser.ParseContext.Value); + fail(); + } catch (AssemblyException ignore) {} + try { + parser.getNextToken(true, TokenParser.ParseContext.TackOn); + fail(); + } catch (AssemblyException ignore) {} + try { + parser.getNextToken(true, TokenParser.ParseContext.TackOn); + fail(); + } catch (AssemblyException ignore) {} + try { + parser.getNextToken(true, TokenParser.ParseContext.Value); + fail(); + } catch (AssemblyException ignore) {} + try { + parser.getNextToken(true, TokenParser.ParseContext.Value); + fail(); + } catch (AssemblyException ignore) {} + try { + parser.getNextToken(true, TokenParser.ParseContext.Value); + fail(); + } catch (AssemblyException ignore) {} + try { + parser.getNextToken(true, TokenParser.ParseContext.Value); + fail(); + } catch (AssemblyException ignore) {} + } + @Test + public void operatorTokenParsing() throws Exception { + String ops = "+~-"; + TokenParser parser = new TokenParser(ops, 0, new HashMap<>()); + assertEquals(TokenParser.TokenType.BinaryOperator, + parser.getNextToken(true, TokenParser.ParseContext.TackOn)); + assertEquals(TokenParser.BinaryOperatorType.Add, parser.lastBinary); + try { + parser.getNextToken(true, TokenParser.ParseContext.TackOn); + fail(); + } catch (AssemblyException ignore) {} + assertEquals(TokenParser.TokenType.BinaryOperator, + parser.getNextToken(true, TokenParser.ParseContext.TackOn)); + assertEquals(TokenParser.BinaryOperatorType.Sub, parser.lastBinary); - assertEquals(TokenParser.TokenType.Constant, parser.GetNextToken(true, TokenParser.ParseContext.Value)); - assertEquals(27, parser.lastInt); + + parser = new TokenParser(ops, 0, new HashMap<>()); + try { + parser.getNextToken(true, TokenParser.ParseContext.Value); + fail(); + } catch (AssemblyException ignore) {} + + assertEquals(TokenParser.TokenType.UnaryOperator, + parser.getNextToken(true, TokenParser.ParseContext.Value)); + assertEquals(TokenParser.UnaryOperatorType.Not, parser.lastUnary); + assertEquals(TokenParser.TokenType.UnaryOperator, + parser.getNextToken(true, TokenParser.ParseContext.Value)); + assertEquals(TokenParser.UnaryOperatorType.Neg, parser.lastUnary); + parser = new TokenParser("()", 0, new HashMap<>()); + assertEquals(TokenParser.TokenType.GroupOperator, + parser.getNextToken(true, TokenParser.ParseContext.Value)); + assertEquals(TokenParser.GroupOperatorType.GroupStart, parser.lastGroup); + assertEquals(TokenParser.TokenType.GroupOperator, + parser.getNextToken(true, TokenParser.ParseContext.Value)); + assertEquals(TokenParser.GroupOperatorType.GroupEnd, parser.lastGroup); + } + + @Test + public void parseTest() throws AssemblyException { + assertEquals(1, Operand.parseConstExpression( + "1", + 0, new HashMap<>()) + ); + assertEquals(2, Operand.parseConstExpression( + "1 + 1", + 0, new HashMap<>()) + ); + assertEquals(82, Operand.parseConstExpression( + "10 + 9 * 8", + 0, new HashMap<>()) + ); + assertEquals(98, Operand.parseConstExpression( + "10 * 9 + 8", + 0, new HashMap<>()) + ); + assertEquals(2, Operand.parseConstExpression( + "(1 + 1)", + 0, new HashMap<>()) + ); + assertEquals(152, Operand.parseConstExpression( + "((10 + 9)) * 8", + 0, new HashMap<>()) + ); + assertEquals(170, Operand.parseConstExpression( + "(10 * ((9 + 8)))", + 0, new HashMap<>()) + ); + assertEquals((char)-170, Operand.parseConstExpression( + "(-10 * ((9 + 8)))", + 0, new HashMap<>()) + ); + assertEquals(2, Operand.parseConstExpression( + "(-3 + 5)", + 0, new HashMap<>()) + ); + try { + Operand.parseConstExpression( + "(1", + 0, new HashMap<>() + ); + fail(); + } catch (AssemblyException ignore) {} + try { + Operand.parseConstExpression( + "1)", + 0, new HashMap<>() + ); + fail(); + } catch (AssemblyException ignore) {} + try { + Operand.parseConstExpression( + "(1+1", + 0, new HashMap<>() + ); + fail(); + } catch (AssemblyException ignore) {} + try { + Operand.parseConstExpression( + "1+1)", + 0, new HashMap<>() + ); + fail(); + } catch (AssemblyException ignore) {} + try { + Operand.parseConstExpression( + "((1+1)", + 0, new HashMap<>() + ); + fail(); + } catch (AssemblyException ignore) {} + try { + Operand.parseConstExpression( + "(1+1))", + 0, new HashMap<>() + ); + fail(); + } catch (AssemblyException ignore) {} } } From 74ab5b6d888c2f633bb2b3a74dd0a7b609716db8 Mon Sep 17 00:00:00 2001 From: chromapid <67613147+chromapid@users.noreply.github.com> Date: Mon, 20 Dec 2021 16:03:14 -0700 Subject: [PATCH 3/8] EQU and Mnemonic constant folding support. --- .../mar/server/assembly/Assembler.java | 29 ++- .../simon987/mar/server/assembly/Operand.java | 71 ++++--- .../mar/server/assembly/TokenParser.java | 199 ++++++++++++++++-- .../server/websocket/CodeRequestHandler.java | 22 +- src/main/resources/config.properties | 2 +- src/main/typescript/GameClient.ts | 2 + src/main/typescript/World.ts | 5 +- src/main/typescript/phaser.d.ts | 20 +- .../mar/server/assembly/OperandTest.java | 3 +- .../mar/server/assembly/TokenTest.java | 140 +++++------- 10 files changed, 316 insertions(+), 177 deletions(-) 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 9369fcb..f82fd80 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Assembler.java +++ b/src/main/java/net/simon987/mar/server/assembly/Assembler.java @@ -304,6 +304,10 @@ public class Assembler { } } + private static final Pattern EQU_PATTERN = Pattern.compile("([\\w]+)[^\\S\\n]+EQU[^\\S\\n]+(.+)", + Pattern.CASE_INSENSITIVE + ); + /** * Check for and handle the EQU instruction * @@ -311,30 +315,25 @@ public class Assembler { * @param labels Map of labels. Constants will be added as labels * @param currentLine Current line number */ - private static void checkForEQUInstruction(String line, HashMap labels, int currentLine) + private static void checkForEQUInstruction(String line, HashMap labels, + int currentLine, RegisterSet registers) throws AssemblyException { /* the EQU pseudo instruction is equivalent to the #define compiler directive in C/C++ * usage: constant_name EQU * A constant treated the same way as a label. */ line = line.trim(); - String[] tokens = line.split("\\s+"); + Matcher matcher = EQU_PATTERN.matcher(line); - if (line.toUpperCase().matches(".*\\bEQU\\b.*")) { - if (tokens[1].toUpperCase().equals("EQU") && tokens.length == 3) { - try { - //Save value as a label - labels.put(tokens[0], (char) (int) Integer.decode(tokens[2])); - } catch (NumberFormatException e) { - throw new InvalidOperandException("Usage: constant_name EQU immediate_value", currentLine); - } - } else { - throw new InvalidOperandException("Usage: constant_name EQU immediate_value", currentLine); - } - + if (matcher.lookingAt()) { + //Save value as a label + TokenParser parser = new TokenParser(matcher.group(2), currentLine, labels); + char value = (char)parser.parseConstExpression(); + labels.put(matcher.group(1), value); throw new PseudoInstructionException(currentLine); } + } /** @@ -452,7 +451,7 @@ public class Assembler { //Check for pseudo instructions checkForSectionDeclaration(line, result, currentLine, currentOffset); - checkForEQUInstruction(line, result.labels, currentLine); + checkForEQUInstruction(line, result.labels, currentLine, registerSet); 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 7029e78..6be8aac 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Operand.java +++ b/src/main/java/net/simon987/mar/server/assembly/Operand.java @@ -27,8 +27,8 @@ public class Operand { private OperandType type; /** - * Value of the the operand, this is the part that will - * written into the instruction. + * Value of the operand, this is the part that will + * be written into the instruction. */ private int value = 0; @@ -73,40 +73,59 @@ public class Operand { this.text = text.replace(",", ""); this.text = this.text.trim(); + if (parseReg(this.text, registerSet)) { + return; + } + if (parseConstExpression(line, labels)) { + type = OperandType.IMMEDIATE16; + value = IMMEDIATE_VALUE; + return; + } + if (this.text.startsWith("[") && this.text.endsWith("]")) { - if (!parseImmediate(this.text) && !parseReg(this.text, registerSet) && !parseLabel(this.text, labels)) { - if (this.text.startsWith("[") && this.text.endsWith("]")) { - - //Remove []s - this.text = this.text.substring(1, this.text.length() - 1); - - if (parseImmediate(this.text) || parseLabel(this.text, labels)) { - //Operand refers to memory - type = OperandType.MEMORY_IMM16; - value = Operand.IMMEDIATE_VALUE_MEM; - - } else if (!parseRegExpr(registerSet, labels)) { - - if (labels == null) { - type = OperandType.MEMORY_IMM16; - data = 0; - } else { - throw new InvalidOperandException("Invalid operand " + this.text, line); - } - - } - - } else { + //Remove []s + this.text = this.text.substring(1, this.text.length() - 1); + if (parseConstExpression(line, labels)) { + type = OperandType.MEMORY_IMM16; + value = Operand.IMMEDIATE_VALUE_MEM; + return; + } + if (!parseRegExpr(registerSet, labels)) { if (labels == null) { - type = OperandType.IMMEDIATE16; + type = OperandType.MEMORY_IMM16; data = 0; } else { throw new InvalidOperandException("Invalid operand " + this.text, line); } + + } + } else { + if (labels == null) { + type = OperandType.IMMEDIATE16; + data = 0; + } else { + throw new InvalidOperandException("Invalid operand " + this.text, line); } } } + /** + * Parses a constant expression made of operators, literals, and labels as a single immediate. + * Sets data to this value. + * @param line The current line of compilation + * @param labels The labels known to the compiler + * @return true on success, false otherwise. + */ + private boolean parseConstExpression(int line, HashMap labels) { + TokenParser parser = new TokenParser(text, line, labels); + if (labels == null) return false; + try { + data = parser.parseConstExpression(); + return true; + } catch (AssemblyException ex) { + return false; + } + } /** * Attempt to parse an integer 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 2423542..66498cb 100644 --- a/src/main/java/net/simon987/mar/server/assembly/TokenParser.java +++ b/src/main/java/net/simon987/mar/server/assembly/TokenParser.java @@ -2,6 +2,7 @@ package net.simon987.mar.server.assembly; import java.util.HashMap; import java.util.Map; +import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -147,6 +148,64 @@ public class TokenParser { } } + /** + * 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 enum ParseContext { TackOn, Value } @@ -155,11 +214,11 @@ public class TokenParser { 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_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 NUMBER_PATTERN_10 = Pattern.compile("([1-9][\\d]*|0)(?![\\w\\d])"); + private static final Pattern NUMBER_PATTERN_START = Pattern.compile("([+]?)[\\d#]"); private static final Pattern RESET_PATTERN = Pattern.compile("[^\\w\\d]"); private static final Pattern SPACE_PATTERN = Pattern.compile("[^\\S\\n]+"); @@ -181,11 +240,11 @@ public class TokenParser { this(sequence, line, labels,0, sequence.length()); } - private int line, start, end; + public int line, start, end; - private Map labels; + private final Map labels; - private Matcher matcher; + private final Matcher matcher; public int lastInt; @@ -238,19 +297,23 @@ public class TokenParser { } else { if (matcher.usePattern(NUMBER_PATTERN_START).lookingAt()) { - + start = matcher.end(1); + matcher.region(start, end); try { - if (matcher.usePattern(NUMBER_PATTERN_10).lookingAt()) + if (matcher.usePattern(NUMBER_PATTERN_10).lookingAt()) { lastInt = Integer.parseInt(matcher.group(1), 10); - else if (matcher.usePattern(NUMBER_PATTERN_16).lookingAt()) + } else if (matcher.usePattern(NUMBER_PATTERN_16).lookingAt()) { lastInt = Integer.parseInt(matcher.group(1), 16); - else if (matcher.usePattern(NUMBER_PATTERN_2).lookingAt()) + } else if (matcher.usePattern(NUMBER_PATTERN_2).lookingAt()) { lastInt = Integer.parseInt(matcher.group(1), 2); - else if (matcher.usePattern(NUMBER_PATTERN_8).lookingAt()) + } else if (matcher.usePattern(NUMBER_PATTERN_8).lookingAt()) { lastInt = Integer.parseInt(matcher.group(1), 8); - else { - if (matcher.usePattern(RESET_PATTERN).find()) start = matcher.start(); - else start = end; + } else { + if (matcher.usePattern(RESET_PATTERN).find()) { + start = matcher.start(); + } else { + start = end; + } throw new AssemblyException("Invalid number found.", line); } } catch (NumberFormatException ex) { @@ -266,7 +329,9 @@ public class TokenParser { String identifier = matcher.group(1); Character val = labels.get(identifier); - if (val == null) throw new AssemblyException("Unknown label found", line); + if (val == null) { + throw new AssemblyException("Unknown label found", line); + } lastInt = val; return TokenType.Constant; @@ -278,14 +343,110 @@ public class TokenParser { 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); + if (lastUnary == null) { + throw new AssemblyException("Unary operator not supported", line); + } return TokenType.UnaryOperator; } } - if (matcher.usePattern(RESET_PATTERN).find()) start = matcher.end(); - else start = end; + matcher.usePattern(RESET_PATTERN); + if (matcher.lookingAt()) { + start = matcher.end(); + } else if (matcher.find()) { + start = matcher.end(); + } else { + start = end; + } throw new AssemblyException("Invalid token found", line); } + public int parseConstExpression() + throws AssemblyException { + 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 = getNextToken(true, context); + if (context == TokenParser.ParseContext.Value) { + // Parse value + if (ty == TokenParser.TokenType.UnaryOperator) { + parseOps.push(new ParseOperatorUnary(closeExpect, lastUnary)); + closeExpect = -1; + } else if (ty == TokenParser.TokenType.GroupOperator) { + if (lastGroup.end) { + throw new AssemblyException("Unexpected group close", line); + } + if (closeExpect != -1) { + parseOps.push(new ParseOperator(closeExpect)); + } + closeExpect = lastGroup.groupType; + } else if (ty == TokenParser.TokenType.Constant) { + lastValue = 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 && !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 != 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 != 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 (lastGroup.groupType != -1) { + throw new AssemblyException("Unexpected group close", line); + } + } + } else if (ty == TokenParser.TokenType.BinaryOperator) { + TokenParser.BinaryOperatorType bop = 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); + } + } + } + } diff --git a/src/main/java/net/simon987/mar/server/websocket/CodeRequestHandler.java b/src/main/java/net/simon987/mar/server/websocket/CodeRequestHandler.java index 4936298..7de7992 100644 --- a/src/main/java/net/simon987/mar/server/websocket/CodeRequestHandler.java +++ b/src/main/java/net/simon987/mar/server/websocket/CodeRequestHandler.java @@ -14,24 +14,16 @@ public class CodeRequestHandler implements MessageHandler { LogManager.LOGGER.fine("(WS) Code request from " + user.getUser().getUsername()); - if (user.getUser().isGuest()) { + JSONObject response = new JSONObject(); - JSONObject response = new JSONObject(); + response.put("t", "code"); - response.put("t", "code"); - response.put("code", GameServer.INSTANCE.getConfig().getString("guest_user_code")); + String code = user.getUser().isGuest() ? + GameServer.INSTANCE.getConfig().getString("guest_user_code") : + user.getUser().getUserCode(); - user.getWebSocket().getRemote().sendString(response.toJSONString()); - - } else { - - JSONObject response = new JSONObject(); - - response.put("t", "code"); - response.put("code", user.getUser().getUserCode()); - - user.getWebSocket().getRemote().sendString(response.toJSONString()); - } + response.put("code", code); + user.getWebSocket().getRemote().sendString(response.toJSONString()); } } } diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties index a5b99c4..7a1e908 100644 --- a/src/main/resources/config.properties +++ b/src/main/resources/config.properties @@ -12,7 +12,7 @@ server_name=MAR dev # ALLOW | BLOCK guest_policy=ALLOW # DEBUG -autologin=simon987 +#autologin=simon987 #Database mongo_dbname=mar_beta3 diff --git a/src/main/typescript/GameClient.ts b/src/main/typescript/GameClient.ts index 88d570d..ecb4b94 100644 --- a/src/main/typescript/GameClient.ts +++ b/src/main/typescript/GameClient.ts @@ -538,6 +538,8 @@ class GameClient { console.log("[MAR] Connected. Sent auth request"); } + if (info.token == null) throw new Error("Auth token not found"); + //Send auth request self.socket.send(info.token); diff --git a/src/main/typescript/World.ts b/src/main/typescript/World.ts index 2089b6e..6fa9702 100644 --- a/src/main/typescript/World.ts +++ b/src/main/typescript/World.ts @@ -17,7 +17,8 @@ enum TileType { VAULT_FLOOR, VAULT_WALL, FLUID, - MAGNETIC + MAGNETIC, + VOID } class Tile extends Phaser.Plugin.Isometric.IsoSprite { @@ -70,7 +71,7 @@ class Tile extends Phaser.Plugin.Isometric.IsoSprite { return new VaultFloorTile(x, y); case TileType.VAULT_WALL: return new VaultWallTile(x, y); - case -1: + case TileType.VOID: return new VoidTile(x, y); case TileType.FLUID: return new FluidTile(x, y); diff --git a/src/main/typescript/phaser.d.ts b/src/main/typescript/phaser.d.ts index 1d5b402..629620d 100644 --- a/src/main/typescript/phaser.d.ts +++ b/src/main/typescript/phaser.d.ts @@ -3697,19 +3697,19 @@ declare module Phaser { button: number; capture: boolean; callbackContext: any; - event: MSPointerEvent; + event: PointerEvent; game: Phaser.Game; input: Phaser.Input; - onPointerDown: (event: MSPointerEvent) => void; - onPointerMove: (event: MSPointerEvent) => void; - onPointerUp: (event: MSPointerEvent) => void; - mouseDownCallback: (event: MSPointerEvent) => void; - mouseMoveCallback: (event: MSPointerEvent) => void; - mouseUpCallback: (event: MSPointerEvent) => void; - pointerDownCallback: (event: MSPointerEvent) => void; - pointerMoveCallback: (event: MSPointerEvent) => void; - pointerUpCallback: (event: MSPointerEvent) => void; + onPointerDown: (event: PointerEvent) => void; + onPointerMove: (event: PointerEvent) => void; + onPointerUp: (event: PointerEvent) => void; + mouseDownCallback: (event: PointerEvent) => void; + mouseMoveCallback: (event: PointerEvent) => void; + mouseUpCallback: (event: PointerEvent) => void; + pointerDownCallback: (event: PointerEvent) => void; + pointerMoveCallback: (event: PointerEvent) => void; + pointerUpCallback: (event: PointerEvent) => void; start(): void; 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 3ee8439..f447714 100644 --- a/src/test/java/net/simon987/mar/server/assembly/OperandTest.java +++ b/src/test/java/net/simon987/mar/server/assembly/OperandTest.java @@ -165,6 +165,7 @@ public class OperandTest { fail(); } catch (InvalidOperandException ignored) { } + /* Tests disabled due to compile time constant folding. try { new Operand("[- 12]", labels, registerSet, 0); fail(); @@ -174,7 +175,7 @@ public class OperandTest { new Operand("[12+1]", labels, registerSet, 0); fail(); } catch (InvalidOperandException ignored) { - } + } */ try { new Operand("[+label1", labels, registerSet, 0); fail(); diff --git a/src/test/java/net/simon987/mar/server/assembly/TokenTest.java b/src/test/java/net/simon987/mar/server/assembly/TokenTest.java index c55ee34..729cd6a 100644 --- a/src/test/java/net/simon987/mar/server/assembly/TokenTest.java +++ b/src/test/java/net/simon987/mar/server/assembly/TokenTest.java @@ -17,7 +17,7 @@ public class TokenTest { labels.put("gamma", (char)-5); labels.put("epsilon", (char)0); TokenParser parser = new TokenParser( - " 27 0x27 0o27 0b11011 alpha beta gamma delta epsilon 0 1a 0xG 0o8 0b2", + " 27 0x27 #27 0o27 027 0b11011 alpha beta gamma delta epsilon 0 1a 0xG 0o8 0b2", 0, labels); assertEquals(TokenParser.TokenType.Space, parser.getNextToken(false, TokenParser.ParseContext.Value)); @@ -27,6 +27,10 @@ public class TokenTest { assertEquals(TokenParser.TokenType.Constant, parser.getNextToken(true, TokenParser.ParseContext.Value)); assertEquals((char)39, parser.lastInt); assertEquals(TokenParser.TokenType.Constant, parser.getNextToken(true, TokenParser.ParseContext.Value)); + assertEquals((char)39, parser.lastInt); + assertEquals(TokenParser.TokenType.Constant, parser.getNextToken(true, TokenParser.ParseContext.Value)); + assertEquals((char)23, parser.lastInt); + assertEquals(TokenParser.TokenType.Constant, parser.getNextToken(true, TokenParser.ParseContext.Value)); assertEquals((char)23, parser.lastInt); assertEquals(TokenParser.TokenType.Constant, parser.getNextToken(true, TokenParser.ParseContext.Value)); assertEquals((char)27, parser.lastInt); @@ -41,31 +45,38 @@ public class TokenTest { try { parser.getNextToken(true, TokenParser.ParseContext.Value); fail(); - } catch (AssemblyException ignore) {} + } catch (AssemblyException expected) { + } try { parser.getNextToken(true, TokenParser.ParseContext.TackOn); fail(); - } catch (AssemblyException ignore) {} + } catch (AssemblyException expected) { + } try { parser.getNextToken(true, TokenParser.ParseContext.TackOn); fail(); - } catch (AssemblyException ignore) {} + } catch (AssemblyException expected) { + } try { parser.getNextToken(true, TokenParser.ParseContext.Value); fail(); - } catch (AssemblyException ignore) {} + } catch (AssemblyException expected) { + } try { parser.getNextToken(true, TokenParser.ParseContext.Value); fail(); - } catch (AssemblyException ignore) {} + } catch (AssemblyException expected) { + } try { parser.getNextToken(true, TokenParser.ParseContext.Value); fail(); - } catch (AssemblyException ignore) {} + } catch (AssemblyException expected) { + } try { parser.getNextToken(true, TokenParser.ParseContext.Value); fail(); - } catch (AssemblyException ignore) {} + } catch (AssemblyException expected) { + } } @Test public void operatorTokenParsing() throws Exception { @@ -77,7 +88,7 @@ public class TokenTest { try { parser.getNextToken(true, TokenParser.ParseContext.TackOn); fail(); - } catch (AssemblyException ignore) {} + } catch (AssemblyException expected) {} assertEquals(TokenParser.TokenType.BinaryOperator, parser.getNextToken(true, TokenParser.ParseContext.TackOn)); assertEquals(TokenParser.BinaryOperatorType.Sub, parser.lastBinary); @@ -87,8 +98,7 @@ public class TokenTest { try { parser.getNextToken(true, TokenParser.ParseContext.Value); fail(); - } catch (AssemblyException ignore) {} - + } catch (AssemblyException expected) {} assertEquals(TokenParser.TokenType.UnaryOperator, parser.getNextToken(true, TokenParser.ParseContext.Value)); assertEquals(TokenParser.UnaryOperatorType.Not, parser.lastUnary); @@ -104,85 +114,39 @@ public class TokenTest { assertEquals(TokenParser.GroupOperatorType.GroupEnd, parser.lastGroup); } - @Test - public void parseTest() throws AssemblyException { - assertEquals(1, Operand.parseConstExpression( - "1", - 0, new HashMap<>()) - ); - assertEquals(2, Operand.parseConstExpression( - "1 + 1", - 0, new HashMap<>()) - ); - assertEquals(82, Operand.parseConstExpression( - "10 + 9 * 8", - 0, new HashMap<>()) - ); - assertEquals(98, Operand.parseConstExpression( - "10 * 9 + 8", - 0, new HashMap<>()) - ); - assertEquals(2, Operand.parseConstExpression( - "(1 + 1)", - 0, new HashMap<>()) - ); - assertEquals(152, Operand.parseConstExpression( - "((10 + 9)) * 8", - 0, new HashMap<>()) - ); - assertEquals(170, Operand.parseConstExpression( - "(10 * ((9 + 8)))", - 0, new HashMap<>()) - ); - assertEquals((char)-170, Operand.parseConstExpression( - "(-10 * ((9 + 8)))", - 0, new HashMap<>()) - ); - assertEquals(2, Operand.parseConstExpression( - "(-3 + 5)", - 0, new HashMap<>()) - ); + private void assertParse(int expect, String in) throws AssemblyException { + assertEquals(expect, new TokenParser(in,0, new HashMap<>()).parseConstExpression()); + } + + private void failParse(String in) { try { - Operand.parseConstExpression( - "(1", - 0, new HashMap<>() - ); - fail(); - } catch (AssemblyException ignore) {} - try { - Operand.parseConstExpression( - "1)", - 0, new HashMap<>() - ); - fail(); - } catch (AssemblyException ignore) {} - try { - Operand.parseConstExpression( - "(1+1", - 0, new HashMap<>() - ); - fail(); - } catch (AssemblyException ignore) {} - try { - Operand.parseConstExpression( - "1+1)", - 0, new HashMap<>() - ); - fail(); - } catch (AssemblyException ignore) {} - try { - Operand.parseConstExpression( - "((1+1)", - 0, new HashMap<>() - ); - fail(); - } catch (AssemblyException ignore) {} - try { - Operand.parseConstExpression( - "(1+1))", - 0, new HashMap<>() - ); + new TokenParser(in,0, new HashMap<>()).parseConstExpression(); fail(); } catch (AssemblyException ignore) {} } + + @Test + public void parseTest() throws AssemblyException { + assertParse(10, "10"); + assertParse(16, "0x10"); + assertParse(8, "0o10"); + assertParse(2, "0b10"); + assertParse(2, "1 + (1)"); + assertParse(98, "(10) * 9 + 8"); + assertParse(82, "10 + (9) * 8"); + assertParse(2, "(1 + 1)"); + assertParse(152, "((10 + 9)) * 8"); + assertParse(170, "(10 * ((9 + 8)))"); + assertParse((char)-170, "(-10 * ((9 + 8)))"); + assertParse((char)-170, "(10 * -((9 + 8)))"); + assertParse(170, "(-10 * -((9 + 8)))"); + assertParse(2, "(-3 + 5)"); + + failParse("1)"); + failParse("(1"); + failParse("(1 + 1"); + failParse("1 + 1)"); + failParse("((1 + 1)"); + failParse("(1 + 1))"); + } } From 292f73797181ff3669ed72f35fe1193ae16dbce8 Mon Sep 17 00:00:00 2001 From: chromapid <67613147+chromapid@users.noreply.github.com> Date: Mon, 20 Dec 2021 21:11:20 -0700 Subject: [PATCH 4/8] DW and relative addressing constant folding support. --- .../mar/server/assembly/Assembler.java | 152 +++------- .../simon987/mar/server/assembly/Operand.java | 286 ++---------------- .../mar/server/assembly/TokenParser.java | 10 +- .../mar/server/assembly/DwDirectiveTest.java | 10 + .../mar/server/assembly/OperandTest.java | 8 +- 5 files changed, 91 insertions(+), 375 deletions(-) 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(); From ad16df32d75401bd60c3aa3262b37263b9d149da Mon Sep 17 00:00:00 2001 From: chromapid <67613147+chromapid@users.noreply.github.com> Date: Mon, 20 Dec 2021 23:50:17 -0700 Subject: [PATCH 5/8] Fixed crash when an empty item is cleared. --- .../java/net/simon987/mar/cubot/CubotInventory.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/simon987/mar/cubot/CubotInventory.java b/src/main/java/net/simon987/mar/cubot/CubotInventory.java index d143118..2b69db7 100644 --- a/src/main/java/net/simon987/mar/cubot/CubotInventory.java +++ b/src/main/java/net/simon987/mar/cubot/CubotInventory.java @@ -55,14 +55,19 @@ public class CubotInventory extends HardwareModule { private void scanItem() { int x = getCpu().getRegisterSet().getRegister("X").getValue(); Item item = inventory.get(position); - item.digitize(unit.getCpu().getMemory(), x); + if (item != null) { + item.clear(unit); + inventory.remove(position); + item.digitize(unit.getCpu().getMemory(), x); + } } public Item clearItem() { Item item = inventory.get(position); - item.clear(unit); - inventory.remove(position); - + if (item != null) { + item.clear(unit); + inventory.remove(position); + } return item; } From 434abb0fb141b012a465928c30f3672fb8fd8dd5 Mon Sep 17 00:00:00 2001 From: chromapid <67613147+chromapid@users.noreply.github.com> Date: Tue, 21 Dec 2021 00:22:05 -0700 Subject: [PATCH 6/8] Minor formatting changes --- src/main/java/net/simon987/mar/server/assembly/Operand.java | 1 - src/main/java/net/simon987/mar/server/assembly/TokenParser.java | 2 -- 2 files changed, 3 deletions(-) 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 90c9cbd..89bf52f 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Operand.java +++ b/src/main/java/net/simon987/mar/server/assembly/Operand.java @@ -126,7 +126,6 @@ public class Operand { } } - /** * 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 index e2da4a8..3fa5ee4 100644 --- a/src/main/java/net/simon987/mar/server/assembly/TokenParser.java +++ b/src/main/java/net/simon987/mar/server/assembly/TokenParser.java @@ -203,8 +203,6 @@ public class TokenParser { } } - - public enum ParseContext { TackOn, Value } From 874b7ae802e9f9f389564e89015f4f247852ca93 Mon Sep 17 00:00:00 2001 From: chromapid <67613147+chromapid@users.noreply.github.com> Date: Tue, 21 Dec 2021 00:55:25 -0700 Subject: [PATCH 7/8] Comments, more testing, and formatting. --- .../mar/server/assembly/TokenParser.java | 58 ++++++++++++++----- .../mar/server/assembly/TokenTest.java | 6 ++ 2 files changed, 51 insertions(+), 13 deletions(-) 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 3fa5ee4..263ca99 100644 --- a/src/main/java/net/simon987/mar/server/assembly/TokenParser.java +++ b/src/main/java/net/simon987/mar/server/assembly/TokenParser.java @@ -84,10 +84,15 @@ public class TokenParser { final public static Map stringMap = new HashMap<>(); static { - for (BinaryOperatorType op : values()) stringMap.put(op.symbol, op); + for (BinaryOperatorType op : values()) { + stringMap.put(op.symbol, op); + } } public final String symbol; + /** + * An operator with higher precedence is evaluated first + */ public final int precedence; BinaryOperatorType(final String symbol, final int precedence) { @@ -261,21 +266,32 @@ public class TokenParser { * 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; + if (start >= end) { + return TokenType.EOF; + } matcher.region(start, end); + + //Handle leading spaces if (matcher.usePattern(SPACE_PATTERN).lookingAt()) { start = matcher.end(); - if (!eatSpace) return TokenType.Space; - if (start >= end) return TokenType.EOF; + if (!eatSpace) { + return TokenType.Space; + } else if (start >= end) { + return TokenType.EOF; + } matcher.region(start, end); } + + //Group operators can occur in both contexts, i.e.: + // ... + ( ... + // ... y ) ... matcher.usePattern(GROUP_OPERATOR_PATTERN); if (matcher.lookingAt()) { start = matcher.end(1); String symbol = matcher.group(1); lastGroup = GroupOperatorType.stringMap.get(symbol); - // Should never happen unless the regex does not agree with GroupOperatorType. + //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; @@ -286,7 +302,7 @@ public class TokenParser { String symbol = matcher.group(1); lastBinary = BinaryOperatorType.stringMap.get(symbol); - // Should never happen unless the regex does not agree with BinaryOperatorType. + //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; @@ -297,6 +313,7 @@ public class TokenParser { start = matcher.end(1); matcher.region(start, end); try { + //Attempt all supported radixes if (matcher.usePattern(NUMBER_PATTERN_10).lookingAt()) { lastInt = Integer.parseInt(matcher.group(1), 10); } else if (matcher.usePattern(NUMBER_PATTERN_16).lookingAt()) { @@ -346,6 +363,8 @@ public class TokenParser { return TokenType.UnaryOperator; } } + //For debug purposes, attempt skip to the next token in case of failure. + //Not reliable enough for a debug tool. matcher.usePattern(RESET_PATTERN); if (matcher.lookingAt()) { start = matcher.end(); @@ -365,50 +384,56 @@ public class TokenParser { public char parseConstExpression() throws AssemblyException { Stack parseOps = new Stack<>(); - int closeExpect = -1; // No closing parenthesis expected + //A value of -1 indicates that no extra closing parenthesis are expected, + //aside from those mandated by the stack. + int closeExpect = -1; TokenParser.ParseContext context = TokenParser.ParseContext.Value; int lastValue = 0; while (true) { TokenParser.TokenType ty = getNextToken(true, context); if (context == TokenParser.ParseContext.Value) { - // Parse value + //Parse value if (ty == TokenParser.TokenType.UnaryOperator) { + //Add unary operator to the stack parseOps.push(new ParseOperatorUnary(closeExpect, lastUnary)); closeExpect = -1; } else if (ty == TokenParser.TokenType.GroupOperator) { if (lastGroup.end) { throw new AssemblyException("Unexpected group close", line); } + //No need to push a pointless operator. if (closeExpect != -1) { parseOps.push(new ParseOperator(closeExpect)); } closeExpect = lastGroup.groupType; } else if (ty == TokenParser.TokenType.Constant) { lastValue = lastInt; + //Look for binary operators next context = TokenParser.ParseContext.TackOn; } else { throw new AssemblyException("Value not found", line); } } else { - // Parse modifier + //Parse modifier if (ty == TokenParser.TokenType.EOF || ty == TokenParser.TokenType.GroupOperator) { if (ty == TokenParser.TokenType.GroupOperator && !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 != lastGroup.groupType) { throw new AssemblyException("Unmatched group ends", line); } else { + //New group operator closes the stored group operator closeExpect = -1; continue; } } boolean completed = false; - - //Evaluation chain + //Evaluation chain, repeats as long as there are operators that do not require closing operators. while (!parseOps.isEmpty()) { ParseOperator op = parseOps.pop(); if (op.closeExpect != -1) { @@ -417,6 +442,7 @@ public class TokenParser { } else if (op.closeExpect != lastGroup.groupType) { throw new AssemblyException("Unmatched group ends", line); } + //Found matching operator, stop evaluating. lastValue = op.apply(lastValue); completed = true; break; @@ -424,6 +450,7 @@ public class TokenParser { lastValue = op.apply(lastValue); } if (!completed) { + // There are no more operators if (ty == TokenParser.TokenType.EOF) { return (char)lastValue; } else if (lastGroup.groupType != -1) { @@ -432,9 +459,12 @@ public class TokenParser { } } else if (ty == TokenParser.TokenType.BinaryOperator) { TokenParser.BinaryOperatorType bop = lastBinary; + //Given the new operator's precedence, + //it may now be possible to evaluate some previous stack items. while (closeExpect == -1 && !parseOps.empty()) { ParseOperator op = parseOps.peek(); - if (bop.precedence <= op.getPrecedence()) { + if (bop.precedence < op.getPrecedence()) { + //The new operator is to be evaluated first. break; } lastValue = op.apply(lastValue); @@ -445,7 +475,9 @@ public class TokenParser { closeExpect = -1; context = TokenParser.ParseContext.Value; } - else throw new AssemblyException("Modifier or end not found", line); + else { + throw new AssemblyException("Modifier or end not 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 index 729cd6a..539e66a 100644 --- a/src/test/java/net/simon987/mar/server/assembly/TokenTest.java +++ b/src/test/java/net/simon987/mar/server/assembly/TokenTest.java @@ -141,6 +141,7 @@ public class TokenTest { assertParse((char)-170, "(10 * -((9 + 8)))"); assertParse(170, "(-10 * -((9 + 8)))"); assertParse(2, "(-3 + 5)"); + assertParse(3, "10 - 4 - 3"); failParse("1)"); failParse("(1"); @@ -148,5 +149,10 @@ public class TokenTest { failParse("1 + 1)"); failParse("((1 + 1)"); failParse("(1 + 1))"); + failParse("+ 1"); + failParse("(+ 1)"); + failParse("(1 +)"); + failParse("1 + ()"); + failParse("() + 1"); } } From ef1b9ee9a81c555a19247e26a1eb526e4b272105 Mon Sep 17 00:00:00 2001 From: chromapid <67613147+chromapid@users.noreply.github.com> Date: Tue, 21 Dec 2021 10:47:13 -0700 Subject: [PATCH 8/8] Update CubotInventory.java --- src/main/java/net/simon987/mar/cubot/CubotInventory.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/net/simon987/mar/cubot/CubotInventory.java b/src/main/java/net/simon987/mar/cubot/CubotInventory.java index 2b69db7..ec785b0 100644 --- a/src/main/java/net/simon987/mar/cubot/CubotInventory.java +++ b/src/main/java/net/simon987/mar/cubot/CubotInventory.java @@ -56,8 +56,6 @@ public class CubotInventory extends HardwareModule { int x = getCpu().getRegisterSet().getRegister("X").getValue(); Item item = inventory.get(position); if (item != null) { - item.clear(unit); - inventory.remove(position); item.digitize(unit.getCpu().getMemory(), x); } }