From 4b3e100730742d7045299c87ec92f2828ca4d363 Mon Sep 17 00:00:00 2001 From: hneemann Date: Wed, 19 Apr 2017 14:28:01 +0200 Subject: [PATCH] better test data parser to allow multi row repeats --- src/main/dig/cmos/sram.dig | 82 +++---------- .../de/neemann/digital/testing/TestData.java | 5 +- .../neemann/digital/testing/TestResult.java | 93 ++++++++------- .../neemann/digital/testing/Transitions.java | 6 +- .../digital/testing/parser/Context.java | 82 +------------ .../testing/parser/ContextWithVar.java | 53 +++++++++ .../digital/testing/parser/LineEmitter.java | 18 +++ .../testing/parser/LineEmitterList.java | 47 ++++++++ .../testing/parser/LineEmitterRepeat.java | 34 ++++++ .../testing/parser/LineEmitterSimple.java | 50 ++++++++ .../digital/testing/parser/LineListener.java | 16 +++ .../digital/testing/parser/Parser.java | 108 ++++++++---------- .../digital/testing/parser/ValueAppender.java | 20 ++++ .../testing/parser/ValueAppenderBits.java | 36 ++++++ .../neemann/digital/testing/TestDataTest.java | 11 +- .../digital/testing/parser/LineCollector.java | 38 ++++++ .../testing/parser/ParserExpressionTest.java | 18 +-- .../testing/parser/ParserLoopTest.java | 86 ++++++++++++++ .../digital/testing/parser/ParserTest.java | 34 ++++-- 19 files changed, 565 insertions(+), 272 deletions(-) create mode 100644 src/main/java/de/neemann/digital/testing/parser/ContextWithVar.java create mode 100644 src/main/java/de/neemann/digital/testing/parser/LineEmitter.java create mode 100644 src/main/java/de/neemann/digital/testing/parser/LineEmitterList.java create mode 100644 src/main/java/de/neemann/digital/testing/parser/LineEmitterRepeat.java create mode 100644 src/main/java/de/neemann/digital/testing/parser/LineEmitterSimple.java create mode 100644 src/main/java/de/neemann/digital/testing/parser/LineListener.java create mode 100644 src/main/java/de/neemann/digital/testing/parser/ValueAppender.java create mode 100644 src/main/java/de/neemann/digital/testing/parser/ValueAppenderBits.java create mode 100644 src/test/java/de/neemann/digital/testing/parser/LineCollector.java create mode 100644 src/test/java/de/neemann/digital/testing/parser/ParserLoopTest.java diff --git a/src/main/dig/cmos/sram.dig b/src/main/dig/cmos/sram.dig index 946b2fac6..edf029e54 100644 --- a/src/main/dig/cmos/sram.dig +++ b/src/main/dig/cmos/sram.dig @@ -1465,73 +1465,25 @@ in der Speichermatrix gespeichert. C0 C1 R0 R1 WE D_In D_Out +# write all cells to one +loop(n,16) +bits(4,n) 0 1 0 +bits(4,n) 1 1 1 +bits(4,n) 0 1 1 +endloop + +# check all cells are one +repeat(16) bits(4,n) 0 x 1 # write all cells to zero -0 0 0 0 0 0 0 -0 0 0 0 1 0 0 -0 0 0 0 0 0 0 - -0 0 0 1 0 0 0 -0 0 0 1 1 0 0 -0 0 0 1 0 0 0 - -0 0 1 0 0 0 0 -0 0 1 0 1 0 0 -0 0 1 0 0 0 0 - -0 0 1 1 0 0 0 -0 0 1 1 1 0 0 -0 0 1 1 0 0 0 - -0 1 0 0 0 0 0 -0 1 0 0 1 0 0 -0 1 0 0 0 0 0 - -0 1 0 1 0 0 0 -0 1 0 1 1 0 0 -0 1 0 1 0 0 0 - -0 1 1 0 0 0 0 -0 1 1 0 1 0 0 -0 1 1 0 0 0 0 - -0 1 1 1 0 0 0 -0 1 1 1 1 0 0 -0 1 1 1 0 0 0 - - -1 0 0 0 0 0 0 -1 0 0 0 1 0 0 -1 0 0 0 0 0 0 - -1 0 0 1 0 0 0 -1 0 0 1 1 0 0 -1 0 0 1 0 0 0 - -1 0 1 0 0 0 0 -1 0 1 0 1 0 0 -1 0 1 0 0 0 0 - -1 0 1 1 0 0 0 -1 0 1 1 1 0 0 -1 0 1 1 0 0 0 - -1 1 0 0 0 0 0 -1 1 0 0 1 0 0 -1 1 0 0 0 0 0 - -1 1 0 1 0 0 0 -1 1 0 1 1 0 0 -1 1 0 1 0 0 0 - -1 1 1 0 0 0 0 -1 1 1 0 1 0 0 -1 1 1 0 0 0 0 - -1 1 1 1 0 0 0 -1 1 1 1 1 0 0 -1 1 1 1 0 0 0 +loop(n,16) +bits(4,n) 0 0 1 +bits(4,n) 1 0 0 +bits(4,n) 0 0 0 +endloop +# check all cells are zero +repeat(16) bits(4,n) 0 x 0 #write cell 00 to 1 @@ -1547,8 +1499,6 @@ in der Speichermatrix gespeichert. # cell 00 is still 1 0 0 0 0 0 0 1 - - diff --git a/src/main/java/de/neemann/digital/testing/TestData.java b/src/main/java/de/neemann/digital/testing/TestData.java index bd3dddd6d..a790e2f6e 100644 --- a/src/main/java/de/neemann/digital/testing/TestData.java +++ b/src/main/java/de/neemann/digital/testing/TestData.java @@ -1,5 +1,6 @@ package de.neemann.digital.testing; +import de.neemann.digital.testing.parser.LineEmitter; import de.neemann.digital.testing.parser.Parser; import de.neemann.digital.testing.parser.ParserException; @@ -19,7 +20,7 @@ public class TestData { public static final TestData DEFAULT = new TestData(""); private String dataString; - private transient ArrayList lines; + private transient LineEmitter lines; private transient ArrayList names; TestData(String data) { @@ -74,7 +75,7 @@ public class TestData { * @return the data lines * @throws TestingDataException TestingDataException */ - public ArrayList getLines() throws TestingDataException { + public LineEmitter getLines() throws TestingDataException { check(); return lines; } diff --git a/src/main/java/de/neemann/digital/testing/TestResult.java b/src/main/java/de/neemann/digital/testing/TestResult.java index 13f25319d..92aecb438 100644 --- a/src/main/java/de/neemann/digital/testing/TestResult.java +++ b/src/main/java/de/neemann/digital/testing/TestResult.java @@ -6,6 +6,9 @@ import de.neemann.digital.core.ObservableValue; import de.neemann.digital.core.Signal; import de.neemann.digital.core.wiring.Clock; import de.neemann.digital.lang.Lang; +import de.neemann.digital.testing.parser.Context; +import de.neemann.digital.testing.parser.LineEmitter; +import de.neemann.digital.testing.parser.ParserException; import java.util.ArrayList; import java.util.HashSet; @@ -21,7 +24,7 @@ import java.util.NoSuchElementException; public class TestResult { private final ArrayList names; - private final ArrayList lines; + private final LineEmitter lines; private final ArrayList results; private boolean allPassed; private Exception exception; @@ -87,55 +90,63 @@ public class TestResult { model.init(); - for (Value[] rowWithDontCare : lines) { + try { + lines.emitLines(rowWithDontCare -> { + for (Value[] row : resolveDontCares(inputs, rowWithDontCare)) { - for (Value[] row : resolveDontCares(inputs, rowWithDontCare)) { + Value[] res = new Value[row.length]; - Value[] res = new Value[row.length]; - - boolean clockIsUsed = false; - // set all values except the clocks - for (TestSignal in : inputs) { - if (row[in.index].getType() != Value.Type.CLOCK) { - row[in.index].copyTo(in.value); - } else { - clockIsUsed = true; - } - res[in.index] = row[in.index]; - } - - if (clockIsUsed) { // a clock signal is used - model.doStep(); // propagate all except clock - - // set clock - for (TestSignal in : inputs) - if (row[in.index].getType() == Value.Type.CLOCK) + boolean clockIsUsed = false; + // set all values except the clocks + for (TestSignal in : inputs) { + if (row[in.index].getType() != Value.Type.CLOCK) { row[in.index].copyTo(in.value); + } else { + clockIsUsed = true; + } + res[in.index] = row[in.index]; + } - // propagate clock change - model.doStep(); + try { + if (clockIsUsed) { // a clock signal is used + model.doStep(); // propagate all except clock - // restore clock - for (TestSignal in : inputs) // invert the clock values - if (row[in.index].getType() == Value.Type.CLOCK) - in.value.setBool(!in.value.getBool()); - } + // set clock + for (TestSignal in : inputs) + if (row[in.index].getType() == Value.Type.CLOCK) + row[in.index].copyTo(in.value); - try { - model.doStep(); - } catch (NodeException | RuntimeException e) { - exception = e; - allPassed = false; - return this; - } + // propagate clock change + model.doStep(); - for (TestSignal out : outputs) { - MatchedValue matchedValue = new MatchedValue(row[out.index], out.value); - res[out.index] = matchedValue; - if (!matchedValue.isPassed()) + // restore clock + for (TestSignal in : inputs) // invert the clock values + if (row[in.index].getType() == Value.Type.CLOCK) + in.value.setBool(!in.value.getBool()); + } + + model.doStep(); + } catch (NodeException | RuntimeException e) { + exception = e; allPassed = false; + throw new RuntimeException(e); + } + + for (TestSignal out : outputs) { + MatchedValue matchedValue = new MatchedValue(row[out.index], out.value); + res[out.index] = matchedValue; + if (!matchedValue.isPassed()) + allPassed = false; + } + results.add(res); } - results.add(res); + }, new Context()); + } catch (ParserException e) { + throw new TestingDataException(e); + } catch (RuntimeException e) { + if (allPassed) { + allPassed = false; + exception = e; } } diff --git a/src/main/java/de/neemann/digital/testing/Transitions.java b/src/main/java/de/neemann/digital/testing/Transitions.java index 1337350b9..e6dfa135a 100644 --- a/src/main/java/de/neemann/digital/testing/Transitions.java +++ b/src/main/java/de/neemann/digital/testing/Transitions.java @@ -1,6 +1,7 @@ package de.neemann.digital.testing; import de.neemann.digital.core.element.PinDescription; +import de.neemann.digital.testing.parser.Context; import de.neemann.digital.testing.parser.Parser; import de.neemann.digital.testing.parser.ParserException; @@ -8,6 +9,7 @@ import java.io.IOException; import java.util.ArrayList; /** + * Helper to create all possible transitions between states. * Created by hneemann on 24.02.17. */ public class Transitions { @@ -44,7 +46,7 @@ public class Transitions { inVarNum.add(i); } - for (Value[] line : p.getLines()) { + p.getLines().emitLines(line -> { if (isNormal(line)) { boolean found = false; for (Value[] u : uniqueLines) { @@ -56,7 +58,7 @@ public class Transitions { if (!found) uniqueLines.add(line); } - } + }, new Context()); } private boolean isInputEqual(Value[] l1, Value[] l2) { diff --git a/src/main/java/de/neemann/digital/testing/parser/Context.java b/src/main/java/de/neemann/digital/testing/parser/Context.java index fd098f1dc..684e93092 100644 --- a/src/main/java/de/neemann/digital/testing/parser/Context.java +++ b/src/main/java/de/neemann/digital/testing/parser/Context.java @@ -1,93 +1,23 @@ package de.neemann.digital.testing.parser; import de.neemann.digital.lang.Lang; -import de.neemann.digital.testing.Value; - -import java.util.ArrayList; -import java.util.HashMap; /** * The context of the calculations. - * Containf the variables to use and the test values list. + *

