Began work on compile time math expressions.

This commit is contained in:
chromapid 2021-12-19 14:22:42 -07:00
parent 18a6858edd
commit 36edfc57dc
4 changed files with 436 additions and 12 deletions

View File

@ -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();
}

View File

@ -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<String, Character> labels)
throws AssemblyException {
TokenParser parser = new TokenParser(text, line, labels);
Stack<ParseOperator> 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
*

View File

@ -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<String, BinaryOperatorType> 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<String, UnaryOperatorType> 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<String, GroupOperatorType> 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<String, Character> 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<String, Character> labels) {
this(sequence, line, labels,0, sequence.length());
}
private int line, start, end;
private Map<String, Character> 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);
}
}

View File

@ -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);
}
}