Ver código fonte

implementing NOP, PORT and WAIT

with tests
Wilfried Klaas 6 anos atrás
pai
commit
d4e8c39e24

+ 2 - 0
.gitignore

@@ -1 +1,3 @@
 /target/
+
+SimpleServo\.hex

+ 3 - 2
SimpleServo.tps

@@ -1,12 +1,13 @@
 :loop
 ; Zeilen kommentar
+NOP
 PORT 5
-WAIT 200
+WAIT 200ms
 /*
 block Kommentar
 */
 PORT 0x0F
-WAIT 200  ; inline Kommentar
+WAIT 200ms  ; inline Kommentar
 NOP
 RJMP :loop
 JMP 0x08 ;this will cause an syntax error

+ 1 - 1
src/main/java/de/mcs/tools/IntelHex.java

@@ -34,7 +34,7 @@ public class IntelHex {
 
   private int byteCount;
 
-  IntelHex() {
+  public IntelHex() {
     byteCount = 8;
   }
 

+ 13 - 0
src/main/java/de/mcs/tools/sps/SPSAssembler.java

@@ -23,6 +23,7 @@ package de.mcs.tools.sps;
 
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
@@ -35,6 +36,7 @@ import java.util.Map.Entry;
 
 import org.apache.commons.lang3.StringUtils;
 
+import de.mcs.tools.IntelHex;
 import de.mcs.tools.sps.exceptions.SyntaxError;
 import de.mcs.tools.sps.mnemonic.JMP;
 import de.mcs.tools.sps.mnemonic.Mnemonic;
@@ -116,6 +118,17 @@ public class SPSAssembler {
     for (Entry<String, Integer> entry : labels.entrySet()) {
       System.out.printf("%s: %03d\r\n", entry.getKey(), entry.getValue());
     }
+
+    IntelHex intelHex = new IntelHex();
+    byte[] data = new byte[mnemonics.size()];
+    int i = 0;
+    for (Mnemonic mnemonic : mnemonics) {
+      data[i] = (byte) mnemonic.getByte();
+      i++;
+    }
+    FileOutputStream output = new FileOutputStream(destination);
+    intelHex.writeHexStream(output, data);
+    output.close();
   }
 
   private static void parseLine(String line) throws SyntaxError {

+ 38 - 0
src/main/java/de/mcs/tools/sps/exceptions/IllegalArgument.java

@@ -0,0 +1,38 @@
+/**
+ * MCS Media Computer Software
+ * Copyright 2018 by Wilfried Klaas
+ * Project: SPSEmulator
+ * File: SyntaxError.java
+ * EMail: W.Klaas@gmx.de
+ * Created: 25.11.2018 wklaa_000
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ */
+package de.mcs.tools.sps.exceptions;
+
+/**
+ * @author wklaa_000
+ *
+ */
+public class IllegalArgument extends SyntaxError {
+
+  public IllegalArgument(String message) {
+    super(message);
+  }
+
+  public IllegalArgument(int lineNumber, String message) {
+    super(String.format("Illegal argument in line %d. %s", lineNumber, message));
+  }
+
+}

+ 28 - 6
src/main/java/de/mcs/tools/sps/mnemonic/AbstractMnemonic.java

@@ -23,6 +23,7 @@ package de.mcs.tools.sps.mnemonic;
 
 import org.apache.commons.lang3.StringUtils;
 
+import de.mcs.tools.sps.exceptions.IllegalArgument;
 import de.mcs.tools.sps.exceptions.SyntaxError;
 
 /**
@@ -48,17 +49,21 @@ public abstract class AbstractMnemonic implements Mnemonic {
   AbstractMnemonic(String line) throws SyntaxError {
     this.srcLine = line.trim();
     this.name = srcLine;
-    if (srcLine.indexOf(" ") >= 0) {
-      String[] values = srcLine.split(" ");
-      this.name = values[0].toUpperCase();
-      if (values.length > 1) {
-        this.argument = values[1];
-      }
+    int pos = srcLine.indexOf(" ");
+    if (pos >= 0) {
+      this.name = srcLine.substring(0, pos).toUpperCase();
+      this.argument = srcLine.substring(pos).trim();
     }
   }
 
+  /**
+   * default implementation for checking no argument mnemonics
+   */
   @Override
   public void checkArgument() throws SyntaxError {
+    if (StringUtils.isNotEmpty(argument)) {
+      throw new SyntaxError(getLineNumber(), String.format("%s has no argument.", this.getClass().getSimpleName()));
+    }
   };
 
   @Override
@@ -82,6 +87,22 @@ public abstract class AbstractMnemonic implements Mnemonic {
     return argument;
   }
 
+  public int getArgumentAsNumber() throws IllegalArgument {
+    int value = 0;
+    try {
+      String argument = getArgument().toLowerCase();
+      if (argument.startsWith("0b")) {
+        value = Integer.parseInt(argument.substring(2), 2);
+      } else {
+        value = Integer.decode(argument);
+      }
+    } catch (Exception e) {
+      throw new IllegalArgument(getLineNumber(),
+          String.format("argument %s is not valid for %s.", getArgument(), this.getClass().getSimpleName()));
+    }
+    return value;
+  };
+
   public abstract int getByte();
 
   @Override
@@ -89,6 +110,7 @@ public abstract class AbstractMnemonic implements Mnemonic {
     return false;
   }
 
+  @Override
   public int getLineNumber() {
     return lineNumber;
   }

+ 2 - 0
src/main/java/de/mcs/tools/sps/mnemonic/Mnemonic.java

@@ -18,4 +18,6 @@ public interface Mnemonic {
 
   void checkArgument() throws SyntaxError;
 
+  int getLineNumber();
+
 }

+ 0 - 6
src/main/java/de/mcs/tools/sps/mnemonic/MnemonicFactory.java

@@ -55,18 +55,12 @@ public class MnemonicFactory {
     } catch (InstantiationException e) {
       e.printStackTrace();
     } catch (IllegalAccessException e) {
-      // TODO Auto-generated catch block
       e.printStackTrace();
     } catch (IllegalArgumentException e) {
-      // TODO Auto-generated catch block
       e.printStackTrace();
     } catch (InvocationTargetException e) {
-      // TODO Auto-generated catch block
       e.printStackTrace();
     }
-    // if (line.startsWith("NOP")) {
-    // return new NOP(line);
-    // }
     return null;
   }
 

+ 2 - 0
src/main/java/de/mcs/tools/sps/mnemonic/NOP.java

@@ -24,6 +24,8 @@ package de.mcs.tools.sps.mnemonic;
 import de.mcs.tools.sps.exceptions.SyntaxError;
 
 /**
+ * NOP: No operation, nothing to do here.
+ * 
  * @author wklaa_000
  *
  */

+ 23 - 1
src/main/java/de/mcs/tools/sps/mnemonic/PORT.java

@@ -21,22 +21,44 @@
  */
 package de.mcs.tools.sps.mnemonic;
 
+import org.apache.commons.lang3.StringUtils;
+
+import de.mcs.tools.sps.exceptions.IllegalArgument;
 import de.mcs.tools.sps.exceptions.SyntaxError;
 
 /**
+ * PORT: output to the digital port. Argument should be in range 0..15.
+ * 
  * @author wklaa_000
  *
  */
 public class PORT extends AbstractMnemonic implements Mnemonic {
 
+  byte output;
+
   public PORT(String line) throws SyntaxError {
     super(line);
+    output = 0;
+  }
+
+  @Override
+  public void checkArgument() throws SyntaxError {
+    if (StringUtils.isEmpty(getArgument())) {
+      throw new SyntaxError(getLineNumber(),
+          String.format("missing argument for %s.", this.getClass().getSimpleName()));
+    }
+    int value = getArgumentAsNumber();
+    if ((value < 0) || (value > 15)) {
+      throw new IllegalArgument(getLineNumber(),
+          String.format("argument %s is not in range 0..15 for %s.", getArgument(), this.getClass().getSimpleName()));
+    }
+    output = (byte) (value & 0xFF);
   }
 
   @Override
   public int getByte() {
     // TODO modifier x (0..15)
-    return 0x10;
+    return 0x10 + output;
   }
 
 }

+ 64 - 1
src/main/java/de/mcs/tools/sps/mnemonic/WAIT.java

@@ -21,6 +21,9 @@
  */
 package de.mcs.tools.sps.mnemonic;
 
+import org.apache.commons.lang3.StringUtils;
+
+import de.mcs.tools.sps.exceptions.IllegalArgument;
 import de.mcs.tools.sps.exceptions.SyntaxError;
 
 /**
@@ -29,14 +32,74 @@ import de.mcs.tools.sps.exceptions.SyntaxError;
  */
 public class WAIT extends AbstractMnemonic implements Mnemonic {
 
+  private enum TIME_VALUE {
+    TIME_1MS("1ms", 0x00), TIME_2MS("2ms", 0x01), TIME_5MS("5ms", 0x02), TIME_10MS("10ms", 0x03), TIME_20MS("20ms",
+        0x04), TIME_50MS("50ms", 0x05), TIME_100MS("100ms", 0x06), TIME_200MS("200ms", 0x07), TIME_500MS("500ms",
+            0x08), TIME_1S("1s", 0x09), TIME_2S("2s", 0x0a), TIME_5S("5s",
+                0x0b), TIME_10S("10s", 0x0c), TIME_20S("20s", 0x0d), TIME_30S("30s", 0x0e), TIME_60S("60s", 0x0f);
+
+    String name;
+    int value;
+
+    TIME_VALUE(String name, int value) {
+      this.name = name;
+      this.value = value;
+    }
+
+  };
+
+  byte output;
+
   public WAIT(String line) throws SyntaxError {
     super(line);
+    output = 0;
+  }
+
+  @Override
+  public void checkArgument() throws SyntaxError {
+    if (StringUtils.isEmpty(getArgument())) {
+      throw new SyntaxError(getLineNumber(),
+          String.format("missing argument for %s.", this.getClass().getSimpleName()));
+    }
+    int value = 0;
+    String argument = getArgument();
+    if (isTimeArgument(argument)) {
+      value = getTime(argument);
+      if (value < 0) {
+        throw new IllegalArgument(getLineNumber(),
+            String.format("argument %s is not in enum for %s.", getArgument(), this.getClass().getSimpleName()));
+      }
+    } else {
+      value = getArgumentAsNumber();
+      if ((value < 0) || (value > 15)) {
+        throw new IllegalArgument(getLineNumber(),
+            String.format("argument %s is not in range 0..15 for %s.", getArgument(), this.getClass().getSimpleName()));
+      }
+    }
+    output = (byte) (value & 0xFF);
+  }
+
+  private int getTime(String argument) throws IllegalArgument {
+    try {
+      TIME_VALUE timeValue = TIME_VALUE.valueOf(String.format("TIME_%S", argument.trim()));
+      if (timeValue != null) {
+        return timeValue.value;
+      }
+    } catch (IllegalArgumentException e) {
+      throw new IllegalArgument(getLineNumber(),
+          String.format("argument %s is not in enum for %s.", getArgument(), this.getClass().getSimpleName()));
+    }
+    return -1;
+  }
+
+  private boolean isTimeArgument(String argument) {
+    return argument.toLowerCase().indexOf("s") > 0;
   }
 
   @Override
   public int getByte() {
     // TODO modifier x (0..15)
-    return 0x20;
+    return 0x20 + output;
   }
 
 }

+ 54 - 0
src/test/java/de/mcs/tools/sps/mnemonic/TestNOP.java

@@ -0,0 +1,54 @@
+package de.mcs.tools.sps.mnemonic;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import de.mcs.tools.sps.exceptions.SyntaxError;
+
+class TestNOP {
+
+  @Test
+  void testNOP() throws SyntaxError {
+    NOP nop = new NOP("NOP");
+    assertEquals(0x00, nop.getByte());
+
+    Assertions.assertThrows(SyntaxError.class, () -> {
+      NOP nop1 = new NOP("NOP akfhaskh");
+      nop1.checkArgument();
+    });
+  }
+
+  @Test
+  void testNOPFactory() throws SyntaxError {
+    Mnemonic mnemonic = MnemonicFactory.getMnemonic("NOP", 0);
+    assertEquals(NOP.class, mnemonic.getClass());
+
+    Assertions.assertThrows(SyntaxError.class, () -> {
+      MnemonicFactory.getMnemonic("NOP kdfhkalsfh", 0);
+    });
+
+    Assertions.assertThrows(SyntaxError.class, () -> {
+      MnemonicFactory.getMnemonic("NOP 0x12", 0);
+    });
+
+    Assertions.assertThrows(SyntaxError.class, () -> {
+      MnemonicFactory.getMnemonic("NOP 12", 0);
+    });
+
+    Assertions.assertThrows(SyntaxError.class, () -> {
+      MnemonicFactory.getMnemonic("NOP 0b01001010", 0);
+    });
+
+    mnemonic = MnemonicFactory.getMnemonic("Nop", 2);
+    assertEquals(NOP.class, mnemonic.getClass());
+
+    mnemonic = MnemonicFactory.getMnemonic("nop", 3);
+    assertEquals(NOP.class, mnemonic.getClass());
+    assertEquals(3, mnemonic.getLineNumber());
+
+    mnemonic = MnemonicFactory.getMnemonic("nop", 4);
+    assertEquals(NOP.class, mnemonic.getClass());
+  }
+}

+ 59 - 0
src/test/java/de/mcs/tools/sps/mnemonic/TestPORT.java

@@ -0,0 +1,59 @@
+package de.mcs.tools.sps.mnemonic;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import de.mcs.tools.sps.exceptions.IllegalArgument;
+import de.mcs.tools.sps.exceptions.SyntaxError;
+
+class TestPORT {
+
+  private final String MNEMONIC = "PORT";
+
+  @Test
+  void testNOP() throws SyntaxError {
+    PORT port = new PORT(MNEMONIC + " 0x00");
+    port.checkArgument();
+    assertEquals(0x10, port.getByte());
+
+    port = new PORT(MNEMONIC + " 0x01");
+    port.checkArgument();
+    assertEquals(0x11, port.getByte());
+
+    port = new PORT(MNEMONIC + " 0x0f");
+    port.checkArgument();
+    assertEquals(0x1f, port.getByte());
+
+    Assertions.assertThrows(SyntaxError.class, () -> {
+      PORT port1 = new PORT(MNEMONIC);
+      port1.checkArgument();
+    });
+
+    Assertions.assertThrows(IllegalArgument.class, () -> {
+      PORT port1 = new PORT(MNEMONIC + " 0x10");
+      port1.checkArgument();
+    });
+  }
+
+  @Test
+  void testNOPFactory() throws SyntaxError {
+    Mnemonic mnemonic = MnemonicFactory.getMnemonic(MNEMONIC + " 0x00", 0);
+    assertEquals(PORT.class, mnemonic.getClass());
+
+    Assertions.assertThrows(SyntaxError.class, () -> {
+      MnemonicFactory.getMnemonic(MNEMONIC, 0);
+    });
+
+    mnemonic = MnemonicFactory.getMnemonic(MNEMONIC + " 0x02", 0);
+    assertEquals(0x12, mnemonic.getByte());
+
+    mnemonic = MnemonicFactory.getMnemonic(MNEMONIC + " 12", 0);
+    assertEquals(0x1C, mnemonic.getByte());
+
+    mnemonic = MnemonicFactory.getMnemonic(MNEMONIC + " 0b00001010", 0);
+    assertEquals(0x1A, mnemonic.getByte());
+
+  }
+}

+ 89 - 0
src/test/java/de/mcs/tools/sps/mnemonic/TestWAIT.java

@@ -0,0 +1,89 @@
+package de.mcs.tools.sps.mnemonic;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import de.mcs.tools.sps.exceptions.IllegalArgument;
+import de.mcs.tools.sps.exceptions.SyntaxError;
+
+class TestWAIT {
+
+  private static final int BYTE_VALUE = 0x20;
+  private final String MNEMONIC = "WAIT";
+
+  @Test
+  void testMnemonic() throws SyntaxError {
+    WAIT wait = new WAIT(MNEMONIC + " 0x00");
+    wait.checkArgument();
+    assertEquals(BYTE_VALUE, wait.getByte());
+
+    wait = new WAIT(MNEMONIC + " 0x01");
+    wait.checkArgument();
+    assertEquals(BYTE_VALUE + 1, wait.getByte());
+
+    wait = new WAIT(MNEMONIC + " 0x0f");
+    wait.checkArgument();
+    assertEquals(BYTE_VALUE + 0x0f, wait.getByte());
+
+    wait = new WAIT(MNEMONIC + " 1ms");
+    wait.checkArgument();
+    assertEquals(BYTE_VALUE, wait.getByte());
+
+    wait = new WAIT(MNEMONIC + " 10ms");
+    wait.checkArgument();
+    assertEquals(BYTE_VALUE + 0x03, wait.getByte());
+
+    wait = new WAIT(MNEMONIC + " 60s");
+    wait.checkArgument();
+    assertEquals(BYTE_VALUE + 0x0f, wait.getByte());
+
+    Assertions.assertThrows(SyntaxError.class, () -> {
+      WAIT wait1 = new WAIT(MNEMONIC);
+      wait1.checkArgument();
+    });
+
+    Assertions.assertThrows(IllegalArgument.class, () -> {
+      WAIT wait1 = new WAIT(MNEMONIC + " 0x10");
+      wait1.checkArgument();
+    });
+
+    Assertions.assertThrows(IllegalArgument.class, () -> {
+      WAIT wait1 = new WAIT(MNEMONIC + " sieben");
+      wait1.checkArgument();
+    });
+
+    Assertions.assertThrows(IllegalArgument.class, () -> {
+      WAIT wait1 = new WAIT(MNEMONIC + " 12ms");
+      wait1.checkArgument();
+    });
+
+    Assertions.assertThrows(IllegalArgument.class, () -> {
+      WAIT wait1 = new WAIT(MNEMONIC + " 50s");
+      wait1.checkArgument();
+    });
+  }
+
+  @Test
+  void testMnemonicFactory() throws SyntaxError {
+    Mnemonic mnemonic = MnemonicFactory.getMnemonic(MNEMONIC + " 0x00", 0);
+    assertEquals(WAIT.class, mnemonic.getClass());
+
+    Assertions.assertThrows(SyntaxError.class, () -> {
+      MnemonicFactory.getMnemonic(MNEMONIC, 0);
+    });
+
+    mnemonic = MnemonicFactory.getMnemonic(MNEMONIC + " 0x02", 0);
+    assertEquals(BYTE_VALUE + 0x02, mnemonic.getByte());
+
+    mnemonic = MnemonicFactory.getMnemonic(MNEMONIC + " 12", 0);
+    assertEquals(BYTE_VALUE + 12, mnemonic.getByte());
+
+    mnemonic = MnemonicFactory.getMnemonic(MNEMONIC + " 0b00001010", 0);
+    assertEquals(BYTE_VALUE + 10, mnemonic.getByte());
+
+    mnemonic = MnemonicFactory.getMnemonic(MNEMONIC + " 10ms", 0);
+    assertEquals(BYTE_VALUE + 3, mnemonic.getByte());
+  }
+}