* Created by hneemann on 02.12.16. */ public class Context { - private final HashMap vars; - private final ArrayList values; /** - * Creates a new instance - */ - public Context() { - this(null); - } - - /** - * Creates a new instance + * Returnes the value of a variable * - * @param values the values array to fill - */ - public Context(ArrayList values) { - vars = new HashMap<>(); - this.values = values; - } - - /** - * @return the actual loop value - * @throws ParserException Thrown if variable not present - */ - public long getN() throws ParserException { - return getVar("n"); - } - - /** - * Returns the value of a variable - * - * @param name the variables name - * @return the long value - * @throws ParserException Thrown if variable not present + * @param name the name of the variable + * @return the value + * @throws ParserException if the variable does not exist */ public long getVar(String name) throws ParserException { - Long l = vars.get(name); - if (l == null) - throw new ParserException(Lang.get("err_variable_N0_notFound", name)); - return l; + throw new ParserException(Lang.get("err_variable_N0_notFound", name)); } - /** - * Adds a simple value to the value list - * - * @param v the value to add - */ - public void addValue(Value v) { - values.add(v); - } - - /** - * Adds bitcount values to the values list. - * Bitcount bits from the given value are added to the values list - * - * @param bitCount the numbers of bits to add - * @param value the bit values - */ - public void addBits(int bitCount, long value) { - long mask = 1L << (bitCount - 1); - for (int i = 0; i < bitCount; i++) { - boolean v = (value & mask) != 0; - values.add(new Value(v ? 1 : 0)); - mask >>= 1; - } - } - - /** - * Sets a variable value to the context - * - * @param name name of the variable - * @param value value - * @return this for chained calls - */ - public Context setVar(String name, long value) { - vars.put(name, value); - return this; - } } diff --git a/src/main/java/de/neemann/digital/testing/parser/ContextWithVar.java b/src/main/java/de/neemann/digital/testing/parser/ContextWithVar.java new file mode 100644 index 000000000..890ddcdec --- /dev/null +++ b/src/main/java/de/neemann/digital/testing/parser/ContextWithVar.java @@ -0,0 +1,53 @@ +package de.neemann.digital.testing.parser; + +/** + * A context with a variable + * Created by hneemann on 19.04.17. + */ +public class ContextWithVar extends Context { + + private final Context parent; + private final String var; + private long value; + + /** + * Creates a new instance + * + * @param parent the parent + * @param var the variable + */ + public ContextWithVar(Context parent, String var) { + this.parent = parent; + this.var = var; + } + + /** + * Creates a new instance + * + * @param var the variable + */ + public ContextWithVar(String var) { + this.parent = new Context(); + this.var = var; + } + + /** + * Sets a value to this variable + * + * @param value the value + * @return this for chained calls + */ + public ContextWithVar setValue(long value) { + this.value = value; + return this; + } + + @Override + public long getVar(String name) throws ParserException { + if (name.equals(var)) + return value; + else + return parent.getVar(name); + } + +} diff --git a/src/main/java/de/neemann/digital/testing/parser/LineEmitter.java b/src/main/java/de/neemann/digital/testing/parser/LineEmitter.java new file mode 100644 index 000000000..98f5b5b42 --- /dev/null +++ b/src/main/java/de/neemann/digital/testing/parser/LineEmitter.java @@ -0,0 +1,18 @@ +package de.neemann.digital.testing.parser; + +/** + * Used to describe a line + * Created by hneemann on 19.04.17. + */ +public interface LineEmitter { + + /** + * Is called to imit the described line to the listener + * + * @param listener the listener to emit the lines + * @param conext the context + * @throws ParserException ParserException + */ + void emitLines(LineListener listener, Context conext) throws ParserException; + +} diff --git a/src/main/java/de/neemann/digital/testing/parser/LineEmitterList.java b/src/main/java/de/neemann/digital/testing/parser/LineEmitterList.java new file mode 100644 index 000000000..432a0b155 --- /dev/null +++ b/src/main/java/de/neemann/digital/testing/parser/LineEmitterList.java @@ -0,0 +1,47 @@ +package de.neemann.digital.testing.parser; + +import java.util.ArrayList; + +/** + * Created by hneemann on 19.04.17. + */ +public class LineEmitterList implements LineEmitter { + + private ArrayList lines; + + /** + * Create a new instance + */ + public LineEmitterList() { + lines = new ArrayList<>(); + } + + /** + * Adds a line to this list + * + * @param line the line to add + */ + public void add(LineEmitter line) { + lines.add(line); + } + + /** + * If this list contains anly a single line this line is returned. + * Otherwise this is returned + * + * @return this of the only lists item + */ + public LineEmitter minimize() { + if (lines.size() == 1) + return lines.get(0); + else + return this; + } + + @Override + public void emitLines(LineListener listener, Context conext) throws ParserException { + for (LineEmitter l : lines) + l.emitLines(listener, conext); + } + +} diff --git a/src/main/java/de/neemann/digital/testing/parser/LineEmitterRepeat.java b/src/main/java/de/neemann/digital/testing/parser/LineEmitterRepeat.java new file mode 100644 index 000000000..a6b0de1d3 --- /dev/null +++ b/src/main/java/de/neemann/digital/testing/parser/LineEmitterRepeat.java @@ -0,0 +1,34 @@ +package de.neemann.digital.testing.parser; + +/** + * Repeats some inner table rows. + * Created by hneemann on 19.04.17. + */ +public class LineEmitterRepeat implements LineEmitter { + + private final String name; + private final int size; + private final LineEmitter inner; + + /** + * Creates a new loop + * + * @param name name of the loop variable + * @param size number of iterations + * @param inner the lines to repeat + */ + public LineEmitterRepeat(String name, int size, LineEmitter inner) { + this.name = name; + this.size = size; + this.inner = inner; + } + + @Override + public void emitLines(LineListener listener, Context conext) throws ParserException { + ContextWithVar c = new ContextWithVar(conext, name); + for (int i = 0; i < size; i++) { + c.setValue(i); + inner.emitLines(listener, c); + } + } +} diff --git a/src/main/java/de/neemann/digital/testing/parser/LineEmitterSimple.java b/src/main/java/de/neemann/digital/testing/parser/LineEmitterSimple.java new file mode 100644 index 000000000..a00bc8971 --- /dev/null +++ b/src/main/java/de/neemann/digital/testing/parser/LineEmitterSimple.java @@ -0,0 +1,50 @@ +package de.neemann.digital.testing.parser; + +import de.neemann.digital.lang.Lang; +import de.neemann.digital.testing.Value; + +import java.util.ArrayList; + +/** + * Line emitter to create a simple row of values. + * Created by hneemann on 19.04.17. + */ +public class LineEmitterSimple implements LineEmitter { + + private final ArrayList appender; + private final int valuesCount; + private final int line; + + /** + * Creates a new Instance + * + * @param valuesCount number of expected columns + * @param line the source line + */ + public LineEmitterSimple(int valuesCount, int line) { + this.valuesCount = valuesCount; + this.line = line; + this.appender = new ArrayList<>(); + } + + /** + * Adds a value appender + * + * @param app the appender + */ + public void add(ValueAppender app) { + appender.add(app); + } + + @Override + public void emitLines(LineListener listener, Context conext) throws ParserException { + ArrayList vals = new ArrayList<>(valuesCount); + for (ValueAppender ve : appender) + ve.appendValues(vals, conext); + + if (vals.size() != valuesCount) + throw new ParserException(Lang.get("err_testDataExpected_N0_found_N1_numbersInLine_N2", valuesCount, vals.size(), line)); + + listener.add(vals.toArray(new Value[vals.size()])); + } +} diff --git a/src/main/java/de/neemann/digital/testing/parser/LineListener.java b/src/main/java/de/neemann/digital/testing/parser/LineListener.java new file mode 100644 index 000000000..06e7e9345 --- /dev/null +++ b/src/main/java/de/neemann/digital/testing/parser/LineListener.java @@ -0,0 +1,16 @@ +package de.neemann.digital.testing.parser; + +import de.neemann.digital.testing.Value; + +/** + * Listener for truth table lines + * Created by hneemann on 19.04.17. + */ +public interface LineListener { + /** + * Adds a line to the table + * + * @param values the values in the line + */ + void add(Value[] values); +} diff --git a/src/main/java/de/neemann/digital/testing/parser/Parser.java b/src/main/java/de/neemann/digital/testing/parser/Parser.java index 5c282df69..fea0b99a9 100644 --- a/src/main/java/de/neemann/digital/testing/parser/Parser.java +++ b/src/main/java/de/neemann/digital/testing/parser/Parser.java @@ -21,9 +21,8 @@ import java.util.ArrayList; public class Parser { private final ArrayList names; - private final ArrayList lines; - private final ArrayList values; private final Tokenizer tok; + private LineEmitter emitter; /** * Creates a new instance @@ -32,8 +31,6 @@ public class Parser { */ public Parser(String data) { names = new ArrayList<>(); - lines = new ArrayList<>(); - values = new ArrayList<>(); tok = new Tokenizer(new BufferedReader(new StringReader(data))); } @@ -46,7 +43,8 @@ public class Parser { */ public Parser parse() throws IOException, ParserException { parseHeader(); - parseValues(); + emitter = parseValues(false); + expect(Tokenizer.Token.EOF); return this; } @@ -71,28 +69,47 @@ public class Parser { return new ParserException(Lang.get("err_unexpectedToken_N0_inLine_N1", name, tok.getLine())); } - private void parseValues() throws IOException, ParserException { + private LineEmitter parseValues(boolean loop) throws IOException, ParserException { + LineEmitterList list = new LineEmitterList(); while (true) { Tokenizer.Token t = tok.peek(); switch (t) { case EOL: break; case EOF: - return; + if (loop) + throw newUnexpectedToken(t); + return list.minimize(); case NUMBER: - parseLine(); + list.add(parseLine()); break; case IDENT: - if (tok.getIdent().equals("repeat")) { + if (tok.getIdent().equals("endloop")) { + tok.consume(); + if (!loop) + throw newUnexpectedToken(t); + return list.minimize(); + } else if (tok.getIdent().equals("repeat")) { tok.consume(); expect(Tokenizer.Token.OPEN); int count = (int) parseInt(); if (count > 1 << 16) throw new ParserException(Lang.get("err_toManyTestEntries")); expect(Tokenizer.Token.CLOSE); - parseForLine(count); + list.add(new LineEmitterRepeat("n", count, parseLine())); + } else if (tok.getIdent().equals("loop")) { + tok.consume(); + expect(Tokenizer.Token.OPEN); + expect(Tokenizer.Token.IDENT); + String var = tok.getIdent(); + expect(Tokenizer.Token.COMMA); + int count = (int) parseInt(); + if (count > 1 << 16) + throw new ParserException(Lang.get("err_toManyTestEntries")); + expect(Tokenizer.Token.CLOSE); + list.add(new LineEmitterRepeat(var, count, parseValues(true))); } else { - parseLine(); + list.add(parseLine()); } break; default: @@ -101,14 +118,16 @@ public class Parser { } } - private void parseForLine(int count) throws IOException, ParserException { - ArrayList entries = new ArrayList<>(); + private LineEmitter parseLine() throws IOException, ParserException { + LineEmitterSimple line = null; while (true) { Tokenizer.Token token = tok.next(); + if (line == null) + line = new LineEmitterSimple(names.size(), tok.getLine()); switch (token) { case NUMBER: Value num = new Value(tok.getIdent()); - entries.add(n -> n.addValue(num)); + line.add((vals, conext) -> vals.add(num)); break; case IDENT: if (tok.getIdent().equals("bits")) { @@ -116,25 +135,25 @@ public class Parser { int bitCount = (int) parseInt(); expect(Tokenizer.Token.COMMA); Expression exp = parseExpression(); - entries.add(c -> c.addBits(bitCount, exp.value(c))); + line.add(new ValueAppenderBits(bitCount, exp)); expect(Tokenizer.Token.CLOSE); } else { - Value v = new Value(tok.getIdent().toUpperCase()); - entries.add(n -> n.addValue(v)); + try { + final Value value = new Value(tok.getIdent().toUpperCase()); + line.add((vals, context) -> vals.add(value)); + } catch (NumberFormatException e) { + throw new ParserException(Lang.get("err_notANumber_N0_inLine_N1", tok.getIdent(), tok.getLine())); + } } break; case OPEN: Expression exp = parseExpression(); - entries.add(c -> c.addValue(new Value((int) exp.value(c)))); + line.add((vals, context) -> vals.add(new Value((int) exp.value(context)))); expect(Tokenizer.Token.CLOSE); break; case EOF: case EOL: - for (int n = 0; n < count; n++) { - for (Entry entry : entries) entry.calculate(new Context(values).setVar("n", n)); - addLine(); - } - return; + return line; default: throw newUnexpectedToken(token); } @@ -145,39 +164,6 @@ public class Parser { return parseExpression().value(new Context()); } - private Tokenizer.Token parseLine() throws IOException, ParserException { - while (true) { - Tokenizer.Token token = tok.next(); - switch (token) { - case IDENT: - case NUMBER: - try { - values.add(new Value(tok.getIdent().toUpperCase())); - } catch (NumberFormatException e) { - throw new ParserException(Lang.get("err_notANumber_N0_inLine_N1", tok.getIdent(), tok.getLine())); - } - break; - case EOF: - case EOL: - addLine(); - return token; - default: - throw newUnexpectedToken(token); - } - } - } - - private void addLine() throws ParserException { - if (values.size() > 0) { - - if (values.size() != names.size()) - throw new ParserException(Lang.get("err_testDataExpected_N0_found_N1_numbersInLine_N2", names.size(), values.size(), tok.getLine())); - - lines.add(values.toArray(new Value[names.size()])); - values.clear(); - } - } - private void expect(Tokenizer.Token token) throws IOException, ParserException { Tokenizer.Token t = tok.next(); if (t != token) @@ -193,14 +179,10 @@ public class Parser { } /** - * @return the test vectors + * @return the line emitter */ - public ArrayList getLines() { - return lines; - } - - private interface Entry { - void calculate(Context c) throws ParserException; + public LineEmitter getLines() { + return emitter; } private boolean isToken(Tokenizer.Token t) throws IOException { diff --git a/src/main/java/de/neemann/digital/testing/parser/ValueAppender.java b/src/main/java/de/neemann/digital/testing/parser/ValueAppender.java new file mode 100644 index 000000000..8be832c4e --- /dev/null +++ b/src/main/java/de/neemann/digital/testing/parser/ValueAppender.java @@ -0,0 +1,20 @@ +package de.neemann.digital.testing.parser; + +import de.neemann.digital.testing.Value; + +import java.util.ArrayList; + +/** + * Used to append some values to a table row + * Created by hneemann on 19.04.17. + */ +public interface ValueAppender { + /** + * Appends some values to the given row + * + * @param values the row + * @param context the context to acess variables + * @throws ParserException ParserException + */ + void appendValues(ArrayList values, Context context) throws ParserException; +} diff --git a/src/main/java/de/neemann/digital/testing/parser/ValueAppenderBits.java b/src/main/java/de/neemann/digital/testing/parser/ValueAppenderBits.java new file mode 100644 index 000000000..deaa7ed52 --- /dev/null +++ b/src/main/java/de/neemann/digital/testing/parser/ValueAppenderBits.java @@ -0,0 +1,36 @@ +package de.neemann.digital.testing.parser; + +import de.neemann.digital.testing.Value; + +import java.util.ArrayList; + +/** + * Appends the bits of an integer value to the given row. + * Created by hneemann on 19.04.17. + */ +public class ValueAppenderBits implements ValueAppender { + private final Expression expression; + private final int bitCount; + + /** + * Creates a new instance + * + * @param bitCount the number of bits to append + * @param expression the expression to calculate the int value + */ + public ValueAppenderBits(int bitCount, Expression expression) { + this.bitCount = bitCount; + this.expression = expression; + } + + @Override + public void appendValues(ArrayList values, Context conext) throws ParserException { + long value = expression.value(conext); + long mask = 1L << (bitCount - 1); + for (int i = 0; i < bitCount; i++) { + boolean v = (value & mask) != 0; + values.add(new Value(v ? 1 : 0)); + mask >>= 1; + } + } +} diff --git a/src/test/java/de/neemann/digital/testing/TestDataTest.java b/src/test/java/de/neemann/digital/testing/TestDataTest.java index d5371bb8c..169d75f8d 100644 --- a/src/test/java/de/neemann/digital/testing/TestDataTest.java +++ b/src/test/java/de/neemann/digital/testing/TestDataTest.java @@ -1,5 +1,6 @@ package de.neemann.digital.testing; +import de.neemann.digital.testing.parser.LineCollector; import de.neemann.digital.testing.parser.ParserException; import junit.framework.TestCase; @@ -15,7 +16,10 @@ public class TestDataTest extends TestCase { public void testSetDataNonParseable() throws Exception { TestData td = new TestData(DATA1); - assertEquals(4, td.getLines().size()); + + LineCollector cl = new LineCollector(td.getLines()); + + assertEquals(4, cl.getLines().size()); assertEquals(DATA1, td.getDataString()); // try to set a non parsable string @@ -31,7 +35,10 @@ public class TestDataTest extends TestCase { public void testSetDataParseable() throws Exception { TestData td = new TestData(DATA1); - assertEquals(4, td.getLines().size()); + + LineCollector cl = new LineCollector(td.getLines()); + + assertEquals(4, cl.getLines().size()); assertEquals(DATA1, td.getDataString()); // try to set a parsable string diff --git a/src/test/java/de/neemann/digital/testing/parser/LineCollector.java b/src/test/java/de/neemann/digital/testing/parser/LineCollector.java new file mode 100644 index 000000000..ab76218de --- /dev/null +++ b/src/test/java/de/neemann/digital/testing/parser/LineCollector.java @@ -0,0 +1,38 @@ +package de.neemann.digital.testing.parser; + +import de.neemann.digital.testing.Value; + +import java.util.ArrayList; + +/** + * Created by hneemann on 19.04.17. + */ +public class LineCollector implements LineListener { + private final ArrayList names; + private final ArrayList list; + + public LineCollector(LineEmitter le) throws ParserException { + this.list = new ArrayList<>(); + le.emitLines(this, new Context()); + names = null; + } + + public LineCollector(Parser parser) throws ParserException { + this.list = new ArrayList<>(); + parser.getLines().emitLines(this, new Context()); + names = parser.getNames(); + } + + @Override + public void add(Value[] values) { + list.add(values); + } + + public ArrayList getLines() { + return list; + } + + public ArrayList getNames() { + return names; + } +} diff --git a/src/test/java/de/neemann/digital/testing/parser/ParserExpressionTest.java b/src/test/java/de/neemann/digital/testing/parser/ParserExpressionTest.java index f13de3c72..0cc05146f 100644 --- a/src/test/java/de/neemann/digital/testing/parser/ParserExpressionTest.java +++ b/src/test/java/de/neemann/digital/testing/parser/ParserExpressionTest.java @@ -12,12 +12,12 @@ public class ParserExpressionTest extends TestCase { public void testParseExpression() throws Exception { assertEquals(7, new Parser("2+5").getValue()); assertEquals(7, new Parser("9-2").getValue()); - assertEquals(6, new Parser("2*n").getValue(new Context().setVar("n", 3))); - assertEquals(7, new Parser("2*n+1").getValue(new Context().setVar("n", 3))); - assertEquals(7, new Parser("1+2*n").getValue(new Context().setVar("n", 3))); - assertEquals(8, new Parser("2*(1+n)").getValue(new Context().setVar("n", 3))); - assertEquals(4, new Parser("2*(n-1)").getValue(new Context().setVar("n", 3))); - assertEquals(2, new Parser("(2*n)/3").getValue(new Context().setVar("n", 3))); + assertEquals(6, new Parser("2*n").getValue(new ContextWithVar("n").setValue(3))); + assertEquals(7, new Parser("2*n+1").getValue(new ContextWithVar("n").setValue(3))); + assertEquals(7, new Parser("1+2*n").getValue(new ContextWithVar("n").setValue(3))); + assertEquals(8, new Parser("2*(1+n)").getValue(new ContextWithVar("n").setValue(3))); + assertEquals(4, new Parser("2*(n-1)").getValue(new ContextWithVar("n").setValue(3))); + assertEquals(2, new Parser("(2*n)/3").getValue(new ContextWithVar("n").setValue(3))); assertEquals(-1, new Parser("-1").getValue()); assertEquals(-2, new Parser("-1-1").getValue()); @@ -37,10 +37,10 @@ public class ParserExpressionTest extends TestCase { assertEquals(-1, new Parser("~0").getValue()); - assertEquals(1, new Parser("(n>>8)*(n&255)").getValue(new Context().setVar("n", 257))); + assertEquals(1, new Parser("(n>>8)*(n&255)").getValue(new ContextWithVar("n").setValue(257))); assertEquals(0x11, new Parser("0x10+1").getValue()); - assertEquals(6, new Parser("a*b").getValue(new Context().setVar("a", 2).setVar("b", 3))); + assertEquals(6, new Parser("a*b").getValue(new ContextWithVar(new ContextWithVar("a").setValue(2),"b").setValue(3))); } public void testVarNotFound() throws IOException { @@ -54,7 +54,7 @@ public class ParserExpressionTest extends TestCase { public void testInvalidExpressionClose() throws IOException { try { - new Parser("n*3)").getValue(new Context().setVar("n", 2)); + new Parser("n*3)").getValue(new ContextWithVar("n").setValue(2)); fail(); } catch (ParserException e) { assertTrue(true); diff --git a/src/test/java/de/neemann/digital/testing/parser/ParserLoopTest.java b/src/test/java/de/neemann/digital/testing/parser/ParserLoopTest.java new file mode 100644 index 000000000..ac72cb52e --- /dev/null +++ b/src/test/java/de/neemann/digital/testing/parser/ParserLoopTest.java @@ -0,0 +1,86 @@ +package de.neemann.digital.testing.parser; + +import de.neemann.digital.testing.Value; +import junit.framework.TestCase; + +import java.io.IOException; + +/** + * Created by hneemann on 19.04.17. + */ +public class ParserLoopTest extends TestCase { + + public void testLoop() throws IOException, ParserException { + Parser parser = new Parser("A B\nloop(n,10)\n C (n*2)\nendloop").parse(); + LineCollector td = new LineCollector(parser); + + assertEquals(2, td.getNames().size()); + assertEquals(10, td.getLines().size()); + + for (int i = 0; i < 10; i++) { + assertEquals(Value.Type.CLOCK, td.getLines().get(i)[0].getType()); + assertEquals(i * 2, td.getLines().get(i)[1].getValue()); + } + } + + public void testLoopVar() throws IOException, ParserException { + Parser parser = new Parser("A B\nloop(i,10)\n C (i*2)\nendloop").parse(); + LineCollector td = new LineCollector(parser); + + assertEquals(2, td.getNames().size()); + assertEquals(10, td.getLines().size()); + + for (int i = 0; i < 10; i++) { + assertEquals(Value.Type.CLOCK, td.getLines().get(i)[0].getType()); + assertEquals(i * 2, td.getLines().get(i)[1].getValue()); + } + } + + public void testNested() throws IOException, ParserException { + Parser parser = new Parser("A B\nloop(i,10)\nloop(j,10)\n C ((i+j)*2)\nendloop\nendloop").parse(); + LineCollector td = new LineCollector(parser); + + assertEquals(2, td.getNames().size()); + assertEquals(100, td.getLines().size()); + + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + int ind = i * 10 + j; + assertEquals(Value.Type.CLOCK, td.getLines().get(ind)[0].getType()); + assertEquals((i + j) * 2, td.getLines().get(ind)[1].getValue()); + } + } + } + + public void testLoopMultiLines() throws IOException, ParserException { + Parser parser = new Parser("A B\nloop(i,10)\n C (i*2)\n C (i*2+1)\nendloop").parse(); + LineCollector td = new LineCollector(parser); + + assertEquals(2, td.getNames().size()); + assertEquals(20, td.getLines().size()); + + for (int i = 0; i < 10; i++) { + assertEquals(Value.Type.CLOCK, td.getLines().get(i * 2)[0].getType()); + assertEquals(i * 2, td.getLines().get(i * 2)[1].getValue()); + assertEquals(Value.Type.CLOCK, td.getLines().get(i * 2 + 1)[0].getType()); + assertEquals(i * 2 + 1, td.getLines().get(i * 2 + 1)[1].getValue()); + } + } + + public void testMissingEndloop() throws IOException, ParserException { + try { + new Parser("A B\nloop(i,10) C ((i+j)*2)").parse(); + fail(); + } catch (ParserException e) { + } + } + + public void testUnexpectedEndloop() throws IOException, ParserException { + try { + new Parser("A B\n C ((i+j)*2)\nendloop").parse(); + fail(); + } catch (ParserException e) { + } + } + +} diff --git a/src/test/java/de/neemann/digital/testing/parser/ParserTest.java b/src/test/java/de/neemann/digital/testing/parser/ParserTest.java index 2b0aa6c82..7cd30747f 100644 --- a/src/test/java/de/neemann/digital/testing/parser/ParserTest.java +++ b/src/test/java/de/neemann/digital/testing/parser/ParserTest.java @@ -13,7 +13,9 @@ import java.io.IOException; public class ParserTest extends TestCase { public void testOk() throws TestingDataException, IOException, ParserException { - Parser td = new Parser("A B\n0 1\n1 0\nX x").parse(); + Parser parser = new Parser("A B\n0 1\n1 0\nX x").parse(); + LineCollector td = new LineCollector(parser); + assertEquals(2, td.getNames().size()); assertEquals(3, td.getLines().size()); @@ -36,7 +38,8 @@ public class ParserTest extends TestCase { } public void testHex() throws TestingDataException, IOException, ParserException { - Parser td = new Parser("A B\n0 0xff").parse(); + Parser parser = new Parser("A B\n0 0xff").parse(); + LineCollector td = new LineCollector(parser); assertEquals(2, td.getNames().size()); assertEquals(1, td.getLines().size()); @@ -50,7 +53,7 @@ public class ParserTest extends TestCase { public void testMissingValue() throws IOException, ParserException { try { - new Parser("A B\n0 0\n1").parse(); + new Parser("A B\n0 0\n1").parse().getLines().emitLines(values -> {}, new Context()); assertTrue(false); } catch (ParserException e) { assertTrue(true); @@ -67,7 +70,9 @@ public class ParserTest extends TestCase { } public void testClock() throws Exception { - Parser td = new Parser("A B\nC 1\nC 0").parse(); + Parser parser = new Parser("A B\nC 1\nC 0").parse(); + LineCollector td = new LineCollector(parser); + assertEquals(2, td.getNames().size()); assertEquals(2, td.getLines().size()); @@ -78,7 +83,9 @@ public class ParserTest extends TestCase { } public void testFor() throws IOException, ParserException { - Parser td = new Parser("A B\nrepeat(10) C (n*2)\n").parse(); + Parser parser = new Parser("A B\nrepeat(10) C (n*2)\n").parse(); + LineCollector td = new LineCollector(parser); + assertEquals(2, td.getNames().size()); assertEquals(10, td.getLines().size()); @@ -89,7 +96,8 @@ public class ParserTest extends TestCase { } public void testForBits() throws IOException, ParserException { - Parser td = new Parser("A B C D \nrepeat(8) X bits(3,n)\n").parse(); + Parser parser = new Parser("A B C D \nrepeat(8) X bits(3,n)\n").parse(); + LineCollector td = new LineCollector(parser); assertEquals(4, td.getNames().size()); assertEquals(8, td.getLines().size()); @@ -102,19 +110,21 @@ public class ParserTest extends TestCase { } public void testComment() throws TestingDataException, IOException, ParserException { - Parser td = new Parser("#test\nA B\n1 1").parse(); + Parser parser = new Parser("#test\nA B\n1 1").parse(); + LineCollector td = new LineCollector(parser); assertEquals(2, td.getNames().size()); assertEquals(1, td.getLines().size()); } public void testHeader() throws TestingDataException, IOException, ParserException { - Parser td = new Parser("A B C D\n1 1 1 1").parse(); + Parser parser = new Parser("A B C D\n1 1 1 1").parse(); + LineCollector td = new LineCollector(parser); assertEquals(4, td.getNames().size()); assertEquals(1, td.getLines().size()); } public void testEmptyLines() throws TestingDataException, IOException, ParserException { - Parser td = new Parser("A_i B_i C_i-1 C_i S_i\n" + + Parser parser = new Parser("A_i B_i C_i-1 C_i S_i\n" + " 0 0 0 0 0\n" + " 0 0 1 0 1\n" + " 0 1 0 0 1\n\n" + @@ -123,6 +133,8 @@ public class ParserTest extends TestCase { " 1 0 1 1 0\n" + " 1 1 0 1 0\n" + " 1 1 1 1 1\n").parse(); + LineCollector td = new LineCollector(parser); + assertEquals(5, td.getNames().size()); assertEquals(8, td.getLines().size()); @@ -132,11 +144,11 @@ public class ParserTest extends TestCase { } public void testBUG1() throws IOException, ParserException { - Parser td = new Parser("C_i-1 A B C S\n" + + Parser parser = new Parser("C_i-1 A B C S\n" + "repeat(1<<16) 0 (n>>8) (n&255) ((n>>8)*(n&255)) 0").parse(); + LineCollector td = new LineCollector(parser); assertEquals(5, td.getNames().size()); assertEquals(1<<16, td.getLines().size()); - } } \ No newline at end of file