package elw.dp.mips; import elw.dp.mips.asm.Data; import elw.dp.mips.asm.MipsAssembler; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class TaskBean { protected static final Pattern PATTERN_TEST_COMMENT = Pattern.compile("#.*$"); protected static final Pattern PATTERN_TEST_SPEC = Pattern.compile( "^" + "([^:\\s]+)" + ":" + "([^=>:\\s]*)" + "(=>|:)" + "([^\\s]*)" + "$" ); protected static final Pattern PATTERN_CONST_SPEC = Pattern.compile( "^" + "([^:\\s]+)" + ":" + "([^\\s]*)" + "$" ); private String statement; private java.util.List<String> tests = new ArrayList<String>(); private String solution; public TaskBean(String statement, List<String> tests, String solution) { this.solution = solution; this.statement = statement; this.tests = tests; } public String getSolution() { return solution; } public String getStatement() { return statement; } public List<String> getTests() { return tests; } public static Test parseTest(String test) { final Map<Reg, TestSpecReg> regsText = new HashMap<Reg, TestSpecReg>(); final Map<Integer, TestSpecMem> memText = new HashMap<Integer, TestSpecMem>(); final ParseFeedback feedback = new ParseFeedback(); final String[] testLines = test.split("\r?\n|\r"); for (int i = 0; i < testLines.length; i++) { final String testLineCompact = testLines[i] .trim() .replaceAll("\\s*:\\s*", ":") .replaceAll("\\s*=>\\s*", "=>") .replaceAll("\\s+", " "); final String testLineNoComm = PATTERN_TEST_COMMENT .matcher(testLineCompact) .replaceAll(""); if (testLineNoComm.length() == 0) { continue; } final ParseFeedback.LineContext line = feedback.forLine(i, testLines[i]); final Matcher matcherTestSpec = PATTERN_TEST_SPEC.matcher(testLineNoComm); final Matcher matcherConstSpec = PATTERN_CONST_SPEC.matcher(testLineNoComm); final String what; final String beforeStr; final String afterStr; if (matcherTestSpec.matches()) { what = matcherTestSpec.group(1); beforeStr = matcherTestSpec.group(2); afterStr = matcherTestSpec.group(4); } else if (matcherConstSpec.matches()) { what = matcherConstSpec.group(1); afterStr = beforeStr = matcherConstSpec.group(2); } else { line.addError("general format eror"); continue; } final Integer before = parseBeforeAfter("before", beforeStr, line); final Integer after = parseBeforeAfter("after", afterStr, line); if (before == null && after == null) { line.addError("both before and after are not specified"); } if (what.startsWith("$")) { final Reg reg = MipsAssembler.parseReg(what, null, null, ""); if (reg == null) { line.addError("register not recognized"); continue; } if (regsText.containsKey(reg)) { line.addError("multiple occurence, ignored"); } if (!Reg.publicRegs.contains(reg)) { line.addError( "register $" + reg.toString() + " is reserved" ); } if (Reg.roRegs.contains(reg)) { line.addError( "register $" + reg.toString() + " is read-only" ); } if (Reg.autoRegs.contains(reg)) { line.addError( "register $" + reg.toString() + " is set/verified automatically" ); } if (Reg.tempRegs.contains(reg)) { line.addError( "register $" + reg.toString() + " is temporary" ); } if (!line.hasErrors()) { regsText.put(reg, new TestSpecReg(reg, before, after)); } } else { if (!Data.isNum(what, 32)) { line.addError("not an address"); continue; } final int address = (int) Data.parse(what); if (address < 0) { line.addError("negative address"); } if (address % 4 > 0) { line.addError("address must be word-aligned"); } if (!line.hasErrors()) { memText.put( address, new TestSpecMem(address, before, after) ); } } } return new Test(regsText, memText, feedback); } protected static Integer parseBeforeAfter( String fieldName, String fieldValue, ParseFeedback.LineContext line ) { Integer beforeVal = null; if (fieldValue.length() > 0) { if (!Data.isNum(fieldValue, 32)) { line.addError(fieldName + ": '" + fieldValue + "' is not a number"); } else { beforeVal = (int) Data.parse(fieldValue); } } return beforeVal; } public static class TestSpec { public final Integer before; public final Integer after; public TestSpec(Integer before, Integer after) { this.after = after; this.before = before; } } public static class TestSpecReg extends TestSpec { public final Reg reg; public TestSpecReg(Reg reg, Integer before, Integer after) { super(before, after); this.reg = reg; } } public static class TestSpecMem extends TestSpec { public final int address; public TestSpecMem(int address, Integer before, Integer after) { super(before, after); this.address = address; } } public static class Test { public final Map<Reg, TestSpecReg> regs; public final Map<Integer, TestSpecMem> mem; public final ParseFeedback parseErrors; public Test( Map<Reg, TestSpecReg> regs, Map<Integer, TestSpecMem> mem, ParseFeedback parseErrors ) { this.mem = Collections.unmodifiableMap(mem); this.regs = Collections.unmodifiableMap(regs); this.parseErrors = parseErrors; } public String errors(Map<Integer, List<String>> lineToErrors) { final Map<Integer, String> lineToText = parseErrors.getLineToText(); final StringBuilder report = new StringBuilder("test syntax broken:"); for (Map.Entry<Integer, List<String>> errEntry : lineToErrors.entrySet()) { for (String err : errEntry.getValue()) { report.append("\n\t") .append(errEntry.getKey()) .append(" : ") .append(err) .append(" : ") .append(lineToText.get(errEntry.getKey())); } } return report.toString(); } } public static class ParseFeedback { final Map<Integer, String> lineToText = new TreeMap<Integer, String>(); final Map<Integer, List<String>> lineToErrors = new TreeMap<Integer, List<String>>(); final Map<Integer, List<String>> lineToWarns = new TreeMap<Integer, List<String>>(); public LineContext forLine(final int lineNo, final String lineText) { lineToText.put(lineNo, lineText); return new LineContext(lineNo); } public Map<Integer, List<String>> getLineToErrors() { return Collections.unmodifiableMap(lineToErrors); } public Map<Integer, String> getLineToText() { return Collections.unmodifiableMap(lineToText); } public Map<Integer, List<String>> getLineToWarns() { return Collections.unmodifiableMap(lineToWarns); } public boolean hasErrors(final int lineNo) { return lineToErrors.containsKey(lineNo) && !lineToErrors.get(lineNo).isEmpty(); } public boolean hasWarns(final int lineNo) { return lineToWarns.containsKey(lineNo) && !lineToWarns.get(lineNo).isEmpty(); } public class LineContext { private final int lineNo; public LineContext(int lineNo) { this.lineNo = lineNo; } public void addError(final String errorText) { ParseFeedback.this.addError(lineNo, errorText); } public void addWarn(final String warnText) { ParseFeedback.this.addWarn(lineNo, warnText); } public boolean hasErrors() { return ParseFeedback.this.hasErrors(lineNo); } public boolean hasWarns() { return ParseFeedback.this.hasWarns(lineNo); } } private static void add(Map<Integer, List<String>> dest, int lineNo, String errorText) { if (!dest.containsKey(lineNo)) { dest.put(lineNo, new ArrayList<String>(1)); } dest.get(lineNo).add(errorText); } protected void addError( final int lineNo, final String errorText ) { add(lineToErrors, lineNo, errorText); } protected void addWarn( final int lineNo, final String warnText ) { add(lineToWarns, lineNo, warnText); } } }