diff --git a/.classpath b/.classpath deleted file mode 100644 index 18d70f0..0000000 --- a/.classpath +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..80dec7e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,14 @@ +name: build +on: [push] +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 21 + uses: actions/setup-java@v1 + with: + java-version: 21 + - name: Build with Maven + run: mvn --batch-mode --update-snapshots verify diff --git a/.gitignore b/.gitignore index e2d08a6..9c989c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ -*.class -*.jar +.classpath +.project +.settings +.DS_Store +bin +build +target *~ diff --git a/.project b/.project deleted file mode 100644 index de77942..0000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - PAL - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index b1aef03..0000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,12 +0,0 @@ -#Wed Mar 14 14:37:03 CST 2012 -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.6 -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.6 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..eb0b08b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +This project adheres to [Semantic +Versioning](https://semver.org/spec/v2.0.0.html). + +## Release 0.3 (2024-03-31) +### Changed +- Migrated to Maven for builds. This included setting up the test + suite via JUnit. [#10](https://github.com/paulhoadley/pal/issues/10) +- Reduced reliance on `System.exit()` (outside of `main()`), largely + to facilitate + testing. [#11](https://github.com/paulhoadley/pal/issues/11) diff --git a/README.markdown b/README.md similarity index 86% rename from README.markdown rename to README.md index 71f21f1..3457b8d 100644 --- a/README.markdown +++ b/README.md @@ -1,3 +1,6 @@ +![](https://github.com/paulhoadley/pal/workflows/build/badge.svg) +[![License](https://img.shields.io/badge/License-BSD-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) + The PAL Abstract Machine—An implementation in Java ================================================== diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..db8975b --- /dev/null +++ b/pom.xml @@ -0,0 +1,97 @@ + + 4.0.0 + net.logicsquad + pal + 0.3 + jar + PAL + The PAL Abstract Machine—An implementation in Java. + 2002 + + + Logic Squad + https://logicsquad.net/ + + + + UTF-8 + UTF-8 + 21 + 21 + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + net.logicsquad.pal.PAL + + + + + + maven-surefire-plugin + 3.2.5 + + 1 + false + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.6.3 + + -Xdoclint:none + + private + + + + build-javadocs + package + + javadoc + + + + + + maven-project-info-reports-plugin + 3.5.0 + + + + + + + + org.junit + junit-bom + 5.10.2 + pom + import + + + + + + + org.apache.logging.log4j + log4j-slf4j2-impl + 2.22.1 + test + + + org.junit.jupiter + junit-jupiter + test + + + diff --git a/src/net/logicsquad/pal/Code.java b/src/main/java/net/logicsquad/pal/Code.java similarity index 100% rename from src/net/logicsquad/pal/Code.java rename to src/main/java/net/logicsquad/pal/Code.java diff --git a/src/net/logicsquad/pal/Data.java b/src/main/java/net/logicsquad/pal/Data.java similarity index 100% rename from src/net/logicsquad/pal/Data.java rename to src/main/java/net/logicsquad/pal/Data.java diff --git a/src/net/logicsquad/pal/DataStack.java b/src/main/java/net/logicsquad/pal/DataStack.java similarity index 100% rename from src/net/logicsquad/pal/DataStack.java rename to src/main/java/net/logicsquad/pal/DataStack.java diff --git a/src/net/logicsquad/pal/Mnemonic.java b/src/main/java/net/logicsquad/pal/Mnemonic.java similarity index 100% rename from src/net/logicsquad/pal/Mnemonic.java rename to src/main/java/net/logicsquad/pal/Mnemonic.java diff --git a/src/net/logicsquad/pal/PAL.java b/src/main/java/net/logicsquad/pal/PAL.java similarity index 90% rename from src/net/logicsquad/pal/PAL.java rename to src/main/java/net/logicsquad/pal/PAL.java index 771089c..41c7cc7 100644 --- a/src/net/logicsquad/pal/PAL.java +++ b/src/main/java/net/logicsquad/pal/PAL.java @@ -1,9 +1,11 @@ package net.logicsquad.pal; import java.io.BufferedReader; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.PushbackReader; import java.util.ArrayList; @@ -52,6 +54,18 @@ public class PAL { private static final int typeMismatch = 3; private static final int reachedEOF = 4; + private enum ExitStatus { + NORMAL(0), + ABNORMAL(1); + + private final int exitCode; + + private ExitStatus(int exitCode) { + this.exitCode = exitCode; + return; + } + } + /** * Main method for command line operation. * @@ -67,19 +81,25 @@ public static void main(String[] args) { } // Make a machine and load the code. - PAL machine = new PAL(); // Execute. + ExitStatus status = null; try { - machine.execute(); + PAL machine = new PAL(new FileInputStream(filename)); + status = machine.execute(); } catch (OutOfMemoryError e) { System.err.println(e.getMessage()); System.exit(1); } catch (IndexOutOfBoundsException e) { System.err.println(e.getMessage()); System.exit(1); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (status == null || status == ExitStatus.ABNORMAL) { + System.exit(ExitStatus.ABNORMAL.exitCode); } - return; } @@ -90,13 +110,13 @@ public static void main(String[] args) { * lexical analysis of the source file is quite rigid. Any deviation from * the prescribed format for source files causes the machine to stop. */ - public PAL() { + PAL(InputStream is) { // Create the code memory. codeMem = new ArrayList(CODESIZE); dataStack = new DataStack(DATASIZE); try { - BufferedReader br = new BufferedReader(new FileReader(filename)); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); int lineno = 1; String line = br.readLine(); String mnemonic = ""; @@ -108,7 +128,7 @@ public PAL() { if (lineno > CODESIZE) { System.err.println("Exceeded code storage limit at line " + lineno); - die(1); + System.exit(ExitStatus.ABNORMAL.exitCode); } st = new StringTokenizer(line); @@ -135,16 +155,16 @@ public PAL() { if (second instanceof String) { System.err.println("Unrecognised second operand" + " on line " + lineno); - die(1); + System.exit(ExitStatus.ABNORMAL.exitCode); } } } catch (NoSuchElementException e) { System.err.println("Not enough tokens on line " + lineno); - die(1); + System.exit(ExitStatus.ABNORMAL.exitCode); } catch (NumberFormatException e) { System.err.println("First operand non-integer on line " + lineno); - die(1); + System.exit(ExitStatus.ABNORMAL.exitCode); } codeMem.add(new Code(mnemonic, first, second, lineno)); line = br.readLine(); @@ -176,7 +196,7 @@ public PAL() { * href="http://www.cs.adelaide.edu.au/users/third/cc/handouts/pal.pdf">The * PAL Machine. */ - private void execute() { + ExitStatus execute() { // Initialise program counter. pc = 0; @@ -217,7 +237,7 @@ private void execute() { if (!(o instanceof Integer)) { error(currInst, "Argument to INC must be an integer."); - die(1); + return ExitStatus.ABNORMAL; } else { dataStack.incTop(((Integer) o).intValue()); } @@ -227,7 +247,7 @@ private void execute() { if (!(o instanceof Integer)) { error(currInst, "Argument to JIF must be an integer."); - die(1); + return ExitStatus.ABNORMAL; } tos = dataStack.pop(); @@ -235,7 +255,7 @@ private void execute() { if (tos.getType() != Data.BOOL) { dataStack.push(tos); error(currInst, "JIF - top of stack not a boolean."); - die(1); + return ExitStatus.ABNORMAL; } if (!((Boolean) tos.getValue()).booleanValue()) { @@ -244,7 +264,7 @@ private void execute() { if (destination < 1 || destination > codeMem.size()) { dataStack.push(tos); error(currInst, "JIF - attempt to jump outside code."); - die(1); + return ExitStatus.ABNORMAL; } // Our code store uses zero-based indexing. For @@ -258,19 +278,19 @@ private void execute() { if (!(o instanceof Integer)) { error(currInst, "Argument to JMP must be an integer."); - die(1); + return ExitStatus.ABNORMAL; } int destination = ((Integer) o).intValue(); if (destination == 0) { // "JMP 0 0" signifies program termination. - die(0); + return ExitStatus.ABNORMAL; } if (destination < 1 || destination > codeMem.size()) { error(currInst, "JMP - attempt to jump outside code."); - die(1); + return ExitStatus.ABNORMAL; } // Our code store uses zero-based indexing. For @@ -283,7 +303,7 @@ private void execute() { if (!(o instanceof Integer)) { error(currInst, "Argument to LCI must be an integer."); - die(1); + return ExitStatus.ABNORMAL; } else { dataStack.push(new Data(Data.INT, o)); } @@ -297,7 +317,7 @@ private void execute() { if (!(o instanceof Float)) { error(currInst, "Argument to LCR must be a real."); - die(1); + return ExitStatus.ABNORMAL; } else { dataStack.push(new Data(Data.REAL, o)); } @@ -307,13 +327,13 @@ private void execute() { if (!(o instanceof String)) { error(currInst, "Argument to LCS must be a string."); - die(1); + return ExitStatus.ABNORMAL; } else { if (!(((String) o).startsWith("'") && (((String) o) .endsWith("'")))) { error(currInst, "String must be delimited by single-quotes."); - die(1); + return ExitStatus.ABNORMAL; } else { String oS = (String) o; oS = oS.substring(1, oS.length() - 1); @@ -327,7 +347,7 @@ private void execute() { if (!(o instanceof Integer)) { error(currInst, "Argument to LDA must be an integer."); - die(1); + return ExitStatus.ABNORMAL; } int address = dataStack.getAddress(currInst.getFirst(), @@ -344,7 +364,7 @@ private void execute() { if (tos.getType() != Data.INT) { dataStack.push(tos); error(currInst, "LDI - top of stack must be an integer."); - die(1); + return ExitStatus.ABNORMAL; } address = ((Integer) tos.getValue()).intValue(); @@ -360,7 +380,7 @@ private void execute() { if (!(o instanceof Integer)) { error(currInst, "Argument to LDV must be an integer"); - die(1); + return ExitStatus.ABNORMAL; } loadedVal = dataStack.get(currInst.getFirst(), @@ -387,7 +407,10 @@ private void execute() { break; case Mnemonic.OPR: - doOperation(currInst); + ExitStatus status = doOperation(currInst); + if (status == ExitStatus.ABNORMAL) { + return ExitStatus.ABNORMAL; + } break; case Mnemonic.RDI: // Read an integer from stdin. @@ -398,7 +421,10 @@ private void execute() { if (intLine == null) { // EOF reached. currentException = reachedEOF; - raiseException(currInst); + ExitStatus exceptionStatus = raiseException(currInst); + if (exceptionStatus == ExitStatus.ABNORMAL) { + return ExitStatus.ABNORMAL; + } break; } int intVal = Integer.parseInt(intLine); @@ -411,7 +437,10 @@ private void execute() { System.err.println(e1); } catch (NumberFormatException e2) { currentException = typeMismatch; - raiseException(currInst); + ExitStatus exceptionStatus = raiseException(currInst); + if (exceptionStatus == ExitStatus.ABNORMAL) { + return ExitStatus.ABNORMAL; + } } break; case Mnemonic.RDR: @@ -423,7 +452,10 @@ private void execute() { if (realLine == null) { // EOF reached. currentException = reachedEOF; - raiseException(currInst); + ExitStatus exceptionStatus = raiseException(currInst); + if (exceptionStatus == ExitStatus.ABNORMAL) { + return ExitStatus.ABNORMAL; + } break; } float realVal = Float.parseFloat(realLine); @@ -436,7 +468,10 @@ private void execute() { System.err.println(e1); } catch (NumberFormatException e2) { currentException = typeMismatch; - raiseException(currInst); + ExitStatus exceptionStatus = raiseException(currInst); + if (exceptionStatus == ExitStatus.ABNORMAL) { + return ExitStatus.ABNORMAL; + } } break; case Mnemonic.REH: @@ -445,7 +480,7 @@ private void execute() { if (!(o instanceof Integer)) { error(currInst, "Argument to REH must be an integer."); - die(1); + return ExitStatus.ABNORMAL; } // Get the location of the exception handler pointer @@ -463,7 +498,7 @@ private void execute() { if (!(o instanceof Integer)) { error(currInst, "Argument to SIG must be an integer."); - die(1); + return ExitStatus.ABNORMAL; } int excType = ((Integer) o).intValue(); @@ -481,7 +516,10 @@ private void execute() { } // Raise the exception... - raiseException(currInst); + ExitStatus exceptionStatus = raiseException(currInst); + if (exceptionStatus == ExitStatus.ABNORMAL) { + return ExitStatus.ABNORMAL; + } break; case Mnemonic.STI: @@ -493,7 +531,7 @@ private void execute() { if (tos.getType() != Data.INT) { dataStack.push(tos); error(currInst, "STI - top of stack must be an integer."); - die(1); + return ExitStatus.ABNORMAL; } ntos = dataStack.pop(); @@ -510,7 +548,7 @@ private void execute() { if (!(o instanceof Integer)) { error(currInst, "Argument to STO must be an integer."); - die(1); + return ExitStatus.ABNORMAL; } tos = dataStack.pop(); @@ -529,7 +567,7 @@ private void execute() { System.err.println("Program failed to execute a termination" + " instruction (JMP 0 0)."); - die(1); + return ExitStatus.ABNORMAL; } /** @@ -543,17 +581,17 @@ private void execute() { * reaches here, that object contains an OPR * mnemonic. */ - public void doOperation(Code currInst) { + public ExitStatus doOperation(Code currInst) { Object o = currInst.getSecond(); int opr; if (!(o instanceof Integer)) { error(currInst, "Argument to OPR must be an integer."); - die(1); + return ExitStatus.ABNORMAL; } opr = ((Integer) o).intValue(); if (opr < 0 || opr > 31) { error(currInst, "Argument to OPR must be in range 0-31."); - die(1); + return ExitStatus.ABNORMAL; } Data returnPoint, tos, ntos, dynamicLink; @@ -620,7 +658,7 @@ public void doOperation(Code currInst) { tos.setValue(new Float(-oldValue)); } else { error(currInst, "Cannot negate boolean, string or UNDEF value."); - die(1); + return ExitStatus.ABNORMAL; } break; case 3: @@ -638,7 +676,7 @@ public void doOperation(Code currInst) { dataStack.push(tos); error(currInst, "Values for arithmetic operations must be" + " of same type."); - die(1); + return ExitStatus.ABNORMAL; } else { int type = tos.getType(); if (type != Data.INT && type != Data.REAL) { @@ -646,7 +684,7 @@ public void doOperation(Code currInst) { dataStack.push(tos); error(currInst, "Values for arithmetic operations must be" + " of type integer or real."); - die(1); + return ExitStatus.ABNORMAL; } if (type == Data.INT) { int int1 = ((Integer) ntos.getValue()).intValue(); @@ -669,7 +707,7 @@ public void doOperation(Code currInst) { dataStack.push(ntos); dataStack.push(tos); error(currInst, "Attempt to divide by zero."); - die(1); + return ExitStatus.ABNORMAL; } dataStack.push(new Data(Data.INT, new Integer(int1 @@ -698,7 +736,7 @@ public void doOperation(Code currInst) { dataStack.push(ntos); dataStack.push(tos); error(currInst, "Attempt to divide by zero."); - die(1); + return ExitStatus.ABNORMAL; } dataStack.push(new Data(Data.REAL, new Float(flt1 @@ -715,7 +753,7 @@ public void doOperation(Code currInst) { if (dataStack.peek().getType() != Data.INT) { error(currInst, "Exponent must be of type integer."); - die(1); + return ExitStatus.ABNORMAL; } tos = dataStack.pop(); int exponent = ((Integer) tos.getValue()).intValue(); @@ -723,7 +761,7 @@ public void doOperation(Code currInst) { int baseType = dataStack.peek().getType(); if (baseType != Data.INT && baseType != Data.REAL) { error(currInst, "Base must be of type integer or real."); - die(1); + return ExitStatus.ABNORMAL; } ntos = dataStack.pop(); if (baseType == Data.INT) { @@ -746,7 +784,7 @@ public void doOperation(Code currInst) { dataStack.push(tos); error(currInst, "Both arguments to OPR 8 must be of type string."); - die(1); + return ExitStatus.ABNORMAL; } String sResult = (String) ntos.getValue(); sResult += (String) tos.getValue(); @@ -757,7 +795,7 @@ public void doOperation(Code currInst) { if (dataStack.peek().getType() != Data.INT) { error(currInst, "Argument to OPR 9 must be of type integer."); - die(1); + return ExitStatus.ABNORMAL; } else { tos = dataStack.pop(); // NB the % operator will give a negative for a @@ -786,7 +824,7 @@ public void doOperation(Code currInst) { dataStack.push(tos); error(currInst, "Values for arithmetic operations must be" + " of same type."); - die(1); + return ExitStatus.ABNORMAL; } else { int type = tos.getType(); if (type != Data.INT && type != Data.REAL) { @@ -794,7 +832,7 @@ public void doOperation(Code currInst) { dataStack.push(tos); error(currInst, "Values for arithmetic operations must be" + " of type integer or real."); - die(1); + return ExitStatus.ABNORMAL; } if (type == Data.INT) { int int1 = ((Integer) ntos.getValue()).intValue(); @@ -867,7 +905,7 @@ public void doOperation(Code currInst) { if (tos.getType() != Data.BOOL) { dataStack.push(tos); error(currInst, "Top of stack must be a boolean."); - die(1); + return ExitStatus.ABNORMAL; } boolean bResult = !((Boolean) tos.getValue()).booleanValue(); @@ -905,7 +943,7 @@ public void doOperation(Code currInst) { || dataStack.peek().getType() == Data.UNDEF) { error(currInst, "OPR 20 can only print values" + " of type integer, real or string."); - die(1); + return ExitStatus.ABNORMAL; } else { tos = dataStack.pop(); System.out.print(tos); @@ -941,7 +979,7 @@ public void doOperation(Code currInst) { if (dataStack.peek().getType() != Data.INT) { error(currInst, "Integer to real conversion can only be" + " performed on a value of type integer."); - die(1); + return ExitStatus.ABNORMAL; } float fAns = ((Integer) dataStack.pop().getValue()).floatValue(); dataStack.push(new Data(Data.REAL, new Float(fAns))); @@ -952,7 +990,7 @@ public void doOperation(Code currInst) { if (dataStack.peek().getType() != Data.REAL) { error(currInst, "Real to integer conversion can only be" + " performed on a value of type real."); - die(1); + return ExitStatus.ABNORMAL; } int iResult = ((Float) dataStack.pop().getValue()).intValue(); dataStack.push(new Data(Data.INT, new Integer(iResult))); @@ -963,7 +1001,7 @@ public void doOperation(Code currInst) { if (dataStack.peek().getType() != Data.INT) { error(currInst, "Integer to string conversion can only be" + " performed on a value of type integer."); - die(1); + return ExitStatus.ABNORMAL; } dataStack.push(new Data(Data.STRING, dataStack.pop().getValue() .toString())); @@ -974,7 +1012,7 @@ public void doOperation(Code currInst) { if (dataStack.peek().getType() != Data.REAL) { error(currInst, "Real to string conversion can only be" + " performed on value of type real."); - die(1); + return ExitStatus.ABNORMAL; } dataStack.push(new Data(Data.STRING, dataStack.pop().getValue() .toString())); @@ -989,7 +1027,7 @@ public void doOperation(Code currInst) { dataStack.push(tos); error(currInst, "Logical and can only be" + " performed on values of type boolean."); - die(1); + return ExitStatus.ABNORMAL; } boolean bool1 = ((Boolean) tos.getValue()).booleanValue(); boolean bool2 = ((Boolean) ntos.getValue()).booleanValue(); @@ -1005,7 +1043,7 @@ public void doOperation(Code currInst) { dataStack.push(tos); error(currInst, "Logical or can only be" + " performed on values of type boolean."); - die(1); + return ExitStatus.ABNORMAL; } bool1 = ((Boolean) tos.getValue()).booleanValue(); bool2 = ((Boolean) ntos.getValue()).booleanValue(); @@ -1020,7 +1058,7 @@ public void doOperation(Code currInst) { dataStack.push(tos); error(currInst, "OPR 0 31 expects an integer value" + "on top of the stack."); - die(1); + return ExitStatus.ABNORMAL; } int testValue = ((Integer) tos.getValue()).intValue(); @@ -1031,7 +1069,7 @@ public void doOperation(Code currInst) { default: System.out.println("OPR " + opr + ": not implemented."); } - return; + return ExitStatus.NORMAL; } /** @@ -1077,11 +1115,11 @@ private Object makeObject(String input) { * The Code object which caused the exception. Used * to add information to error messages. */ - private void raiseException(Code currInst) { + private ExitStatus raiseException(Code currInst) { // The Program Abort signal cannot be caught. if (currentException == programAbort) { error(currInst, "A Program Abort signal was raised."); - die(1); + return ExitStatus.ABNORMAL; } Data handlerLocation, dynamicLink; @@ -1094,14 +1132,14 @@ private void raiseException(Code currInst) { if (handlerLocation.getType() != Data.INT) { error(currInst, "Exception handler address must be an integer."); - die(1); + return ExitStatus.ABNORMAL; } handlerAddress = ((Integer) handlerLocation.getValue()).intValue(); if (handlerAddress < 0 || handlerAddress > codeMem.size()) { error(currInst, "Exception handler address out of code range."); - die(1); + return ExitStatus.ABNORMAL; } if (handlerAddress == 0) { @@ -1143,8 +1181,9 @@ private void raiseException(Code currInst) { // No handler was found. error(currInst, "Exception #" + currentException + " never handled!"); - die(1); + return ExitStatus.ABNORMAL; } + return ExitStatus.NORMAL; } /** @@ -1169,17 +1208,6 @@ private void error(Code currInst, String s) { return; } - /** - * Die due to an error. - * - * @param err - * An arbitrary error code. This integer is returned to the - * operating system via the System.exit method. - */ - private void die(int err) { - System.exit(err); - } - /** * Simple usage information. */ diff --git a/src/test/java/net/logicsquad/pal/PALTest.java b/src/test/java/net/logicsquad/pal/PALTest.java new file mode 100644 index 0000000..dc1894b --- /dev/null +++ b/src/test/java/net/logicsquad/pal/PALTest.java @@ -0,0 +1,93 @@ +package net.logicsquad.pal; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * "Functional tests" on {@link PAL}. What we're doing here is just running the existing "test suite" using JUnit. We already have expected + * output for a set of input files. Due to {@link PAL}'s fondness for calling {@code System.exit()}, we can't run all of these just yet. + * + * @author paulh + */ +public class PALTest { + private static final List NON_INTERACTIVE_INPUTS = List.of( + "BASICS", + "BOOLS", + "INC", + "LOCALS", + "OPR-11", + "OPR-12", + "OPR-13", + "OPR-14", + "OPR-15", + "OPR-2", + "OPR-24", + "OPR-29-30", + "OPR-3-4-5-6", + "OPR-6a", + "OPR-6b", + "OPR-7", + "OPR-8", + "OPR-8-27-28", + "SIGa", + "SIGb", + "SIGc", + "SIGd", + "STRINGPARSE"); + + private static final List INTERACTIVE_INPUTS = List.of( + "FACTITER", + "FACTREC", + "LAB", + "OPR-19", + "OPR-22", + "OPR-23", + "SIGe", + "SIGf", + "SIGg", + "SIGh"); + + @Test + public void nonInteractiveTests() throws IOException { + for (String input : NON_INTERACTIVE_INPUTS) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + System.setOut(new PrintStream(baos)); + System.setErr(new PrintStream(baos)); + InputStream is = PALTest.class.getResourceAsStream("/basic/" + input); + PAL pal = new PAL(is); + pal.execute(); + baos.flush(); + String output = new String(baos.toByteArray()); + String expected = new String(PALTest.class.getResourceAsStream("/basic/" + input + ".ref").readAllBytes(), StandardCharsets.UTF_8); + assertEquals(expected, output); + } + return; + } + + @Test + public void interactiveTests() throws IOException { + for (String input : INTERACTIVE_INPUTS) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + System.setOut(new PrintStream(baos)); + System.setErr(new PrintStream(baos)); + InputStream response = PALTest.class.getResourceAsStream("/interactive/" + input + ".in"); + System.setIn(response); + InputStream is = PALTest.class.getResourceAsStream("/interactive/" + input); + PAL pal = new PAL(is); + pal.execute(); + baos.flush(); + String output = new String(baos.toByteArray()); + String expected = new String(PALTest.class.getResourceAsStream("/interactive/" + input + ".ref").readAllBytes(), StandardCharsets.UTF_8); + assertEquals(expected, output); + } + return; + } +} diff --git a/test/basic/BASICS b/src/test/resources/basic/BASICS similarity index 100% rename from test/basic/BASICS rename to src/test/resources/basic/BASICS diff --git a/test/basic/BASICS.ref b/src/test/resources/basic/BASICS.ref similarity index 100% rename from test/basic/BASICS.ref rename to src/test/resources/basic/BASICS.ref diff --git a/test/basic/BOOLS b/src/test/resources/basic/BOOLS similarity index 100% rename from test/basic/BOOLS rename to src/test/resources/basic/BOOLS diff --git a/test/basic/BOOLS.ref b/src/test/resources/basic/BOOLS.ref similarity index 100% rename from test/basic/BOOLS.ref rename to src/test/resources/basic/BOOLS.ref diff --git a/test/basic/INC b/src/test/resources/basic/INC similarity index 100% rename from test/basic/INC rename to src/test/resources/basic/INC diff --git a/test/basic/INC.ref b/src/test/resources/basic/INC.ref similarity index 100% rename from test/basic/INC.ref rename to src/test/resources/basic/INC.ref diff --git a/test/basic/LOCALS b/src/test/resources/basic/LOCALS similarity index 100% rename from test/basic/LOCALS rename to src/test/resources/basic/LOCALS diff --git a/test/basic/LOCALS.ref b/src/test/resources/basic/LOCALS.ref similarity index 100% rename from test/basic/LOCALS.ref rename to src/test/resources/basic/LOCALS.ref diff --git a/test/basic/OPR-11 b/src/test/resources/basic/OPR-11 similarity index 100% rename from test/basic/OPR-11 rename to src/test/resources/basic/OPR-11 diff --git a/test/basic/OPR-11.ref b/src/test/resources/basic/OPR-11.ref similarity index 100% rename from test/basic/OPR-11.ref rename to src/test/resources/basic/OPR-11.ref diff --git a/test/basic/OPR-12 b/src/test/resources/basic/OPR-12 similarity index 100% rename from test/basic/OPR-12 rename to src/test/resources/basic/OPR-12 diff --git a/test/basic/OPR-12.ref b/src/test/resources/basic/OPR-12.ref similarity index 100% rename from test/basic/OPR-12.ref rename to src/test/resources/basic/OPR-12.ref diff --git a/test/basic/OPR-13 b/src/test/resources/basic/OPR-13 similarity index 100% rename from test/basic/OPR-13 rename to src/test/resources/basic/OPR-13 diff --git a/test/basic/OPR-13.ref b/src/test/resources/basic/OPR-13.ref similarity index 100% rename from test/basic/OPR-13.ref rename to src/test/resources/basic/OPR-13.ref diff --git a/test/basic/OPR-14 b/src/test/resources/basic/OPR-14 similarity index 100% rename from test/basic/OPR-14 rename to src/test/resources/basic/OPR-14 diff --git a/test/basic/OPR-14.ref b/src/test/resources/basic/OPR-14.ref similarity index 100% rename from test/basic/OPR-14.ref rename to src/test/resources/basic/OPR-14.ref diff --git a/test/basic/OPR-15 b/src/test/resources/basic/OPR-15 similarity index 100% rename from test/basic/OPR-15 rename to src/test/resources/basic/OPR-15 diff --git a/test/basic/OPR-15.ref b/src/test/resources/basic/OPR-15.ref similarity index 100% rename from test/basic/OPR-15.ref rename to src/test/resources/basic/OPR-15.ref diff --git a/test/basic/OPR-2 b/src/test/resources/basic/OPR-2 similarity index 100% rename from test/basic/OPR-2 rename to src/test/resources/basic/OPR-2 diff --git a/test/basic/OPR-2.ref b/src/test/resources/basic/OPR-2.ref similarity index 100% rename from test/basic/OPR-2.ref rename to src/test/resources/basic/OPR-2.ref diff --git a/test/basic/OPR-24 b/src/test/resources/basic/OPR-24 similarity index 100% rename from test/basic/OPR-24 rename to src/test/resources/basic/OPR-24 diff --git a/test/basic/OPR-24.ref b/src/test/resources/basic/OPR-24.ref similarity index 51% rename from test/basic/OPR-24.ref rename to src/test/resources/basic/OPR-24.ref index 983e76c..acdfe3c 100644 --- a/test/basic/OPR-24.ref +++ b/src/test/resources/basic/OPR-24.ref @@ -1,6 +1,6 @@ Runtime Error: -test/basic/OPR-24:3:JIF - top of stack not a boolean. +CODE:3:JIF - top of stack not a boolean. JIF 0 42 Stack dump: diff --git a/test/basic/OPR-29-30 b/src/test/resources/basic/OPR-29-30 similarity index 100% rename from test/basic/OPR-29-30 rename to src/test/resources/basic/OPR-29-30 diff --git a/src/test/resources/basic/OPR-29-30.ref b/src/test/resources/basic/OPR-29-30.ref new file mode 100644 index 0000000..8ea1b07 --- /dev/null +++ b/src/test/resources/basic/OPR-29-30.ref @@ -0,0 +1,12 @@ + +Runtime Error: +CODE:4:OPR 20 can only print values of type integer, real or string. +OPR 0 20 + +Stack dump: +---------- +true +0 +0 +0 +0 diff --git a/test/basic/OPR-3-4-5-6 b/src/test/resources/basic/OPR-3-4-5-6 similarity index 100% rename from test/basic/OPR-3-4-5-6 rename to src/test/resources/basic/OPR-3-4-5-6 diff --git a/test/basic/OPR-3-4-5-6.ref b/src/test/resources/basic/OPR-3-4-5-6.ref similarity index 100% rename from test/basic/OPR-3-4-5-6.ref rename to src/test/resources/basic/OPR-3-4-5-6.ref diff --git a/test/basic/OPR-6a b/src/test/resources/basic/OPR-6a similarity index 100% rename from test/basic/OPR-6a rename to src/test/resources/basic/OPR-6a diff --git a/test/basic/OPR-6a.ref b/src/test/resources/basic/OPR-6a.ref similarity index 56% rename from test/basic/OPR-6a.ref rename to src/test/resources/basic/OPR-6a.ref index 6bd76e4..6120aa4 100644 --- a/test/basic/OPR-6a.ref +++ b/src/test/resources/basic/OPR-6a.ref @@ -1,6 +1,6 @@ Runtime Error: -test/basic/OPR-6a:3:Attempt to divide by zero. +CODE:3:Attempt to divide by zero. OPR 0 6 Stack dump: diff --git a/test/basic/OPR-6b b/src/test/resources/basic/OPR-6b similarity index 100% rename from test/basic/OPR-6b rename to src/test/resources/basic/OPR-6b diff --git a/test/basic/OPR-6b.ref b/src/test/resources/basic/OPR-6b.ref similarity index 57% rename from test/basic/OPR-6b.ref rename to src/test/resources/basic/OPR-6b.ref index a6a4161..855dd73 100644 --- a/test/basic/OPR-6b.ref +++ b/src/test/resources/basic/OPR-6b.ref @@ -1,6 +1,6 @@ Runtime Error: -test/basic/OPR-6b:5:Attempt to divide by zero. +CODE:5:Attempt to divide by zero. OPR 0 6 Stack dump: diff --git a/test/basic/OPR-7 b/src/test/resources/basic/OPR-7 similarity index 100% rename from test/basic/OPR-7 rename to src/test/resources/basic/OPR-7 diff --git a/test/basic/OPR-7.ref b/src/test/resources/basic/OPR-7.ref similarity index 55% rename from test/basic/OPR-7.ref rename to src/test/resources/basic/OPR-7.ref index 4a04d16..bbd93f4 100644 --- a/test/basic/OPR-7.ref +++ b/src/test/resources/basic/OPR-7.ref @@ -1,6 +1,6 @@ 86.25 Runtime Error: -test/basic/OPR-7:11:Exponent must be of type integer. +CODE:11:Exponent must be of type integer. OPR 0 7 Stack dump: diff --git a/test/basic/OPR-8 b/src/test/resources/basic/OPR-8 similarity index 100% rename from test/basic/OPR-8 rename to src/test/resources/basic/OPR-8 diff --git a/test/basic/OPR-8-27-28 b/src/test/resources/basic/OPR-8-27-28 similarity index 100% rename from test/basic/OPR-8-27-28 rename to src/test/resources/basic/OPR-8-27-28 diff --git a/test/basic/OPR-8-27-28.ref b/src/test/resources/basic/OPR-8-27-28.ref similarity index 100% rename from test/basic/OPR-8-27-28.ref rename to src/test/resources/basic/OPR-8-27-28.ref diff --git a/test/basic/OPR-8.ref b/src/test/resources/basic/OPR-8.ref similarity index 100% rename from test/basic/OPR-8.ref rename to src/test/resources/basic/OPR-8.ref diff --git a/test/basic/SIGa b/src/test/resources/basic/SIGa similarity index 100% rename from test/basic/SIGa rename to src/test/resources/basic/SIGa diff --git a/test/basic/SIGa.ref b/src/test/resources/basic/SIGa.ref similarity index 54% rename from test/basic/SIGa.ref rename to src/test/resources/basic/SIGa.ref index 69ac777..ca31af7 100644 --- a/test/basic/SIGa.ref +++ b/src/test/resources/basic/SIGa.ref @@ -1,6 +1,6 @@ Runtime Error: -test/basic/SIGa:1:Exception #0 never handled! +CODE:1:Exception #0 never handled! SIG 0 0 Stack dump: diff --git a/test/basic/SIGb b/src/test/resources/basic/SIGb similarity index 100% rename from test/basic/SIGb rename to src/test/resources/basic/SIGb diff --git a/test/basic/SIGb.ref b/src/test/resources/basic/SIGb.ref similarity index 54% rename from test/basic/SIGb.ref rename to src/test/resources/basic/SIGb.ref index 40a78b0..59aa070 100644 --- a/test/basic/SIGb.ref +++ b/src/test/resources/basic/SIGb.ref @@ -1,6 +1,6 @@ Runtime Error: -test/basic/SIGb:2:Exception #0 never handled! +CODE:2:Exception #0 never handled! SIG 0 0 Stack dump: diff --git a/test/basic/SIGc b/src/test/resources/basic/SIGc similarity index 100% rename from test/basic/SIGc rename to src/test/resources/basic/SIGc diff --git a/test/basic/SIGc.ref b/src/test/resources/basic/SIGc.ref similarity index 51% rename from test/basic/SIGc.ref rename to src/test/resources/basic/SIGc.ref index 4115614..0f83722 100644 --- a/test/basic/SIGc.ref +++ b/src/test/resources/basic/SIGc.ref @@ -1,6 +1,6 @@ Runtime Error: -test/basic/SIGc:2:A Program Abort signal was raised. +CODE:2:A Program Abort signal was raised. SIG 0 1 Stack dump: diff --git a/test/basic/SIGd b/src/test/resources/basic/SIGd similarity index 100% rename from test/basic/SIGd rename to src/test/resources/basic/SIGd diff --git a/test/basic/SIGd.ref b/src/test/resources/basic/SIGd.ref similarity index 100% rename from test/basic/SIGd.ref rename to src/test/resources/basic/SIGd.ref diff --git a/test/basic/STRINGPARSE b/src/test/resources/basic/STRINGPARSE similarity index 100% rename from test/basic/STRINGPARSE rename to src/test/resources/basic/STRINGPARSE diff --git a/test/basic/STRINGPARSE.ref b/src/test/resources/basic/STRINGPARSE.ref similarity index 100% rename from test/basic/STRINGPARSE.ref rename to src/test/resources/basic/STRINGPARSE.ref diff --git a/test/interactive/FACTITER b/src/test/resources/interactive/FACTITER similarity index 100% rename from test/interactive/FACTITER rename to src/test/resources/interactive/FACTITER diff --git a/test/interactive/FACTITER.in b/src/test/resources/interactive/FACTITER.in similarity index 100% rename from test/interactive/FACTITER.in rename to src/test/resources/interactive/FACTITER.in diff --git a/test/interactive/FACTITER.ref b/src/test/resources/interactive/FACTITER.ref similarity index 100% rename from test/interactive/FACTITER.ref rename to src/test/resources/interactive/FACTITER.ref diff --git a/test/interactive/FACTREC b/src/test/resources/interactive/FACTREC similarity index 100% rename from test/interactive/FACTREC rename to src/test/resources/interactive/FACTREC diff --git a/test/interactive/FACTREC.in b/src/test/resources/interactive/FACTREC.in similarity index 100% rename from test/interactive/FACTREC.in rename to src/test/resources/interactive/FACTREC.in diff --git a/test/interactive/FACTREC.ref b/src/test/resources/interactive/FACTREC.ref similarity index 100% rename from test/interactive/FACTREC.ref rename to src/test/resources/interactive/FACTREC.ref diff --git a/test/interactive/LAB b/src/test/resources/interactive/LAB similarity index 100% rename from test/interactive/LAB rename to src/test/resources/interactive/LAB diff --git a/test/interactive/LAB.in b/src/test/resources/interactive/LAB.in similarity index 100% rename from test/interactive/LAB.in rename to src/test/resources/interactive/LAB.in diff --git a/test/interactive/LAB.ref b/src/test/resources/interactive/LAB.ref similarity index 100% rename from test/interactive/LAB.ref rename to src/test/resources/interactive/LAB.ref diff --git a/test/interactive/OPR-19 b/src/test/resources/interactive/OPR-19 similarity index 100% rename from test/interactive/OPR-19 rename to src/test/resources/interactive/OPR-19 diff --git a/test/interactive/OPR-19.in b/src/test/resources/interactive/OPR-19.in similarity index 100% rename from test/interactive/OPR-19.in rename to src/test/resources/interactive/OPR-19.in diff --git a/test/interactive/OPR-19.ref b/src/test/resources/interactive/OPR-19.ref similarity index 100% rename from test/interactive/OPR-19.ref rename to src/test/resources/interactive/OPR-19.ref diff --git a/test/interactive/OPR-22 b/src/test/resources/interactive/OPR-22 similarity index 100% rename from test/interactive/OPR-22 rename to src/test/resources/interactive/OPR-22 diff --git a/test/interactive/OPR-22.in b/src/test/resources/interactive/OPR-22.in similarity index 100% rename from test/interactive/OPR-22.in rename to src/test/resources/interactive/OPR-22.in diff --git a/test/interactive/OPR-22.ref b/src/test/resources/interactive/OPR-22.ref similarity index 100% rename from test/interactive/OPR-22.ref rename to src/test/resources/interactive/OPR-22.ref diff --git a/test/interactive/OPR-23 b/src/test/resources/interactive/OPR-23 similarity index 100% rename from test/interactive/OPR-23 rename to src/test/resources/interactive/OPR-23 diff --git a/test/interactive/OPR-23.in b/src/test/resources/interactive/OPR-23.in similarity index 100% rename from test/interactive/OPR-23.in rename to src/test/resources/interactive/OPR-23.in diff --git a/test/interactive/OPR-23.ref b/src/test/resources/interactive/OPR-23.ref similarity index 100% rename from test/interactive/OPR-23.ref rename to src/test/resources/interactive/OPR-23.ref diff --git a/test/interactive/SIGe b/src/test/resources/interactive/SIGe similarity index 100% rename from test/interactive/SIGe rename to src/test/resources/interactive/SIGe diff --git a/test/interactive/SIGe.in b/src/test/resources/interactive/SIGe.in similarity index 100% rename from test/interactive/SIGe.in rename to src/test/resources/interactive/SIGe.in diff --git a/test/interactive/SIGe.ref b/src/test/resources/interactive/SIGe.ref similarity index 74% rename from test/interactive/SIGe.ref rename to src/test/resources/interactive/SIGe.ref index f3ff4a0..6bace3d 100644 --- a/test/interactive/SIGe.ref +++ b/src/test/resources/interactive/SIGe.ref @@ -4,7 +4,7 @@ pTwo exh: exception 0. Main exh: exception 0. Runtime Error: -test/interactive/SIGe:146:Exception #0 never handled! +CODE:146:Exception #0 never handled! SIG 0 0 Stack dump: diff --git a/test/interactive/SIGf b/src/test/resources/interactive/SIGf similarity index 100% rename from test/interactive/SIGf rename to src/test/resources/interactive/SIGf diff --git a/test/interactive/SIGf.in b/src/test/resources/interactive/SIGf.in similarity index 100% rename from test/interactive/SIGf.in rename to src/test/resources/interactive/SIGf.in diff --git a/test/interactive/SIGf.ref b/src/test/resources/interactive/SIGf.ref similarity index 71% rename from test/interactive/SIGf.ref rename to src/test/resources/interactive/SIGf.ref index fbbceea..ea7f5e6 100644 --- a/test/interactive/SIGf.ref +++ b/src/test/resources/interactive/SIGf.ref @@ -1,7 +1,7 @@ Enter the exception type. Runtime Error: -test/interactive/SIGf:18:A Program Abort signal was raised. +CODE:18:A Program Abort signal was raised. SIG 0 1 Stack dump: diff --git a/test/interactive/SIGg b/src/test/resources/interactive/SIGg similarity index 100% rename from test/interactive/SIGg rename to src/test/resources/interactive/SIGg diff --git a/test/interactive/SIGg.in b/src/test/resources/interactive/SIGg.in similarity index 100% rename from test/interactive/SIGg.in rename to src/test/resources/interactive/SIGg.in diff --git a/test/interactive/SIGg.ref b/src/test/resources/interactive/SIGg.ref similarity index 79% rename from test/interactive/SIGg.ref rename to src/test/resources/interactive/SIGg.ref index 4f23ead..25cdea2 100644 --- a/test/interactive/SIGg.ref +++ b/src/test/resources/interactive/SIGg.ref @@ -6,7 +6,7 @@ pTwo exh: exception 8. Main exh: exception 8. Runtime Error: -test/interactive/SIGg:167:Exception #8 never handled! +CODE:167:Exception #8 never handled! SIG 0 0 Stack dump: diff --git a/test/interactive/SIGh b/src/test/resources/interactive/SIGh similarity index 100% rename from test/interactive/SIGh rename to src/test/resources/interactive/SIGh diff --git a/test/interactive/SIGh.in b/src/test/resources/interactive/SIGh.in similarity index 100% rename from test/interactive/SIGh.in rename to src/test/resources/interactive/SIGh.in diff --git a/test/interactive/SIGh.ref b/src/test/resources/interactive/SIGh.ref similarity index 54% rename from test/interactive/SIGh.ref rename to src/test/resources/interactive/SIGh.ref index f08e622..f3a1816 100644 --- a/test/interactive/SIGh.ref +++ b/src/test/resources/interactive/SIGh.ref @@ -1,6 +1,6 @@ Runtime Error: -test/interactive/SIGh:4:Exception #4 never handled! +CODE:4:Exception #4 never handled! SIG 0 0 Stack dump: diff --git a/test/basic/OPR-29-30.ref b/test/basic/OPR-29-30.ref deleted file mode 100644 index 1cc528c..0000000 --- a/test/basic/OPR-29-30.ref +++ /dev/null @@ -1,12 +0,0 @@ - -Runtime Error: -test/basic/OPR-29-30:4:OPR 20 can only print values of type integer, real or string. -OPR 0 20 - -Stack dump: ----------- -true -0 -0 -0 -0