diff --git a/distribution/ReleaseNotes.txt b/distribution/ReleaseNotes.txt index 5d6480023..2f76f37b4 100644 --- a/distribution/ReleaseNotes.txt +++ b/distribution/ReleaseNotes.txt @@ -2,7 +2,9 @@ Release Notes HEAD, planned as v0.27 -- Added fixed point and floating point number formats +- Added fixed point and floating point number formats. +- Added a CSV truth table export and import. +- Added 74299 - Refactoring of the expression format setting. CAUTION: All the general settings are maybe lost at restart! To avoid this, open the .digital.cfg file and remove the diff --git a/src/main/dig/lib/DIL Chips/74xx/shift register/74299.dig b/src/main/dig/lib/DIL Chips/74xx/shift register/74299.dig new file mode 100644 index 000000000..135b8cff2 --- /dev/null +++ b/src/main/dig/lib/DIL Chips/74xx/shift register/74299.dig @@ -0,0 +1,1843 @@ + + + 1 + + + shapeType + DIL + + + pinCount + 20 + + + Description + 8-Input Universal Shift/Storage Register +with Common Parallel I/O Pins + + + Label + 74299 + + + lockedMode + true + + + Width + 5 + + + + + D_FF_AS + + + + + Ground + + + rotation + + + + + + + Driver + + + flipSelPos + true + + + + + + D_FF_AS + + + + + Ground + + + rotation + + + + + + + Driver + + + flipSelPos + true + + + + + + D_FF_AS + + + + + Ground + + + rotation + + + + + + + Driver + + + flipSelPos + true + + + + + + D_FF_AS + + + + + Ground + + + rotation + + + + + + + Driver + + + flipSelPos + true + + + + + + D_FF_AS + + + + + Ground + + + rotation + + + + + + + Driver + + + flipSelPos + true + + + + + + D_FF_AS + + + + + Ground + + + rotation + + + + + + + Driver + + + flipSelPos + true + + + + + + D_FF_AS + + + + + Ground + + + rotation + + + + + + + Driver + + + flipSelPos + true + + + + + + D_FF_AS + + + + + Ground + + + rotation + + + + + + + Driver + + + flipSelPos + true + + + + + + In + + + Label + DSR + + + pinNumber + 11 + + + + + + In + + + Label + S0 + + + pinNumber + 1 + + + + + + In + + + Label + S1 + + + pinNumber + 19 + + + + + + NAnd + + + rotation + + + + + + + Not + + + + + Not + + + + + In + + + Label + ~OE1 + + + pinNumber + 2 + + + InDefault + + + + + + + In + + + Label + ~OE2 + + + pinNumber + 3 + + + InDefault + + + + + + + Clock + + + Label + CP + + + pinNumber + 12 + + + + + + Out + + + Label + Q0 + + + pinNumber + 8 + + + + + + In + + + Label + ~MR + + + pinNumber + 9 + + + InDefault + + + + + + + Not + + + + + Out + + + Label + Q7 + + + pinNumber + 17 + + + + + + In + + + Label + DSL + + + pinNumber + 18 + + + + + + PowerSupply + + + + + In + + + Label + VCC + + + pinNumber + 20 + + + InDefault + + + + + + + In + + + Label + GND + + + pinNumber + 10 + + + + + + And + + + Inputs + 3 + + + + + + In + + + rotation + + + + Label + I/O0 + + + pinNumber + 7 + + + InDefault + + + + isHighZ + true + + + + + + In + + + rotation + + + + Label + I/O1 + + + pinNumber + 13 + + + InDefault + + + + isHighZ + true + + + + + + In + + + rotation + + + + Label + I/O2 + + + pinNumber + 6 + + + InDefault + + + + isHighZ + true + + + + + + In + + + rotation + + + + Label + I/O3 + + + pinNumber + 14 + + + InDefault + + + + isHighZ + true + + + + + + In + + + rotation + + + + Label + I/O4 + + + pinNumber + 5 + + + InDefault + + + + isHighZ + true + + + + + + In + + + rotation + + + + Label + I/O5 + + + pinNumber + 15 + + + InDefault + + + + isHighZ + true + + + + + + In + + + rotation + + + + Label + I/O6 + + + pinNumber + 4 + + + InDefault + + + + isHighZ + true + + + + + + In + + + rotation + + + + Label + I/O7 + + + pinNumber + 16 + + + InDefault + + + + isHighZ + true + + + + + + Testcase + + + Label + shift + + + Testdata + + CP S1 S0 ~OE1 ~OE2 DSL I/O7 I/O6 I/O5 I/O4 I/O3 I/O2 I/O1 I/O0 DSR I/O7_out I/O6_out I/O5_out I/O4_out I/O3_out I/O2_out I/O1_out I/O0_out +# left +c 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +c 1 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 1 z z z z z z z z 0 1 0 0 0 0 0 0 0 +c 1 0 0 0 0 z z z z z z z z 0 0 1 0 0 0 0 0 0 +c 1 0 0 0 0 z z z z z z z z 0 0 0 1 0 0 0 0 0 +c 1 0 0 0 0 z z z z z z z z 0 0 0 0 1 0 0 0 0 +c 1 0 0 0 0 z z z z z z z z 0 0 0 0 0 1 0 0 0 +c 1 0 0 0 0 z z z z z z z z 0 0 0 0 0 0 1 0 0 +c 1 0 0 0 0 z z z z z z z z 0 0 0 0 0 0 0 1 0 +c 1 0 0 0 0 z z z z z z z z 0 0 0 0 0 0 0 0 1 +c 1 0 0 0 0 z z z z z z z z 0 0 0 0 0 0 0 0 0 + +# right +c 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +c 0 1 1 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 z z z z z z z z 1 0 0 0 0 0 0 0 1 +c 0 1 0 0 0 z z z z z z z z 0 0 0 0 0 0 0 1 0 +c 0 1 0 0 0 z z z z z z z z 0 0 0 0 0 0 1 0 0 +c 0 1 0 0 0 z z z z z z z z 0 0 0 0 0 1 0 0 0 +c 0 1 0 0 0 z z z z z z z z 0 0 0 0 1 0 0 0 0 +c 0 1 0 0 0 z z z z z z z z 0 0 0 1 0 0 0 0 0 +c 0 1 0 0 0 z z z z z z z z 0 0 1 0 0 0 0 0 0 +c 0 1 0 0 0 z z z z z z z z 0 1 0 0 0 0 0 0 0 +c 0 1 0 0 0 z z z z z z z z 0 0 0 0 0 0 0 0 0 + + + + + + + + Testcase + + + Label + load + + + Testdata + + CP S1 S0 ~MR ~OE1 ~OE2 DSL I/O7 I/O6 I/O5 I/O4 I/O3 I/O2 I/O1 I/O0 DSR I/O7_out I/O6_out I/O5_out I/O4_out I/O3_out I/O2_out I/O1_out I/O0_out +# load +c 1 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 +0 0 0 1 1 1 0 z z z z z z z z 0 z z z z z z z z +0 0 0 1 0 0 0 z z z z z z z z 0 1 1 1 1 1 1 1 1 + +c 1 1 1 1 1 0 0 1 0 1 0 1 0 1 0 0 1 0 1 0 1 0 1 +0 0 0 1 1 1 0 z z z z z z z z 0 z z z z z z z z +0 0 0 1 0 0 0 z z z z z z z z 0 0 1 0 1 0 1 0 1 + +c 1 1 1 1 1 0 1 0 1 0 1 0 1 0 0 1 0 1 0 1 0 1 0 +0 0 0 1 1 1 0 z z z z z z z z 0 z z z z z z z z +0 0 0 1 0 0 0 z z z z z z z z 0 1 0 1 0 1 0 1 0 + +#clear +c 1 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 +0 0 0 1 0 0 0 z z z z z z z z 0 1 1 1 1 1 1 1 1 +0 0 0 0 0 0 0 z z z z z z z z 0 0 0 0 0 0 0 0 0 + + + + + + + + + Multiplexer + + + flipSelPos + true + + + Selector Bits + 2 + + + + + + Splitter + + + Input Splitting + 1,1 + + + Output Splitting + 2 + + + + + + Multiplexer + + + Selector Bits + 2 + + + + + + Multiplexer + + + Selector Bits + 2 + + + + + + Multiplexer + + + Selector Bits + 2 + + + + + + Multiplexer + + + Selector Bits + 2 + + + + + + Multiplexer + + + Selector Bits + 2 + + + + + + Multiplexer + + + Selector Bits + 2 + + + + + + Multiplexer + + + Selector Bits + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/de/neemann/digital/analyse/CSVImporter.java b/src/main/java/de/neemann/digital/analyse/CSVImporter.java new file mode 100644 index 000000000..d081ea51b --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/CSVImporter.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2021 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.analyse; + +import de.neemann.digital.analyse.expression.Variable; +import de.neemann.digital.analyse.quinemc.BoolTableByteArray; +import de.neemann.digital.lang.Lang; + +import java.io.*; +import java.util.ArrayList; + +/** + * Used to read a CSV file + */ +public final class CSVImporter { + + private CSVImporter() { + } + + /** + * Reads a CSV file + * + * @param file the file + * @return the truth table + * @throws IOException IOException + */ + public static TruthTable readCSV(File file) throws IOException { + return readCSV(new FileReader(file)); + } + + /** + * Reads a CSV file + * + * @param csv the string to read + * @return the truth table + * @throws IOException IOException + */ + public static TruthTable readCSV(String csv) throws IOException { + return readCSV(new StringReader(csv)); + } + + /** + * Reads a CSV file + * + * @param csv the reader + * @return the truth table + * @throws IOException IOException + */ + public static TruthTable readCSV(Reader csv) throws IOException { + BufferedReader r = new BufferedReader(csv); + TruthTable tt = readHeader(r); + + while (true) { + String line = r.readLine(); + if (line == null) + return tt; + line = line.trim(); + if (!line.isEmpty()) + parseLine(tt, line); + } + + } + + private static TruthTable readHeader(BufferedReader r) throws IOException { + String header; + do { + header = r.readLine(); + } while (header != null && header.length() == 0); + + if (header == null) + throw new IOException(Lang.get("err_csvNoHeaderFound")); + + ArrayList vars = new ArrayList<>(); + + TruthTable tt = null; + for (String ss : header.split(",")) { + String h = ss.trim(); + if (h.isEmpty()) + tt = new TruthTable(vars); + else { + if (tt == null) + vars.add(new Variable(h)); + else + tt.addResult(h, new BoolTableByteArray(1 << vars.size())); + + } + } + + if (tt == null || tt.getResultCount() == 0) + throw new IOException(Lang.get("err_csvNoOutputValuesFound")); + + return tt; + } + + private static void parseLine(TruthTable tt, String line) throws IOException { + int resNum = tt.getResultCount(); + int varNum = tt.getVars().size(); + int mask = 1 << (varNum - 1); + ArrayList dc = new ArrayList<>(); + int row = 0; + int rCol = 0; + Generator generator = null; + for (String ss : line.split(",")) { + String e = ss.trim().toLowerCase(); + if (e.isEmpty()) + generator = new Generator(row, dc); + else { + if (generator == null) { + if (mask == 0) + throw new IOException(Lang.get("err_csvToManyValues")); + if (e.equals("1")) + row |= mask; + else if (e.equals("x")) + dc.add(mask); + mask = mask >> 1; + } else { + if (rCol >= resNum) + throw new IOException(Lang.get("err_csvToManyValues")); + if (e.equals("1")) + generator.addCol(rCol); + rCol++; + } + } + } + + if (mask != 0) + throw new IOException(Lang.get("err_csvNotEnoughValues")); + + if (generator != null) + generator.applyTo(tt); + } + + private static final class Generator { + private final int row; + private final ArrayList dc; + private final ArrayList cols; + + private Generator(int row, ArrayList dc) { + this.row = row; + this.dc = dc; + cols = new ArrayList<>(); + } + + public void addCol(int rCol) { + cols.add(rCol); + } + + public void applyTo(TruthTable tt) { + if (cols.isEmpty()) + return; + + int vars = tt.getVars().size(); + + if (dc.isEmpty()) + for (int col : cols) + tt.setValue(row, vars + col, 1); + else { + int dcRows = 1 << dc.size(); + for (int i = 0; i < dcRows; i++) { + int r = row; + int bitMask = 1; + for (int orMask : dc) { + if ((i & bitMask) != 0) + r = r | orMask; + bitMask *= 2; + } + + for (int col : cols) + tt.setValue(r, vars + col, 1); + + } + } + } + } + +} diff --git a/src/main/java/de/neemann/digital/analyse/TruthTable.java b/src/main/java/de/neemann/digital/analyse/TruthTable.java index 842fe017b..1de93dd3f 100644 --- a/src/main/java/de/neemann/digital/analyse/TruthTable.java +++ b/src/main/java/de/neemann/digital/analyse/TruthTable.java @@ -14,8 +14,6 @@ import de.neemann.digital.analyse.expression.ExpressionException; import de.neemann.digital.analyse.expression.Variable; import de.neemann.digital.analyse.quinemc.BoolTable; import de.neemann.digital.analyse.quinemc.BoolTableByteArray; -import de.neemann.digital.analyse.quinemc.ThreeStateValue; -import de.neemann.digital.lang.Lang; import de.neemann.digital.undo.Copyable; import java.io.*; @@ -41,9 +39,13 @@ public class TruthTable implements Copyable { * @throws IOException IOException */ public static TruthTable readFromFile(File filename) throws IOException { - XStream xStream = getxStream(); - try (InputStream in = new FileInputStream(filename)) { - return (TruthTable) xStream.fromXML(in); + if (filename.getName().toLowerCase().endsWith(".csv")) + return CSVImporter.readCSV(filename); + else { + XStream xStream = getxStream(); + try (InputStream in = new FileInputStream(filename)) { + return (TruthTable) xStream.fromXML(in); + } } } @@ -61,45 +63,6 @@ public class TruthTable implements Copyable { } } - /** - * Save the table as hex file to be loaded in a ROM or LUT element. - * - * @param filename filename - * @throws IOException IOException - */ - public void saveHex(File filename) throws IOException { - if (results.size() > 63) - throw new IOException(Lang.get("err_tableHasToManyResultColumns")); - - try (Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename), StandardCharsets.UTF_8))) { - saveHex(out); - } - } - - /** - * Save the table as hex file to be loaded in a ROM or LUT element. - * - * @param writer the filename to use - * @throws IOException IOException - */ - public void saveHex(Writer writer) throws IOException { - writer.write("v2.0 raw\n"); - int count = results.get(0).getValues().size(); - for (int i = 0; i < count; i++) { - long val = 0; - long mask = 1; - for (Result r : results) { - ThreeStateValue v = r.getValues().get(i); - if (v == ThreeStateValue.one) - val |= mask; - mask *= 2; - } - writer.write(Long.toHexString(val)); - writer.write('\n'); - } - } - - private static XStream getxStream() { XStream xStream = new XStreamValid(); xStream.alias("truthTable", TruthTable.class); diff --git a/src/main/java/de/neemann/digital/analyse/format/TruthTableFormatterCSV.java b/src/main/java/de/neemann/digital/analyse/format/TruthTableFormatterCSV.java new file mode 100644 index 000000000..638645049 --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/format/TruthTableFormatterCSV.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.analyse.format; + +import de.neemann.digital.analyse.TruthTable; +import de.neemann.digital.analyse.expression.ExpressionException; +import de.neemann.digital.analyse.quinemc.ThreeStateValue; +import de.neemann.digital.core.Bits; + +/** + * Exports a table in LogicFriday format + */ +public class TruthTableFormatterCSV implements TruthTableFormatter { + + @Override + public String format(TruthTable truthTable) throws ExpressionException { + StringBuilder sb = new StringBuilder(); + for (String n : truthTable.getVarNames()) + sb.append(n).append(","); + for (String n : truthTable.getResultNames()) + sb.append(',').append(n); + sb.append('\n'); + + export(sb, truthTable); + + return sb.toString(); + } + + private void export(StringBuilder sb, TruthTable truthTable) { + int vars = truthTable.getVars().size(); + for (int r = 0; r < truthTable.getRows(); r++) { + long m = Bits.up(1, vars - 1); + for (int c = 0; c < vars; c++) { + if ((r & m) == 0) + sb.append('0'); + else + sb.append('1'); + sb.append(','); + m = m >> 1; + } + for (int c = 0; c < truthTable.getResultCount(); c++) { + ThreeStateValue v = truthTable.getResult(c).get(r); + sb.append(',').append(v.toString()); + } + sb.append('\n'); + } + } +} diff --git a/src/main/java/de/neemann/digital/analyse/format/TruthTableFormatterHex.java b/src/main/java/de/neemann/digital/analyse/format/TruthTableFormatterHex.java new file mode 100644 index 000000000..4ec68c028 --- /dev/null +++ b/src/main/java/de/neemann/digital/analyse/format/TruthTableFormatterHex.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016 Helmut Neemann + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.analyse.format; + +import de.neemann.digital.analyse.TruthTable; +import de.neemann.digital.analyse.expression.ExpressionException; +import de.neemann.digital.analyse.quinemc.ThreeStateValue; +import de.neemann.digital.lang.Lang; + +/** + * Formats a truth table + */ +public class TruthTableFormatterHex implements TruthTableFormatter { + + @Override + public String format(TruthTable truthTable) throws ExpressionException { + if (truthTable.getResultCount() > 63) + throw new ExpressionException(Lang.get("err_tableHasToManyResultColumns")); + + StringBuilder sb = new StringBuilder(); + sb.append("v2.0 raw\n"); + + int count = truthTable.getResult(0).size(); + for (int i = 0; i < count; i++) { + long val = 0; + long mask = 1; + for (int j = 0; j < truthTable.getResultCount(); j++) { + ThreeStateValue v = truthTable.getResult(j).get(i); + if (v == ThreeStateValue.one) + val |= mask; + mask *= 2; + } + sb.append(Long.toHexString(val)); + sb.append('\n'); + } + return sb.toString(); + } +} diff --git a/src/main/java/de/neemann/digital/core/Bits.java b/src/main/java/de/neemann/digital/core/Bits.java index c8dcb60db..7ab3e7ad0 100644 --- a/src/main/java/de/neemann/digital/core/Bits.java +++ b/src/main/java/de/neemann/digital/core/Bits.java @@ -145,10 +145,14 @@ public final class Bits { if (str.indexOf(':') >= 0) return decodeFixed(str); if (str.indexOf('.') > -1) { - if (str.endsWith("d") || str.endsWith("D")) - return Double.doubleToLongBits(Double.parseDouble(str.substring(0, str.length() - 1))); - else - return Float.floatToIntBits(Float.parseFloat(str)); + try { + if (str.endsWith("d") || str.endsWith("D")) + return Double.doubleToLongBits(Double.parseDouble(str.substring(0, str.length() - 1))); + else + return Float.floatToIntBits(Float.parseFloat(str)); + } catch (java.lang.NumberFormatException e) { + throw new NumberFormatException(str, 0); + } } int p = 0; diff --git a/src/main/java/de/neemann/digital/core/IntFormat.java b/src/main/java/de/neemann/digital/core/IntFormat.java index c1c33e87a..b12984c2f 100644 --- a/src/main/java/de/neemann/digital/core/IntFormat.java +++ b/src/main/java/de/neemann/digital/core/IntFormat.java @@ -499,9 +499,17 @@ public enum IntFormat { switch (inValue.getBits()) { case 32: - return Float.toString(Float.intBitsToFloat((int) inValue.getValue())); + float f = Float.intBitsToFloat((int) inValue.getValue()); + if (Float.isFinite(f)) + return Float.toString(f); + else + return HEX_FORMATTER.formatToEdit(inValue); case 64: - return Double.longBitsToDouble(inValue.getValue()) + "d"; + double d = Double.longBitsToDouble(inValue.getValue()); + if (Double.isFinite(d)) + return d + "d"; + else + return HEX_FORMATTER.formatToEdit(inValue); default: return HEX_FORMATTER.formatToEdit(inValue); } diff --git a/src/main/java/de/neemann/digital/gui/components/table/ExpressionListenerCSVCondensed.java b/src/main/java/de/neemann/digital/gui/components/table/ExpressionListenerCSVCondensed.java new file mode 100644 index 000000000..5d1b52dc1 --- /dev/null +++ b/src/main/java/de/neemann/digital/gui/components/table/ExpressionListenerCSVCondensed.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2021 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.gui.components.table; + +import de.neemann.digital.analyse.expression.*; +import de.neemann.digital.analyse.expression.format.FormatterException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + +/** + * Exports a CSV table containing only the prime implicants + */ +public class ExpressionListenerCSVCondensed implements ExpressionListener { + private final ArrayList results; + private final HashSet names; + private final VariableVisitor variables; + private StringBuilder str; + + /** + * Creates a new instance + */ + public ExpressionListenerCSVCondensed() { + results = new ArrayList<>(); + names = new HashSet<>(); + variables = new VariableVisitor(); + } + + @Override + public void resultFound(String name, Expression expression) throws FormatterException, ExpressionException { + if (!names.contains(name)) { + names.add(name); + results.add(new Result(name, expression, results.size())); + expression.traverse(variables); + } + } + + @Override + public void close() throws FormatterException, ExpressionException { + str = new StringBuilder(); + for (Variable var : variables.getVariables()) + str.append(var.getIdentifier()).append(","); + for (Result r : results) + str.append(",").append(r.name); + str.append("\n"); + + for (Result r : results) + r.createString(str, variables.getVariables(), results.size()); + } + + @Override + public String toString() { + return str.toString(); + } + + private static final class Result { + private final String name; + private final Expression expression; + private final int number; + + private Result(String name, Expression expression, int number) { + this.name = name; + this.expression = expression; + this.number = number; + } + + public void createString(StringBuilder sb, Collection variables, int results) throws ExpressionException { + if (expression instanceof Operation.Or) { + ArrayList o = ((Operation.Or) expression).getExpressions(); + for (Expression e : o) + addPrime(sb, e, variables, results); + } else + addPrime(sb, expression, variables, results); + } + + private void addPrime(StringBuilder sb, Expression and, Collection variables, int results) throws ExpressionException { + if (and instanceof Operation.And) + addAnd(sb, and, variables, results); + else if (and instanceof Variable) + addVar(sb, ((Variable) and).getIdentifier(), variables, results, false); + else if (and instanceof Not && ((Not) and).getExpression() instanceof Variable) + addVar(sb, ((Variable) (((Not) and).getExpression())).getIdentifier(), variables, results, true); + else + throw new ExpressionException("invalid expression"); + } + + private void addVar(StringBuilder sb, String identifier, Collection variables, int results, boolean invert) { + for (Variable var : variables) { + if (var.getIdentifier().endsWith(identifier)) { + if (invert) + sb.append("0,"); + else + sb.append("1,"); + } else + sb.append("X,"); + } + + for (int i = 0; i < results; i++) { + if (i == number) + sb.append(",1"); + else + sb.append(",0"); + } + sb.append('\n'); + } + + private void addAnd(StringBuilder sb, Expression and, Collection variables, int results) throws ExpressionException { + HashSet v = new HashSet<>(); + HashSet nv = new HashSet<>(); + if (and instanceof Operation.And) { + Operation.And a = (Operation.And) and; + for (Expression va : a.getExpressions()) { + Expression var = va; + HashSet map = v; + if (var instanceof Not) { + map = nv; + var = ((Not) var).getExpression(); + } + if (var instanceof Variable) + map.add(((Variable) var).getIdentifier()); + else + throw new ExpressionException("invalid expression"); + } + } else + throw new ExpressionException("invalid expression"); + + for (Variable var : variables) { + if (v.contains(var.getIdentifier())) + sb.append("1,"); + else if (nv.contains(var.getIdentifier())) + sb.append("0,"); + else + sb.append("X,"); + } + + for (int i = 0; i < results; i++) { + if (i == number) + sb.append(",1"); + else + sb.append(",0"); + } + sb.append('\n'); + } + } +} diff --git a/src/main/java/de/neemann/digital/gui/components/table/TableDialog.java b/src/main/java/de/neemann/digital/gui/components/table/TableDialog.java index bbeb58f97..ead2f6659 100644 --- a/src/main/java/de/neemann/digital/gui/components/table/TableDialog.java +++ b/src/main/java/de/neemann/digital/gui/components/table/TableDialog.java @@ -16,6 +16,8 @@ import de.neemann.digital.analyse.expression.Variable; import de.neemann.digital.analyse.expression.format.FormatterException; import de.neemann.digital.analyse.expression.modify.*; import de.neemann.digital.analyse.format.TruthTableFormatter; +import de.neemann.digital.analyse.format.TruthTableFormatterCSV; +import de.neemann.digital.analyse.format.TruthTableFormatterHex; import de.neemann.digital.analyse.format.TruthTableFormatterTestCase; import de.neemann.digital.analyse.quinemc.BoolTableByteArray; import de.neemann.digital.builder.ATF150x.ATFDevice; @@ -60,7 +62,9 @@ import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; +import java.io.FileWriter; import java.io.IOException; +import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -373,6 +377,7 @@ public class TableDialog extends JDialog { JFileChooser fc = new MyFileChooser(); if (TableDialog.this.filename != null) fc.setSelectedFile(SaveAsHelper.checkSuffix(TableDialog.this.filename, "tru")); + fc.setFileFilter(new FileNameExtensionFilter(Lang.get("msg_truthTableCSV"), "csv")); fc.setFileFilter(new FileNameExtensionFilter(Lang.get("msg_truthTable"), "tru")); if (fc.showOpenDialog(TableDialog.this) == JFileChooser.APPROVE_OPTION) { try { @@ -437,22 +442,29 @@ public class TableDialog extends JDialog { } }.setToolTip(Lang.get("menu_table_createFunctionFixture_tt")).createJMenuItem()); - fileMenu.add(new ToolTipAction(Lang.get("menu_table_exportHex")) { + JMenu export = new JMenu(Lang.get("menu_export")); + fileMenu.add(export); + export.add(new FileExportActionConfirm(Lang.get("menu_table_exportHex"), "hex") { @Override - public void actionPerformed(ActionEvent e) { - int res = JOptionPane.OK_OPTION; - if (undoManager.getActual().getVars().size() > 20) - res = JOptionPane.showConfirmDialog(TableDialog.this, Lang.get("msg_tableHasManyRowsConfirm")); - if (res == JOptionPane.OK_OPTION) { - JFileChooser fc = new MyFileChooser(); - if (TableDialog.this.filename != null) - fc.setSelectedFile(SaveAsHelper.checkSuffix(TableDialog.this.filename, "hex")); - new SaveAsHelper(TableDialog.this, fc, "hex") - .checkOverwrite(file -> undoManager.getActual().saveHex(file)); - } + protected String getString() throws ExpressionException { + return new TruthTableFormatterHex().format(undoManager.getActual()); } }.setToolTip(Lang.get("menu_table_exportHex_tt")).createJMenuItem()); - + export.add(new FileExportAction(Lang.get("menu_table_exportCSVCondensed"), "csv") { + @Override + protected String getString() throws FormatterException, ExpressionException { + ExpressionListenerCSVCondensed expressionListener = new ExpressionListenerCSVCondensed(); + lastGeneratedExpressions.replayTo(expressionListener); + expressionListener.close(); + return expressionListener.toString(); + } + }.setToolTip(Lang.get("menu_table_exportCSVCondensed_tt")).createJMenuItem()); + export.add(new FileExportActionConfirm(Lang.get("menu_table_exportCSV"), "csv") { + @Override + protected String getString() throws ExpressionException { + return new TruthTableFormatterCSV().format(undoManager.getActual()); + } + }.setToolTip(Lang.get("menu_table_exportCSV_tt")).createJMenuItem()); createJK = new JCheckBoxMenuItem(Lang.get("menu_table_JK")); createJK.addActionListener(e -> calculateExpressions()); @@ -998,4 +1010,55 @@ public class TableDialog extends JDialog { abstract ExpressionListener createExpressionListener() throws ExpressionException; } + + private abstract class FileExportAction extends ToolTipAction { + private final String suffix; + + private FileExportAction(String name, String suffix) { + super(name); + this.suffix = suffix; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (confirmExport()) { + JFileChooser fc = new MyFileChooser(); + if (TableDialog.this.filename != null) + fc.setSelectedFile(SaveAsHelper.checkSuffix(TableDialog.this.filename, suffix)); + new SaveAsHelper(TableDialog.this, fc, suffix) + .checkOverwrite(file -> { + try { + try (Writer w = new FileWriter(file)) { + w.write(getString()); + } + } catch (FormatterException | ExpressionException ex) { + throw new IOException(ex); + } + }); + } + } + + protected boolean confirmExport() { + return true; + } + + protected abstract String getString() throws FormatterException, ExpressionException; + } + + private abstract class FileExportActionConfirm extends FileExportAction { + + private FileExportActionConfirm(String name, String suffix) { + super(name, suffix); + } + + @Override + protected boolean confirmExport() { + int res = JOptionPane.OK_OPTION; + if (undoManager.getActual().getVars().size() > 20) + res = JOptionPane.showConfirmDialog(TableDialog.this, Lang.get("msg_tableHasManyRowsConfirm")); + return res == JOptionPane.OK_OPTION; + } + + } + } diff --git a/src/main/java/de/neemann/digital/testing/parser/Tokenizer.java b/src/main/java/de/neemann/digital/testing/parser/Tokenizer.java index b5d54a7b5..bcefb69ff 100644 --- a/src/main/java/de/neemann/digital/testing/parser/Tokenizer.java +++ b/src/main/java/de/neemann/digital/testing/parser/Tokenizer.java @@ -184,7 +184,7 @@ public class Tokenizer { boolean wasChar = true; do { c = readChar(); - if (isNumberChar(c) || isHexChar(c) || c == 'x' || c == 'X') { + if (isNumberChar(c) || isHexChar(c) || c == 'x' || c == 'X' || c == ':' || c == '.') { builder.append((char) c); } else { unreadChar(c); diff --git a/src/main/resources/lang/lang_de.xml b/src/main/resources/lang/lang_de.xml index 498c1aef9..f5a245c18 100644 --- a/src/main/resources/lang/lang_de.xml +++ b/src/main/resources/lang/lang_de.xml @@ -1216,6 +1216,11 @@ Sind evtl. die Namen der Variablen nicht eindeutig? Es ist kein Dateiname für das automatische Neuladen verfügbar! Virtuelles Signal {0} doppelt definiert in Zeile {1}! + Kein Header gefunden! + Keine Ausganswerte gefunden! + Nicht genug Werte in einer Zeile! + Zu viel Werte in einer Zeile! + Adress-Bits Anzahl der Adress-Bits, die verwendet werden. Daten-Bits @@ -1835,8 +1840,12 @@ Sind evtl. die Namen der Variablen nicht eindeutig? rein kombinatorisch ist! - Erzeuge HEX + HEX Die HEX-Datei kann in ein ROM oder eine LUT geladen werden. + CSV + Eine CSV Datei, welche die komplette Warheitstabelle enthält. + CSV, Primimplikanten + Eine CSV Datei, welche nur die Primimplikanten enthält. Neu Kombinatorisch Automat @@ -2059,6 +2068,7 @@ Stellen Sie sicher, dass der Flash-Vorgang abgeschlossen ist, bevor Sie diesen D {0} Testzeilen überprüft Testdatei Wahrheitstabelle + Comma Separated Values, CSV Fehler beim Import der SVG-Datei. Fehler beim Erzeugen der SVG-Datei. Statistik konnte nicht erzeugt werden. diff --git a/src/main/resources/lang/lang_en.xml b/src/main/resources/lang/lang_en.xml index bb5b2edaf..a718975e2 100644 --- a/src/main/resources/lang/lang_en.xml +++ b/src/main/resources/lang/lang_en.xml @@ -1203,6 +1203,11 @@ There is no file name available for the automatic reload! Virtual signal {0} declared twice in line {1}! + No header found! + No output values found! + Not enough values in one line! + Too many values in one line! + Address Bits Number of address bits used. Data Bits @@ -1822,8 +1827,12 @@ purely combinatorial! - Export HEX + HEX You can load the HEX file to a ROM or a LUT. + CSV + A CSV file containing the complete truth table. + CSV, prime implicants + A CSV file containing only the prime implicants. New Combinatorial Sequential @@ -2054,6 +2063,7 @@ {0} test rows passed File Tested Truth Table + Comma Separated Values, CSV Error while importing the SVG file. Error creating the SVG template. Statistics could not be created. diff --git a/src/test/java/de/neemann/digital/analyse/CSVImporterTest.java b/src/test/java/de/neemann/digital/analyse/CSVImporterTest.java new file mode 100644 index 000000000..caa5fa81d --- /dev/null +++ b/src/test/java/de/neemann/digital/analyse/CSVImporterTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2021 Helmut Neemann + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.analyse; + +import de.neemann.digital.analyse.expression.Variable; +import de.neemann.digital.analyse.quinemc.ThreeStateValue; +import de.neemann.digital.gui.components.table.ExpressionListenerCSVCondensed; +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.ArrayList; + +public class CSVImporterTest extends TestCase { + + public void testSimple() throws IOException { + TruthTable tt = CSVImporter.readCSV("A,B,,Y\n0,0,,0\n0,1,,0\n1,0,,0\n1,1,,1"); + assertNotNull(tt); + + ArrayList vars = tt.getVars(); + assertEquals(2, vars.size()); + assertEquals("A", vars.get(0).getIdentifier()); + assertEquals("B", vars.get(1).getIdentifier()); + assertEquals(1, tt.getResultCount()); + assertEquals("Y", tt.getResultName(0)); + + assertEquals(ThreeStateValue.zero, tt.getResult(0).get(0)); + assertEquals(ThreeStateValue.zero, tt.getResult(0).get(1)); + assertEquals(ThreeStateValue.zero, tt.getResult(0).get(2)); + assertEquals(ThreeStateValue.one, tt.getResult(0).get(3)); + } + + public void testDC() throws IOException { + TruthTable tt = CSVImporter.readCSV("A,B,,Y,X\nx,x,,1,0\n1,x,,0,1"); + assertNotNull(tt); + + ArrayList vars = tt.getVars(); + assertEquals(2, vars.size()); + assertEquals("A", vars.get(0).getIdentifier()); + assertEquals("B", vars.get(1).getIdentifier()); + assertEquals(2, tt.getResultCount()); + assertEquals("Y", tt.getResultName(0)); + assertEquals("X", tt.getResultName(1)); + + assertEquals(ThreeStateValue.one, tt.getResult(0).get(0)); + assertEquals(ThreeStateValue.one, tt.getResult(0).get(1)); + assertEquals(ThreeStateValue.one, tt.getResult(0).get(2)); + assertEquals(ThreeStateValue.one, tt.getResult(0).get(3)); + + assertEquals(ThreeStateValue.zero, tt.getResult(1).get(0)); + assertEquals(ThreeStateValue.zero, tt.getResult(1).get(1)); + assertEquals(ThreeStateValue.one, tt.getResult(1).get(2)); + assertEquals(ThreeStateValue.one, tt.getResult(1).get(3)); + } + + public void testDC2() throws IOException { + TruthTable tt = CSVImporter.readCSV("A,B,C,,Y\nx,1,x,,1\n"); + assertNotNull(tt); + + ArrayList vars = tt.getVars(); + assertEquals(3, vars.size()); + assertEquals("A", vars.get(0).getIdentifier()); + assertEquals("B", vars.get(1).getIdentifier()); + assertEquals("C", vars.get(2).getIdentifier()); + assertEquals(1, tt.getResultCount()); + assertEquals("Y", tt.getResultName(0)); + + assertEquals(ThreeStateValue.zero, tt.getResult(0).get(0)); + assertEquals(ThreeStateValue.zero, tt.getResult(0).get(1)); + assertEquals(ThreeStateValue.one, tt.getResult(0).get(2)); + assertEquals(ThreeStateValue.one, tt.getResult(0).get(3)); + assertEquals(ThreeStateValue.zero, tt.getResult(0).get(4)); + assertEquals(ThreeStateValue.zero, tt.getResult(0).get(5)); + assertEquals(ThreeStateValue.one, tt.getResult(0).get(6)); + assertEquals(ThreeStateValue.one, tt.getResult(0).get(7)); + } + + public void testMultiplePrimeUsages() throws IOException { + TruthTable tt = CSVImporter.readCSV("A,B,,Y,X\n1,1,,1,1\n1,0,,0,1\n0,1,,0,1"); + assertNotNull(tt); + + ArrayList vars = tt.getVars(); + assertEquals(2, vars.size()); + assertEquals("A", vars.get(0).getIdentifier()); + assertEquals("B", vars.get(1).getIdentifier()); + assertEquals(2, tt.getResultCount()); + assertEquals("Y", tt.getResultName(0)); + assertEquals("X", tt.getResultName(1)); + + assertEquals(ThreeStateValue.zero, tt.getResult(0).get(0)); + assertEquals(ThreeStateValue.zero, tt.getResult(0).get(1)); + assertEquals(ThreeStateValue.zero, tt.getResult(0).get(2)); + assertEquals(ThreeStateValue.one, tt.getResult(0).get(3)); + + assertEquals(ThreeStateValue.zero, tt.getResult(1).get(0)); + assertEquals(ThreeStateValue.one, tt.getResult(1).get(1)); + assertEquals(ThreeStateValue.one, tt.getResult(1).get(2)); + assertEquals(ThreeStateValue.one, tt.getResult(1).get(3)); + } + + public void testBug1() throws IOException { + TruthTable tt = CSVImporter.readCSV("A,B,,Y,X,Z\n1,1,,1,1,1\n1,0,,0,1,1\n0,1,,0,1,1"); + assertNotNull(tt); + + try { + CSVImporter.readCSV("A,B,,Y,X,Z\n1,1,,1,1,1\n1,0,,0,1,1,1\n0,1,,0,1,1"); + fail(); + } catch (IOException e) { + assertTrue(true); + } + } + + public void testIncomplete() { + try { + CSVImporter.readCSV("\n\n"); + fail(); + } catch (IOException e) { + assertTrue(true); + } + + try { + CSVImporter.readCSV("A,B,Y,X\n1,"); + fail(); + } catch (IOException e) { + assertTrue(true); + } + + try { + CSVImporter.readCSV("A,B,Y,,\n1,"); + fail(); + } catch (IOException e) { + assertTrue(true); + } + + try { + CSVImporter.readCSV("A,B,,Y,X\n1,"); + fail(); + } catch (IOException e) { + assertTrue(true); + } + + try { + CSVImporter.readCSV("A,B,,Y,X\n1,1,1"); + fail(); + } catch (IOException e) { + assertTrue(true); + } + + try { + CSVImporter.readCSV("A,B,,Y,X\n1,1,,1,1,1"); + fail(); + } catch (IOException e) { + assertTrue(true); + } + } + + + public void testAdder() throws Exception { + loopTest("A_0,A_1,B_0,B_1,,S_1,S_0,C\n" + + "1,0,1,0,,1,0,0\n" + + "1,1,1,1,,1,0,0\n" + + "0,0,X,1,,1,0,0\n" + + "0,1,X,0,,1,0,0\n" + + "X,0,0,1,,1,0,0\n" + + "X,1,0,0,,1,0,0\n" + + "0,X,1,X,,0,1,0\n" + + "1,X,0,X,,0,1,0\n" + + "1,X,1,1,,0,0,1\n" + + "1,1,1,X,,0,0,1\n" + + "X,1,X,1,,0,0,1\n"); + } + + public void testAndOr() throws Exception { + loopTest("A,B,,X,Y\n" + + "1,1,,1,0\n" + + "1,X,,0,1\n" + + "X,1,,0,1\n"); + } + + private void loopTest(String csv) throws Exception { + TruthTable tt = CSVImporter.readCSV(csv); + + ExpressionListenerCSVCondensed elCSV = new ExpressionListenerCSVCondensed(); + for (int i = 0; i < tt.getResultCount(); i++) { + MinimizerInterface mi = new MinimizerQuineMcCluskey(); + mi.minimize(tt.getVars(), tt.getResult(i), tt.getResultName(i), elCSV); + } + elCSV.close(); + + assertEquals(csv, elCSV.toString()); + + } +} \ No newline at end of file diff --git a/src/test/java/de/neemann/digital/analyse/TruthTableTest.java b/src/test/java/de/neemann/digital/analyse/TruthTableTest.java index b844baef1..9026ba3b6 100644 --- a/src/test/java/de/neemann/digital/analyse/TruthTableTest.java +++ b/src/test/java/de/neemann/digital/analyse/TruthTableTest.java @@ -38,52 +38,4 @@ public class TruthTableTest extends TestCase { } } - public void testHexExportSingle() throws Exception { - ArrayList vars = Variable.vars(3); - TruthTable t = new TruthTable(vars).addResult(); - BoolTableByteArray result = (BoolTableByteArray) t.getResult(0); - for (int i = 0; i < t.getRows(); i++) { - result.set(i, i % 2); - } - StringWriter w = new StringWriter(); - t.saveHex(w); - w.close(); - - assertEquals("v2.0 raw\n" + - "0\n" + - "1\n" + - "0\n" + - "1\n" + - "0\n" + - "1\n" + - "0\n" + - "1\n", w.toString()); - } - - public void testHexExportTwo() throws Exception { - ArrayList vars = Variable.vars(3); - TruthTable t = new TruthTable(vars).addResult(); - BoolTableByteArray result = (BoolTableByteArray) t.getResult(0); - for (int i = 0; i < t.getRows(); i++) { - result.set(i, i % 2); - } - t.addResult(); - result = (BoolTableByteArray) t.getResult(1); - for (int i = 0; i < t.getRows(); i++) { - result.set(i, (i + 1) % 2); - } - StringWriter w = new StringWriter(); - t.saveHex(w); - w.close(); - - assertEquals("v2.0 raw\n" + - "2\n" + - "1\n" + - "2\n" + - "1\n" + - "2\n" + - "1\n" + - "2\n" + - "1\n", w.toString()); - } } diff --git a/src/test/java/de/neemann/digital/analyse/format/TruthTableFormatterCSVTest.java b/src/test/java/de/neemann/digital/analyse/format/TruthTableFormatterCSVTest.java new file mode 100644 index 000000000..27005b48d --- /dev/null +++ b/src/test/java/de/neemann/digital/analyse/format/TruthTableFormatterCSVTest.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 Helmut Neemann + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.analyse.format; + +import de.neemann.digital.analyse.TruthTable; +import de.neemann.digital.analyse.expression.ExpressionException; +import de.neemann.digital.analyse.quinemc.BoolTableByteArray; +import junit.framework.TestCase; + +public class TruthTableFormatterCSVTest extends TestCase { + + public void testSimple() throws ExpressionException { + TruthTable tt = new TruthTable(2); + tt.addResult("X", new BoolTableByteArray(new byte[]{0, 1, 1, 1})); + tt.addResult("Y", new BoolTableByteArray(new byte[]{0, 0, 0, 1})); + String res = new TruthTableFormatterCSV().format(tt); + + assertEquals("A,B,,X,Y\n" + + "0,0,,0,0\n" + + "0,1,,1,0\n" + + "1,0,,1,0\n" + + "1,1,,1,1\n", res); + } +} \ No newline at end of file diff --git a/src/test/java/de/neemann/digital/analyse/format/TruthTableFormatterHexTest.java b/src/test/java/de/neemann/digital/analyse/format/TruthTableFormatterHexTest.java new file mode 100644 index 000000000..f4afb40f6 --- /dev/null +++ b/src/test/java/de/neemann/digital/analyse/format/TruthTableFormatterHexTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 Helmut Neemann + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.analyse.format; + +import de.neemann.digital.analyse.TruthTable; +import de.neemann.digital.analyse.expression.Variable; +import de.neemann.digital.analyse.quinemc.BoolTableByteArray; +import junit.framework.TestCase; + +import java.io.StringWriter; +import java.util.ArrayList; + +public class TruthTableFormatterHexTest extends TestCase { + + public void testHexExportSingle() throws Exception { + ArrayList vars = Variable.vars(3); + TruthTable t = new TruthTable(vars).addResult(); + BoolTableByteArray result = (BoolTableByteArray) t.getResult(0); + for (int i = 0; i < t.getRows(); i++) { + result.set(i, i % 2); + } + String hex = new TruthTableFormatterHex().format(t); + + assertEquals("v2.0 raw\n" + + "0\n" + + "1\n" + + "0\n" + + "1\n" + + "0\n" + + "1\n" + + "0\n" + + "1\n", hex); + } + + public void testHexExportTwo() throws Exception { + ArrayList vars = Variable.vars(3); + TruthTable t = new TruthTable(vars).addResult(); + BoolTableByteArray result = (BoolTableByteArray) t.getResult(0); + for (int i = 0; i < t.getRows(); i++) { + result.set(i, i % 2); + } + t.addResult(); + result = (BoolTableByteArray) t.getResult(1); + for (int i = 0; i < t.getRows(); i++) { + result.set(i, (i + 1) % 2); + } + + String hex = new TruthTableFormatterHex().format(t); + + assertEquals("v2.0 raw\n" + + "2\n" + + "1\n" + + "2\n" + + "1\n" + + "2\n" + + "1\n" + + "2\n" + + "1\n", hex); + } + +} \ No newline at end of file diff --git a/src/test/java/de/neemann/digital/gui/components/table/ExpressionListenerCSVCondensedTest.java b/src/test/java/de/neemann/digital/gui/components/table/ExpressionListenerCSVCondensedTest.java new file mode 100644 index 000000000..4fcc8d276 --- /dev/null +++ b/src/test/java/de/neemann/digital/gui/components/table/ExpressionListenerCSVCondensedTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2021 Helmut Neemann + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.gui.components.table; + +import de.neemann.digital.analyse.CSVImporter; +import de.neemann.digital.analyse.TruthTable; +import de.neemann.digital.analyse.expression.*; +import de.neemann.digital.analyse.expression.format.FormatterException; +import de.neemann.digital.analyse.quinemc.ThreeStateValue; +import junit.framework.TestCase; + +import java.io.IOException; +import java.util.ArrayList; + +import static de.neemann.digital.analyse.expression.Not.not; +import static de.neemann.digital.analyse.expression.Operation.and; +import static de.neemann.digital.analyse.expression.Operation.or; + +public class ExpressionListenerCSVCondensedTest extends TestCase { + Variable a = new Variable("A"); + Variable b = new Variable("B"); + Variable c = new Variable("C"); + + public void testSimple() throws FormatterException, ExpressionException, IOException { + Expression expression = or(and(a, b), c); + + assertEquals("A,B,C,,Y\n" + + "1,1,X,,1\n" + + "X,X,1,,1\n", create(expression)); + } + + public void testNot() throws FormatterException, ExpressionException, IOException { + Expression expression = or(and(a, not(b)), not(c)); + + assertEquals("A,B,C,,Y\n" + + "1,0,X,,1\n" + + "X,X,0,,1\n", create(expression)); + } + + public void testTwo() throws FormatterException, ExpressionException, IOException { + Expression e1 = new NamedExpression("Y", or(and(a, b), c)); + Expression e2 = new NamedExpression("X", a); + + assertEquals("A,B,C,,Y,X\n" + + "1,1,X,,1,0\n" + + "X,X,1,,1,0\n" + + "1,X,X,,0,1\n", create(e1, e2)); + } + + public void testXor() throws FormatterException, ExpressionException, IOException { + Expression expression = or( + and(not(a), not(b), c), + and(not(a), b, not(c)), + and(a, not(b), not(c)), + and(a, b, c) + ); + + assertEquals("A,B,C,,Y\n" + + "0,0,1,,1\n" + + "0,1,0,,1\n" + + "1,0,0,,1\n" + + "1,1,1,,1\n", create(expression)); + } + + private String create(Expression... expressions) throws FormatterException, ExpressionException, IOException { + ExpressionListenerCSVCondensed el = new ExpressionListenerCSVCondensed(); + + for (Expression e : expressions) { + String name = "Y"; + if (e instanceof NamedExpression) { + NamedExpression ne = (NamedExpression) e; + name = ne.getName(); + e = ne.getExpression(); + } + el.resultFound(name, e); + } + el.close(); + String s = el.toString(); + loopCheck(s, expressions); + return s; + } + + private void loopCheck(String csv, Expression[] expressions) throws IOException, ExpressionException { + TruthTable tt = CSVImporter.readCSV(csv); + for (int e = 0; e < expressions.length; e++) { + ArrayList vars = tt.getVars(); + int count = 1 << vars.size(); + ContextFiller cf = new ContextFiller(vars); + for (int i = 0; i < count; i++) { + cf.setContextTo(i); + boolean expected = expressions[e].calculate(cf); + ThreeStateValue found = tt.getResult(e).get(i); + assertEquals(ThreeStateValue.value(expected), found); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/de/neemann/digital/integration/TestExamples.java b/src/test/java/de/neemann/digital/integration/TestExamples.java index a54fcd83e..d2f91c110 100644 --- a/src/test/java/de/neemann/digital/integration/TestExamples.java +++ b/src/test/java/de/neemann/digital/integration/TestExamples.java @@ -40,8 +40,8 @@ public class TestExamples extends TestCase { */ public void testDistExamples() throws Exception { File examples = new File(Resources.getRoot().getParentFile().getParentFile(), "/main/dig"); - assertEquals(315, new FileScanner(this::check).scan(examples)); - assertEquals(500, testCasesInFiles); + assertEquals(316, new FileScanner(this::check).scan(examples)); + assertEquals(502, testCasesInFiles); } /** diff --git a/src/test/java/de/neemann/digital/integration/TestInGUI.java b/src/test/java/de/neemann/digital/integration/TestInGUI.java index 84ddc667f..bbe55d2c1 100644 --- a/src/test/java/de/neemann/digital/integration/TestInGUI.java +++ b/src/test/java/de/neemann/digital/integration/TestInGUI.java @@ -1220,14 +1220,13 @@ public class TestInGUI extends TestCase { xMin -= loc.x + SIZE * 5; yMin -= loc.y + SIZE * 2; + boolean firstWire = true; for (Wire w : circuit.getWires()) { - guiTester.mouseClickNow(w.p1.x - xMin, w.p1.y - yMin, InputEvent.BUTTON1_DOWN_MASK); - if (w.p1.x != w.p2.x && w.p1.y != w.p2.y) - guiTester.typeNow("typed d"); - - guiTester.mouseClickNow(w.p2.x - xMin, w.p2.y - yMin, InputEvent.BUTTON1_DOWN_MASK); - Thread.sleep(50); - guiTester.mouseClickNow(w.p2.x - xMin, w.p2.y - yMin, InputEvent.BUTTON3_DOWN_MASK); + if (firstWire) { // Draw first wire twice! Sometimes the first wire is lost and i can't figure out why! + firstWire = false; // This is a dirty hack! + drawWire(guiTester, xMin, yMin, w); + } + drawWire(guiTester, xMin, yMin, w); } for (VisualElement v : circuit.getElements()) { @@ -1240,6 +1239,16 @@ public class TestInGUI extends TestCase { Thread.sleep(400); } } + + private void drawWire(GuiTester guiTester, int xMin, int yMin, Wire w) throws InterruptedException { + guiTester.mouseClickNow(w.p1.x - xMin, w.p1.y - yMin, InputEvent.BUTTON1_DOWN_MASK); + if (w.p1.x != w.p2.x && w.p1.y != w.p2.y) + guiTester.typeNow("typed d"); + + guiTester.mouseClickNow(w.p2.x - xMin, w.p2.y - yMin, InputEvent.BUTTON1_DOWN_MASK); + Thread.sleep(50); + guiTester.mouseClickNow(w.p2.x - xMin, w.p2.y - yMin, InputEvent.BUTTON3_DOWN_MASK); + } } private static Point getCircuitPos(Main main) { 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 eae8e8a7f..ae471c271 100644 --- a/src/test/java/de/neemann/digital/testing/parser/ParserTest.java +++ b/src/test/java/de/neemann/digital/testing/parser/ParserTest.java @@ -50,21 +50,27 @@ public class ParserTest extends TestCase { } public void testHex() throws IOException, ParserException { - Parser parser = new Parser("A B\n0 0xff").parse(); - LineCollector td = new LineCollector(parser); - assertEquals(2, td.getNames().size()); - - assertEquals(1, td.getLines().size()); - - assertEquals(0, td.getLines().get(0).getValue(0).getValue()); - assertEquals(Value.Type.NORMAL, td.getLines().get(0).getValue(0).getType()); - - assertEquals(255, td.getLines().get(0).getValue(1).getValue()); - assertEquals(Value.Type.NORMAL, td.getLines().get(0).getValue(1).getType()); + checkValueParser("A B\n0 0xff", 255); } public void testBin() throws IOException, ParserException { - Parser parser = new Parser("A B\n0 0b11111111").parse(); + checkValueParser("A B\n0 0b11111111", 255); + } + + public void testFixed() throws IOException, ParserException { + checkValueParser("A B\n0 0.5:4", 8); + } + + public void testFloat() throws IOException, ParserException { + checkValueParser("A B\n0 0.5", Float.floatToIntBits(0.5f)); + } + + public void testDouble() throws IOException, ParserException { + checkValueParser("A B\n0 0.5d", Double.doubleToLongBits(0.5)); + } + + void checkValueParser(String s, long val) throws IOException, ParserException { + Parser parser = new Parser(s).parse(); LineCollector td = new LineCollector(parser); assertEquals(2, td.getNames().size()); @@ -73,7 +79,7 @@ public class ParserTest extends TestCase { assertEquals(0, td.getLines().get(0).getValue(0).getValue()); assertEquals(Value.Type.NORMAL, td.getLines().get(0).getValue(0).getType()); - assertEquals(255, td.getLines().get(0).getValue(1).getValue()); + assertEquals(val, td.getLines().get(0).getValue(1).getValue()); assertEquals(Value.Type.NORMAL, td.getLines().get(0).getValue(1).getType()); }