diff --git a/src/main/java/net/simon987/mar/cubot/CubotInventory.java b/src/main/java/net/simon987/mar/cubot/CubotInventory.java index d143118..ec785b0 100644 --- a/src/main/java/net/simon987/mar/cubot/CubotInventory.java +++ b/src/main/java/net/simon987/mar/cubot/CubotInventory.java @@ -55,14 +55,17 @@ 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.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; } 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..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,6 +248,9 @@ public class Assembler { } } + private static final Pattern EQU_PATTERN = Pattern.compile("([\\w]+)\\s+EQU\\s+(.+)", + Pattern.CASE_INSENSITIVE); + /** * Check for and handle the EQU instruction * @@ -311,30 +258,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) 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); } + } /** @@ -348,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"); @@ -541,19 +480,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..89bf52f 100755 --- a/src/main/java/net/simon987/mar/server/assembly/Operand.java +++ b/src/main/java/net/simon987/mar/server/assembly/Operand.java @@ -1,5 +1,6 @@ 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; @@ -25,8 +26,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; @@ -34,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; } @@ -71,101 +72,56 @@ 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, line)) { if (labels == null) { - type = OperandType.IMMEDIATE16; + type = OperandType.MEMORY_IMM16; data = 0; } else { throw new InvalidOperandException("Invalid operand " + this.text, line); } + } - } - } - - - /** - * 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 { + 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; } } @@ -196,7 +152,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; @@ -216,7 +172,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. @@ -224,64 +183,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; } } @@ -294,7 +211,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 new file mode 100644 index 0000000..263ca99 --- /dev/null +++ b/src/main/java/net/simon987/mar/server/assembly/TokenParser.java @@ -0,0 +1,486 @@ +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; + +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; + /** + * An operator with higher precedence is evaluated first + */ + 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; + } + } + + /** + * 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 + } + + 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("([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]+"); + + /** + * @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()); + } + + public int line, start, end; + + private final Map labels; + + private final 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); + + //Handle leading spaces + if (matcher.usePattern(SPACE_PATTERN).lookingAt()) { + start = matcher.end(); + 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. + 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()) { + 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()) { + 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 { + 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); + lastInt &= 0xFFFF; + return TokenType.Constant; + } + if (matcher.usePattern(IDENTIFIER_PATTERN).lookingAt()) { + 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(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.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(); + } else if (matcher.find()) { + start = matcher.end(); + } else { + start = end; + } + throw new AssemblyException("Invalid token found", line); + } + + /** + * 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<>(); + //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 + 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 + 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, repeats as long as there are operators that do not require closing operators. + 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); + } + //Found matching operator, stop evaluating. + lastValue = op.apply(lastValue); + completed = true; + break; + } + lastValue = op.apply(lastValue); + } + if (!completed) { + // There are no more operators + if (ty == TokenParser.TokenType.EOF) { + return (char)lastValue; + } else if (lastGroup.groupType != -1) { + throw new AssemblyException("Unexpected group close", line); + } + } + } 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()) { + //The new operator is to be evaluated first. + 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/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 3ee8439..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) { @@ -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(); @@ -200,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(); 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..539e66a --- /dev/null +++ b/src/test/java/net/simon987/mar/server/assembly/TokenTest.java @@ -0,0 +1,158 @@ +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.*; + +public class TokenTest { + @Test + 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 #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)); + 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)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); + + // 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 expected) { + } + try { + parser.getNextToken(true, TokenParser.ParseContext.TackOn); + fail(); + } catch (AssemblyException expected) { + } + try { + parser.getNextToken(true, TokenParser.ParseContext.TackOn); + fail(); + } catch (AssemblyException expected) { + } + try { + parser.getNextToken(true, TokenParser.ParseContext.Value); + fail(); + } catch (AssemblyException expected) { + } + try { + parser.getNextToken(true, TokenParser.ParseContext.Value); + fail(); + } catch (AssemblyException expected) { + } + try { + parser.getNextToken(true, TokenParser.ParseContext.Value); + fail(); + } catch (AssemblyException expected) { + } + try { + parser.getNextToken(true, TokenParser.ParseContext.Value); + fail(); + } catch (AssemblyException expected) { + } + } + @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 expected) {} + assertEquals(TokenParser.TokenType.BinaryOperator, + parser.getNextToken(true, TokenParser.ParseContext.TackOn)); + assertEquals(TokenParser.BinaryOperatorType.Sub, parser.lastBinary); + + + parser = new TokenParser(ops, 0, new HashMap<>()); + try { + parser.getNextToken(true, TokenParser.ParseContext.Value); + fail(); + } catch (AssemblyException expected) {} + 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); + } + + private void assertParse(int expect, String in) throws AssemblyException { + assertEquals(expect, new TokenParser(in,0, new HashMap<>()).parseConstExpression()); + } + + private void failParse(String in) { + try { + 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)"); + assertParse(3, "10 - 4 - 3"); + + failParse("1)"); + failParse("(1"); + failParse("(1 + 1"); + failParse("1 + 1)"); + failParse("((1 + 1)"); + failParse("(1 + 1))"); + failParse("+ 1"); + failParse("(+ 1)"); + failParse("(1 +)"); + failParse("1 + ()"); + failParse("() + 1"); + } +}