DW and relative addressing constant folding support.

This commit is contained in:
chromapid 2021-12-20 21:11:20 -07:00
parent 74ab5b6d88
commit 292f737971
5 changed files with 91 additions and 375 deletions

View File

@ -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<String, Character> labels, int currentLine)
private byte[] parseDWInstruction(String line, HashMap<String, Character> 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<String, Character> 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: <factor> DUP(<value>)", 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<String, Character> 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 <immediate_value>
@ -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()) {

View File

@ -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<String, Character> 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<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) {
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<String, Character> labels) {
private boolean parseRegExpr(RegisterSet registerSet, HashMap<String, Character> 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;
}

View File

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

View File

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

View File

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