/*
* Copyright (c) Anton Kraievoy, IASA, Kiev, Ukraine, 2006.
* This work is based on code of Dr. Dalton R. Hunkins, CS dept. of St. Bonaventure University, 2006.
*/
package elw.dp.mips.asm;
import base.pattern.Result;
import elw.dp.mips.*;
import gnu.trove.TIntIntHashMap;
import org.akraievoy.base.ObjArrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Pattern;
public class MipsAssembler {
private static final Logger log = LoggerFactory.getLogger(MipsAssembler.class);
private static final Pattern PATTERN_LABEL =
Pattern.compile("^[a-zA-Z_][a-zA-Z_0-9]*$");
private final static Map<Pattern, String> codeNormMap = createCodeNormMap();
private static Map<Pattern, String> createCodeNormMap() {
final Map<Pattern, String> map = new LinkedHashMap<Pattern, String>();
map.put(Pattern.compile("\\s+"), " ");
map.put(Pattern.compile("^ "), "");
map.put(Pattern.compile("\" $\""), "");
map.put(Pattern.compile(" ?, ?"), ",");
map.put(Pattern.compile(" ?: ?"), ":");
map.put(Pattern.compile(" ?\\( ?"), "(");
map.put(Pattern.compile(" ?\\) ?"), ")");
//added to throw away comments at the end of code lines
map.put(Pattern.compile("\\s*#.*"), "");
return map;
}
public Instruction[] loadInstructions(List<String> codeLines, Result[] resRef, final Map<String, Integer> labelIndex) {
labelIndex.clear();
final List<Instruction> instructions = new ArrayList<Instruction>();
try {
if (loadInstructionsFirstPass(codeLines, instructions, labelIndex, resRef)) {
return null;
}
for (Instruction instruction : instructions) {
final String prefix = "Code(line " + instruction.getLineIndex() + "): ";
if (instruction.resolve(0, labelIndex)) {
Result.failure(log, resRef, prefix + "missing label '" + instruction.getAddr() + "'");
return null;
}
if (!instruction.isAssembled()) {
Result.failure(log, resRef, prefix + "instruction assembly incomplete: " + instruction);
return null;
}
}
} catch (Exception e) {
Result.failure(log, resRef, "failed: " + e.getMessage());
log.warn("failed:", e);
return null;
}
Result.success(log, resRef, "Instructions Assembled");
return instructions.toArray(new Instruction[instructions.size()]);
}
private boolean loadInstructionsFirstPass(List<String> codeLines, List<Instruction> instructions, Map<String, Integer> labelIndex, Result[] resRef) {
final StringBuilder code = new StringBuilder();
final StringBuilder syntax = new StringBuilder();
final List<String> labels = new ArrayList<String>();
int instructionIndex = 0;
for (int lineIndex = 0, codeLinesLength = codeLines.size(); lineIndex < codeLinesLength; lineIndex++) {
final String prefix = "Code(line " + (lineIndex + 1) + "): ";
final String codeLine = codeLines.get(lineIndex).trim();
if (codeLine.startsWith("#") || codeLine.isEmpty()) {
continue;
}
loadNormed(codeLine, code);
final String[] labelList;
final int labelsEnd = code.indexOf(":");
if (labelsEnd >= 0) {
labelList = code.substring(0, labelsEnd).split(",");
code.delete(0, labelsEnd + 1);
} else {
labelList = new String[0];
}
if (labelList.length > 0) {
for (String label : labelList) {
if (!PATTERN_LABEL.matcher(label).matches()) {
Result.failure(log, resRef, prefix + "wrong label syntax '" + label + "'");
return true;
}
if (labelIndex.get(label) != null) {
Result.failure(log, resRef, prefix + "ambiguous label '" + label + "'");
return true;
}
labelIndex.put(label, instructionIndex);
}
labels.addAll(Arrays.asList(labelList));
}
if (code.length() > 0) {
final String codeLineStr = code.toString();
final String opName = removeOpName(code);
final Method aluMethod;
try {
aluMethod = Alu.class.getDeclaredMethod(opName, InstructionContext.class);
} catch (NoSuchMethodException e) {
Result.failure(log, resRef, prefix + "unspecified operation '" + opName + "'");
return true;
}
final InstructionDesc desc = aluMethod.getAnnotation(InstructionDesc.class);
loadNormed(desc.syntax(), syntax);
removeOpName(syntax); // it must be the same as code stated above
Instruction inst = new Instruction(
desc, codeLineStr, instructionIndex, lineIndex + 1,
labels.toArray(new String[labels.size()])
);
int argIndex = 1;
int syntaxLen;
while ((syntaxLen = syntax.length()) > 0) {
trim(syntax);
trim(code);
final String prefixBefore = prefix + "before arg. " + argIndex + " ";
if (checkSeparator(',', syntax, code, resRef, prefixBefore) ||
checkSeparator('(', syntax, code, resRef, prefixBefore) ||
checkSeparator(')', syntax, code, resRef, prefixBefore)) {
return true;
}
final String prefixOn = prefix + "arg. " + argIndex + " ";
if (parseReg(Instruction.T_REG_D, syntax, code, desc, inst, resRef, prefixOn) ||
parseReg(Instruction.T_REG_T, syntax, code, desc, inst, resRef, prefixOn) ||
parseReg(Instruction.T_REG_S, syntax, code, desc, inst, resRef, prefixOn)) {
return true;
}
if (parseNum(Instruction.T_IMM16, syntax, code, inst, resRef, prefixOn) ||
parseNum(Instruction.T_H5, syntax, code, inst, resRef, prefixOn)) {
return true;
}
if (parseAddr(Instruction.T_ADDR16, Instruction.T_IMM16, syntax, code, inst, resRef, prefixOn) ||
parseAddr(Instruction.T_ADDR26, Instruction.T_IMM26, syntax, code, inst, resRef, prefixOn)) {
return true;
}
if (syntaxLen == syntax.length()) {
// unable to cut any of tokens
throw new IllegalStateException("syntax broken: " + desc.syntax() + " -> " + syntax.toString());
}
argIndex++;
}
if (code.length() > 0) {
Result.failure(log, resRef, prefix + "redundant code '" + code.toString() + "'");
return true;
}
instructions.add(inst);
labels.clear();
instructionIndex++;
}
}
return false;
}
private static void trim(StringBuilder syntax) {
while (syntax.length() > 0 && syntax.charAt(0) == ' ') {
syntax.deleteCharAt(0);
}
}
private static boolean parseAddr(final String id, final String numId, StringBuilder syntax, StringBuilder code, Instruction inst, Result[] resRef, String prefixOn) {
if (syntax.indexOf(id) == 0) {
final String token = scanChunk(code, ",()");
syntax.delete(0, id.length());
if (PATTERN_LABEL.matcher(token).matches()) {
inst.setAddr(id, token);
return false;
}
final int bits = Instruction.getWidth(id) + 2;
if (!Data.isNum(token, bits)) {
Result.failure(log, resRef, prefixOn + "must be a " + bits + "-bit number");
return true;
}
final int num = (int) Data.parse(token);
inst.setBits(numId, num >> 4);
}
return false;
}
private static boolean parseNum(final String id, StringBuilder syntax, StringBuilder code, Instruction inst, Result[] resRef, String prefixOn) {
if (syntax.indexOf(id) == 0) {
final String numToken = scanChunk(code, ",()");
syntax.delete(0, id.length());
final int bits = Instruction.getWidth(id);
if (!Data.isNum(numToken, bits)) {
Result.failure(log, resRef, prefixOn + "must be a " + bits + "-bit number");
return true;
}
final int num = (int) Data.parse(numToken);
inst.setBits(id, num);
}
return false;
}
private static boolean parseReg(
final String regId, StringBuilder syntax, StringBuilder code,
InstructionDesc desc, Instruction inst,
Result[] resRef, String prefixOn
) {
if (syntax.indexOf(regId) == 0) {
final String regToken = scanChunk(code, ",()");
syntax.delete(0, regId.length());
final Reg reg = parseReg(regToken, log, resRef, prefixOn);
if (reg == null) {
return true;
}
if (!Reg.publicRegs.contains(reg)) {
Result.failure(log, resRef, prefixOn + "direct ref to $" + reg.toString());
return true;
}
if (desc.writeRegs().contains(regId) && Reg.roRegs.contains(reg)) {
Result.failure(log, resRef, prefixOn + "write to $" + reg.toString());
return true;
}
inst.setReg(regId, reg);
}
return false;
}
private static String scanChunk(StringBuilder code, final String term) {
for (int l = 0; l < code.length(); l++) {
if (term.indexOf(code.charAt(l)) >= 0) {
final String chunk = code.substring(0, l);
code.delete(0, l);
return chunk;
}
}
final String chunk = code.toString();
code.setLength(0);
return chunk;
}
private static boolean checkSeparator(final char sep, StringBuilder syntax, StringBuilder code, Result[] resRef, String prefix) {
if (syntax.charAt(0) == sep) {
if (code.length() > 0 && code.charAt(0) == sep) {
syntax.delete(0, 1);
code.delete(0, 1);
} else {
Result.failure(log, resRef, prefix + "expecting '" + sep + "'");
return true;
}
}
return false;
}
private static void loadNormed(String codeLine, StringBuilder code) {
code.setLength(0);
code.insert(0, codeLine.toLowerCase());
for (Pattern pat : codeNormMap.keySet()) {
final String replace = pat.matcher(code).replaceAll(codeNormMap.get(pat));
code.setLength(0);
code.insert(0, replace);
}
}
private static String removeOpName(StringBuilder code) {
String opName;
final int opNameEnd = code.indexOf(" ");
if (opNameEnd < 0) {
opName = code.toString();
code.setLength(0);
} else {
opName = code.substring(0, opNameEnd);
code.delete(0, opNameEnd + 1);
}
return opName;
}
public TIntIntHashMap[] loadData(final Map<Integer,TaskBean.TestSpecMem> dataSpecs, Result[] resRef) {
final TIntIntHashMap dataIn = new TIntIntHashMap();
final TIntIntHashMap dataOut = new TIntIntHashMap();
for (TaskBean.TestSpecMem specMem : dataSpecs.values()) {
if (specMem.before != null) {
dataIn.put(specMem.address, specMem.before);
}
if (specMem.after != null) {
dataOut.put(specMem.address, specMem.after);
}
}
Result.success(log, resRef, "Data loaded fine");
return new TIntIntHashMap[]{dataIn, dataOut};
}
public TIntIntHashMap[] loadRegs(final Map<Reg,TaskBean.TestSpecReg> regsLines, Result[] resRef) {
final TIntIntHashMap regsIn = new TIntIntHashMap();
final TIntIntHashMap regsOut = new TIntIntHashMap();
for (TaskBean.TestSpecReg spec : regsLines.values()) {
if (spec.before != null) {
regsIn.put(spec.reg.ordinal(), spec.before);
}
if (spec.after != null) {
regsOut.put(spec.reg.ordinal(), spec.after);
}
}
Result.success(resRef, "Registers validated and loaded fine");
return new TIntIntHashMap[]{regsIn, regsOut};
}
public static Reg parseReg(final String regToken, @Nullable final Logger log, @Nullable final Result[] resRef, final String prefix) {
if (!regToken.startsWith("$")) {
Result.failure(log, resRef, prefix + "register token must be either $name or $number");
return null;
}
final String regName = regToken.substring(1).toLowerCase();
final Reg regByName = Reg.getByName().get(regName);
if (regByName != null) {
return regByName;
}
if (!Data.isNum(regName, 5)) {
Result.failure(log, resRef, prefix + "refers to unknown register '" + regName + "'");
return null;
}
final long regNumLong = Data.parse(regName);
if (regNumLong < 0) {
Result.failure(log, resRef, prefix + "refers to register with negative number '" + regNumLong + "'");
return null;
}
if (regNumLong > Reg.values().length) {
Result.failure(log, resRef, prefix + "refers to register with illegal number '" + regNumLong + "'");
return null;
}
return Reg.values()[(int) regNumLong];
}
}