From 76780829b0d29f6b6fcd8cbaf04f589adcca1ad2 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 11 Feb 2023 12:28:47 +0100 Subject: [PATCH 01/28] fix publishing --- .github/workflows/release-tags.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-tags.yml b/.github/workflows/release-tags.yml index 6763c37..e429d31 100644 --- a/.github/workflows/release-tags.yml +++ b/.github/workflows/release-tags.yml @@ -28,8 +28,8 @@ jobs: - name: Build and Publish with Maven run: mvn --batch-mode --update-snapshots deploy env: - MAVEN_DEPLOY_USER: ${{ secrets.MAVEN_DEPLOY_USER }} - MAVEN_DEPLOY_PASSWORD: ${{ secrets.MAVEN_DEPLOY_PASSWORD }} + MAVEN_USER: ${{ secrets.MAVEN_DEPLOY_USER }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_DEPLOY_PASSWORD }} - name: Release under current tag uses: "marvinpinto/action-automatic-releases@latest" From 8d9bc973a8f7f6602b6137c6136e9d81dad735d3 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 11 Feb 2023 12:55:46 +0100 Subject: [PATCH 02/28] update actions --- .github/workflows/release-tags.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-tags.yml b/.github/workflows/release-tags.yml index e429d31..4aca775 100644 --- a/.github/workflows/release-tags.yml +++ b/.github/workflows/release-tags.yml @@ -9,7 +9,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 From cf2d35526218a88984a02727878032682af12d8c Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 11 Feb 2023 13:08:39 +0100 Subject: [PATCH 03/28] update readme --- README.MD | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/README.MD b/README.MD index 26c1956..534a185 100644 --- a/README.MD +++ b/README.MD @@ -4,9 +4,12 @@ A standard-conforming Funge-98 interpreter. ### Usage ``` -usage: jfunge [-f | --help | --license | --version] [-i ] - [--maxiter ] [-o ] [--syscall] [-t] [--trefunge] +usage: jfunge [--env] [-f | --help | --license | --version] [-i ] + [--maxiter ] [-o ] [--perl] [--syscall] [-t] + [--trefunge] JFunge, a Funge98 interpeter for java. + --env Allows the interpreter to access the environment + variables of the host system. -f,--file The file to load into the interpreter at the origin on startup. --help Displays this help page @@ -22,6 +25,10 @@ JFunge, a Funge98 interpeter for java. directory (o instruction). Specify / to allow write access to every file on the system (dangerous). Can specify multiple files/folders. + --perl Enable the PERL fingerprint. This requires the + working directory of the interpreter to be + writable, and is also an arbitrary code execution + risk. --syscall Enables the syscall feature (= instruction). This is a very dangerous permission to grant, it can call any arbitrary program on your system. @@ -33,11 +40,14 @@ JFunge, a Funge98 interpeter for java. --version Prints the current program version, along with the handprint and version given by befunge's y instruction. + +Process finished with exit code 0 + ``` ### Compatibility -The interpreter's handprint is `0xfa15e9a7` (`-99227225` in decimal), which is a hexadecimal approximation of "`falsepat`" from FalsePattern. +The interpreter's handprint is `0x74708578` ("JFUN") The version number given to befunge is `major * 256 * 256 + minor * 256 + patch`, where major, minor, patch are the 3 primary version numbers in the standard semver format. @@ -52,17 +62,23 @@ The interpreter supports the following Funge-98 specification extensions: Additionally, the following fingerprints are currently supported (more to come): - [3DSP](http://rcfunge98.com/rcsfingers.html#3DSP) +- [BASE](https://rcfunge98.com/rcsfingers.html#BASE) +- [CPLI](https://rcfunge98.com/rcsfingers.html#CPLI) +- [DATE](https://rcfunge98.com/rcsfingers.html#DATE) +- [EVAR](https://rcfunge98.com/rcsfingers.html#EVAR) +- [FIXP](https://rcfunge98.com/rcsfingers.html#FIXP) +- [FPDP](http://rcfunge98.com/rcsfingers.html#FPDP) - [FPSP](http://rcfunge98.com/rcsfingers.html#FPSP) - [HRTI](./docs/catseye/library/HRTI.markdown) - [MODE](./docs/catseye/library/MODE.markdown) - [MODU](./docs/catseye/library/MODU.markdown) - [NULL](./docs/catseye/library/NULL.markdown) - [ORTH](./docs/catseye/library/ORTH.markdown) -- [PERL](./docs/catseye/library/PERL.markdown) +- [PERL](./docs/catseye/library/PERL.markdown) (Disabled by default, needs command line flag) - [REFC](./docs/catseye/library/REFC.markdown) - [ROMA](./docs/catseye/library/ROMA.markdown) - [TOYS](./docs/catseye/library/TOYS.markdown) -- [TURT](./docs/catseye/library/TURT.markdown) +- [TURT](./docs/catseye/library/TURT.markdown) (Broken, disabled in source code, will be fixed in the future) ### Version Release Checklist - Update the version number inside the [pom](./pom.xml) From 117e7b3021c557448f021c730742786ef331d36c Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 11 Feb 2023 13:46:08 +0100 Subject: [PATCH 04/28] implemented DIRF, Restructure IO logic --- README.MD | 5 +- .../jfunge/interpreter/ExecutionContext.java | 20 ++- .../jfunge/interpreter/Interpreter.java | 118 +++++++++++++++--- .../interpreter/PermissionException.java | 8 ++ .../interpreter/instructions/Funge98.java | 29 ++--- .../instructions/fingerprints/DIRF.java | 59 +++++++++ .../jfunge/storage/TestInterpreter.java | 75 ++++++++++- 7 files changed, 265 insertions(+), 49 deletions(-) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/PermissionException.java create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DIRF.java diff --git a/README.MD b/README.MD index 534a185..f1e66db 100644 --- a/README.MD +++ b/README.MD @@ -65,10 +65,11 @@ Additionally, the following fingerprints are currently supported (more to come): - [BASE](https://rcfunge98.com/rcsfingers.html#BASE) - [CPLI](https://rcfunge98.com/rcsfingers.html#CPLI) - [DATE](https://rcfunge98.com/rcsfingers.html#DATE) +- [DIRF](https://rcfunge98.com/rcsfingers.html#DIRF) - [EVAR](https://rcfunge98.com/rcsfingers.html#EVAR) - [FIXP](https://rcfunge98.com/rcsfingers.html#FIXP) -- [FPDP](http://rcfunge98.com/rcsfingers.html#FPDP) -- [FPSP](http://rcfunge98.com/rcsfingers.html#FPSP) +- [FPDP](https://rcfunge98.com/rcsfingers.html#FPDP) +- [FPSP](https://rcfunge98.com/rcsfingers.html#FPSP) - [HRTI](./docs/catseye/library/HRTI.markdown) - [MODE](./docs/catseye/library/MODE.markdown) - [MODU](./docs/catseye/library/MODU.markdown) diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java b/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java index 11658f4..be2f6d7 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java @@ -3,9 +3,7 @@ import com.falsepattern.jfunge.ip.IP; import com.falsepattern.jfunge.ip.IStack; import com.falsepattern.jfunge.storage.FungeSpace; -import org.joml.Vector3i; -import java.io.IOException; import java.io.OutputStream; import java.util.List; import java.util.Map; @@ -47,22 +45,20 @@ public interface ExecutionContext { OutputStream output(); - byte[] readFile(String file); + byte[] readFile(String file) throws PermissionException; - boolean writeFile(String file, byte[] data); + boolean writeFile(String file, byte[] data) throws PermissionException; int envFlags(); - default boolean concurrentAllowed() { - return (envFlags() & 0x01) != 0; - } + boolean changeDirectory(String dir) throws PermissionException; - default boolean fileInputAllowed(String path) throws IOException { - return (envFlags() & 0x02) != 0; - } + boolean makeDirectory(String dir) throws PermissionException; - default boolean fileOutputAllowed(String path) throws IOException { - return (envFlags() & 0x04) != 0; + boolean removeDirectory(String dir) throws PermissionException; + + default boolean concurrentAllowed() { + return (envFlags() & 0x01) != 0; } default boolean syscallAllowed() { diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java index e34b325..99a780b 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java @@ -28,11 +28,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.TreeMap; @Accessors(fluent = true) public class Interpreter implements ExecutionContext { public static final FileIOSupplier DEFAULT_FILE_IO_SUPPLIER = new FileIOSupplier() { + private Path currentDirectory = Paths.get(System.getProperty("user.dir")); @Override public byte[] readFile(String file) throws IOException { return Files.readAllBytes(Paths.get(file)); @@ -43,6 +43,60 @@ public boolean writeFile(String file, byte[] data) throws IOException { Files.write(Paths.get(file), data); return true; } + + @Override + public Path toRealPath(String file) { + var path = Paths.get(file); + if (!path.isAbsolute()) { + path = currentDirectory.resolve(path); + } + try { + return path.toRealPath(); + } catch (IOException e) { + return path.normalize(); + } + } + + @Override + public boolean changeDirectory(String dir) { + val path = toRealPath(dir); + if (Files.isDirectory(path)) { + currentDirectory = path; + return true; + } else { + return false; + } + } + + @Override + public boolean createDirectory(String dir) { + val path = toRealPath(dir); + if (Files.exists(path)) { + return false; + } else { + try { + Files.createDirectory(path); + return true; + } catch (IOException e) { + return false; + } + } + } + + @Override + public boolean deleteDirectory(String dir) { + val path = toRealPath(dir); + if (Files.isDirectory(path)) { + try { + Files.delete(path); + return true; + } catch (IOException e) { + return false; + } + } else { + return false; + } + } }; @Getter private final FungeSpace fungeSpace = new FungeSpace(' '); @@ -331,7 +385,10 @@ public int input(boolean stagger) { } @Override - public byte[] readFile(String file) { + public byte[] readFile(String file) throws PermissionException { + if (fileInputBlocked(file)) { + throw new PermissionException("Cannot read file " + file + " (input not allowed)."); + } try { return fileIOSupplier.readFile(file); } catch (IOException e) { @@ -340,7 +397,10 @@ public byte[] readFile(String file) { } @Override - public boolean writeFile(String file, byte[] data) { + public boolean writeFile(String file, byte[] data) throws PermissionException { + if (fileOutputBlocked(file)) { + throw new PermissionException("Cannot write to file " + file + " (output not allowed)."); + } try { return fileIOSupplier.writeFile(file, data); } catch (IOException e) { @@ -349,23 +409,45 @@ public boolean writeFile(String file, byte[] data) { } @Override - public boolean fileInputAllowed(String file) throws IOException { - if ((envFlags & 0x02) == 0) { - return false; + public boolean changeDirectory(String dir) throws PermissionException { + if (fileInputBlocked(dir)) { + throw new PermissionException("Cannot change directory to " + dir + " (input not allowed)."); } - if (unrestrictedInput) return true; - val path = Paths.get(file).toRealPath(); - return Arrays.stream(allowedInputPaths).anyMatch(path::startsWith); + return fileIOSupplier.changeDirectory(dir); } @Override - public boolean fileOutputAllowed(String file) throws IOException { + public boolean makeDirectory(String dir) throws PermissionException { + if (fileOutputBlocked(dir)) { + throw new PermissionException("Cannot create directory " + dir + " (output not allowed)."); + } + return fileIOSupplier.createDirectory(dir); + } + + @Override + public boolean removeDirectory(String dir) throws PermissionException { + if (fileOutputBlocked(dir)) { + throw new PermissionException("Cannot delete directory " + dir + " (output not allowed)."); + } + return fileIOSupplier.deleteDirectory(dir); + } + + private boolean fileInputBlocked(String file) { + if ((envFlags & 0x02) == 0) { + return true; + } + if (unrestrictedInput) return false; + val path = fileIOSupplier.toRealPath(file); + return Arrays.stream(allowedInputPaths).noneMatch(path::startsWith); + } + + private boolean fileOutputBlocked(String file) { if ((envFlags & 0x04) == 0) { - return false; + return true; } - if (unrestrictedOutput) return true; - val path = Paths.get(file).toRealPath(); - return Arrays.stream(allowedOutputPaths).anyMatch(path::startsWith); + if (unrestrictedOutput) return false; + val path = fileIOSupplier.toRealPath(file); + return Arrays.stream(allowedOutputPaths).noneMatch(path::startsWith); } @Override @@ -402,5 +484,13 @@ public interface FileIOSupplier { byte[] readFile(String file) throws IOException; boolean writeFile(String file, byte[] data) throws IOException; + + Path toRealPath(String file); + + boolean changeDirectory(String dir); + + boolean createDirectory(String dir); + + boolean deleteDirectory(String dir); } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/PermissionException.java b/src/main/java/com/falsepattern/jfunge/interpreter/PermissionException.java new file mode 100644 index 0000000..4ef2936 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/PermissionException.java @@ -0,0 +1,8 @@ +package com.falsepattern.jfunge.interpreter; + +public class PermissionException extends Exception { + + public PermissionException(String message) { + super(message); + } +} diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java index 78c5038..43a8047 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -2,9 +2,11 @@ import com.falsepattern.jfunge.Globals; import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.PermissionException; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.BASE; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.CPLI; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.DATE; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.DIRF; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.EVAR; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FIXP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPDP; @@ -51,6 +53,7 @@ public class Funge98 implements InstructionSet { addFingerprint(BASE.INSTANCE); addFingerprint(CPLI.INSTANCE); addFingerprint(DATE.INSTANCE); + addFingerprint(DIRF.INSTANCE); addFingerprint(EVAR.INSTANCE); addFingerprint(FPSP.INSTANCE); addFingerprint(FPDP.INSTANCE); @@ -564,19 +567,16 @@ public static void input(ExecutionContext ctx) { val flags = s.pop(); val pos = s.popVecDimProof(ctx.dimensions(), mem.vec3i()); pos.add(ctx.IP().storageOffset()); + byte[] file; try { - if (!ctx.fileInputAllowed(filename)) { - System.err.println("Code tried to read file not on whitelist: " + filename); - reflect(ctx); - return; - } - } catch (IOException e) { + file = ctx.readFile(filename); + } catch (PermissionException e) { + System.err.println(e.getMessage()); reflect(ctx); return; } - val file = ctx.readFile(filename); if (file == null) { - ctx.interpret('r'); + reflect(ctx); return; } val delta = ((flags & 1) == 1) ? ctx.fungeSpace().loadBinaryFileAt(pos.x, pos.y, pos.z, file) : ctx.fungeSpace().loadFileAt(pos.x, pos.y, pos.z, file, ctx.dimensions() == 3); @@ -601,19 +601,14 @@ public static void output(ExecutionContext ctx) { delta.z = 1; } pos.add(ctx.IP().storageOffset()); + val data = ctx.fungeSpace().readDataAt(pos.x, pos.y, pos.z, delta.x, delta.y, delta.z, (flags & 1) == 1); try { - if (!ctx.fileOutputAllowed(filename)) { - System.err.println("Code tried to write file not on whitelist: " + filename); + if (!ctx.writeFile(filename, data)) { reflect(ctx); - return; } - } catch (IOException e) { + } catch (PermissionException e) { + System.err.println(e.getMessage()); reflect(ctx); - return; - } - val data = ctx.fungeSpace().readDataAt(pos.x, pos.y, pos.z, delta.x, delta.y, delta.z, (flags & 1) == 1); - if (!ctx.writeFile(filename, data)) { - ctx.interpret('r'); } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DIRF.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DIRF.java new file mode 100644 index 0000000..1edd0f5 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DIRF.java @@ -0,0 +1,59 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.PermissionException; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.val; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class DIRF implements Fingerprint { + public static final DIRF INSTANCE = new DIRF(); + @Override + public int code() { + return 0x44495246; + } + + @Instr('C') + public static void changeDirectory(ExecutionContext ctx) { + val stack = ctx.stack(); + val path = stack.popString(); + try { + if (!ctx.changeDirectory(path)) { + ctx.interpret('r'); + } + } catch (PermissionException e) { + System.err.println(e.getMessage()); + ctx.interpret('r'); + } + } + + @Instr('M') + public static void makeDirectory(ExecutionContext ctx) { + val stack = ctx.stack(); + val path = stack.popString(); + try { + if (!ctx.makeDirectory(path)) { + ctx.interpret('r'); + } + } catch (PermissionException e) { + System.err.println(e.getMessage()); + ctx.interpret('r'); + } + } + + @Instr('R') + public static void removeDirectory(ExecutionContext ctx) { + val stack = ctx.stack(); + val path = stack.popString(); + try { + if (!ctx.removeDirectory(path)) { + ctx.interpret('r'); + } + } catch (PermissionException e) { + System.err.println(e.getMessage()); + ctx.interpret('r'); + } + } +} diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java index 0641917..ba77394 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java @@ -15,6 +15,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -22,15 +24,26 @@ public class TestInterpreter { private static final Interpreter.FileIOSupplier fakeSupplier = new Interpreter.FileIOSupplier() { - private final Map files = new HashMap<>(); + private Path currentDirectory = Paths.get("/"); + + private final Map files; + + { + files = new HashMap<>(); + files.put(Paths.get("/"), null); + } @Override - public byte[] readFile(String file) throws IOException { + public byte[] readFile(String name) throws IOException { + val file = toRealPath(name); if (files.containsKey(file)) { val b = files.get(file); + if (b == null) { + return null; + } return Arrays.copyOf(b, b.length); } else { - try (val s = TestInterpreter.class.getResourceAsStream("/" + file)) { + try (val s = TestInterpreter.class.getResourceAsStream(file.toString())) { if (s == null) { throw new FileNotFoundException("Could not find resource " + file); } @@ -49,9 +62,63 @@ public byte[] readFile(String file) throws IOException { @Override public boolean writeFile(String file, byte[] data) { - files.put(file, Arrays.copyOf(data, data.length)); + val path = toRealPath(file); + if (files.containsKey(path)) { + if (files.get(path) == null) { + return false; + } + } + files.put(path, Arrays.copyOf(data, data.length)); return true; } + + @Override + public Path toRealPath(String file) { + var path = Paths.get(file); + if (path.startsWith("/")) { + return path; + } + if (!path.isAbsolute()) { + path = currentDirectory.resolve(path); + } + return path.normalize(); + } + + @Override + public boolean changeDirectory(String dir) { + val path = toRealPath(dir); + if (files.containsKey(path)) { + if (files.get(path) == null) { + currentDirectory = path; + return true; + } else { + return false; + } + } + return false; + } + + @Override + public boolean createDirectory(String dir) { + val path = toRealPath(dir); + if (files.containsKey(path)) { + return false; + } + files.put(path, null); + return true; + } + + @Override + public boolean deleteDirectory(String dir) { + val path = toRealPath(dir); + if (files.containsKey(path)) { + if (files.get(path) == null) { + files.remove(path); + return true; + } + } + return false; + } }; @SuppressWarnings("SameParameterValue") From 74c40a87f9a056d51a57ee11e75f60620d85d384 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 11 Feb 2023 15:41:00 +0100 Subject: [PATCH 05/28] implemented FING, Restructure fingerprint logic --- README.MD | 1 + .../interpreter/instructions/Funge98.java | 6 +- .../instructions/InstructionManager.java | 6 +- .../instructions/InstructionSet.java | 6 +- .../instructions/fingerprints/FING.java | 92 +++++++++++++++++++ .../instructions/fingerprints/NULL.java | 6 +- 6 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FING.java diff --git a/README.MD b/README.MD index f1e66db..46703da 100644 --- a/README.MD +++ b/README.MD @@ -67,6 +67,7 @@ Additionally, the following fingerprints are currently supported (more to come): - [DATE](https://rcfunge98.com/rcsfingers.html#DATE) - [DIRF](https://rcfunge98.com/rcsfingers.html#DIRF) - [EVAR](https://rcfunge98.com/rcsfingers.html#EVAR) +- [FING](https://rcfunge98.com/rcsfingers.html#FING) - [FIXP](https://rcfunge98.com/rcsfingers.html#FIXP) - [FPDP](https://rcfunge98.com/rcsfingers.html#FPDP) - [FPSP](https://rcfunge98.com/rcsfingers.html#FPSP) diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java index 43a8047..130d6d3 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -8,6 +8,7 @@ import com.falsepattern.jfunge.interpreter.instructions.fingerprints.DATE; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.DIRF; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.EVAR; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FING; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FIXP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPDP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPSP; @@ -20,7 +21,6 @@ import com.falsepattern.jfunge.interpreter.instructions.fingerprints.REFC; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.ROMA; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.TOYS; -import com.falsepattern.jfunge.interpreter.instructions.fingerprints.TURT; import com.falsepattern.jfunge.interpreter.instructions.fingerprints._3DSP; import com.falsepattern.jfunge.ip.IStack; import com.falsepattern.jfunge.ip.impl.Stack; @@ -41,6 +41,7 @@ import java.util.Map; import java.util.function.Consumer; import java.util.function.IntConsumer; +import java.util.function.IntFunction; import java.util.function.ObjIntConsumer; @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -57,6 +58,7 @@ public class Funge98 implements InstructionSet { addFingerprint(EVAR.INSTANCE); addFingerprint(FPSP.INSTANCE); addFingerprint(FPDP.INSTANCE); + addFingerprint(FING.INSTANCE); addFingerprint(FIXP.INSTANCE); addFingerprint(HRTI.INSTANCE); addFingerprint(MODE.INSTANCE); @@ -691,7 +693,7 @@ public void load(ObjIntConsumer instructionSet) { } @Override - public void unload(IntConsumer instructionSet) { + public void unload(IntFunction instructionSet) { throw new UnsupportedOperationException("Cannot unload the base syntax"); } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionManager.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionManager.java index 88685df..52a0d53 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionManager.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionManager.java @@ -31,14 +31,16 @@ private void loadInstruction(Instruction instr, int c) { q.push(instr); } - private void unloadInstruction(int c) { + private Instruction unloadInstruction(int c) { + Instruction instr = null; var q = instructionMap.get(c); if (q != null) { - q.pop(); + instr = q.pop(); if (q.isEmpty()) { instructionMap.remove(c); } } + return instr; } public void loadInstructionSet(InstructionSet set) { diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSet.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSet.java index a97382d..f8928a4 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSet.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSet.java @@ -7,7 +7,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Modifier; import java.util.Arrays; -import java.util.function.IntConsumer; +import java.util.function.IntFunction; import java.util.function.ObjIntConsumer; public interface InstructionSet { @@ -26,13 +26,13 @@ default void load(ObjIntConsumer instructionSet) { }); } - default void unload(IntConsumer instructionSet) { + default void unload(IntFunction instructionSet) { val clazz = this.getClass(); Arrays.stream(clazz.getDeclaredMethods()) .filter((method) -> Modifier.isStatic(method.getModifiers()) && method.isAnnotationPresent(Instr.class)) .forEach((method) -> { val ann = method.getAnnotation(Instr.class); - instructionSet.accept(ann.value()); + instructionSet.apply(ann.value()); }); } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FING.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FING.java new file mode 100644 index 0000000..2fa8773 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FING.java @@ -0,0 +1,92 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; +import com.falsepattern.jfunge.interpreter.instructions.Instruction; +import com.falsepattern.jfunge.interpreter.instructions.InstructionSet; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.val; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.IntFunction; +import java.util.function.ObjIntConsumer; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class FING implements Fingerprint { + public static final FING INSTANCE = new FING(); + + @Override + public int code() { + return 0x46494e47; + } + + private static Instruction popInstr(ExecutionContext ctx, int instruction) { + val result = new AtomicReference(); + ctx.IP().instructionManager().unloadInstructionSet(new InstructionSet() { + @Override + public void unload(IntFunction instructionSet) { + result.set(instructionSet.apply(instruction <= 25 ? instruction + 'A' : instruction)); + } + }); + return result.get(); + } + + private static void pushInstr(ExecutionContext ctx, int instruction, Instruction value) { + ctx.IP().instructionManager().loadInstructionSet(new InstructionSet() { + @Override + public void load(ObjIntConsumer instructionSet) { + instructionSet.accept(value, instruction <= 25 ? instruction + 'A' : instruction); + } + }); + } + + private static boolean invalidValue(int value) { + return (value < 0 || value > 25) && (value < 'A' || value > 'Z'); + } + + @SuppressWarnings("DuplicatedCode") + @Instr('X') + public static void swap(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + val b = stack.pop(); + if (invalidValue(a) || invalidValue(b)) { + ctx.interpret('r'); + return; + } + val aI = Optional.ofNullable(popInstr(ctx, a)).orElse((c) -> c.interpret('r')); + val bI = Optional.ofNullable(popInstr(ctx, b)).orElse((c) -> c.interpret('r')); + pushInstr(ctx, a, bI); + pushInstr(ctx, b, aI); + } + + @Instr('Y') + public static void pop(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + if (invalidValue(a)) { + ctx.interpret('r'); + return; + } + popInstr(ctx, a); + } + + @SuppressWarnings("DuplicatedCode") + @Instr('Z') + public static void move(ExecutionContext ctx) { + val stack = ctx.stack(); + val b = stack.pop(); + val a = stack.pop(); + if (invalidValue(a) || invalidValue(b)) { + ctx.interpret('r'); + return; + } + val aI = popInstr(ctx, a); + if (aI != null) { + pushInstr(ctx, a, aI); + } + pushInstr(ctx, b, Optional.ofNullable(aI).orElse((c) -> c.interpret('r'))); + } +} diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/NULL.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/NULL.java index b6f433b..3d7e6b7 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/NULL.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/NULL.java @@ -5,7 +5,7 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; -import java.util.function.IntConsumer; +import java.util.function.IntFunction; import java.util.function.ObjIntConsumer; @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -20,9 +20,9 @@ public void load(ObjIntConsumer instructionSet) { } @Override - public void unload(IntConsumer instructionSet) { + public void unload(IntFunction instructionSet) { for (int i = 'A'; i <= 'Z'; i++) { - instructionSet.accept(i); + instructionSet.apply(i); } } From 0048d32cc21e1768d5e274af62a95fd4f2670d92 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 11 Feb 2023 17:14:57 +0100 Subject: [PATCH 06/28] update readme --- README.MD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.MD b/README.MD index 46703da..2953b2e 100644 --- a/README.MD +++ b/README.MD @@ -61,7 +61,7 @@ The interpreter supports the following Funge-98 specification extensions: - Optional Trefunge mode (experimental) Additionally, the following fingerprints are currently supported (more to come): -- [3DSP](http://rcfunge98.com/rcsfingers.html#3DSP) +- [3DSP](https://rcfunge98.com/rcsfingers.html#3DSP) - [BASE](https://rcfunge98.com/rcsfingers.html#BASE) - [CPLI](https://rcfunge98.com/rcsfingers.html#CPLI) - [DATE](https://rcfunge98.com/rcsfingers.html#DATE) From db8d0302432c66715c9c924ff3cee8defcd8d602 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 11 Feb 2023 17:15:08 +0100 Subject: [PATCH 07/28] print fingerprint info at the end --- .../jfunge/storage/TestInterpreter.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java index ba77394..630db22 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java @@ -19,6 +19,7 @@ import java.nio.file.Paths; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; public class TestInterpreter { @@ -162,10 +163,17 @@ public void testMycology() { String currentlyActiveFingerprint = null; boolean fingerprintHadError = false; boolean good = true; + val implementedFingerprints = new HashSet(); + val unimplementedFingerprints = new HashSet(); for (val line: txt.split("\n")) { if (line.startsWith("Testing fingerprint ")) { int start = "Testing fingerprint ".length(); currentlyActiveFingerprint = line.substring(start, start + 4); + if (line.endsWith("not loaded.")) { + unimplementedFingerprints.add(currentlyActiveFingerprint); + } else { + implementedFingerprints.add(currentlyActiveFingerprint); + } fingerprintHadError = false; } else if (line.equals("About to test detailed () behaviour with two fingerprints.")) { //Fingerprint core checks are over, stop tracking. @@ -189,6 +197,16 @@ public void testMycology() { System.err.println(line); } } + System.out.println("Implemented fingerprints: "); + for (val fingerprint: implementedFingerprints) { + System.out.print(" "); + System.out.println(fingerprint); + } + System.out.println("Unimplemented fingerprints: "); + for (val fingerprint: unimplementedFingerprints) { + System.out.print(" "); + System.out.println(fingerprint); + } Assertions.assertTrue(good); Assertions.assertEquals(15, returnCode); } From 0ff0ea9c6ecbc03f9d42a7a3b5cee7c2ae271867 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 10:17:10 +0100 Subject: [PATCH 08/28] some small fixes --- .../com/falsepattern/jfunge/interpreter/Interpreter.java | 5 +++-- .../jfunge/interpreter/instructions/Funge98.java | 2 +- .../com/falsepattern/jfunge/storage/TestInterpreter.java | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java index 99a780b..efada0c 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java @@ -464,12 +464,13 @@ public void tick() { currentIP = null; for (int i = 0; i < IPs.size(); i++) { currentIP = IPs.get(i); + if (!IP().dead()) { + interpret(fungeSpace().get(IP().position())); + } if (IP().dead()) { IPs.remove(i); i--; - continue; } - interpret(fungeSpace().get(IP().position())); if (clone != null) { IPs.add(i++, clone); clone = null; diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java index 130d6d3..d7fb394 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -103,7 +103,7 @@ public static void loadFinger(ExecutionContext ctx) { @Instr(')') public static void unloadFinger(ExecutionContext ctx) { val code = getFingerCode(ctx.stack()); - if (fingerprints.containsKey(code)) { + if (fingerprints.containsKey(code) && ctx.fingerprintAllowed(code)) { ctx.IP().instructionManager().unloadInstructionSet(fingerprints.get(code)); } else { ctx.interpret('r'); diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java index 630db22..9278cbc 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java @@ -155,6 +155,7 @@ public void testMycology() { .allowedOutputFiles(new String[]{"/"}) .sysCall(false) .concurrent(true) + .environment(false) .perl(true) .maxIter(300000L) .build(); From 73415ef5d0a538142d2fc90c7ba76ee0be3f3a5a Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 11:22:24 +0100 Subject: [PATCH 09/28] update to java 17, switch from LambdaMetaFactory to ASM for performance, instruction set optimizations --- pom.xml | 23 ++++- .../java/com/falsepattern/jfunge/Main.java | 1 - .../jfunge/interpreter/Interpreter.java | 1 - .../interpreter/instructions/Funge98.java | 11 ++- .../instructions/InstructionFactory.java | 89 +++++++++++++++++++ .../instructions/InstructionManager.java | 1 - .../instructions/InstructionSet.java | 26 +----- .../instructions/InstructionSetHelper.java | 52 +++++++++++ .../instructions/fingerprints/DATE.java | 1 - .../instructions/fingerprints/FING.java | 8 +- .../instructions/fingerprints/MODE.java | 1 - .../instructions/fingerprints/NULL.java | 8 +- .../falsepattern/jfunge/storage/Chunk.java | 1 - .../jfunge/storage/FungeSpace.java | 1 - .../jfunge/storage/TestChunk.java | 1 - .../jfunge/storage/TestInterpreter.java | 1 - 16 files changed, 180 insertions(+), 46 deletions(-) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionFactory.java create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSetHelper.java diff --git a/pom.xml b/pom.xml index 8ba9421..d241fea 100644 --- a/pom.xml +++ b/pom.xml @@ -9,11 +9,12 @@ 1.1.0 - 8 + 17 1.18.24 3.0.3 1.10.2 1.5.0 + 9.4 UTF-8 mavenpattern https://mvn.falsepattern.com/releases/ @@ -58,6 +59,26 @@ ${commons-io.version} test + + org.ow2.asm + asm + ${asm.version} + + + org.ow2.asm + asm-tree + ${asm.version} + + + org.ow2.asm + asm-commons + ${asm.version} + + + org.ow2.asm + asm-util + ${asm.version} + diff --git a/src/main/java/com/falsepattern/jfunge/Main.java b/src/main/java/com/falsepattern/jfunge/Main.java index 7d5683e..7c99b37 100644 --- a/src/main/java/com/falsepattern/jfunge/Main.java +++ b/src/main/java/com/falsepattern/jfunge/Main.java @@ -3,7 +3,6 @@ import com.falsepattern.jfunge.interpreter.FeatureSet; import com.falsepattern.jfunge.interpreter.Interpreter; import lombok.val; -import lombok.var; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java index efada0c..6d04e00 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java @@ -15,7 +15,6 @@ import lombok.SneakyThrows; import lombok.experimental.Accessors; import lombok.val; -import lombok.var; import java.io.IOException; import java.io.InputStream; diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java index d7fb394..72692bb 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -32,7 +32,6 @@ import lombok.NoArgsConstructor; import lombok.SneakyThrows; import lombok.val; -import lombok.var; import java.io.File; import java.io.IOException; @@ -680,20 +679,20 @@ public static void binop(ExecutionContext ctx, BinaryOperator op) { } @Override - public void load(ObjIntConsumer instructionSet) { - InstructionSet.super.load(instructionSet); + public void load(ObjIntConsumer loader) { + InstructionSet.super.load(loader); for (int i = 0; i <= 9; i++) { int finalI = i; - instructionSet.accept((ctx) -> ctx.stack().push(finalI), '0' + i); + loader.accept((ctx) -> ctx.stack().push(finalI), '0' + i); } for (int i = 0; i <= 5; i++) { int finalI = i; - instructionSet.accept((ctx) -> ctx.stack().push(10 + finalI), 'a' + i); + loader.accept((ctx) -> ctx.stack().push(10 + finalI), 'a' + i); } } @Override - public void unload(IntFunction instructionSet) { + public void unload(IntFunction unLoader) { throw new UnsupportedOperationException("Cannot unload the base syntax"); } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionFactory.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionFactory.java new file mode 100644 index 0000000..495944c --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionFactory.java @@ -0,0 +1,89 @@ +package com.falsepattern.jfunge.interpreter.instructions; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import lombok.val; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +class InstructionFactory extends ClassLoader { + private static final InstructionFactory INSTANCE = new InstructionFactory(); + private final Map proxies = new HashMap<>(); + private final Map> nameToClass = new HashMap<>(); + + private InstructionFactory() {} + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (nameToClass.containsKey(name)) { + return nameToClass.get(name); + } + return super.findClass(name); + } + + private static final AtomicLong counter = new AtomicLong(); + + static Instruction createInstruction(Method method) { + return INSTANCE.createInstructionImpl(method); + } + + private Instruction createInstructionImpl(Method method) { + if (!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers())) { + throw new IllegalArgumentException("createInstruction only works with public static methods! " + method.getDeclaringClass().getName() + "." + method.getName() + " is not an interface!"); + } + if (!proxies.containsKey(method)) { + defineInstructionProxy(method); + } + return proxies.get(method); + } + + private static final String[] INTERFACES_INTERNAL_NAME; + private static final String DESCRIPTOR; + + static { + try { + INTERFACES_INTERNAL_NAME = new String[]{Type.getInternalName(Instruction.class)}; + DESCRIPTOR = Type.getMethodDescriptor(Instruction.class.getMethod("process", ExecutionContext.class)); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + private void defineInstructionProxy(Method targetMethod) { + try { + val declClass = targetMethod.getDeclaringClass(); + val newClassName = "com/falsepattern/jfunge/interpreter/instructions/proxy/" + (declClass.getName() + "." + targetMethod.getName()).replace('.', '_').replace('$', '_') + "_" + counter.incrementAndGet(); + val writer = new ClassWriter(0); + writer.visit(Opcodes.V17, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, newClassName, null, "java/lang/Object", INTERFACES_INTERNAL_NAME); + val method = writer.visitMethod(Opcodes.ACC_PUBLIC, "process", DESCRIPTOR, null, null); + method.visitVarInsn(Opcodes.ALOAD, 1); + method.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(declClass), targetMethod.getName(), DESCRIPTOR, false); + method.visitInsn(Opcodes.RETURN); + method.visitMaxs(1, 2); + method.visitEnd(); + val initMethod = writer.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); + initMethod.visitVarInsn(Opcodes.ALOAD, 0); + initMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); + initMethod.visitInsn(Opcodes.RETURN); + initMethod.visitMaxs(1, 1); + initMethod.visitEnd(); + writer.visitEnd(); + var bytes = writer.toByteArray(); + var name = newClassName.replace('/', '.'); + var cl = (Class) defineClass(name, bytes, 0, bytes.length); + var constructor = cl.getConstructor(); + proxies.put(targetMethod, constructor.newInstance()); + nameToClass.put(name, cl); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionManager.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionManager.java index 52a0d53..a84962e 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionManager.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionManager.java @@ -5,7 +5,6 @@ import gnu.trove.map.hash.TIntObjectHashMap; import lombok.NoArgsConstructor; import lombok.val; -import lombok.var; import java.util.ArrayDeque; import java.util.Deque; diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSet.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSet.java index f8928a4..de94e59 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSet.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSet.java @@ -1,6 +1,5 @@ package com.falsepattern.jfunge.interpreter.instructions; -import com.falsepattern.jfunge.interpreter.LambdaHelper; import lombok.val; import java.lang.annotation.Retention; @@ -11,29 +10,12 @@ import java.util.function.ObjIntConsumer; public interface InstructionSet { - default void load(ObjIntConsumer instructionSet) { - val clazz = this.getClass(); - Arrays.stream(clazz.getDeclaredMethods()) - .filter((method) -> Modifier.isStatic(method.getModifiers()) && method.isAnnotationPresent(Instr.class)) - .forEach((method) -> { - try { - val lambda = (Instruction) LambdaHelper.newLambdaMetaFactory(Instruction.class, method).invokeExact(); - val ann = method.getAnnotation(Instr.class); - instructionSet.accept(lambda, ann.value()); - } catch (Throwable e) { - throw new RuntimeException(e); - } - }); + default void load(ObjIntConsumer loader) { + InstructionSetHelper.loadInstructionSet(this.getClass(), loader); } - default void unload(IntFunction instructionSet) { - val clazz = this.getClass(); - Arrays.stream(clazz.getDeclaredMethods()) - .filter((method) -> Modifier.isStatic(method.getModifiers()) && method.isAnnotationPresent(Instr.class)) - .forEach((method) -> { - val ann = method.getAnnotation(Instr.class); - instructionSet.apply(ann.value()); - }); + default void unload(IntFunction unLoader) { + InstructionSetHelper.unloadInstructionSet(this.getClass(), unLoader); } @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSetHelper.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSetHelper.java new file mode 100644 index 0000000..3bd64e8 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSetHelper.java @@ -0,0 +1,52 @@ +package com.falsepattern.jfunge.interpreter.instructions; + +import com.falsepattern.jfunge.interpreter.LambdaHelper; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.val; + +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.IntFunction; +import java.util.function.ObjIntConsumer; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +final class InstructionSetHelper { + private static final Map, Map> sets = new HashMap<>(); + + private static Map retrieve(Class instructionSet) { + if (!sets.containsKey(instructionSet)) { + val currentSet = new HashMap(); + for (val method: instructionSet.getDeclaredMethods()) { + if (!Modifier.isStatic(method.getModifiers()) || !method.isAnnotationPresent(InstructionSet.Instr.class)) { + continue; + } + val proxy = InstructionFactory.createInstruction(method); + val ann = method.getAnnotation(InstructionSet.Instr.class); + val id = ann.value(); + currentSet.put(id, proxy); + } + sets.put(instructionSet, currentSet); + return currentSet; + } else { + return sets.get(instructionSet); + } + } + + static void loadInstructionSet(Class instructionSet, ObjIntConsumer loader) { + val set = retrieve(instructionSet); + for (val entry: set.entrySet()) { + loader.accept(entry.getValue(), entry.getKey()); + } + } + + static void unloadInstructionSet(Class instructionSet, IntFunction unLoader) { + val set = retrieve(instructionSet); + for (val i: set.keySet()) { + unLoader.apply(i); + } + } +} diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DATE.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DATE.java index 1d4df4e..bc52b27 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DATE.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DATE.java @@ -4,7 +4,6 @@ import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; import lombok.NoArgsConstructor; import lombok.val; -import lombok.var; import java.time.DateTimeException; import java.time.Duration; diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FING.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FING.java index 2fa8773..9a7f4d3 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FING.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FING.java @@ -26,8 +26,8 @@ private static Instruction popInstr(ExecutionContext ctx, int instruction) { val result = new AtomicReference(); ctx.IP().instructionManager().unloadInstructionSet(new InstructionSet() { @Override - public void unload(IntFunction instructionSet) { - result.set(instructionSet.apply(instruction <= 25 ? instruction + 'A' : instruction)); + public void unload(IntFunction unLoader) { + result.set(unLoader.apply(instruction <= 25 ? instruction + 'A' : instruction)); } }); return result.get(); @@ -36,8 +36,8 @@ public void unload(IntFunction instructionSet) { private static void pushInstr(ExecutionContext ctx, int instruction, Instruction value) { ctx.IP().instructionManager().loadInstructionSet(new InstructionSet() { @Override - public void load(ObjIntConsumer instructionSet) { - instructionSet.accept(value, instruction <= 25 ? instruction + 'A' : instruction); + public void load(ObjIntConsumer loader) { + loader.accept(value, instruction <= 25 ? instruction + 'A' : instruction); } }); } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/MODE.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/MODE.java index 3fcb3dc..e06855a 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/MODE.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/MODE.java @@ -7,7 +7,6 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.val; -import lombok.var; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class MODE implements Fingerprint { diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/NULL.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/NULL.java index 3d7e6b7..942205c 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/NULL.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/NULL.java @@ -13,16 +13,16 @@ public class NULL implements Fingerprint { public static final NULL INSTANCE = new NULL(); @Override - public void load(ObjIntConsumer instructionSet) { + public void load(ObjIntConsumer loader) { for (int i = 'A'; i <= 'Z'; i++) { - instructionSet.accept((ctx) -> ctx.interpret('r'), i); + loader.accept((ctx) -> ctx.interpret('r'), i); } } @Override - public void unload(IntFunction instructionSet) { + public void unload(IntFunction unLoader) { for (int i = 'A'; i <= 'Z'; i++) { - instructionSet.apply(i); + unLoader.apply(i); } } diff --git a/src/main/java/com/falsepattern/jfunge/storage/Chunk.java b/src/main/java/com/falsepattern/jfunge/storage/Chunk.java index bf27747..0658583 100644 --- a/src/main/java/com/falsepattern/jfunge/storage/Chunk.java +++ b/src/main/java/com/falsepattern/jfunge/storage/Chunk.java @@ -4,7 +4,6 @@ import com.falsepattern.jfunge.Releasable; import lombok.AccessLevel; import lombok.NoArgsConstructor; -import lombok.var; import org.joml.Vector3ic; import java.util.Arrays; diff --git a/src/main/java/com/falsepattern/jfunge/storage/FungeSpace.java b/src/main/java/com/falsepattern/jfunge/storage/FungeSpace.java index af04b17..f202d4a 100644 --- a/src/main/java/com/falsepattern/jfunge/storage/FungeSpace.java +++ b/src/main/java/com/falsepattern/jfunge/storage/FungeSpace.java @@ -5,7 +5,6 @@ import gnu.trove.map.hash.TIntObjectHashMap; import lombok.RequiredArgsConstructor; import lombok.val; -import lombok.var; import org.joml.Vector3i; import org.joml.Vector3ic; diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestChunk.java b/src/test/java/com/falsepattern/jfunge/storage/TestChunk.java index c2e5a22..293b86f 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestChunk.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestChunk.java @@ -3,7 +3,6 @@ import lombok.Cleanup; import lombok.val; -import lombok.var; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java index 9278cbc..ee4a462 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java @@ -3,7 +3,6 @@ import com.falsepattern.jfunge.interpreter.FeatureSet; import com.falsepattern.jfunge.interpreter.Interpreter; import lombok.val; -import lombok.var; import org.apache.commons.io.output.TeeOutputStream; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; From 450b0e7098ebb81c73ec3ad545d10b84d151dcc2 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 12:59:54 +0100 Subject: [PATCH 10/28] FungeSpace performance improvements (improved gc logic and bound checks) --- .../jfunge/storage/FungeSpace.java | 71 +++++++++++++------ 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/falsepattern/jfunge/storage/FungeSpace.java b/src/main/java/com/falsepattern/jfunge/storage/FungeSpace.java index f202d4a..562351f 100644 --- a/src/main/java/com/falsepattern/jfunge/storage/FungeSpace.java +++ b/src/main/java/com/falsepattern/jfunge/storage/FungeSpace.java @@ -1,7 +1,8 @@ package com.falsepattern.jfunge.storage; import com.falsepattern.jfunge.Copiable; -import gnu.trove.map.TIntObjectMap; +import gnu.trove.list.TIntList; +import gnu.trove.list.array.TIntArrayList; import gnu.trove.map.hash.TIntObjectHashMap; import lombok.RequiredArgsConstructor; import lombok.val; @@ -22,20 +23,45 @@ @RequiredArgsConstructor public class FungeSpace implements Copiable { - private final TIntObjectMap>> storage = new TIntObjectHashMap<>(); + private static class KeyTrackingMap extends TIntObjectHashMap { + public final TIntList keys = new TIntArrayList(); + + public KeyTrackingMap() { + } + + @Override + public T remove(int key) { + val ret = super.remove(key); + if (ret != null) { + keys.remove(key); + } + return ret; + } + + @Override + public T put(int key, T value) { + val ret = super.put(key, value); + if (ret == null) { + keys.add(key); + } + return ret; + } + } + private final KeyTrackingMap>> storage = new KeyTrackingMap<>(); private final Vector3i cachePos = new Vector3i(); private final Bounds bounds = new Bounds(); private final int defaultValue; private Chunk cacheChunk; private boolean boundsRecheck = false; + private boolean needGC = false; private FungeSpace(FungeSpace original) { original.storage.forEachEntry((z, oPlane) -> { - val nPlane = new TIntObjectHashMap>(); + val nPlane = new KeyTrackingMap>(); storage.put(z, nPlane); oPlane.forEachEntry((y, oRow) -> { - val nRow = new TIntObjectHashMap(); + val nRow = new KeyTrackingMap(); nPlane.put(y, nRow); oRow.forEachEntry((x, oChunk) -> { val nChunk = oChunk.deepCopy(); @@ -99,12 +125,12 @@ public void set(int x, int y, int z, int value) { } var plane = storage.get(cZ); if (plane == null) { - plane = new TIntObjectHashMap<>(); + plane = new KeyTrackingMap<>(); storage.put(cZ, plane); } var row = plane.get(cY); if (row == null) { - row = new TIntObjectHashMap<>(); + row = new KeyTrackingMap<>(); plane.put(cY, row); } var chunk = row.get(cX); @@ -115,6 +141,7 @@ public void set(int x, int y, int z, int value) { row.put(cX, chunk); } boundsRecheck |= chunk.set(inChunkX(x), inChunkY(y), inChunkZ(z), value); + needGC |= boundsRecheck && chunk.isEmpty(); cacheChunk = chunk; cachePos.set(cX, cY, cZ); } @@ -125,14 +152,11 @@ public void set(Vector3ic v, int value) { public void gc() { cacheChunk = null; - val planes = storage.keys(); - for (val iPlane : planes) { + for (val iPlane : storage.keys.toArray()) { val plane = storage.get(iPlane); - val rows = plane.keys(); - for (val iRow : rows) { + for (val iRow : plane.keys.toArray()) { val row = plane.get(iRow); - val chunks = row.keys(); - for (val iChunk : chunks) { + for (val iChunk : row.keys.toArray()) { val chunk = row.get(iChunk); if (chunk.isEmpty()) { row.remove(iChunk); @@ -237,7 +261,10 @@ public BoundsC bounds() { public void recheckBounds() { if (!boundsRecheck) return; - gc(); + if (needGC) { + gc(); + needGC = false; + } boundsRecheck = false; if (storage.size() == 0) { bounds.zero(); @@ -250,15 +277,15 @@ public void recheckBounds() { int xMinFinal = Integer.MAX_VALUE; int xMaxFinal = Integer.MIN_VALUE; int[] mm = new int[2]; - int[] cZArr = storage.keys(); + int[] cZArr = storage.keys.toArray(); minMax(cZArr, mm); int cZMin = mm[0]; int cZMax = mm[1]; int best = Integer.MAX_VALUE; var plane = storage.get(cZMin); - for (val iRow : plane.keys()) { + for (val iRow : plane.keys.toArray()) { val row = plane.get(iRow); - for (val iChunk : row.keys()) { + for (val iChunk : row.keys.toArray()) { val chunk = row.get(iChunk); int minZ = chunk.minZ(); if (minZ < best) { @@ -269,9 +296,9 @@ public void recheckBounds() { zMinFinal = fromChunkZ(cZMin) + best; best = Integer.MIN_VALUE; plane = storage.get(cZMax); - for (val iRow : plane.keys()) { + for (val iRow : plane.keys.toArray()) { val row = plane.get(iRow); - for (val iChunk : row.keys()) { + for (val iChunk : row.keys.toArray()) { val chunk = row.get(iChunk); int maxZ = chunk.maxZ(); if (maxZ > best) { @@ -284,13 +311,13 @@ public void recheckBounds() { plane = storage.get(cZ); int yMin; int yMax; - int[] cYArr = plane.keys(); + int[] cYArr = plane.keys.toArray(); minMax(cYArr, mm); int cYMin = mm[0]; int cYMax = mm[1]; best = Integer.MAX_VALUE; var row = plane.get(cYMin); - for (val iChunk : row.keys()) { + for (val iChunk : row.keys.toArray()) { val chunk = row.get(iChunk); int minY = chunk.minY(); if (minY < best) { @@ -300,7 +327,7 @@ public void recheckBounds() { yMin = fromChunkY(cYMin) + best; best = Integer.MIN_VALUE; row = plane.get(cYMax); - for (val iChunk : row.keys()) { + for (val iChunk : row.keys.toArray()) { val chunk = row.get(iChunk); int maxY = chunk.maxY(); if (maxY > best) { @@ -314,7 +341,7 @@ public void recheckBounds() { row = plane.get(cY); int xMin; int xMax; - int[] cXArr = row.keys(); + int[] cXArr = row.keys.toArray(); minMax(cXArr, mm); int cXMin = mm[0]; int cXMax = mm[1]; From 9bd7a9d73934b2750fe4a6bf2eae120800868a70 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 13:00:26 +0100 Subject: [PATCH 11/28] 3DSP performance improvements (matrix optimizations) --- .../instructions/fingerprints/_3DSP.java | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/_3DSP.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/_3DSP.java index e7aeb74..b602bd9 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/_3DSP.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/_3DSP.java @@ -28,22 +28,49 @@ private interface Op { private static Matrix4f getMatrix(ExecutionContext ctx, Vector3i origin, Matrix4f output) { val space = ctx.fungeSpace(); - for (int y = 0; y < 4; y++) { - for (int x = 0; x < 4; x++) { - output.set(x, y, Float.intBitsToFloat(space.get(origin.x + x, origin.y + y, origin.z))); - } - } - output.determineProperties(); + val x = origin.x; + val y = origin.y; + val z = origin.z; + output.set(Float.intBitsToFloat(space.get(x, y, z)), + Float.intBitsToFloat(space.get(x, y + 1, z)), + Float.intBitsToFloat(space.get(x, y + 2, z)), + Float.intBitsToFloat(space.get(x, y + 3, z)), + Float.intBitsToFloat(space.get(x + 1, y, z)), + Float.intBitsToFloat(space.get(x + 1, y + 1, z)), + Float.intBitsToFloat(space.get(x + 1, y + 2, z)), + Float.intBitsToFloat(space.get(x + 1, y + 3, z)), + Float.intBitsToFloat(space.get(x + 2, y, z)), + Float.intBitsToFloat(space.get(x + 2, y + 1, z)), + Float.intBitsToFloat(space.get(x + 2, y + 2, z)), + Float.intBitsToFloat(space.get(x + 2, y + 3, z)), + Float.intBitsToFloat(space.get(x + 3, y, z)), + Float.intBitsToFloat(space.get(x + 3, y + 1, z)), + Float.intBitsToFloat(space.get(x + 3, y + 2, z)), + Float.intBitsToFloat(space.get(x + 3, y + 3, z))); return output; } private static void putMatrix(ExecutionContext ctx, Vector3i origin, Matrix4f matrix) { val space = ctx.fungeSpace(); - for (int y = 0; y < 4; y++) { - for (int x = 0; x < 4; x++) { - space.set(origin.x + x, origin.y + y, origin.z, Float.floatToRawIntBits(matrix.get(x, y))); - } - } + val x = origin.x; + val y = origin.y; + val z = origin.z; + space.set(x, y, z, Float.floatToRawIntBits(matrix.m00())); + space.set(x, y + 1, z, Float.floatToRawIntBits(matrix.m01())); + space.set(x, y + 2, z, Float.floatToRawIntBits(matrix.m02())); + space.set(x, y + 3, z, Float.floatToRawIntBits(matrix.m03())); + space.set(x + 1, y, z, Float.floatToRawIntBits(matrix.m10())); + space.set(x + 1, y + 1, z, Float.floatToRawIntBits(matrix.m11())); + space.set(x + 1, y + 2, z, Float.floatToRawIntBits(matrix.m12())); + space.set(x + 1, y + 3, z, Float.floatToRawIntBits(matrix.m13())); + space.set(x + 2, y, z, Float.floatToRawIntBits(matrix.m20())); + space.set(x + 2, y + 1, z, Float.floatToRawIntBits(matrix.m21())); + space.set(x + 2, y + 2, z, Float.floatToRawIntBits(matrix.m22())); + space.set(x + 2, y + 3, z, Float.floatToRawIntBits(matrix.m23())); + space.set(x + 3, y, z, Float.floatToRawIntBits(matrix.m30())); + space.set(x + 3, y + 1, z, Float.floatToRawIntBits(matrix.m31())); + space.set(x + 3, y + 2, z, Float.floatToRawIntBits(matrix.m32())); + space.set(x + 3, y + 3, z, Float.floatToRawIntBits(matrix.m33())); } private static void binOp(ExecutionContext ctx, Op op) { From c28acc0c2397a78611d4ace5fc825f39822d87f4 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 13:07:34 +0100 Subject: [PATCH 12/28] replaced all context.interpret('r') calls with context.IP().reflect() --- .../jfunge/interpreter/Interpreter.java | 4 +--- .../interpreter/instructions/Funge98.java | 18 +++++++++--------- .../instructions/fingerprints/DATE.java | 16 ++++++++-------- .../instructions/fingerprints/DIRF.java | 12 ++++++------ .../instructions/fingerprints/EVAR.java | 6 +++--- .../instructions/fingerprints/FING.java | 12 ++++++------ .../instructions/fingerprints/FIXP.java | 6 +++--- .../instructions/fingerprints/FPDP.java | 2 +- .../instructions/fingerprints/FPSP.java | 2 +- .../instructions/fingerprints/HRTI.java | 4 ++-- .../instructions/fingerprints/MODE.java | 4 ++-- .../instructions/fingerprints/NULL.java | 2 +- .../instructions/fingerprints/TOYS.java | 10 +++++----- .../instructions/fingerprints/TURT.java | 2 +- .../instructions/fingerprints/_3DSP.java | 2 +- 15 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java index 6d04e00..c02b283 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java @@ -310,9 +310,7 @@ public void interpret(int opcode) { if ((instr = IP().instructionManager().fetch(opcode)) != null || (instr = baseInstructionManager.fetch(opcode)) != null) { instr.process(this); } else { - if (opcode == 'r') - throw new IllegalArgumentException("Language does not implement 'r' reflect instruction."); - interpret('r'); + IP().reflect(); } } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java index 72692bb..acab5bb 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -95,7 +95,7 @@ public static void loadFinger(ExecutionContext ctx) { stack.push(1); ctx.IP().instructionManager().loadInstructionSet(fingerprints.get(code)); } else { - ctx.interpret('r'); + ctx.IP().reflect(); } } @@ -105,7 +105,7 @@ public static void unloadFinger(ExecutionContext ctx) { if (fingerprints.containsKey(code) && ctx.fingerprintAllowed(code)) { ctx.IP().instructionManager().unloadInstructionSet(fingerprints.get(code)); } else { - ctx.interpret('r'); + ctx.IP().reflect(); } } @@ -124,7 +124,7 @@ public static void high(ExecutionContext ctx) { if (ctx.dimensions() == 3) ctx.IP().delta().set(0, 0, 1); else - ctx.interpret('r'); + ctx.IP().reflect(); } @Instr('l') @@ -132,7 +132,7 @@ public static void low(ExecutionContext ctx) { if (ctx.dimensions() == 3) ctx.IP().delta().set(0, 0, -1); else - ctx.interpret('r'); + ctx.IP().reflect(); } @Instr('?') @@ -283,7 +283,7 @@ public static void branchHighLow(ExecutionContext ctx) { if (ctx.dimensions() == 3) { ctx.interpret(ctx.stack().pop() == 0 ? 'h' : 'l'); } else { - ctx.interpret('r'); + ctx.IP().reflect(); } } @@ -394,7 +394,7 @@ public static void duplicate(ExecutionContext ctx) { public static void blockStart(ExecutionContext ctx) { @Cleanup val mem = MemoryStack.stackPush(); if (!ctx.IP().stackStack().push()) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } val SOSS = ctx.IP().stackStack().SOSS().get(); @@ -426,7 +426,7 @@ public static void blockEnd(ExecutionContext ctx) { val TOSS = ctx.stack(); val SOSSt = ctx.IP().stackStack().SOSS(); if (!SOSSt.isPresent() || !ctx.IP().stackStack().pop()) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } val SOSS = SOSSt.get(); @@ -454,7 +454,7 @@ public static void stackUnderStack(ExecutionContext ctx) { val TOSS = ctx.stack(); val SOSSt = ctx.IP().stackStack().SOSS(); if (!SOSSt.isPresent()) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } val SOSS = SOSSt.get(); @@ -640,7 +640,7 @@ public static void readInt(ExecutionContext ctx) { if (found) { ctx.stack().push(counter); } else { - ctx.interpret('r'); + ctx.IP().reflect(); } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DATE.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DATE.java index bc52b27..5a28b10 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DATE.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DATE.java @@ -26,7 +26,7 @@ public static void addDaysToDate(ExecutionContext ctx) { val m = stack.pop(); val y = stack.pop(); if (y == 0) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } val date = dateOrReflect(ctx, () -> LocalDate.of(y, m, d)); @@ -49,7 +49,7 @@ public static void julianDayToDate(ExecutionContext ctx) { } var y = date.getYear(); if (y == 0) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } if (y < 0) y--; @@ -68,7 +68,7 @@ public static void daysBetweenDates(ExecutionContext ctx) { val m1 = stack.pop(); val y1 = stack.pop(); if (y1 == 0 || y2 == 0) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } val date1 = dateOrReflect(ctx, () -> LocalDate.of(y1, m1, d1)); @@ -89,7 +89,7 @@ public static void dateToJulianDay(ExecutionContext ctx) { val m = stack.pop(); var y = stack.pop(); if (y == 0) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } if (y < 0) y++; @@ -109,7 +109,7 @@ public static void yearPlusDayToDate(ExecutionContext ctx) { val day = stack.pop() + 1; val year = stack.pop(); if (year == 0) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } val date = dateOrReflect(ctx, () -> LocalDate.ofYearDay(year, day)); @@ -128,7 +128,7 @@ public static void dayOfWeek(ExecutionContext ctx) { val m = stack.pop(); val y = stack.pop(); if (y == 0) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } val date = dateOrReflect(ctx, () -> LocalDate.of(y, m, d)); @@ -145,7 +145,7 @@ public static void dayOfYear(ExecutionContext ctx) { val m = stack.pop(); val y = stack.pop(); if (y == 0) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } val date = dateOrReflect(ctx, () -> LocalDate.of(y, m, d)); @@ -163,7 +163,7 @@ private static LocalDate dateOrReflect(ExecutionContext ctx, DateSupplier suppli try { return supplier.supply(); } catch (DateTimeException e) { - ctx.interpret('r'); + ctx.IP().reflect(); return null; } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DIRF.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DIRF.java index 1edd0f5..30a29b1 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DIRF.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DIRF.java @@ -21,11 +21,11 @@ public static void changeDirectory(ExecutionContext ctx) { val path = stack.popString(); try { if (!ctx.changeDirectory(path)) { - ctx.interpret('r'); + ctx.IP().reflect(); } } catch (PermissionException e) { System.err.println(e.getMessage()); - ctx.interpret('r'); + ctx.IP().reflect(); } } @@ -35,11 +35,11 @@ public static void makeDirectory(ExecutionContext ctx) { val path = stack.popString(); try { if (!ctx.makeDirectory(path)) { - ctx.interpret('r'); + ctx.IP().reflect(); } } catch (PermissionException e) { System.err.println(e.getMessage()); - ctx.interpret('r'); + ctx.IP().reflect(); } } @@ -49,11 +49,11 @@ public static void removeDirectory(ExecutionContext ctx) { val path = stack.popString(); try { if (!ctx.removeDirectory(path)) { - ctx.interpret('r'); + ctx.IP().reflect(); } } catch (PermissionException e) { System.err.println(e.getMessage()); - ctx.interpret('r'); + ctx.IP().reflect(); } } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/EVAR.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/EVAR.java index 2f23ec8..4e4b48d 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/EVAR.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/EVAR.java @@ -19,7 +19,7 @@ public static void getEnvironmentVariable(ExecutionContext ctx) { val key = stack.popString(); val value = ctx.env().get(key); if (value == null) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } else { stack.pushString(value); @@ -37,7 +37,7 @@ public static void setEnvironmentVariable(ExecutionContext ctx) { val keyValuePar = stack.popString(); val equalsIndex = keyValuePar.indexOf('='); if (equalsIndex == -1) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } val key = keyValuePar.substring(0, equalsIndex); @@ -54,7 +54,7 @@ public static void getEnvironmentVariableAtIndex(ExecutionContext ctx) { val index = stack.pop(); val keys = ctx.envKeys(); if (index < 0 || index >= keys.size()) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } val key = keys.get(index); diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FING.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FING.java index 9a7f4d3..dab1a55 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FING.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FING.java @@ -53,11 +53,11 @@ public static void swap(ExecutionContext ctx) { val a = stack.pop(); val b = stack.pop(); if (invalidValue(a) || invalidValue(b)) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } - val aI = Optional.ofNullable(popInstr(ctx, a)).orElse((c) -> c.interpret('r')); - val bI = Optional.ofNullable(popInstr(ctx, b)).orElse((c) -> c.interpret('r')); + val aI = Optional.ofNullable(popInstr(ctx, a)).orElse((c) -> c.IP().reflect()); + val bI = Optional.ofNullable(popInstr(ctx, b)).orElse((c) -> c.IP().reflect()); pushInstr(ctx, a, bI); pushInstr(ctx, b, aI); } @@ -67,7 +67,7 @@ public static void pop(ExecutionContext ctx) { val stack = ctx.stack(); val a = stack.pop(); if (invalidValue(a)) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } popInstr(ctx, a); @@ -80,13 +80,13 @@ public static void move(ExecutionContext ctx) { val b = stack.pop(); val a = stack.pop(); if (invalidValue(a) || invalidValue(b)) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } val aI = popInstr(ctx, a); if (aI != null) { pushInstr(ctx, a, aI); } - pushInstr(ctx, b, Optional.ofNullable(aI).orElse((c) -> c.interpret('r'))); + pushInstr(ctx, b, Optional.ofNullable(aI).orElse((c) -> c.IP().reflect())); } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FIXP.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FIXP.java index d891ea5..4864678 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FIXP.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FIXP.java @@ -29,7 +29,7 @@ public static void acos(ExecutionContext ctx) { val a = stack.pop(); val result = Math.toDegrees(Math.acos(a / 10000D)); if (Double.isNaN(result)) { - ctx.interpret('r'); + ctx.IP().reflect(); } else { stack.push((int) (result * 10000)); } @@ -62,7 +62,7 @@ public static void asin(ExecutionContext ctx) { val a = stack.pop(); val result = Math.toDegrees(Math.asin(a / 10000D)); if (Double.isNaN(result)) { - ctx.interpret('r'); + ctx.IP().reflect(); } else { stack.push((int) (result * 10000)); } @@ -104,7 +104,7 @@ public static void pow(ExecutionContext ctx) { val a = stack.pop(); val result = Math.pow(a, b); if (Double.isNaN(result) || Double.isInfinite(result)) { - ctx.interpret('r'); + ctx.IP().reflect(); } else { stack.push((int) result); } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FPDP.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FPDP.java index 7973a4a..5308195 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FPDP.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FPDP.java @@ -127,7 +127,7 @@ public static void parseDouble(ExecutionContext ctx) { try { stack.pushD(Double.parseDouble(str)); } catch (NumberFormatException e) { - ctx.interpret('r'); + ctx.IP().reflect(); } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FPSP.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FPSP.java index f7c346f..4b3cb0a 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FPSP.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FPSP.java @@ -127,7 +127,7 @@ public static void parseFloat(ExecutionContext ctx) { try { stack.pushF(Float.parseFloat(str)); } catch (NumberFormatException e) { - ctx.interpret('r'); + ctx.IP().reflect(); } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/HRTI.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/HRTI.java index 69ad95a..b88761b 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/HRTI.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/HRTI.java @@ -40,7 +40,7 @@ public static void timer(ExecutionContext ctx) { val marks = getMarkMap(ctx); val ip = ctx.IP(); if (!marks.containsKey(ip.UUID())) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } ctx.stack().push((int)((System.nanoTime() - marks.get(ip.UUID())) / 1000L)); @@ -57,7 +57,7 @@ public static void erase(ExecutionContext ctx) { val marks = getMarkMap(ctx); val ip = ctx.IP(); // if (!marks.containsKey(ip.UUID)) { -// ctx.interpret('r'); +// ctx.IP().reflect(); // return; // } marks.remove(ip.UUID()); diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/MODE.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/MODE.java index e06855a..08b83a3 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/MODE.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/MODE.java @@ -81,7 +81,7 @@ public static void high(ExecutionContext ctx) { if (ctx.dimensions() == 3) { ctx.IP().delta().add(0, 0, 1); } else { - ctx.interpret('r'); + ctx.IP().reflect(); } } @@ -90,7 +90,7 @@ public static void low(ExecutionContext ctx) { if (ctx.dimensions() == 3) { ctx.IP().delta().add(0, 0, -1); } else { - ctx.interpret('r'); + ctx.IP().reflect(); } } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/NULL.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/NULL.java index 942205c..5a9c2a7 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/NULL.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/NULL.java @@ -15,7 +15,7 @@ public class NULL implements Fingerprint { @Override public void load(ObjIntConsumer loader) { for (int i = 'A'; i <= 'Z'; i++) { - loader.accept((ctx) -> ctx.interpret('r'), i); + loader.accept((ctx) -> ctx.IP().reflect(), i); } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/TOYS.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/TOYS.java index 77269f5..86e386e 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/TOYS.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/TOYS.java @@ -267,9 +267,9 @@ public static void getMatrix(ExecutionContext ctx) { public static void storeBehind(ExecutionContext ctx) { @Cleanup val mem = MemoryStack.stackPush(); val pos = mem.vec3i().set(ctx.IP().position()); - ctx.interpret('r'); + ctx.IP().reflect(); ctx.interpret('s'); - ctx.interpret('r'); + ctx.IP().reflect(); ctx.IP().position().set(pos); } @@ -285,7 +285,7 @@ public static void dimBranch(ExecutionContext ctx) { break; case 2: if (ctx.dimensions() != 3) { - ctx.interpret('r'); + ctx.IP().reflect(); } else { ctx.interpret('m'); } @@ -318,7 +318,7 @@ public static void waitForValue(ExecutionContext ctx) { stack.pushVecDimProof(ctx.dimensions(), pos); ctx.IP().position().sub(ctx.IP().delta()); } else if (valueAtCell > value) { - ctx.interpret('r'); + ctx.IP().reflect(); } } @@ -337,7 +337,7 @@ public static void incrementZ(ExecutionContext ctx) { if (ctx.dimensions() == 3) { ctx.IP().position().z++; } else { - ctx.interpret('r'); + ctx.IP().reflect(); } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/TURT.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/TURT.java index 149a9fe..9dbe3b6 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/TURT.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/TURT.java @@ -182,7 +182,7 @@ public static void printDrawing(ExecutionContext ctx) { ImageIO.write(image, "PNG", file); } catch (IOException e) { e.printStackTrace(); - ctx.interpret('r'); + ctx.IP().reflect(); } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/_3DSP.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/_3DSP.java index b602bd9..3afe8c0 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/_3DSP.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/_3DSP.java @@ -145,7 +145,7 @@ public static void genRotMatrix(ExecutionContext ctx) { val axis = stack.pop(); val pos = stack.popVecDimProof(ctx.dimensions(), mem.vec3i()); if (axis <= 0 || axis >= 4) { - ctx.interpret('r'); + ctx.IP().reflect(); return; } val matrix = mem.mat4f(); From 5147c44ed3069fa40d4d2776b98904b52ecacb49 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 13:09:34 +0100 Subject: [PATCH 13/28] update license --- COPYING | 675 ++++++++++++++++++++++++++++++++++++- LICENSE | 16 +- src/main/resources/LICENSE | 2 +- 3 files changed, 690 insertions(+), 3 deletions(-) mode change 120000 => 100644 COPYING mode change 120000 => 100644 LICENSE diff --git a/COPYING b/COPYING deleted file mode 120000 index 250de62..0000000 --- a/COPYING +++ /dev/null @@ -1 +0,0 @@ -src/main/resources/COPYING \ No newline at end of file diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LICENSE b/LICENSE deleted file mode 120000 index 5cf0161..0000000 --- a/LICENSE +++ /dev/null @@ -1 +0,0 @@ -src/main/resources/LICENSE \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..899c8f4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +JFunge - A standard-conforming Befunge-98 and Trefunge-98 interpreter +Copyright (C) 2022-2023 FalsePattern + +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 . \ No newline at end of file diff --git a/src/main/resources/LICENSE b/src/main/resources/LICENSE index fc14f61..899c8f4 100644 --- a/src/main/resources/LICENSE +++ b/src/main/resources/LICENSE @@ -1,5 +1,5 @@ JFunge - A standard-conforming Befunge-98 and Trefunge-98 interpreter -Copyright (C) 2022 FalsePattern +Copyright (C) 2022-2023 FalsePattern 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 From cc08bab14af6edea75f88ee8c35da3ddb1761132 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 13:10:51 +0100 Subject: [PATCH 14/28] update globals --- src/main/java/com/falsepattern/jfunge/Globals.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/falsepattern/jfunge/Globals.java b/src/main/java/com/falsepattern/jfunge/Globals.java index f863f8b..eeeb1f1 100644 --- a/src/main/java/com/falsepattern/jfunge/Globals.java +++ b/src/main/java/com/falsepattern/jfunge/Globals.java @@ -1,7 +1,10 @@ package com.falsepattern.jfunge; public class Globals { - public static final String VERSION = "1.1.0"; - public static final int FUNGE_VERSION = 1 * 256 * 256 + 1 * 256 + 0; + public static final int MAJOR_VERSION = 1; + public static final int MINOR_VERSION = 1; + public static final int PATCH_VERSION = 0; public static final int HANDPRINT = 0x74_70_85_78; //"JFUN" + public static final String VERSION = MAJOR_VERSION + "." + MINOR_VERSION + "." + PATCH_VERSION; + public static final int FUNGE_VERSION = MAJOR_VERSION * 256 * 256 + MINOR_VERSION * 256 + PATCH_VERSION; } From 476c83e3b480e3d0bb6e7f893a7d2fa9fc6329cc Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 14:49:01 +0100 Subject: [PATCH 15/28] fix BASE input for base 10+ --- .../jfunge/interpreter/instructions/fingerprints/BASE.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/BASE.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/BASE.java index 5c88be0..67afa53 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/BASE.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/BASE.java @@ -33,7 +33,7 @@ public static void readIntBase(ExecutionContext ctx) { //Read input until non-digit character is encountered StringBuilder sb = new StringBuilder(); int c; - while (Character.isDigit(c = ctx.input(true))) { + while (Character.isLetterOrDigit(c = ctx.input(true))) { sb.append((char) c); ctx.input(false); } From 0f47b384eb11d8d211f6bd8254cc78f8ad656194 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 14:49:51 +0100 Subject: [PATCH 16/28] force flush output when asking for input --- .../java/com/falsepattern/jfunge/interpreter/Interpreter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java index c02b283..fac8503 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java @@ -372,6 +372,10 @@ public int input(boolean stagger) { inputStagger = -1; } else { try { + try { + output.flush(); + } catch (IOException ignored) { + } value = input.read(); } catch (IOException ignored) {} } From 313b9dea1cf7b729c54c1803f73141f019373391 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 14:50:02 +0100 Subject: [PATCH 17/28] fix m semantics --- .../falsepattern/jfunge/interpreter/instructions/Funge98.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java index acab5bb..8ff43da 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -281,7 +281,7 @@ public static void branchNorthSouth(ExecutionContext ctx) { @Instr('m') public static void branchHighLow(ExecutionContext ctx) { if (ctx.dimensions() == 3) { - ctx.interpret(ctx.stack().pop() == 0 ? 'h' : 'l'); + ctx.interpret(ctx.stack().pop() == 0 ? 'l' : 'h'); } else { ctx.IP().reflect(); } From 9d17c37ad1bda1ac90ed98daf9500ff91c154b73 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 14:54:11 +0100 Subject: [PATCH 18/28] added mycouser test --- .../jfunge/storage/TestInterpreter.java | 45 +++++++++++++------ src/test/resources/mycouser.b98 | 25 +++++++++++ 2 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 src/test/resources/mycouser.b98 diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java index ee4a462..42d280c 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java @@ -144,21 +144,10 @@ private static InputStream nullStream() { return new ByteArrayInputStream(new byte[0]); } - @Test - public void testMycology() { + private static void execMycoProgram(String program, int expectedReturnCode, FeatureSet featureSet, String input) { val checkingOutput = new ByteArrayOutputStream(); val output = new TeeOutputStream(checkingOutput, System.out); - val program = readProgram("/mycology.b98"); - val featureSet = FeatureSet.builder() - .allowedInputFiles(new String[]{"/"}) - .allowedOutputFiles(new String[]{"/"}) - .sysCall(false) - .concurrent(true) - .environment(false) - .perl(true) - .maxIter(300000L) - .build(); - val returnCode = interpret(new String[]{"mycology.b98"}, program, nullStream(), output, featureSet); + val returnCode = interpret(new String[]{program}, readProgram("/" + program), new ByteArrayInputStream(input.getBytes()), output, featureSet); val txt = checkingOutput.toString(); String currentlyActiveFingerprint = null; boolean fingerprintHadError = false; @@ -208,7 +197,35 @@ public void testMycology() { System.out.println(fingerprint); } Assertions.assertTrue(good); - Assertions.assertEquals(15, returnCode); + Assertions.assertEquals(expectedReturnCode, returnCode); + } + + @Test + public void testMycology() { + val featureSet = FeatureSet.builder() + .allowedInputFiles(new String[]{"/"}) + .allowedOutputFiles(new String[]{"/"}) + .sysCall(false) + .concurrent(true) + .environment(false) + .perl(true) + .maxIter(300000L) + .build(); + execMycoProgram("mycology.b98", 15, featureSet, ""); + } + + @Test + public void testMycoUser() { + val featureSet = FeatureSet.builder() + .allowedInputFiles(new String[]{"/"}) + .allowedOutputFiles(new String[]{"/"}) + .sysCall(false) + .concurrent(true) + .environment(false) + .perl(true) + .maxIter(300000L) + .build(); + execMycoProgram("mycouser.b98", 0, featureSet, "123\nt\n16\nf0f0\n"); } @Test diff --git a/src/test/resources/mycouser.b98 b/src/test/resources/mycouser.b98 new file mode 100644 index 0000000..a748b03 --- /dev/null +++ b/src/test/resources/mycouser.b98 @@ -0,0 +1,25 @@ +92#v/4-#v_55+"4 = 2 / 9 :DOOG">:#,_92#v%1-#v_55+"1 = 2 % 9 :DOOG">:#,_ vv$$$$$$< + >055+ "stcelfer / :DAB" ^>'",,@ >055+ "stcelfer % :DAB" ^ 5>$$$ v$ + >055+"4 =! 2 / 9 :DAB"^^ a_,#! #:<;>055+"1 =! 2 % 9 :DAB"^;< 5v" wo"<$ +v,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"About to test division by zero..."+<>",de"v$ +>10#v/#v_055+"0 = 0 / 1 :DOOG" >:#,_ 10#v% #v_055+"0 = 0 % 1 :DOOG"v "$ + >05 5+"...)llits( stcelfer /"^ $0 $0"UNDEF: 1 / 0 != 0," < d$ + >055+"noisivid 89-egnuF tcerrocni r"# "o " "nevig rewsna gnorw "^ a" +v"answer given or incorrect Funge-98 division"+55< > >:#,_#<#< #ov + < >55+"stcel"v>55+"...)llits( stcelfer %"^v^ "" +>" gnorw ,0 =! 0 % 1 :FEDNU"^ @_,#! #:< # *25"UNDEF: STRN fingerprint not l":#,_#^&0" tog :FEDNU">:#,_$.$55+".tcerroc " v> +"yllufepoh si hcihw">:#,_ v >"fer & :DAB"55+".dnim reveN">:#,_v > +>:#,_v#:"Please input a character: "< < X + v < >#v~0" tog :FEDNU">:#,_$:."'",,$55+".tcerroc yllufepoh si hcihw '">:#,_ v +#v~^ v>55+"stcelfer ~ :DAB"55+".dnim reveN" >:#,_ > + > ">:#,_@" $$$$$$$ 52*:"~ & % / :snoitcurtsni gniwollof eht gnikcehc enod llA"v +v55_,#! #:<"Loaded BASE fingerprint: testing I instruction."an(v#4"BASE"_,#! #:< + #:<"UNDEF: BASE fingerprint not loaded, won't check I."*520< >:#,_@ v _,#! ++ ;^>0\"detcelfer I :DAB"; x3*a5 a"BAD: & reflected. Never mind..."\0< # +>"...krow snoitcurtsni 89-egnufeB gnimussA">:#,_" ?ni tupni daer ot esab hcihW"v +v # "Input a number in that base: "0,a.:$_,#! #:<"Selected base "\&^#_,#! #:< +>:#,_$#vI\" tog :FEDNU">:#,_$.a".tcerroc yllufepoh si hcihw">:#,_$ v$_,#! #:<" +v".."an<;a"BAD: I reflected"a"As 10I should probably " ".esab dilav a rof tcelfer t'ndluohs I ,& ekil evaheb"^>' v>025*".I kcehc t'n"^ + '"'I^# _,#! #: Date: Sun, 12 Feb 2023 16:19:19 +0100 Subject: [PATCH 19/28] Better testing order --- pom.xml | 41 ++++++++++++----- .../storage/AsWrittenMethodOrderer.java | 29 ++++++++++++ .../jfunge/storage/TestChunk.java | 4 ++ .../jfunge/storage/TestFungeSpace.java | 4 ++ .../jfunge/storage/TestInterpreter.java | 45 ++++++++++--------- 5 files changed, 91 insertions(+), 32 deletions(-) create mode 100644 src/test/java/com/falsepattern/jfunge/storage/AsWrittenMethodOrderer.java diff --git a/pom.xml b/pom.xml index d241fea..41cdd9b 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,9 @@ 2.11.0 5.8.2 + 2.0.0 + 3.29.2-GA + @@ -47,18 +50,6 @@ commons-cli ${commons-cli.version} - - org.junit.jupiter - junit-jupiter-engine - ${junit.version} - test - - - commons-io - commons-io - ${commons-io.version} - test - org.ow2.asm asm @@ -79,6 +70,32 @@ asm-util ${asm.version} + + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + org.junit-pioneer + junit-pioneer + ${junit-pioneer.version} + test + + + commons-io + commons-io + ${commons-io.version} + test + + + org.javassist + javassist + ${javassist.version} + test + diff --git a/src/test/java/com/falsepattern/jfunge/storage/AsWrittenMethodOrderer.java b/src/test/java/com/falsepattern/jfunge/storage/AsWrittenMethodOrderer.java new file mode 100644 index 0000000..73045f3 --- /dev/null +++ b/src/test/java/com/falsepattern/jfunge/storage/AsWrittenMethodOrderer.java @@ -0,0 +1,29 @@ +package com.falsepattern.jfunge.storage; + +import javassist.ClassPool; +import javassist.CtMethod; +import lombok.*; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.MethodOrdererContext; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + + +public class AsWrittenMethodOrderer implements MethodOrderer { + @Override + public void orderMethods(MethodOrdererContext context) { + val orderedMethodNames = orderedMethodNames(context); + context.getMethodDescriptors() + .sort(Comparator.comparingInt(o -> orderedMethodNames.indexOf(o.getMethod().getName()))); + } + + @SneakyThrows + protected List orderedMethodNames(@NonNull MethodOrdererContext context) { + return Arrays.stream(ClassPool.getDefault().get(context.getTestClass().getName()).getDeclaredMethods()) + .map(CtMethod::getName) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestChunk.java b/src/test/java/com/falsepattern/jfunge/storage/TestChunk.java index 293b86f..45a1bad 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestChunk.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestChunk.java @@ -5,9 +5,13 @@ import lombok.val; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junitpioneer.jupiter.DisableIfTestFails; import java.util.Random; +@DisableIfTestFails +@TestMethodOrder(AsWrittenMethodOrderer.class) public class TestChunk { @Test public void testSetGet() { diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestFungeSpace.java b/src/test/java/com/falsepattern/jfunge/storage/TestFungeSpace.java index 823405e..12a4032 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestFungeSpace.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestFungeSpace.java @@ -3,6 +3,8 @@ import lombok.val; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junitpioneer.jupiter.DisableIfTestFails; import java.nio.charset.StandardCharsets; @@ -10,6 +12,8 @@ import static com.falsepattern.jfunge.storage.Chunk.CHUNK_EDGE_SIZE_Y; import static com.falsepattern.jfunge.storage.Chunk.CHUNK_EDGE_SIZE_Z; +@DisableIfTestFails +@TestMethodOrder(AsWrittenMethodOrderer.class) public class TestFungeSpace { private static int toPos(int fragment, int es) { int sign = -((fragment >>> 1) & 1); diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java index 42d280c..8081133 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java @@ -6,6 +6,8 @@ import org.apache.commons.io.output.TeeOutputStream; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junitpioneer.jupiter.DisableIfTestFails; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -21,6 +23,8 @@ import java.util.HashSet; import java.util.Map; +@DisableIfTestFails +@TestMethodOrder(AsWrittenMethodOrderer.class) public class TestInterpreter { private static final Interpreter.FileIOSupplier fakeSupplier = new Interpreter.FileIOSupplier() { @@ -200,6 +204,27 @@ private static void execMycoProgram(String program, int expectedReturnCode, Feat Assertions.assertEquals(expectedReturnCode, returnCode); } + + @Test + public void testPutCharAtStart() { + System.out.println("Testing edge case 'a,@"); + val output = new ByteArrayOutputStream(); + val returnCode = interpret(new String[0], "'a,@".getBytes(StandardCharsets.UTF_8), nullStream(), output, FeatureSet.builder().maxIter(50).build()); + val txt = output.toString(); + Assertions.assertEquals("a", txt); + Assertions.assertEquals(0, returnCode); + } + + @Test + public void testSemicolonAtStart() { + System.out.println("Testing edge case ;;.@"); + val output = new ByteArrayOutputStream(); + val returnCode = interpret(new String[0], ";;.@".getBytes(StandardCharsets.UTF_8), nullStream(), output, FeatureSet.builder().maxIter(50).build()); + val txt = output.toString(); + Assertions.assertEquals("0 ", txt); + Assertions.assertEquals(0, returnCode); + } + @Test public void testMycology() { val featureSet = FeatureSet.builder() @@ -227,24 +252,4 @@ public void testMycoUser() { .build(); execMycoProgram("mycouser.b98", 0, featureSet, "123\nt\n16\nf0f0\n"); } - - @Test - public void testSemicolonAtStart() { - System.out.println("Testing edge case ;;.@"); - val output = new ByteArrayOutputStream(); - val returnCode = interpret(new String[0], ";;.@".getBytes(StandardCharsets.UTF_8), nullStream(), output, FeatureSet.builder().maxIter(50).build()); - val txt = output.toString(); - Assertions.assertEquals("0 ", txt); - Assertions.assertEquals(0, returnCode); - } - - @Test - public void testPutCharAtStart() { - System.out.println("Testing edge case 'a,@"); - val output = new ByteArrayOutputStream(); - val returnCode = interpret(new String[0], "'a,@".getBytes(StandardCharsets.UTF_8), nullStream(), output, FeatureSet.builder().maxIter(50).build()); - val txt = output.toString(); - Assertions.assertEquals("a", txt); - Assertions.assertEquals(0, returnCode); - } } From 11051e99a65191945e417926d03093dec15bb569 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 16:19:38 +0100 Subject: [PATCH 20/28] implement STRN --- .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/STRN.java | 178 ++++++++++++++++++ .../jfunge/storage/TestInterpreter.java | 2 +- 3 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/STRN.java diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java index 8ff43da..b0e76c5 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -20,6 +20,7 @@ import com.falsepattern.jfunge.interpreter.instructions.fingerprints.PERL; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.REFC; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.ROMA; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.STRN; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.TOYS; import com.falsepattern.jfunge.interpreter.instructions.fingerprints._3DSP; import com.falsepattern.jfunge.ip.IStack; @@ -67,6 +68,7 @@ public class Funge98 implements InstructionSet { addFingerprint(PERL.INSTANCE); addFingerprint(REFC.INSTANCE); addFingerprint(ROMA.INSTANCE); + addFingerprint(STRN.INSTANCE); addFingerprint(TOYS.INSTANCE); //TODO Fix TURT, it's broken // addFingerprint(TURT.INSTANCE); diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/STRN.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/STRN.java new file mode 100644 index 0000000..ff62b5d --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/STRN.java @@ -0,0 +1,178 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; +import com.falsepattern.jfunge.util.MemoryStack; +import lombok.AccessLevel; +import lombok.Cleanup; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import lombok.val; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class STRN implements Fingerprint { + public static final STRN INSTANCE = new STRN(); + @Override + public int code() { + return 0x5354524E; + } + + @Instr('A') + public static void append(ExecutionContext ctx) { + val stack = ctx.stack(); + val b = stack.popString(); + val a = stack.popString(); + stack.pushString(b + a); + } + + @Instr('C') + public static void compare(ExecutionContext ctx) { + val stack = ctx.stack(); + val b = stack.popString(); + val a = stack.popString(); + stack.push(b.compareTo(a)); + } + + @SneakyThrows + @Instr('D') + public static void display(ExecutionContext ctx) { + val stack = ctx.stack(); + val str = stack.popString(); + ctx.output().write(str.getBytes()); + } + + @Instr('F') + public static void find(ExecutionContext ctx) { + val stack = ctx.stack(); + val b = stack.popString(); + val a = stack.popString(); + val index = b.indexOf(a); + if (index < 0) + stack.push(0); + else + stack.pushString(b.substring(index)); + } + + @Instr('G') + public static void getString(ExecutionContext ctx) { + val stack = ctx.stack(); + + @Cleanup val mStack = MemoryStack.stackPush(); + val vec = mStack.vec3i(); + stack.popVecDimProof(ctx.dimensions(), vec); + + vec.add(ctx.IP().storageOffset()); + + val fs = ctx.fungeSpace(); + val bounds = fs.bounds(); + + val str = new StringBuilder(); + while (bounds.inBounds(vec)) { + val c = fs.get(vec); + if (c == 0) break; + str.append((char)c); + vec.add(1, 0, 0); + } + stack.pushString(str.toString()); + } + + @Instr('I') + public static void readStringFromInput(ExecutionContext ctx) { + val stack = ctx.stack(); + val str = new StringBuilder(); + while (ctx.input(true) == '\n') { + ctx.input(false); + } + int c; + while ((c = ctx.input(true)) != -1 && c != 0 && c != '\n') { + str.append((char)ctx.input(false)); + } + stack.pushString(str.toString()); + } + + @Instr('L') + public static void leftMost(ExecutionContext ctx) { + val stack = ctx.stack(); + val offset = stack.pop(); + val str = stack.popString(); + subString(ctx, str, 0, offset); + } + + @Instr('M') + public static void section(ExecutionContext ctx) { + val stack = ctx.stack(); + val count = stack.pop(); + val offset = stack.pop(); + val str = stack.popString(); + if (offset < 0) { + ctx.IP().reflect(); + return; + } + subString(ctx, str, offset, offset + count); + } + + @Instr('N') + public static void length(ExecutionContext ctx) { + val stack = ctx.stack(); + val str = stack.popString(); + stack.pushString(str); + stack.push(str.length()); + } + + @Instr('P') + public static void putString(ExecutionContext ctx) { + val stack = ctx.stack(); + + @Cleanup val mStack = MemoryStack.stackPush(); + val vec = mStack.vec3i(); + stack.popVecDimProof(ctx.dimensions(), vec); + vec.add(ctx.IP().storageOffset()); + + val str = stack.popString(); + + val fs = ctx.fungeSpace(); + for (int i = 0; i < str.length(); i++) { + fs.set(vec, str.charAt(i)); + vec.add(1, 0, 0); + } + fs.set(vec, 0); + } + + @Instr('R') + public static void rightMost(ExecutionContext ctx) { + val stack = ctx.stack(); + val offset = stack.pop(); + val str = stack.popString(); + subString(ctx, str, str.length() - offset, str.length()); + } + + @Instr('S') + public static void intToString(ExecutionContext ctx) { + val stack = ctx.stack(); + val num = stack.pop(); + stack.pushString(Integer.toString(num)); + } + + @Instr('V') + public static void parseInt(ExecutionContext ctx) { + val stack = ctx.stack(); + val str = stack.popString(); + try { + stack.push(Integer.parseInt(str)); + } catch (NumberFormatException e) { + ctx.IP().reflect(); + } + } + + private static void subString(ExecutionContext ctx, String str, int start, int end) { + val stack = ctx.stack(); + val length = str.length(); + if (start < 0) start = 0; + if (end > length) end = length; + if (start > end) { + ctx.IP().reflect(); + } else { + stack.pushString(str.substring(start, end)); + } + } +} diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java index 8081133..b507565 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java @@ -250,6 +250,6 @@ public void testMycoUser() { .perl(true) .maxIter(300000L) .build(); - execMycoProgram("mycouser.b98", 0, featureSet, "123\nt\n16\nf0f0\n"); + execMycoProgram("mycouser.b98", 0, featureSet, "123\nt\n16\nf0f0\nHello\n"); } } From 7e1b321c420784dd3cd9741629f6c953abc15034 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 16:20:06 +0100 Subject: [PATCH 21/28] start testing Trefunge --- .../jfunge/storage/TestInterpreter.java | 15 ++++++++++++++ src/test/resources/mycotre.b98 | 20 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/test/resources/mycotre.b98 diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java index b507565..fbf4088 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java @@ -252,4 +252,19 @@ public void testMycoUser() { .build(); execMycoProgram("mycouser.b98", 0, featureSet, "123\nt\n16\nf0f0\nHello\n"); } + + @Test + public void testMycoTre() { + val featureSet = FeatureSet.builder() + .allowedInputFiles(new String[]{"/"}) + .allowedOutputFiles(new String[]{"/"}) + .sysCall(false) + .concurrent(true) + .environment(false) + .trefunge(true) + .perl(true) + .maxIter(300000L) + .build(); + execMycoProgram("mycotre.b98", 0, featureSet, ""); + } } diff --git a/src/test/resources/mycotre.b98 b/src/test/resources/mycotre.b98 new file mode 100644 index 0000000..4931101 --- /dev/null +++ b/src/test/resources/mycotre.b98 @@ -0,0 +1,20 @@ +vMake sure that you have tested mycology, mycorand, and mycouser, and all of them worked properly before running this. + + >0" stroper y7 :DAB">:#,_$.a"3 eb dluohS .snoisnemid">:#,_@ +>7y:3-#^_0a"egnuferT stroper y7 :DOOG">:#,_v +v $$< h>h >a"oreznon no wol seog m :DAB">:#,_@ +>#vh>a"gnihton seod h :DAB">:#,_@>a"skrow l :DOOG">:#,_^ + >a"stcelfer h :DAB">:#,_@ + + + + + >m>1m + >a"skrow h :DOOG">:#,_ #vla"gnihtol seod l :DAB">:#,_@ + >a"stcelfer l :DAB">:#,_@ + + + + >a"orez no hgih seog m :DAB">:#,_@ + ^l >a"skrow m :DOOG">:#,_@ + >a"1- ot atled Z stes h :DAB">:#,_@ \ No newline at end of file From 1fdca67154ac24f9523ac81a97037d92bde7780a Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 16:25:29 +0100 Subject: [PATCH 22/28] update readme --- README.MD | 1 + 1 file changed, 1 insertion(+) diff --git a/README.MD b/README.MD index 2953b2e..033e60b 100644 --- a/README.MD +++ b/README.MD @@ -79,6 +79,7 @@ Additionally, the following fingerprints are currently supported (more to come): - [PERL](./docs/catseye/library/PERL.markdown) (Disabled by default, needs command line flag) - [REFC](./docs/catseye/library/REFC.markdown) - [ROMA](./docs/catseye/library/ROMA.markdown) +- [STRN](https://rcfunge98.com/rcsfingers.html#STRN) - [TOYS](./docs/catseye/library/TOYS.markdown) - [TURT](./docs/catseye/library/TURT.markdown) (Broken, disabled in source code, will be fixed in the future) From 33bcdd66d37401b52edc89321f1c442d007f00e4 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 16:39:50 +0100 Subject: [PATCH 23/28] implement INDV --- .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/INDV.java | 111 ++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/INDV.java diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java index b0e76c5..9cf5b87 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -13,6 +13,7 @@ import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPDP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPSP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.HRTI; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.INDV; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.MODE; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.MODU; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.NULL; @@ -61,6 +62,7 @@ public class Funge98 implements InstructionSet { addFingerprint(FING.INSTANCE); addFingerprint(FIXP.INSTANCE); addFingerprint(HRTI.INSTANCE); + addFingerprint(INDV.INSTANCE); addFingerprint(MODE.INSTANCE); addFingerprint(MODU.INSTANCE); addFingerprint(NULL.INSTANCE); diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/INDV.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/INDV.java new file mode 100644 index 0000000..d27861b --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/INDV.java @@ -0,0 +1,111 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; +import com.falsepattern.jfunge.storage.FungeSpace; +import com.falsepattern.jfunge.util.MemoryStack; +import lombok.AccessLevel; +import lombok.Cleanup; +import lombok.NoArgsConstructor; +import lombok.val; +import org.joml.Vector3i; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class INDV implements Fingerprint { + public static final INDV INSTANCE = new INDV(); + @Override + public int code() { + return 0x494e4456; + } + + private static void getVector(FungeSpace fs, int dimensions, int x, int y, int z, Vector3i output) { + switch (dimensions) { + default: throw new IllegalStateException("Pointer logic only works on 3d or lower funge"); + case 3: + output.z = fs.get(x, y, z); + x++; + case 2: + output.y = fs.get(x, y, z); + x++; + case 1: + output.x = fs.get(x, y, z); + } + } + + private static void putVector(FungeSpace fs, int dimensions, int x, int y, int z, Vector3i input) { + switch (dimensions) { + default: throw new IllegalStateException("Pointer logic only works on 3d or lower funge"); + case 3: + fs.set(x, y, z, input.z); + x++; + case 2: + fs.set(x, y, z, input.y); + x++; + case 1: + fs.set(x, y, z, input.x); + } + } + + private static void retrievePointer(ExecutionContext ctx, Vector3i output) { + val stack = ctx.stack(); + val ip = ctx.IP(); + val fs = ctx.fungeSpace(); + @Cleanup val mStack = MemoryStack.stackPush(); + + val pointer = mStack.vec3i(); + stack.popVecDimProof(ctx.dimensions(), pointer); + pointer.add(ip.storageOffset()); + getVector(fs, ctx.dimensions(), pointer.x, pointer.y, pointer.z, output); + } + + @Instr('G') + public static void getNumberAtPointer(ExecutionContext ctx) { + val stack = ctx.stack(); + val ip = ctx.IP(); + + @Cleanup val mStack = MemoryStack.stackPush(); + val pointer = mStack.vec3i(); + retrievePointer(ctx, pointer); + pointer.add(ip.storageOffset()); + stack.push(ctx.fungeSpace().get(pointer)); + } + + @Instr('P') + public static void putNumberAtPointer(ExecutionContext ctx) { + val stack = ctx.stack(); + val ip = ctx.IP(); + + @Cleanup val mStack = MemoryStack.stackPush(); + val pointer = mStack.vec3i(); + retrievePointer(ctx, pointer); + pointer.add(ip.storageOffset()); + ctx.fungeSpace().set(pointer, stack.pop()); + } + + @Instr('V') + public static void getVectorAtPointer(ExecutionContext ctx) { + val stack = ctx.stack(); + val ip = ctx.IP(); + + @Cleanup val mStack = MemoryStack.stackPush(); + val pointer = mStack.vec3i(); + retrievePointer(ctx, pointer); + pointer.add(ip.storageOffset()); + getVector(ctx.fungeSpace(), ctx.dimensions(), pointer.x, pointer.y, pointer.z, pointer); + stack.pushVecDimProof(ctx.dimensions(), pointer); + } + + @Instr('W') + public static void putVectorAtPointer(ExecutionContext ctx) { + val stack = ctx.stack(); + val ip = ctx.IP(); + + @Cleanup val mStack = MemoryStack.stackPush(); + val pointer = mStack.vec3i(); + retrievePointer(ctx, pointer); + pointer.add(ip.storageOffset()); + val vec = mStack.vec3i(); + stack.popVecDimProof(ctx.dimensions(), vec); + putVector(ctx.fungeSpace(), ctx.dimensions(), pointer.x, pointer.y, pointer.z, vec); + } +} From 3a5aa87da4568cbd545b2f6a800c68b8c42d72cc Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 16:47:36 +0100 Subject: [PATCH 24/28] implement JSTR --- .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/JSTR.java | 59 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/JSTR.java diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java index 9cf5b87..1aa361a 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -14,6 +14,7 @@ import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPSP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.HRTI; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.INDV; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.JSTR; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.MODE; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.MODU; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.NULL; @@ -63,6 +64,7 @@ public class Funge98 implements InstructionSet { addFingerprint(FIXP.INSTANCE); addFingerprint(HRTI.INSTANCE); addFingerprint(INDV.INSTANCE); + addFingerprint(JSTR.INSTANCE); addFingerprint(MODE.INSTANCE); addFingerprint(MODU.INSTANCE); addFingerprint(NULL.INSTANCE); diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/JSTR.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/JSTR.java new file mode 100644 index 0000000..1d709b2 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/JSTR.java @@ -0,0 +1,59 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; +import com.falsepattern.jfunge.util.MemoryStack; +import lombok.AccessLevel; +import lombok.Cleanup; +import lombok.NoArgsConstructor; +import lombok.val; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class JSTR implements Fingerprint { + public static final JSTR INSTANCE = new JSTR(); + @Override + public int code() { + return 0x4a535452; + } + + @Instr('P') + public static void putString(ExecutionContext ctx) { + val stack = ctx.stack(); + val ip = ctx.IP(); + val fs = ctx.fungeSpace(); + @Cleanup val mStack = MemoryStack.stackPush(); + val position = mStack.vec3i(); + val delta = mStack.vec3i(); + + val count = stack.pop(); + stack.popVecDimProof(ctx.dimensions(), position); + stack.popVecDimProof(ctx.dimensions(), delta); + + position.add(ip.storageOffset()); + for (int i = 0; i < count; i++) { + fs.set(position, stack.pop()); + position.add(delta); + } + } + + @Instr('G') + public static void getString(ExecutionContext ctx) { + val stack = ctx.stack(); + val ip = ctx.IP(); + val fs = ctx.fungeSpace(); + @Cleanup val mStack = MemoryStack.stackPush(); + val position = mStack.vec3i(); + val delta = mStack.vec3i(); + + val count = stack.pop(); + stack.popVecDimProof(ctx.dimensions(), position); + stack.popVecDimProof(ctx.dimensions(), delta); + + position.add(ip.storageOffset()); + stack.push(0); + for (int i = 0; i < count; i++) { + stack.push(fs.get(position)); + position.add(delta); + } + } +} From cbd17b0b40590071be5218b91ae57123555be372 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 12 Feb 2023 16:50:49 +0100 Subject: [PATCH 25/28] update readme --- README.MD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.MD b/README.MD index 033e60b..bb91086 100644 --- a/README.MD +++ b/README.MD @@ -72,6 +72,8 @@ Additionally, the following fingerprints are currently supported (more to come): - [FPDP](https://rcfunge98.com/rcsfingers.html#FPDP) - [FPSP](https://rcfunge98.com/rcsfingers.html#FPSP) - [HRTI](./docs/catseye/library/HRTI.markdown) +- [INDV](https://rcfunge98.com/rcsfingers.html#INDV) +- [JSTR](https://web.archive.org/web/20070525220700/http://www.jess2.net:80/code/funge/myexts.txt) - [MODE](./docs/catseye/library/MODE.markdown) - [MODU](./docs/catseye/library/MODU.markdown) - [NULL](./docs/catseye/library/NULL.markdown) From 9ef39b5ea2958e70b8822b35abcec6472c1fe664 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Mon, 13 Feb 2023 00:06:16 +0100 Subject: [PATCH 26/28] Implement SOCK and SCKE --- README.MD | 2 + .../java/com/falsepattern/jfunge/Main.java | 4 + .../jfunge/interpreter/FeatureSet.java | 2 + .../jfunge/interpreter/Interpreter.java | 6 + .../interpreter/instructions/Funge98.java | 3 + .../instructions/fingerprints/SOCK.java | 523 ++++++++++++++++++ .../jfunge/storage/TestInterpreter.java | 11 +- 7 files changed, 541 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/SOCK.java diff --git a/README.MD b/README.MD index bb91086..711f6dd 100644 --- a/README.MD +++ b/README.MD @@ -81,6 +81,8 @@ Additionally, the following fingerprints are currently supported (more to come): - [PERL](./docs/catseye/library/PERL.markdown) (Disabled by default, needs command line flag) - [REFC](./docs/catseye/library/REFC.markdown) - [ROMA](./docs/catseye/library/ROMA.markdown) +- [SOCK](https://rcfunge98.com/rcsfingers.html#SOCK) +- [SCKE](https://www.rcfunge98.com/rcfunge2_manual.html#SCKE) - [STRN](https://rcfunge98.com/rcsfingers.html#STRN) - [TOYS](./docs/catseye/library/TOYS.markdown) - [TURT](./docs/catseye/library/TURT.markdown) (Broken, disabled in source code, will be fixed in the future) diff --git a/src/main/java/com/falsepattern/jfunge/Main.java b/src/main/java/com/falsepattern/jfunge/Main.java index 7c99b37..8560314 100644 --- a/src/main/java/com/falsepattern/jfunge/Main.java +++ b/src/main/java/com/falsepattern/jfunge/Main.java @@ -69,6 +69,10 @@ public static void main(String[] args) throws IOException, ParseException { .longOpt("perl") .desc("Enable the PERL fingerprint. This requires the working directory of the interpreter to be writable, and is also an arbitrary code execution risk.") .build()); + options.addOption(Option.builder() + .longOpt("sock") + .desc("Enable the SOCK and SCKE fingerprints. This allows the program to open a socket and listen for connections, as well as connect to external hosts. This is a very dangerous permission to grant, it can potentially allow remote code execution.") + .build()); val parser = new DefaultParser(); val cmd = parser.parse(options, args); if (cmd.hasOption("help")) { diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java b/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java index 1da9ae1..3fa4a83 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java @@ -14,5 +14,7 @@ public class FeatureSet { public final boolean environment; public final long maxIter; + //Dangerous fingerpritns public final boolean perl; + public final boolean socket; } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java index fac8503..16b525b 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java @@ -4,6 +4,7 @@ import com.falsepattern.jfunge.interpreter.instructions.Instruction; import com.falsepattern.jfunge.interpreter.instructions.InstructionManager; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.PERL; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.SOCK; import com.falsepattern.jfunge.ip.IP; import com.falsepattern.jfunge.ip.impl.InstructionPointer; import com.falsepattern.jfunge.storage.FungeSpace; @@ -203,6 +204,11 @@ public Interpreter(String[] args, InputStream input, OutputStream output, FileIO if (!featureSet.perl) { fingerprintBlackList.add(PERL.INSTANCE.code()); } + + if (!featureSet.socket) { + fingerprintBlackList.add(SOCK.INSTANCE.code()); + fingerprintBlackList.add(SOCK.SCKE.INSTANCE.code()); + } } @SneakyThrows diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java index 1aa361a..99ec65c 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -22,6 +22,7 @@ import com.falsepattern.jfunge.interpreter.instructions.fingerprints.PERL; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.REFC; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.ROMA; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.SOCK; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.STRN; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.TOYS; import com.falsepattern.jfunge.interpreter.instructions.fingerprints._3DSP; @@ -72,6 +73,8 @@ public class Funge98 implements InstructionSet { addFingerprint(PERL.INSTANCE); addFingerprint(REFC.INSTANCE); addFingerprint(ROMA.INSTANCE); + addFingerprint(SOCK.INSTANCE); + addFingerprint(SOCK.SCKE.INSTANCE); addFingerprint(STRN.INSTANCE); addFingerprint(TOYS.INSTANCE); //TODO Fix TURT, it's broken diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/SOCK.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/SOCK.java new file mode 100644 index 0000000..f669dde --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/SOCK.java @@ -0,0 +1,523 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; +import com.falsepattern.jfunge.util.MemoryStack; +import lombok.AccessLevel; +import lombok.Cleanup; +import lombok.NoArgsConstructor; +import lombok.val; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketOption; +import java.net.StandardSocketOptions; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) +public class SOCK implements Fingerprint { + private static final int AF_UNIX = 1; + private static final int AF_INET = 2; + + private static final int SO_DEBUG = 1; + private static final int SO_REUSEADDR = 2; + private static final int SO_KEEPALIVE = 3; + private static final int SO_DONTROUTE = 4; + private static final int SO_BROADCAST = 5; + private static final int OOBINLINE = 6; + + private static final int PF_UNIX = 1; + private static final int PF_INET = 2; + + private static final int SOCK_DGRAM = 1; + private static final int SOCK_STREAM = 2; + + private static final int PROTO_TCP = 1; + private static final int PROTO_UDP = 2; + + private static final Map sockets = Collections.synchronizedMap(new HashMap<>()); + private static final AtomicInteger indexCounter = new AtomicInteger(0); + + public static final SOCK INSTANCE = new SOCK(); + @Override + public int code() { + return 0x534f434b; + } + + @Instr('A') + public static void accept(ExecutionContext ctx) { + val stack = ctx.stack(); + val s = stack.pop(); + + val socket = sockets.get(s); + if (socket == null) { + ctx.IP().reflect(); + return; + } + try { + val newSocket = socket.accept(); + val newIndex = indexCounter.getAndIncrement(); + sockets.put(newIndex, newSocket); + val ip = newSocket.bindAddress.getAddress().getAddress(); + var ipInt = 0; + for (int i = 0; i < 4; i++) { + ipInt = ipInt * 256 + (ip[i] & 0xFF); + } + stack.push(newSocket.bindAddress.getPort()); + stack.push(ipInt); + stack.push(newIndex); + } catch (IOException e) { + e.printStackTrace(); + ctx.IP().reflect(); + } + } + + @Instr('B') + public static void bind(ExecutionContext ctx) { + val stack = ctx.stack(); + val addr = stack.pop(); + val prt = stack.pop(); + val ct = stack.pop(); + val s = stack.pop(); + + if (ct == AF_UNIX) { + ctx.IP().reflect(); + return; + } + + val socket = sockets.get(s); + if (socket == null) { + ctx.IP().reflect(); + return; + } + + try { + socket.bind(new InetSocketAddress(InetAddress.getByAddress(new byte[]{(byte) (addr >> 24), (byte) (addr >> 16), (byte) (addr >> 8), (byte) addr}), prt)); + } catch (IOException e) { + e.printStackTrace(); + ctx.IP().reflect(); + } + } + + @Instr('C') + public static void connect(ExecutionContext ctx) { + val stack = ctx.stack(); + val addr = stack.pop(); + val prt = stack.pop(); + val ct = stack.pop(); + val s = stack.pop(); + + if (ct == AF_UNIX) { + ctx.IP().reflect(); + return; + } + + val socket = sockets.get(s); + if (socket == null) { + ctx.IP().reflect(); + return; + } + + try { + socket.connect(new InetSocketAddress(InetAddress.getByAddress(new byte[]{(byte) (addr >> 24), (byte) (addr >> 16), (byte) (addr >> 8), (byte) addr}), prt)); + } catch (IOException e) { + e.printStackTrace(); + ctx.IP().reflect(); + } + } + + @Instr('I') + public static void parseIP(ExecutionContext ctx) { + val stack = ctx.stack(); + val ipString = stack.popString(); + try { + val ip = Arrays.stream(ipString.split("\\.")).mapToInt(Integer::parseInt).reduce(0, (a, b) -> a * 256 + b); + stack.push(ip); + } catch (NumberFormatException e) { + ctx.IP().reflect(); + } + } + + @Instr('K') + public static void kill(ExecutionContext ctx) { + val s = ctx.stack().pop(); + val socket = sockets.get(s); + if (socket == null) { + ctx.IP().reflect(); + return; + } + try { + socket.kill(); + } catch (IOException e) { + e.printStackTrace(); + ctx.IP().reflect(); + } + } + + @Instr('L') + public static void listen(ExecutionContext ctx) { + val stack = ctx.stack(); + val s = stack.pop(); + val backlog = stack.pop(); + + val socket = sockets.get(s); + if (socket == null) { + ctx.IP().reflect(); + return; + } + try { + socket.listen(backlog); + } catch (IOException e) { + e.printStackTrace(); + ctx.IP().reflect(); + } + } + + @Instr('O') + public static void setOption(ExecutionContext ctx) { + val stack = ctx.stack(); + val s = stack.pop(); + val o = stack.pop(); + val n = stack.pop(); + + val realOpt = switch (o) { + default -> null; + case SO_REUSEADDR -> StandardSocketOptions.SO_REUSEADDR; + case SO_KEEPALIVE -> StandardSocketOptions.SO_KEEPALIVE; + case SO_BROADCAST -> StandardSocketOptions.SO_BROADCAST; + }; + if (realOpt == null) { + ctx.IP().reflect(); + } + try { + val socket = sockets.get(s); + if (socket != null) { + socket.setOption(realOpt, n != 0); + } else { + ctx.IP().reflect(); + } + } catch (IOException e) { + e.printStackTrace(); + ctx.IP().reflect(); + } + } + + @Instr('R') + public static void readSocket(ExecutionContext ctx) { + val stack = ctx.stack(); + val s = stack.pop(); + val l = stack.pop(); + @Cleanup val mStack = MemoryStack.stackPush(); + val ptr = mStack.vec3i(); + stack.popVecDimProof(ctx.dimensions(), ptr); + ptr.add(ctx.IP().storageOffset()); + + val socket = sockets.get(s); + try { + val buf = new byte[l]; + val readLength = socket.receive(buf, l); + val fs = ctx.fungeSpace(); + var x = ptr.x(); + val y = ptr.y(); + val z = ptr.z(); + for (int i = 0; i < readLength; i++) { + fs.set(x + i, y, z, buf[i]); + } + stack.push(readLength); + } catch (IOException e) { + e.printStackTrace(); + ctx.IP().reflect(); + } + } + + @Instr('S') + public static void createSocket(ExecutionContext ctx) { + val stack = ctx.stack(); + val pro = stack.pop(); + val typ = stack.pop(); + val pf = stack.pop(); + if (pf == PF_UNIX) { + ctx.IP().reflect(); + return; + } + + switch (typ) { + case SOCK_STREAM -> { + switch (pro) { + case PROTO_TCP -> { + val key = indexCounter.getAndIncrement(); + sockets.put(key, new SocketWrapper(SOCK_STREAM)); + stack.push(key); + } + case PROTO_UDP -> ctx.IP().reflect(); + } + } + case SOCK_DGRAM -> { + switch (pro) { + case PROTO_TCP -> ctx.IP().reflect(); + case PROTO_UDP -> { + val key = indexCounter.getAndIncrement(); + sockets.put(key, new SocketWrapper(SOCK_DGRAM)); + stack.push(key); + } + } + } + } + } + + + @Instr('W') + public static void writeSocket(ExecutionContext ctx) { + val stack = ctx.stack(); + val s = stack.pop(); + val l = stack.pop(); + @Cleanup val mStack = MemoryStack.stackPush(); + val ptr = mStack.vec3i(); + stack.popVecDimProof(ctx.dimensions(), ptr); + ptr.add(ctx.IP().storageOffset()); + + val socket = sockets.get(s); + if (socket == null) { + ctx.IP().reflect(); + return; + } + val fs = ctx.fungeSpace(); + val data = new byte[l]; + var x = ptr.x(); + val y = ptr.y(); + val z = ptr.z(); + for (int i = 0; i < l; i++) { + data[i] = (byte)fs.get(x + i, y, z); + } + try { + stack.push(socket.send(data)); + } catch (IOException e) { + e.printStackTrace(); + ctx.IP().reflect(); + } + } + + private static class SocketWrapper { + private final int kind; + private ServerSocket streamListen = null; + private Socket streamConn = null; + private DatagramSocket dgram = null; + private InetSocketAddress bindAddress = null; + private final Map, Boolean> options = new HashMap<>(); + + public SocketWrapper(int kind) { + this.kind = kind; + } + + private ServerSocket streamListen() throws IOException { + if (streamConn != null || dgram != null) { + throw new IOException("Invalid socket type"); + } + if (streamListen == null) { + streamListen = new ServerSocket(); + for (val entry : options.entrySet()) { + streamListen.setOption(entry.getKey(), entry.getValue()); + } + } + return streamListen; + } + + private Socket streamConn() throws IOException { + if (streamListen != null || dgram != null) { + throw new IOException("Invalid socket type"); + } + if (streamConn == null) { + streamConn = new Socket(); + for (val entry : options.entrySet()) { + streamConn.setOption(entry.getKey(), entry.getValue()); + } + } + return streamConn; + } + + private DatagramSocket dgram() throws IOException { + if (streamListen != null || streamConn != null) { + throw new IOException("Invalid socket type"); + } + if (dgram == null) { + dgram = new DatagramSocket(); + for (val entry : options.entrySet()) { + dgram.setOption(entry.getKey(), entry.getValue()); + } + if (bindAddress != null) { + dgram.bind(bindAddress); + } + } + return dgram; + } + + public void setOption(SocketOption option, boolean value) throws IOException { + if (streamListen != null) { + streamListen.setOption(option, value); + } + if (streamConn != null) { + streamConn.setOption(option, value); + } + if (dgram != null) { + dgram.setOption(option, value); + } + options.put(option, value); + } + + public SocketWrapper accept() throws IOException { + switch (kind) { + case SOCK_STREAM -> { + if (streamListen == null) { + throw new IOException("Socket not listening"); + } + val newSocket = streamListen.accept(); + val sock = new SocketWrapper(SOCK_STREAM); + sock.bindAddress = bindAddress; + sock.streamConn = newSocket; + return sock; + } + } + throw new IllegalStateException("Invalid socket type"); + } + + public void bind(InetSocketAddress address) throws IOException { + bindAddress = address; + if (kind == SOCK_DGRAM) { + dgram(); + } + } + + public void connect(InetSocketAddress address) throws IOException { + switch (kind) { + case SOCK_STREAM -> streamConn().connect(address); + case SOCK_DGRAM -> dgram().connect(address); + } + } + + public void kill() throws IOException { + if (streamConn != null) { + streamConn.close(); + } + if (streamListen != null) { + streamListen.close(); + } + if (dgram != null) { + dgram.close(); + } + } + + public void listen(int backlog) throws IOException { + switch (kind) { + case SOCK_STREAM -> streamListen().bind(bindAddress, backlog); + } + } + + public int receive(byte[] buf, int len) throws IOException { + switch (kind) { + case SOCK_STREAM -> { + if (streamConn == null) { + throw new IOException("Socket not connected"); + } + val in = streamConn.getInputStream(); + val read = in.read(buf); + return read; + } + case SOCK_DGRAM -> { + val packet = new DatagramPacket(buf, len); + dgram().receive(packet); + return packet.getLength(); + } + } + throw new IllegalStateException("Invalid socket type"); + } + + public int send(byte[] data) throws IOException { + switch (kind) { + case SOCK_STREAM -> { + if (streamConn == null) { + throw new IOException("Socket not connected"); + } + OutputStream out; + out = streamConn.getOutputStream(); + out.write(data); + return data.length; + } + case SOCK_DGRAM -> { + val packet = new DatagramPacket(data, data.length); + if (dgram == null) { + return 0; + } + dgram.send(packet); + return data.length; + } + } + return 0; + } + + public int available() throws IOException { + switch (kind) { + case SOCK_STREAM -> { + if (streamConn == null) { + throw new IOException("Socket not connected"); + } + return streamConn.getInputStream().available(); + } + } + throw new IOException("Invalid socket type"); + } + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + public static class SCKE implements Fingerprint { + public static final SCKE INSTANCE = new SCKE(); + @Override + public int code() { + return 0x53434B45; + } + + @Instr('H') + public static void parseDomainName(ExecutionContext ctx) { + val domainName = ctx.stack().popString(); + try { + val ip = InetAddress.getByName(domainName); + val ipBytes = ip.getAddress(); + var ipInt = 0; + for (byte ipByte : ipBytes) { + ipInt = ipInt * 256 + (ipByte & 0xFF); + } + ctx.stack().push(ipInt); + } catch (UnknownHostException e) { + e.printStackTrace(); + ctx.IP().reflect(); + } + } + + @Instr('P') + public static void checkAvailable(ExecutionContext ctx) { + val s = ctx.stack().pop(); + val socket = sockets.get(s); + if (socket == null) { + ctx.IP().reflect(); + return; + } + try { + val available = socket.streamConn().getInputStream().available(); + ctx.stack().push(available); + } catch (IOException e) { + e.printStackTrace(); + ctx.IP().reflect(); + } + } + } +} diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java index fbf4088..1f0a9d7 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java @@ -230,9 +230,8 @@ public void testMycology() { val featureSet = FeatureSet.builder() .allowedInputFiles(new String[]{"/"}) .allowedOutputFiles(new String[]{"/"}) - .sysCall(false) .concurrent(true) - .environment(false) + .socket(true) .perl(true) .maxIter(300000L) .build(); @@ -244,10 +243,6 @@ public void testMycoUser() { val featureSet = FeatureSet.builder() .allowedInputFiles(new String[]{"/"}) .allowedOutputFiles(new String[]{"/"}) - .sysCall(false) - .concurrent(true) - .environment(false) - .perl(true) .maxIter(300000L) .build(); execMycoProgram("mycouser.b98", 0, featureSet, "123\nt\n16\nf0f0\nHello\n"); @@ -258,11 +253,7 @@ public void testMycoTre() { val featureSet = FeatureSet.builder() .allowedInputFiles(new String[]{"/"}) .allowedOutputFiles(new String[]{"/"}) - .sysCall(false) - .concurrent(true) - .environment(false) .trefunge(true) - .perl(true) .maxIter(300000L) .build(); execMycoProgram("mycotre.b98", 0, featureSet, ""); From 9cb1c1a9b036fc7965204d44c8650f1b8a8debf0 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Mon, 13 Feb 2023 00:13:00 +0100 Subject: [PATCH 27/28] bump version --- pom.xml | 2 +- src/main/java/com/falsepattern/jfunge/Globals.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 41cdd9b..ba7bbe9 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.falsepattern jfunge - 1.1.0 + 1.2.0 17 diff --git a/src/main/java/com/falsepattern/jfunge/Globals.java b/src/main/java/com/falsepattern/jfunge/Globals.java index eeeb1f1..13d0dd2 100644 --- a/src/main/java/com/falsepattern/jfunge/Globals.java +++ b/src/main/java/com/falsepattern/jfunge/Globals.java @@ -2,7 +2,7 @@ public class Globals { public static final int MAJOR_VERSION = 1; - public static final int MINOR_VERSION = 1; + public static final int MINOR_VERSION = 2; public static final int PATCH_VERSION = 0; public static final int HANDPRINT = 0x74_70_85_78; //"JFUN" public static final String VERSION = MAJOR_VERSION + "." + MINOR_VERSION + "." + PATCH_VERSION; From 92ff3f539398a2f66469eb6c3b32e54960230868 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Mon, 13 Feb 2023 00:14:02 +0100 Subject: [PATCH 28/28] fix java version in CI --- .github/workflows/build-and-test.yml | 4 ++-- .github/workflows/release-tags.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 95f39d3..9c7c33f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -11,11 +11,11 @@ jobs: with: fetch-depth: 0 - - name: Set up JDK 8 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: 'adopt' - java-version: '8' + java-version: '17' - name: Build with Maven run: mvn --batch-mode --update-snapshots verify diff --git a/.github/workflows/release-tags.yml b/.github/workflows/release-tags.yml index 4aca775..748a3ca 100644 --- a/.github/workflows/release-tags.yml +++ b/.github/workflows/release-tags.yml @@ -16,11 +16,11 @@ jobs: - name: Set release version run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - - name: Set up JDK 8 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: distribution: 'adopt' - java-version: '8' + java-version: '17' server-id: mavenpattern server-username: MAVEN_DEPLOY_USER server-password: MAVEN_DEPLOY_PASSWORD