From fbd6bebc69b6fcd13eee597957d2f683a57959cc Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 16 Oct 2022 14:17:42 +0200 Subject: [PATCH 01/35] set up automatic publishing with git tags --- .../{test.yaml => build-and-test.yml} | 12 ++++-- .github/workflows/release-tags.yml | 41 +++++++++++++++++++ README.MD | 7 ++++ 3 files changed, 56 insertions(+), 4 deletions(-) rename .github/workflows/{test.yaml => build-and-test.yml} (55%) create mode 100644 .github/workflows/release-tags.yml diff --git a/.github/workflows/test.yaml b/.github/workflows/build-and-test.yml similarity index 55% rename from .github/workflows/test.yaml rename to .github/workflows/build-and-test.yml index 889135f..95f39d3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/build-and-test.yml @@ -1,4 +1,4 @@ -name: Java CI +name: Build and Test on: [push] @@ -8,10 +8,14 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 17 + with: + fetch-depth: 0 + + - name: Set up JDK 8 uses: actions/setup-java@v3 with: distribution: 'adopt' - java-version: '17' + java-version: '8' + - name: Build with Maven - run: mvn --batch-mode --update-snapshots package \ No newline at end of file + run: mvn --batch-mode --update-snapshots verify diff --git a/.github/workflows/release-tags.yml b/.github/workflows/release-tags.yml new file mode 100644 index 0000000..6763c37 --- /dev/null +++ b/.github/workflows/release-tags.yml @@ -0,0 +1,41 @@ +name: Release tagged build + +on: + push: + tags: + - '*' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set release version + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + + - name: Set up JDK 8 + uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: '8' + server-id: mavenpattern + server-username: MAVEN_DEPLOY_USER + server-password: MAVEN_DEPLOY_PASSWORD + + - 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 }} + + - name: Release under current tag + uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + automatic_release_tag: "${{ env.RELEASE_VERSION }}" + prerelease: false + title: "${{ env.RELEASE_VERSION }}" + files: target/*.jar diff --git a/README.MD b/README.MD index 7a28fd0..e48d422 100644 --- a/README.MD +++ b/README.MD @@ -35,6 +35,13 @@ Additionally, the following fingerprints are currently supported (more to come): - [NULL](https://catseye.tc/view/funge-98/library/NULL.markdown) - [ROMA](https://catseye.tc/view/funge-98/library/ROMA.markdown) + +### Version Release Checklist +- Update the version number inside the [pom](./pom.xml) +- Update the version string and FUNGE_VERSION inside [Globals](./src/main/java/com/falsepattern/jfunge/Globals.java) +- Create a new git tag with the same version number, and push it to master +- Never push tags outside master! + ### References - [Esolang wiki article of Funge-98](https://esolangs.org/wiki/Funge-98) From 38823a6ddb0f4fe4b073c42942970c25727fccc1 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 16 Oct 2022 14:37:31 +0200 Subject: [PATCH 02/35] implement ORTH --- README.MD | 1 + .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/ORTH.java | 113 ++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java diff --git a/README.MD b/README.MD index e48d422..76fbced 100644 --- a/README.MD +++ b/README.MD @@ -33,6 +33,7 @@ Additionally, the following fingerprints are currently supported (more to come): - [MODE](https://catseye.tc/view/funge-98/library/MODE.markdown) - [MODU](https://catseye.tc/view/funge-98/library/MODU.markdown) - [NULL](https://catseye.tc/view/funge-98/library/NULL.markdown) +- [ORTH](https://catseye.tc/view/funge-98/library/ORTH.markdown) - [ROMA](https://catseye.tc/view/funge-98/library/ROMA.markdown) 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 80d5936..b837dbd 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -6,6 +6,7 @@ import com.falsepattern.jfunge.interpreter.instructions.fingerprints.MODE; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.MODU; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.NULL; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.ORTH; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.ROMA; import com.falsepattern.jfunge.ip.Stack; import gnu.trove.map.TIntObjectMap; @@ -35,6 +36,7 @@ public class Funge98 implements InstructionSet { addFingerprint(MODE.INSTANCE); addFingerprint(MODU.INSTANCE); addFingerprint(NULL.INSTANCE); + addFingerprint(ORTH.INSTANCE); addFingerprint(ROMA.INSTANCE); } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java new file mode 100644 index 0000000..c1725c9 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java @@ -0,0 +1,113 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Funge98; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import lombok.val; +import org.joml.Vector2i; +import org.joml.Vector3i; + +import java.nio.charset.StandardCharsets; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ORTH implements Fingerprint { + public static final ORTH INSTANCE = new ORTH(); + + @Instr('A') + public static void and(ExecutionContext ctx) { + Funge98.binop(ctx, (a, b) -> a & b); + } + + @Instr('O') + public static void or(ExecutionContext ctx) { + Funge98.binop(ctx, (a, b) -> a | b); + } + + @Instr('E') + public static void xor(ExecutionContext ctx) { + Funge98.binop(ctx, (a, b) -> a ^ b); + } + + @Instr('X') + public static void setX(ExecutionContext ctx) { + ctx.IP().position.x = ctx.IP().stackStack.TOSS().pop(); + } + + @Instr('Y') + public static void setY(ExecutionContext ctx) { + ctx.IP().position.y = ctx.IP().stackStack.TOSS().pop(); + } + + @Instr('V') + public static void setDX(ExecutionContext ctx) { + ctx.IP().delta.x = ctx.IP().stackStack.TOSS().pop(); + } + + @Instr('W') + public static void setDY(ExecutionContext ctx) { + ctx.IP().delta.y = ctx.IP().stackStack.TOSS().pop(); + } + + @Instr('G') + public static void get(ExecutionContext ctx) { + val ip = ctx.IP(); + val toss = ip.stackStack.TOSS(); + if (ctx.dimensions() == 3) { + val vec = new Vector3i(); + vec.x = toss.pop(); + vec.y = toss.pop(); + vec.z = toss.pop(); + vec.add(ip.storageOffset); + toss.push(ctx.fungeSpace().get(vec)); + } else { + val vec = new Vector2i(); + vec.x = toss.pop(); + vec.y = toss.pop(); + vec.add(ip.storageOffset.x, ip.storageOffset.y); + toss.push(ctx.fungeSpace().get(vec.x, vec.y, ip.storageOffset.z)); + } + } + + @Instr('P') + public static void put(ExecutionContext ctx) { + val ip = ctx.IP(); + val toss = ip.stackStack.TOSS(); + if (ctx.dimensions() == 3) { + val vec = new Vector3i(); + vec.x = toss.pop(); + vec.y = toss.pop(); + vec.z = toss.pop(); + vec.add(ip.storageOffset); + val i = toss.pop(); + ctx.fungeSpace().set(vec, i); + } else { + val vec = new Vector2i(); + vec.x = toss.pop(); + vec.y = toss.pop(); + vec.add(ip.storageOffset.x, ip.storageOffset.y); + val i = toss.pop(); + ctx.fungeSpace().set(vec.x, vec.y, ip.storageOffset.z, i); + } + } + + @Instr('Z') + public static void rampIfZero(ExecutionContext ctx) { + if (ctx.IP().stackStack.TOSS().pop() == 0) { + Funge98.trampoline(ctx); + } + } + + @SneakyThrows + @Instr('S') + public static void outputString(ExecutionContext ctx) { + val str = ctx.IP().stackStack.TOSS().popString(); + ctx.output().write(str.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public int code() { + return 0x4f525448; + } +} From e3dcf1e7564d122fceb813a5c2b3adc44428f231 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 16 Oct 2022 14:48:42 +0200 Subject: [PATCH 03/35] pull in official docs for safekeeping --- README.MD | 12 +- docs/catseye/README.md | 38 + docs/catseye/doc/ERRATA.markdown | 45 + docs/catseye/doc/funge98.markdown | 1344 ++++++++++++++++++++++++++++ docs/catseye/doc/laheys.jpg | Bin 0 -> 18389 bytes docs/catseye/doc/wrap.jpg | Bin 0 -> 3615 bytes docs/catseye/library/HRTI.markdown | 33 + docs/catseye/library/MODE.markdown | 29 + docs/catseye/library/MODU.markdown | 17 + docs/catseye/library/NULL.markdown | 10 + docs/catseye/library/ORTH.markdown | 51 ++ docs/catseye/library/PERL.markdown | 25 + docs/catseye/library/REFC.markdown | 24 + docs/catseye/library/ROMA.markdown | 19 + docs/catseye/library/TOYS.markdown | 118 +++ docs/catseye/library/TURT.markdown | 50 ++ 16 files changed, 1809 insertions(+), 6 deletions(-) create mode 100644 docs/catseye/README.md create mode 100644 docs/catseye/doc/ERRATA.markdown create mode 100644 docs/catseye/doc/funge98.markdown create mode 100644 docs/catseye/doc/laheys.jpg create mode 100644 docs/catseye/doc/wrap.jpg create mode 100644 docs/catseye/library/HRTI.markdown create mode 100644 docs/catseye/library/MODE.markdown create mode 100644 docs/catseye/library/MODU.markdown create mode 100644 docs/catseye/library/NULL.markdown create mode 100644 docs/catseye/library/ORTH.markdown create mode 100644 docs/catseye/library/PERL.markdown create mode 100644 docs/catseye/library/REFC.markdown create mode 100644 docs/catseye/library/ROMA.markdown create mode 100644 docs/catseye/library/TOYS.markdown create mode 100644 docs/catseye/library/TURT.markdown diff --git a/README.MD b/README.MD index 76fbced..92744b0 100644 --- a/README.MD +++ b/README.MD @@ -30,11 +30,11 @@ The interpreter supports the following Funge-98 specification extensions: - Optional Trefunge mode (experimental) Additionally, the following fingerprints are currently supported (more to come): -- [MODE](https://catseye.tc/view/funge-98/library/MODE.markdown) -- [MODU](https://catseye.tc/view/funge-98/library/MODU.markdown) -- [NULL](https://catseye.tc/view/funge-98/library/NULL.markdown) -- [ORTH](https://catseye.tc/view/funge-98/library/ORTH.markdown) -- [ROMA](https://catseye.tc/view/funge-98/library/ROMA.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) +- [ROMA](./docs/catseye/library/ROMA.markdown) ### Version Release Checklist @@ -46,5 +46,5 @@ Additionally, the following fingerprints are currently supported (more to come): ### References - [Esolang wiki article of Funge-98](https://esolangs.org/wiki/Funge-98) -- [Official funge98 language specification](https://github.com/catseye/Funge-98/blob/master/doc/funge98.markdown) +- [Official funge98 language specification](./docs/catseye/doc/funge98.markdown) - [Mycology Befunge-98 test suite](https://github.com/Deewiant/Mycology) diff --git a/docs/catseye/README.md b/docs/catseye/README.md new file mode 100644 index 0000000..223a2de --- /dev/null +++ b/docs/catseye/README.md @@ -0,0 +1,38 @@ +Funge-98 Specification Documents +================================ +Mirrored from the official repo in case it goes down. + +Edition: May 22, 2018. + +**Funge-98** is a family of programming languages (primarily Befunge-98, +Unefunge-98, and Trefunge-98) which was designed as a successor to +[Befunge-93][]. + +This distribution is the canonical place to find the specs for Funge-98. + +They are: + +* [Funge-98 Final Specification](doc/funge98.markdown) +* [Funge-98 Fingerprints Library](library) +* [Funge-98 ERRATA](doc/ERRATA.markdown) + +This distribution contains only specification documents. It does not +contain any implementations. + +This distribution is hosted in a git repository, which is currently +[available on GitHub](https://github.com/catseye/Funge-98). Releases +of this distribution are [available on catseye.tc](http://catseye.tc/distribution/Funge-98_distribution). + +External resources +------------------ + +* [Befunge Silver Jubilee Retrospective][], a look back from 2018 + which might help provide perspective on what this all is +* [Funge-98][] entry at [Cat's Eye Technologies][] +* [FBBI][], an early and terrible implementation of Befunge-98 + +[Befunge-93]: http://catseye.tc/node/Befunge-93 +[Funge-98]: http://catseye.tc/node/Funge-98 +[FBBI]: https://github.com/catseye/FBBI +[Cat's Eye Technologies]: http://catseye.tc/ +[Befunge Silver Jubilee Retrospective]: http://catseye.tc/view/The-Dossier/article/Befunge%20Silver%20Jubilee%20Retrospective.md diff --git a/docs/catseye/doc/ERRATA.markdown b/docs/catseye/doc/ERRATA.markdown new file mode 100644 index 0000000..0ca00ce --- /dev/null +++ b/docs/catseye/doc/ERRATA.markdown @@ -0,0 +1,45 @@ +Funge-98 ERRATA +=============== + +Being a collection of lies, damned lies, and statistics about the +[Funge-98 Final Specification](funge98.markdown). + +Edition: May 22, 2018. + +This document does not aim to list all the ambiguities in the +spec. There are several instances where some implementors have +interpreted a statement one way, and other implementors have +interpreted it another way. Such instances may or may not be +listed in this document, but if they are, please do not expect +to find a normative interpretation of them here. + +Befunge-93 cells were not unsigned +---------------------------------- + +Reported by [James Holderness](https://github.com/j4james) +on January 16th, 2016, in +[this GitHub issue](https://github.com/catseye/Funge-98/issues/2). + +In the section "Code and Data" the claim is made that +Befunge-93 "defines unsigned 8-bit Funge-Space cells". + +In fact, cells in the original Befunge-93 implementation were +`char` type, which ANSI C does not mandate as either signed or +unsigned. Because the compilers typically used to compile this +code used signed chars, and several prominent example programs +relied on this behaviour, Befunge-93 was popularly understood to +have signed cells, so if you are looking for a de facto standard +for signedness of the cells in Befunge-93, that would probably be +it. But that's Befunge-93, not Funge-98, anyway. + +Incorrect delta for south/north +------------------------------- + +Reported by [Bennett Bernardoni](https://github.com/bbernardoni) +on June 21st, 2017, in +[this GitHub issue](https://github.com/catseye/Funge-98/issues/5). + +Contra the statement in the section titled "Instruction Pointer", +it is in fact the case that (0,-1) is called _north_ and is +depicted as being more up the page or screen, and (0,1) is called +_south_ and is depicted as being more down the page or screen. diff --git a/docs/catseye/doc/funge98.markdown b/docs/catseye/doc/funge98.markdown new file mode 100644 index 0000000..26df046 --- /dev/null +++ b/docs/catseye/doc/funge98.markdown @@ -0,0 +1,1344 @@ +Funge-98 Final Specification +============================ + +Chris Pressey, Sept 11, 1998 +revised for clarity: Sept 30, 1998 +converted to Markdown: Feb 24, 2013 +restored omissions in Markdown conversion + (thanks to: James Holderness): March 27, 2016 +added [ERRATA](ERRATA.markdown) document: May 22, 2018 + +* * * * * + +### Table of Contents + +- [Introduction](#Introduction) + + - [What is a Funge?](#what-is-a-funge) + - [About this Document](#about-this-document) + +- [The Funge Virtual Machine](#the-funge-virtual-machine) + + - [Code and Data](#code-and-data) + - [Funge-Space](#funge-space) + - [Stack Stack](#stack-stack) + - [Funge Source File Format](#funge-source-file-format) + +- [Code: Program Flow](#code-program-flow) + + - [Instruction Pointer](#instruction-pointer) + - [Instructions](#instructions) + - [Direction Changing](#direction-changing) + - [Wrapping](#wrapping) + - [Flow Control](#flow-control) + - [Decision-Making](#decision-making) + +- [Data: Cell Crunching](#data-cell-crunching) + + - [Integers](#integers) + - [Strings](#strings) + - [Stack Manipulation](#stack-manipulation) + - [Stack Stack Manipulation](#stack-stack-manipulation) + +- [Media: Communications and Storage](#media-communications-and-storage) + + - [Funge-Space Storage](#funge-space-storage) + - [Standard Input/Output](#standard-inputoutput) + - [File Input/Output](#file-inputoutput) + - [System Execution](#system-execution) + - [System Information Retrieval](#system-information-retrieval) + +- [Scale: Extension and Customization](#scale-extension-and-customization) + + - [Handprints](#handprints) + - [Fingerprints](#fingerprints) + - [Funge Central Registry](#funge-central-registry) + +- [Appendix](#appendix) + + - [Instruction Quick Reference](#instruction-quick-reference) + - [Concurrent Funge-98](#concurrent-funge-98) + - [Lahey-Space](#lahey-space) + - [Other Topologies](#other-topologies) + +* * * * * + +Introduction +------------ + +### What is a Funge? + +Funges are programming languages whose programs are typically expressed +in a given topological pattern and number of dimensions. + +Funge-98 is currently an official prototype standard for Funges. +Funge-98 has evolved from Funge-97, which was a generalization of +Befunge-97, which was an improvement over Befunge-96, which was an +update of the original Befunge-93 language definition. + +Funge-98 is a *class* of three real and officially sanctioned +programming languages (Unefunge, Befunge, and Trefunge) and provides a +paradigm for describing any number of imaginary ones with different +topologies and any number of dimensions. + +The most popular Funge by far is Befunge, which is two-dimensional and +based on a Cartesian Lahey-Space (or Cartesian Torus, in Befunge-93 +only) topology. Other Cartesian Lahey-Space Funges include Unefunge +(one-dimensional) and Trefunge (three-dimensional.) Since not all Funge +instructions are appropriate in all Funges, comparison to Befunge is +often used to clarify points in this document. + +* * * * * + +### About this Document + +This is a final document. The information it contains has been formally +approved and it is endorsed by its supporters as the 'official' +technical specification of the Funge language family. + +This document is suitable for an audience not already familiar with any +Funge of any kind or year. + +* * * * * + +The Funge Virtual Machine +------------------------- + +### Code and Data + +Any given piece of code or data in a Funge can be stored in one of two +places (called a *cell*): + +- *Funge-Space*, a matrix appropriate to the dimensionality, topology + and tiling pattern of the Funge, where each *node* in its + topological net contains a cell; or +- the *stack* in Befunge-93 or the *stack stack* in Funge-98; either + way, it's often called *the stack* and it's accessed as a last-in, + first-out (LIFO) stack of cells. + +Befunge-93 defines signed 32-bit stack cells and unsigned 8-bit +Funge-Space cells. In Funge-98, stack and Funge-Space cells alike should +be treated as signed integers of the same size. + +What size exactly is left up to the implementer. 32 bits is typical. 16 +bit and 8 bit versions are discussed as separate variations on Funge-98. +More than 32 bits is just fine. The important thing is that the stack +cells have the same memory size as the Funge-Space cells. + +* * * * * + +### Funge-Space + +In Befunge-93, Funge-Space is restricted to 80 cells in the *x* +dimension and 25 cells in the *y* dimension. No such limits are imposed +on Funge-98 programs. A Funge-98 interpreter, ideally, has an addressing +range equal to that of its cell size. i.e. A 32-bit implementation of +Funge-98 uses signed 32-bit integers as each of its coordinate indices. + +With such a large typical addressing range, the Funge-98-Space is +generally considered to be dynamically allocated by some +behind-the-scenes mechanism in the compiler. It *needn't* be, of course, +but in practice, it usually is. + +So, the storage mechanism has be consistently trustworthy about how it +provides Funge-Space to the running program. A Funge-98 program should +be able to rely on all code and data that it puts in Funge-Space through +this mechanism not disappearing. A Funge-98 program should also be able +to rely on the memory mechanism acting as if a cell contains blank space +(ASCII 32) if it is unallocated, and setting memory to be full of blank +space cells upon actual allocation (program load, or `p` instruction). +If the underlying memory mechanism cannot provide this (e.g. no more +memory is available to be allocated,) the interpreter should complain +with an error and do what it can to recover, (but not necessarily +gracefully). + +The co-ordinate mapping used for both Befunge-93 and Funge-98 reflects +the "Computer Storage" co-ordinate system used in screen graphics and +spreadsheets; a larger *y* coordinate means further down the page. +Compared to a standard mathematical representation of the usual +Cartesian co-ordinate system, it is upside-down. + + Befunge-93 32-bit Befunge-98 + ========== ================= + 0 x 79 |-2,147,483,648 + 0+-------------+ | + | | x + | -----+----- + y| -2,147,483,648 | 2,147,483,647 + | | + | y|2,147,483,647 + 24+ + +* * * * * + +### Stack Stack + +The Funge stack stack is a LIFO stack of typical LIFO stacks of cells. +In Befunge-93, only two operations are possible on only one stack +(referred to as *the stack*): to *push* a cell onto the top of the +stack, and to *pop* a cell off the top of the stack. + +In the case of Funge-98, however, *the stack* refers to the topmost +stack on the stack stack. The push and pop operations are possible on +the stack stack as well, but they push and pop entire stacks. + +There is also a Funge-98 instruction to rid the stack (that is, the +topmost stack of the stack stack) of cells, completely emptying it. + +If a program attempts to pop a cell off the stack when it is empty, no +error occurs; the program acts as if it popped a 0. + +In this document, short stacks are generally notated left to right to +mean **bottom to top**. The **leftmost** values listed in the +documentation are the **bottommost** and the **first** to be pushed onto +the stack. Long stacks are notated top to bottom, to mean precisely +that, **top to bottom.**. + +* * * * * + +### Funge Source File Format + +A Befunge-93 source (program) file name, by common convention, ends in +the extension `.bf`. There is no enforced convention for what any given +Funge-98 source file name ends in (e.g. you could easily write a +C-Befunge polyglot whose file name ends in `.c`), but `.b98` is a good +choice for Befunge-98 sources - "standard" example programs use this +suffix. + +Befunge-93 source files are plain text files containing only printable +ASCII characters and the end-of-line controls described below. + +Funge-98 source files are made up of Funge characters. The Funge-98 +character set overlays the ASCII subset used by Befunge-93 and may have +characters greater than 127 present in it (and greater than 255 on +systems where characters are stored in multiple bytes; but no greater +than 2,147,483,647.) The Funge character set is 'display-independent.' +That is to say, character \#417 may look like a squiggle on system Foo +and a happy face on system Bar, but the meaning is always the same to +Funge, 'character \#417', regardless of what it looks like. + +In other words, what Funge characters look like on a particular computer +or OS depends entirely on that computer or OS. However, when characters +are not generally considered to be printable, they can have special +meaning to Funge-98: + +- 0..31 : "ASCII controls" (only 10 is currently defined to mean EOL) +- 32..126 : "ASCII printable characters" (all are input/output and + fixed-width) +- 127 : "delete control" (undefined) +- 128..2bil: "extended printable characters" (machine and font + specific) + +In Befunge-93, each line ends with the current operating system's "end +of line" character, which may be a line feed (10) (Linux), carriage +return (13) (MacOS), or carriage return-line feed (13, 10) (MS-DOS). + +In Funge-98, however, *any* of the following sequences should, ideally, +be recognized by the interpreter as an end-of-line marker, no matter +*what* operating system it's running on: + +- Line Feed (10) +- Carriage Return (13) +- Carriage Return, Line Feed (13, 10) + +If an interpreter cannot support all three varieties of end-of-line +marker, it should be clearly noted in that interpreter's documentation. + +End-of-line markers do **not** appear in Funge-Space once the program is +loaded. + +In Befunge-93, each line can contain up to 80 significant characters +before the "End of Line" marker. There can be up to 25 such lines in the +source file. There are no such restrictions on Befunge-98 and the user +can reasonably expect to be able to have as many lines of as many +characters as they want, for non-contrived cases. + +Before load, every cell in Funge-Space contains a space (32) character. +These default contents are written over by characters in the program +source when it is loaded. However, spaces in the program source do not +overwrite anything in Funge-Space; in essence the space character is +transparent in source files. This becomes important when the `i` "Input +File" instruction is used to include overlapping files. + +The source file begins at the *origin* of Funge-Space. Subsequent +columns of characters increment the *x* coordinate, and subsequent lines +increment the *y* coordinate (if one is present) and reset the *x* +coordinate to zero. Subsequent lines in Unefunge are simply appended to +the first, and the end of the source file indicates the end of the +(single) line. End-of-line markers are never copied into Funge-Space. + +In Trefunge-98, the Form Feed (12) character increments the *z* +coordinate and resets the *x* and *y* coordinates to zero. + +* * * * * + +Code: Program Flow +------------------ + +### Instruction Pointer + +The *instruction pointer* (IP) can be thought of as a *vector* (set of +co-ordinates) which represents the "current position" of a running Funge +program. It holds the same function as the instruction pointer (or +*program counter* (PC)) in any other language or processor - to indicate +where the currently executing instruction is located. + +In most other languages and machines (both virtual and real,) the IP/PC +is restricted to unidirectional travel in a single dimension, with +random jumps. However, in Funge, the IP keeps track of another vector +called the *delta*. Every *tick*, the IP executes its current +instruction (that is, the instruction at the location pointed to by the +IP), then travels to a new location, by adding its delta vector to its +position vector. + +At the beginning of a program, in Funge-98 as in Befunge-93, the IP +always begins at the origin and starts with a delta of (1, 0). The +origin is (0, 0) in Befunge, (0) in Unefunge, and (0, 0, 0) in Trefunge. + +In two dimensions, we have the following terminology. + +If the IP's delta is either (0,-1) (*south*), (1,0) (*east*), (0,1) +(*north*), or (-1,0) (*west*), it is said to be traveling *cardinally*. +This is the same as how a rook moves in chess and this is in fact the +only way the IP can move in Befunge-93. + +Any IP with a nonzero delta is considered *moving*. Any IP with a zero +delta is said to be *stopped*. Any moving IP that is not traveling +cardinally is said to be *flying*. + +* * * * * + +### Instructions + +All standard instructions are one character long and range from ASCII 32 +(space) to ASCII 126 (`~`). There are no multicharacter instructions in +Funge. + +An instruction is executed by an IP every tick. The IP executed is the +one at the current position of the IP. Only after that does the IP moves +by its delta to a new position. + +Instructions `A` to `Z` all initially act like the `r` "Reflect" +instruction. However, other instructions assign semantics to these +instructions dynamically, allowing the Funge programmer to use libraries +of both standard and proprietary instruction sets tagged with unique +ID's (or *fingerprints*.) + +However, a Funge-98 interpreter may also expose any number of +proprietary instructions above ASCII 127 or below ASCII 0. + +For all these reasons, when encountering any unimplemented instruction +(this includes instructions like `|` in Unefunge,) the Funge interpreter +should at least provide an option for informing the user that it was +told to execute an instruction that isn't implemented, and possibly +warning the user that this file might be an incorrect language or +version. + +An unimplemented instruction must otherwise act as if `r` was executed, +and must not touch the stack. All undefined or otherwise unimplemented +instructions must be considered unimplemented. + +Also, any of the instructions `t` (concurrent execution,) `=` (execute,) +`i` (input-file,) and `o` (output-file) may be unavailable in different +interpreters for many reasons, and are routinely bound to (i.e. act just +like) `r` as well. However, they may also act like `r` when they fail to +execute. To test if they are actually supported, execute `1y` and +examine the cell it produces. + +* * * * * + +### Direction Changing + +A few instructions are essential for changing the delta of the IP. The +`>` "Go East" instruction causes the IP to travel east; the `<` "Go +West" instruction causes the IP to travel west. These instructions are +valid in all Funges. + +The `^` "Go North" instruction causes the IP to travel north; the `v` +"Go South" instruction causes the IP to travel south. These instructions +are not available in Unefunge. + +The `h` "Go High" instruction causes the IP to travel up (delta \<- +(0,0,1)); the `l` "Go Low" instruction causes the IP to travel down +(delta \<- (0,0,-1)). These instructions are not available in Unefunge +or Befunge. + +The `?` "Go Away" instruction causes the IP to travel in a random +cardinal direction appropriate to the number of dimensions in use: east +or west in Unefunge; north, south, east or west in Befunge, etc. + +The following instructions are not in Befunge-93, but they are in +Funge-98. + +The `]` "Turn Right" and `[` "Turn Left" instructions rotate by 90 +degrees the delta of the IP which encounters them. They always rotate on +the *z* axis. These instructions are not available in Unefunge. + +To remember which is which, visualize yourself on the seat of a bicycle, +looking down at the handlebars: + + +- +-+ -+ + | | | | + +- -+ + + [ ] + + Turn Go Turn + Left  Forward Right + +The `r` "Reverse" instruction multiplies the IP's delta by -1. In two +dimensions, this is the equivalent of reflecting the delta of the IP +about the z-axis. + +The `x` "Absolute Vector" instruction pops a vector off the stack, and +sets the IP delta to that vector. + +A vector on the stack is stored bottom-to-top, so that in Befunge, `x` +(and all other vector-popping instructions) pops a value it calls *dy*, +then pops a value it calls *dx*, then sets the delta to (*dx*, *dy*). + +* * * * * + +### Wrapping + +Befunge-93 handles the case of the IP travelling out of bounds (off the +map of Funge-Space) by treating the space as a torus. If the IP leaves +the west edge, it reappears on the east edge at the same row; if it +leaves the south edge, it reappears at the north edge at the same +column, and vice versa in both cases. + +For various reasons, toroidal wrapping is problematic in Funge-98. +Instead, we use a special wrapping technique that has more consistent +results in this new, more flexible environment where Funge-Space can +have an arbitrary size and the IP can fly. It is called *same-line +wrapping*. + +Same-line wrapping can be described in several ways, but the crucial +idea it encompasses is this: unless the delta or position of the IP were +to be changed by an intervening instruction, the IP will always wrap +such that it would eventually return to the instruction it was on before +it wrapped. + +The mathematical description of same-line wrapping is known as +*Lahey-space* wrapping, which defines a special topological space. It is +generally of more interest to topologists and mathematicians than +programmers. We won't cover it here, but it is included in the +[Appendix](#lahey-space) for completeness. + +The algorithmic description of same-line wrapping can be described as +*backtrack wrapping*. It is more of interest to Funge interpreter +implementers than Funge programmers. However, it does describe exactly +how the wrapping *acts* in terms that a programmer can understand, so we +will include it here. + +When the IP attempts to travel into the whitespace between the code and +the end of known, addressable space, it backtracks. This means that its +delta is reversed and it ignores (skips over without executing) all +instructions. Travelling thus, it finds the other 'edge' of code when +there is again nothing but whitespace in front of it. It is reflected +180 degrees once more (to restore its original delta) and stops ignoring +instructions. Execution then resumes normally - the wrap is complete. + +![(wrap.jpg - Wrapping pictorial diagram)](wrap.jpg) + +It is easy to see at this point that the IP remains on the same line: +thus the name. (Also note that this **never** takes any ticks in regards +to multithreading, as would be expected from any wrapping process.) + +Same-line wrapping has the advantage of being backward-compatible with +Befunge-93's toroidal wrapping. It also works safely both when the IP +delta is flying (non-cardinal), and when the size of the program +changes. + +As noted, by default every cell in Funge-Space contains a space (32) +character. (This holds true with most decent Befunge-93 interpreters, +too, although it was not in the original.) + +In Befunge-93, when interpreted as an instruction, a space is treated as +a "no operation" or *nop*. The interpreter does nothing and continues on +its merry way. + +Funge-98 acts much the same way, except technically, Funge-98 processes +any number of spaces in "no time whatsoever", and this becomes important +when you have more than one IP in Funge-Space at the same time +(*multithreading*), which you'll read about later. For an explicit nop +instruction in Funge-98, use `z`. + +Space also takes on special properties (in Funge-98) with a special +*mode* of the interpreter called stringmode, which you'll also read +about later. + +* * * * * + +### Flow Control + +The `#` "Trampoline" instruction moves the IP one position beyond the +next Funge-Space cell in its path. + +The `@` "Stop" instruction kills the current IP. In non-Concurrent +Funge, there is only a single IP. Either way, when no IP's are left +alive, the program will subsequently end with no error (returning error +code 0). + +The following instructions and markers are not in Befunge-93, but they +are in Funge-98. + +The `;` "Jump Over" marker causes the IP to jump over all subsequent +instructions until the next `;` marker. Like space, this takes zero +ticks to execute, so that subroutines, comments, and satellite code can +be insulated by surrounding it with `;` markers, with no effect on +multithreading. + +`;` is truly ethereal; like space, it cannot ever be truly executed, in +the sense of it taking up a tick and doing something. + +The `j` "Jump Forward" instruction pops a value off the stack, and jumps +over that many spaces. If there is a 1 on the stack, `j` will work like +`#` does. e.g. `2j789.` would print 9 and leave an empty stack. Negative +values are legal arguments for `j`, such that `04-j@` is an infinite +loop. + +The `q` "Quit" instruction, only in Funge-98, ends the entire program +immediately (regardless of the number of IPs active in Concurrent +Funge). It also pops a cell off the stack and uses that value as the +return value of the Funge interpreter to the operating system. + +Note that most operating systems will only look at the least significant +byte your return value, unsigned. But you can return a full cell, and +the OS will interpret as much of it as it can handle, treating it as +signed or unsigned as the OS demands. + +The `k` "Iterate" instruction pops a value *n* off the stack. Then it +finds the next instruction in Funge-space in the path of the IP (note +that this cannot be a marker such as space or `;`), treats it as an +instruction, executing it *n* times. This takes only one tick with +respect to concurrent operation. + +Note that some instructions don't make much sense within the context of +`k` unless you include zero as one of the possibilities for how many +times the instruction is repeated. For example, no matter how many times +after the first time `k` execute `^`, the result is the same. However, +you may pass a zero count to `k`, and the `^` instruction will not be +executed; this can be a valuable behaviour. + +Also, note `k` will never, ever actually execute instruction \#32, +space, or `;`. + +* * * * * + +### Decision Making + +The `!` "Logical Not" instruction pops a value off the stack and pushes +a value which is the logical negation of it. If the value is zero, it +pushes one; if it is non-zero, it pushes zero. + +The `` ` `` "Greater Than" instruction pops two cells off the stack, +then pushes a one if second cell is greater than the first. Otherwise +pushes a zero. + +Funge has instructions that act like directional 'if' statements. The +`_` "East-West If" instruction pops a value off the stack; if it is zero +it acts like `>`, and if non-zero it acts like `<`. + +The `|` "North-South If" instruction pops a value off the stack; if it +is zero it acts like `v`, and if non-zero it acts like `^`. `|` is not +available in Unefunge. + +The `m` "High-Low If" (think *middle*) instruction pops a value off the +stack; if it is zero it acts like `l`, and if non-zero it acts like `h`. +`m` is not available in Unefunge or Befunge. + +The `w` "Compare" instruction pops a value *b* off the stack, then pops +a value *a*, then compares them. (*a* is called *a* because it was the +first of the two values to be *pushed* onto the stack.) If the *a* is +smaller, `w` acts like `[`, and turns left. If the *a* is greater, `w` +acts like `]`, and turns right. If *a* and *b* are equal, `w` does not +affect the IP's delta. This instruction is not available in Befunge-93, +nor Unefunge. + +* * * * * + +Data: Cell Crunching +-------------------- + +### Integers + +Instructions `0` "Push Zero" through `9` "Push Niner" push the values +zero through nine onto the stack, respectively. In Funge-98, `a` through +`f` also push 10 through 15 respectively. + +The `+` "Add" instruction pops two cells from the stack, adds them using +integer addition, and pushes the result back onto the stack. + +The `*` "Multiply" instruction pops two cells from the stack, multiplies +them using integer multiplication, and pushes the result back onto the +stack. In this way numbers larger than 9 (or 15) can be pushed onto the +stack. + +For example, to push the value 123 onto the stack, one might write + + 99*76*+ + +or + + 555**2- + +Funge also offers the following arithmetic instructions: + +- the `-` "Subtract" instruction, which pops two values, subtracts the + first from the second using integer subtraction, and pushes the + result; +- the `/` "Divide" instruction, which pops two values, divides the + second by the first using integer division, and pushes the result + (note that division by zero produces a result of zero in Funge-98, + but Befunge-93 instead is supposed to *ask* the user what they want + the result of the division to be); and +- the `%` "Remainder" instruction, which pops two values, divides the + second by the first using integer division, and pushes the + remainder, of those. Remainder by zero is subject to the same rules + as division by zero, but if either argument is negative, the result + is implementation-defined. + +* * * * * + +### Strings + +The instruction `"` "Toggle Stringmode" toggles a special mode of the +Funge interpreter called *stringmode*. In stringmode, every cell +encountered by the IP (except `"` and in Funge-98, space) is not +interpreted as an instruction, but rather as a Funge character, and is +pushed onto the stack. A subsequent `"` toggles stringmode once more, +turning it off. + +In Funge-98 stringmode, spaces are treated "SGML-style"; that is, when +any contiguous series of spaces is processed, it only takes one tick and +pushes one space onto the stack. This introduces a small +backward-incompatibility with Befunge-93; programs that have multiple +spaces and/or wrap while in stringmode will have to be changed to work +the same under Funge-98. + + Befunge-93 Befunge-98 + + "hello world" "hello world" + "hello world" "hello "::"world" + +There is also a `'` "Fetch Character" instruction in Funge-98. This +pushes the Funge character value of the next encountered cell +(position + delta) onto the stack, then adds the delta to the position +(like `#`), skipping over the character (in no ticks). For example, +the following two snippets perform the same function, printing a Q: + + "Q", + + 'Q, + +`s` "Store Character" is the mirror image of the `'` instruction: this +instead pops a value off the stack and writes it into (position + +delta). + +Some instructions expect a Funge string on the stack as one of their +arguments. The standard format for these strings is called *0"gnirts"* - +that is, a null-terminated string with the start of the string at the +top of the stack and the null termination at the bottom. + +* * * * * + +### Stack Manipulation + +The `$` "Pop" instruction pops a cell off the stack and discards it. + +The `:` "Duplicate" instruction pops a cell off the stack, then pushes +it back onto the stack twice, duplicating it. + +The `\` "Swap" instruction pops two cells off the stack, then pushes the +first cell back on, then the second cell, in effect swapping the top two +cells on the stack. + +The `n` "Clear Stack" instruction (not available in Befunge-93) +completely wipes the stack (popping and discarding elements until it is +empty.) + +* * * * * + +### Stack Stack Manipulation + +The stack stack transparently overlays the stack - that is to say, the +top stack of Funge-98's stack stack is treated the same as Befunge-93's +sole stack. The Funge programmer will never notice the difference unless +they use the `{`, `}`, or `u` instructions of Funge-98. + +When working with different stacks on the stack stack, though, it's +useful to give two of them names: the *top of stack stack* or TOSS, +which indicates the topmost stack on the stack stack, which works to +emulate the sole stack of Befunge-93; and the *second on stack stack* or +SOSS, which is the stack directly under the TOSS. + +The `{` "Begin Block" instruction pops a cell it calls *n*, then pushes +a new stack on the top of the stack stack, transfers *n* elements from +the SOSS to the TOSS, then pushes the storage offset as a vector onto +the SOSS, then sets the new storage offset to the location to be +executed next by the IP (storage offset \<- position + delta). It copies +these elements as a block, so order is preserved. + +If the SOSS contains *k* elements, where *k* is less than *n*, the *k* +elements are transferred as the top *k* elements and the remaining +bottom (*n-k*) elements are filled in with zero-value cells. + +If *n* is zero, no elements are transferred. + +If *n* is negative, |*n*| zeroes are pushed onto the SOSS. + +The corresponding `}` "End Block" instruction pops a cell off the stack +that it calls *n*, then pops a vector off the SOSS which it assigns to +the storage offset, then transfers *n* elements (as a block) from the +TOSS to the SOSS, then pops the top stack off the stack stack. + +The transfer of elements for `}` "End Block" is in all respects similar +to the transfer of elements for `{` "Begin Block", except for the +direction in which elements are transferred. "Transfer" is used here in +the sense of "move," not "copy": the original cells are removed. + +If *n* is zero, no elements are transferred. + +If *n* is negative, |*n*| cells are popped off of the (original) SOSS. + +`{` makes the current TOSS the new SOSS. `}` makes the current SOSS the +new TOSS. + +`{` may act like `r` if no more memory is available for another stack. +`}` acts like `r` if a stack-stack underflow would otherwise occur (i.e. +when there is only one stack on the stack-stack.) + +The practical use of these instructions is to "insulate" and "localize" +procedures or other blocks of Funge code. + +The `u` "Stack under Stack" instruction pops a *count* and transfers +that many cells from the SOSS to the TOSS. It transfers these cells in a +pop-push loop. In other words, the order is not preserved during +transfer, it is reversed. + +If there is no SOSS (the TOSS is the only stack), `u` should act like +`r`. + +If *count* is negative, |*count*| cells are transferred (similarly in a +pop-push loop) from the TOSS to the SOSS. + +If *count* is zero, nothing happens. + +* * * * * + +Media: Communications and Storage +--------------------------------- + +### Funge-Space Storage + +The `g` "Get" and `p` "Put" instructions are used to store and retrieve +data and code in Funge-Space. + +In Befunge-93, `g` pops a vector (that is, it pops a y value, then an x +value,) and pushes the value (character) in the Befunge-Space cell at +(x, y) onto the stack. `p` pops a vector, then it pops a value, then it +places that value in the Funge-Space cell at (x, y). + +In Funge-98, each IP has an additional vector property called the +*storage offset*. Initially this vector is the set to the origin. As +such, it works to emulate Befunge-93. The arguments to `g` and `p` are +the same, but instead of pointing to absolute locations in Funge-Space, +they reference a cell relative to the storage offset. + +* * * * * + +### Standard Input/Output + +The `.` "Output Decimal" and `,` "Output Character" instructions provide +numeric and Funge character/control output, respectively; they pop a +cell off the stack and send it in numeric or Funge character format to +the *standard output*, which is usually displayed by the interpreter in +an interactive user terminal. + +Outputting character number 10 will result in a new line being displayed +on the standard output. + +Numeric output is formatted as a decimal integer followed by a space. + +These instructions will act as `r` does, should the standard output fail +for any reason. + +The `&` "Input Decimal" and `~` "Input Character" instructions provide +numeric and Funge character/control input, respectively. They each +suspend the program and wait for the user to enter a value in numeric or +Funge character format to the *standard input*, which is usually +displayed with the standard output. They then push this value on the +stack. + +An input of character number 10 indicates that the user pressed the +'Enter' key, or the equivalent key on their keyboard. + +Decimal input reads and discards characters until it encounters decimal +digit characters, at which point it reads a decimal number from those +digits, up until (but not including) the point at which input characters +stop being digits, or the point where the next digit would cause a cell +overflow, whichever comes first. + +Although the standard input and output are generally displayed in some +sort of interactive user terminal, they needn't be; many operating +systems support *redirection*. In the case of an end-of-file or other +file error condition, the `&` and `~` both act like `r`. + +* * * * * + +### File Input/Output + +File I/O is done with the `i` "Input File" and `o` "Output File" +instructions, which are only available in Funge-98. + +`i` pops a null-terminated 0"gnirts" string for the filename, followed +by a flags cell, then a vector Va telling it where to operate. If the +file can be opened for reading, it is inserted into Funge-Space at Va, +and immediately closed. Two vectors are then pushed onto the stack, Va +and Vb, suitable arguments to a corresponding `o` instruction. If the +file open failed, the instruction acts like `r`. + +`i` is in all respects similar to the procedure used to load the main +Funge source code file, except it may specify any file, not necessarily +Funge source code, and at any location, not necessarily the origin. + +Also, if the least significant bit of the flags cell is high, `i` treats +the file as a binary file; that is, EOL and FF sequences are stored in +Funge-space instead of causing the dimension counters to be reset and +incremented. + +`o` first pops a null-terminated 0"gnirts" string to use for a filename. +Then it pops a flags cell. It then pops a vector Va indicating a *least +point* (point with the smallest numerical coordinates of a region; also +known as the upper-left corner, when used in the context of Befunge) in +space, and another vector Vb describing the size of a rectangle (or a +rectangular prism in Trefunge, etc). If the file named by the filename +can be opened for writing, the contents of the rectangle of Funge-Space +from Va to Va+Vb are written into it, and it is immediately closed. If +not, the instruction acts like `r`. + +The first vectors popped by both of these instructions are considered +relative to the storage offset. (The size vector Vb, of course, is +relative to the least point Va.) + +Note that in a greater-than-two-dimensional environment, specifying a +more-than-two-dimensional size such as (3,3,3) is not guaranteed to +produce sensical results. + +Also, if the least significant bit of the flags cell is high, `o` treats +the file as a linear text file; that is, any spaces before each EOL, and +any EOLs before the EOF, are not written out. The resulting text file is +identical in appearance and takes up less storage space. + +* * * * * + +### System Execution + +The `=` "Execute" instruction pops a string off the stack, and attempts +to execute it. How it executes it is implementation dependant. However, +an implementation may support one of several standardized ways of +interpreting the string, and which routine it uses can be determined by +querying `y`. Typically methods include treating it as a C system() call +would, or on a platform such as MacOS, for example, treating the string +as AppleScript would be in order. + +After execution, a failure value is pushed onto the stack. If this value +is zero, everything went as expected. If the value is non-zero, it may +be the return-code of the program that was executed; at any rate it +means that the attempt to execute the program, or the program itself, +did not succeed. + +* * * * * + +### System Information Retrieval + +The `y` "Get SysInfo" instruction, only available in Funge-98, pops one +value off the stack. If the value is zero or negative, `y` tells you far +more than you ever really want to know about the underlying Funge +interpreter, operating system, and computer (and is thus usually +followed soon after by a `n` instruction). + +Each cell of information retrieved by `y`, only applies to either the +current IP, the current environment (that is, the interpreter of the +current IP,) or the global environment (the environment of every IP in +the same Funge-space, regardless of which interpreter it's running on.) + +After an execution of `y` with a non-positive argument, the stack +contains many more cells (listed from top to bottom:) + +1. 1 cell containing flags (env). + - Least Significant Bit 0 (0x01): high if `t` is implemented. (is + this Concurrent Funge-98?) + - Bit 1 (0x02): high if `i` is implemented. + - Bit 2 (0x04): high if `o` is implemented. + - Bit 3 (0x08): high if `=` is implemented. + - Most Significant Bit 4 (0x10): high if unbuffered standard I/O + (like `getch()`) is in effect, low if the usual buffered variety + (like `scanf("%c")`) is being used. + - Further more significant bits: undefined, should all be low in + Funge-98 + +2. 1 cell containing the number of bytes per cell (global env). + + aka cell size. Typically 4, could also be 2, 8, really really large, + infinity, etc. + +3. 1 cell containing the implementation's handprint (env). +4. 1 cell containing the implementation's version number (env). + + If the version number contains points, they're stripped. + v2.01 == 201, v1.03.05 = 10305, v1.5g = 1507. Don't use non-numbers + in the version number to indicate 'personalizations' - change the + handprint instead. + +5. 1 cell containing an ID code for the Operating Paradigm (global env) + - 0 = Unavailable + - 1 = Equivalent to C-language `system()` call behaviour + - 2 = Equivalent to interpretation by a specific shell or program + + This shell or program is specified by the interpreter but should + ideally be customizable by the interpreter-user, if applicable. + Befunge programs that run under this paradigm should document + what program they expect to interpret the string passed to `=`. + + - 3 = Equivalent to interpretation by the same shell as started + this Funge interpreter, if applicable + + If the interpreter supports this paradigm, then in this manner, + the user executing a Befunge source can easily choose which + shell to use for `=` instructions. + + This value is included so the program can have a reasonable idea of + what `=` will do. The values shown here are only the most basic set + available at the time of publication. See the [Registry](#funge-central-registry) + for any late-breaking headway into further Operating Paradigms. + +6. 1 cell containing a path seperator character (global env) + + This is what path seperators for i and o filenames should look like. + +7. 1 cell containing the number of scalars per vector (global env) + + aka number of dimensions. 2 for Befunge, 1 for Unefunge, 3 for + Trefunge. + +8. 1 cell containing a unique ID for the current IP (ip) + + Only significant for Concurrent Funge. This ID differentiates this + IP from all others currently in the IP list. + +9. 1 cell containing a unique team number for the current IP (ip) + + Only significant for NetFunge, BeGlad, and the like. + +10. 1 vector containing the Funge-Space position of the current IP (ip) +11. 1 vector containing the Funge-Space delta of the current IP (ip) +12. 1 vector containing the Funge-Space storage offset of the current IP + (ip) +13. 1 vector containing the least point which contains a non-space cell, + relative to the origin (env) +14. 1 vector containing the greatest point which contains a non-space + cell, relative to the least point (env) + + These two vectors are useful to give to the `o` instruction to + output the entire program source as a text file. + +15. 1 cell containing current ((year - 1900) \* 256 \* 256) + + (month \* 256) + (day of month) (env) +16. 1 cell containing current (hour \* 256 \* 256) + (minute \* 256) + + (second) (env) +17. 1 cell containing the total number of stacks currently in use by the + IP (size of stack stack) (ip) +18. *size-of-stack-stack* cells containing size of each stack, listed + from TOSS to BOSS (ip) + + Stack sizes are pushed as if they were measured **before** `y` + began pushing elements onto the stack. + +19. a series of sequences of characters (strings), each terminated by a + null, the series terminated by an additional double null, containing + the command-line arguments. (env) + + This means any isolated argument can be a null string, but no two + consecutive arguments may be null strings - a rather contrived + scenario, null string arguments being rare in themselves. + + The first string is the name of the Funge source program being run. + +20. a series of strings, each terminated by a null, the series + terminated by an additional null, containing the environment + variables. (env) + + The format for each variable setting is `NAME=VALUE`. + +If `y` is given a positive argument, all these cells are pushed onto the +stack as if the argument was non-positive. However, `y` then goes on to +copy the *argument*th stack cell (counting from the top) into a +temporary location, subsequently removing all the cells it pushed onto +the stack. It then pushes the temporary cell onto the stack. For +example, `3y` will act as if only the handprint was pushed onto the +stack. + +An interesting side-effect of this behaviour is that if `y` is given an +argument that exceeds the number of cells it pushes onto the stack, it +can act as a 'pick' instruction on data that was on the stack before `y` +was even executed. + +* * * * * + +Scale: Extension and Customization +---------------------------------- + +Funge-98 is totally customizable and scalable in terms of functionality, +and non-trivial programs can now be written portably in it without any +sort of directives. The fingerprint mechanism allows for the definition +of a virtually unlimited number of libraries in the form of +fingerprinted extensions. The handprint mechanism allows a program to +identify exactly what interpreter it's running on. + +* * * * * + +### Handprints + +A handprint is a bitstring which uniquely identifies a particular +implementation (interpreter, compiler, etc.) of Funge-98. + +These should really only be used by Funge-98 programs which know about +**bugs** in a known interpreter, and want to work around them when their +portable source is run on that interpreter. (*Features*, on the other +hand, should be fingerprints: if everything is properly implemented, the +current handprint **should** be irrelevant. Fingerprints should always +be used in preference to handprints when making a design decision - +handprints are a fallback for the less-than-ideal case.) + +Handprints generally stay they same between revisions of an interpreter. +They are accompanied by a version number when retrieved from `y`. + +* * * * * + +### Fingerprints + +Extension and customization of Funge-98 are accomplished through the +so-called "fingerprint mechanism". No such mechanism exists for +Befunge-93. + +To be more precise, a fingerprint is a unique ID code which indicates a +library of routines (a *fingerprinted extension*) to be assigned +temporarily to what the instructions `A` to `Z` do. Multiple loaded +fingerprints can overlap and overload, so even object-oriented-like +inheritance is possible. + +Generally speaking, these new semantics and instructions are only +available to and only apply to the IP which loaded them. The fingerprint +spec itself may make exceptions to this rule, but it must clearly +specify what they are. + +The semantics of any given extension are generally coded directly into +the interpreter. There is no reason, however, they may not be made +available in a dynamically-loadable form; but there is no convenient, +standard format for such a form. + +Whether the semantics are dynamically loaded from a disk file containing +some form of executable code, or hard-wired into the Funge interpreter, +is really a moot point from the Funge programmer's perspective. + +However, it is useful to explain at this point for the benefit of both +Funge programmers and potential Funge extension writers, that there are +two classes of fingerprinted extensions: those that interact with and +change the behaviour of the underlying Funge Virtual Machine and/or are +not re-entrant (*feral extensions*), and those which are self-contained +and re-entrant (*tame extensions*). + +The main difference is that a feral extension cannot simply or easily be +"dropped into" a Funge interpreter which is not aware of it. When +specifying fingerprinted extensions for public use, please try to make +them as tame as possible - although many times it is unavoidable to have +a feral fingerprint in order to accomplish a certain semantic goal, such +as program control of proprietary interpreter features. + +The `(` "Load Semantics" instruction loads the semantics for a given +fingerprint onto any or all of the instructions `A` to `Z`. `(` pops a +*count*. It then pops *count* cells. For each cell that it pops, it +multiplies a temporary value (which is initially zero) by 256, and adds +the cell value to it. + +In this way, `(` builds a fingerprint. This mechanism makes it simple to +express large fingerprints like 0x452e472e in printable ASCII such as +`".G.E"4( ... )`, while not requiring the use of ASCII as the medium for +all fingerprints. + +`(` then uses this fingerprint to locate and load a set of semantics for +the instructions `A` to `Z`. If the Funge implementation cannot find the +correct library for the given fingerprint, `(` acts like `r`. If, +however, the semantic library load is successful, the new fingerprint, +then a 1, are pushed onto the stack (to be accepted by a subsequent `)` +instruction.) + +The corresponding `)` "Unload Semantics" instruction unloads the +semantics for a given fingerprint from any or all of the instructions +`A` to `Z` (even if that fingerprint had never been loaded before). It +pops a *count* and builds a fingerprint in exactly the same way. + +`()` in Funge are kind of like "use x ... no x" in Perl. The +interpreter-writer or interpreter-customizer is allowed to make +fingerprints do anything they like, and they may implement fingerprints +however they like, but they have to follow some general rules as a +'contract' between the fingerprint and the fingerprint user. + +- A fingerprint should not affect the semantics of any instructions + besides `A` to `Z`, except under exceptional circumstances which + must be clearly documented in the fingerprint's spec. +- When loaded, a fingerprint which implements `A` and `B` semantics + should act something like: + + save(A); bind(A, myAsemantic()); save(B); bind(B, myBsemantic()); + +- When unloaded, the same fingerprint should act something like + + restore(B); restore(A); + +- In this sense, 'bind' means to change what the execution of an + instruction does in the current Funge-98 program. +- 'save' means to save (push onto a stack) the semantics of an + instruction for later use. +- 'restore' means to recall (pop from a stack) the semantics of an + instruction from the last saved version. + +This allows 'overloading' fingerprints. Say three fingerprints are +implemented on your interpreter, + +- `E.G.` which implements `D` as 'Destroy' and `R` as 'Restore' +- `TEST` which implements `S` as 'Send', `R` as 'Receive', and `E` as + 'Encrypt' +- `WORK` which implements `S` as 'Slice', `D` as 'Dice', `P` as + 'Puree' + +With these, the Funge programmer ought to be able to make code like: + + ".G.E"4( ... "TSET"4( ... "KROW"4( ... S ... ) ... ) ... ) + +Here, the `S` instruction indicates the 'Slice' instruction in the WORK +fingerprint, not 'Send' in TEST. But if it was written as: + + "KROW"4( ... "TSET"4( ... ".G.E"4( ... S ... ) ... ) ... ) + +The `S` would indicate 'Send' from TEST, since there is no `S` in E.G., +and a `D` instruction in the same location would indicate 'Destroy' in +E.G., but `P` would be a 'Puree' from WORK, because it's not defined in +either TEST or E.G. + +* * * * * + +### Funge Central Registry + +_Nota bene: information in this section is obsolete_ + +The Funge Central Registry is an online database located at +`http://www.catseye.mb.ca/esoteric/befunge/`. Before developing and +releasing your own Funge-98 implementation or extension, please register +all applicable handprints and fingerprints you will require here. + +This system is in place both to reduce conflicts between fingerprints +and handprints world-wide, ensuring their uniqueness, and to provide a +quick reference to all known extant handprints and fingerprints. + +There really are no standard libraries in Befunge. A Funge-98 +interpreter need not implement any fingerprints whatsoever. Funge-98 +"standard" libraries are no more than "officially sanctioned" +extensions, catalogued in the Registry by their fingerprints. Since +extensions are forever accumulating, see the registry for a list of +"standard" fingerprints. + +* * * * * + +Appendix +-------- + +### Instruction Quick Reference + +`/` modifiers: + +- `98` Funge-98 only, not in Befunge-93. +- `2D` Minimum 2 dimensions (not in Unefunge). +- `3D` Minimum 3 dimensions (not in Unefunge or Befunge). +- `c` Concurrent Funge. Check `y` to see if these instructions are + implemented. +- `f` Filesystem Funge. Check `y` to see if these instructions are + implemented. + +"Before" column is the state of the TOSS before the instruction is +executed. "After" column is the state of the TOSS after the instruction +is executed. Both are notated from bottom (on the left) to top (on the +right). + + ASCII Instruction Before After Other Effects + ----- ----------- ------ ----- ------------- + space Space not normally executed + ! Logical Not b NOT b + " Toggle Stringmode stringmode <- NOT stringmode + # Trampoline pos <- pos + delta + $ Pop n + % Remainder a b a REM b + & Input Integer a a = readint() + ' Fetch Character/98 c pos <- pos + delta + ( Load Semantics/98 en..e1 n f 1 overloads A-Z + ) Unload Semantics/98 en..e1 n unloads last A-Z + * Multiply a b a * b + + Add a b a + b + , Output Character c writechar(c) + - Subtract a b a - b + . Output Integer a writeint(a) + / Divide a b a / b + 0 Push Zero 0 + 1 Push One 1 + 2 Push Two 2 + 3 Push Three 3 + 4 Push Four 4 + 5 Push Five 5 + 6 Push Six 6 + 7 Push Seven 7 + 8 Push Eight 8 + 9 Push Niner 9 + : Duplicate v v v + ; Jump Over/98 nothing executed until next ; + < Go West delta <- (-1,0) + = Execute/98/f STR r r = system-execute(STR) + > Go East delta <- (1,0) + ? Go Away delta <- (1,0)?(-1,0)?(0,1)?(0,-1) + @ Stop halt IP + A-Z Fingerprint-Defined/98 + [ Turn Left/98/2D delta <- rot(-90, delta) + \ Swap a b b a + ] Turn Right/98/2D delta <- rot(90, delta) + ^ Go North/2D delta <- (0,-1) + _ East-West If b delta <- if (b) (-1,0) else (1,0) + ` Greater Than a b a > b either 1 or 0 + a Push Ten/98 10 + b Push Eleven/98 11 + c Push Twelve/98 12 + d Push Thirteen/98 13 + e Push Fourteen/98 14 + f Push Fifteen/98 15 + g Get Va v v = fetch-funge-space(offset+Va) + h Go High/98/3D delta <- (0,0,-1) + i Input File/98/f Va f STR Va Vb inputs file + j Jump Forward/98 s pos <- pos + delta * s + k Iterate/98 n execute next instruction now, n times + l Go Low/98/3D delta <- (0,0,1) + m High-Low If/98/3D b delta <- if (b) (0,0,-1) else (0,0,1) + n Clear Stack/98 en..e1 + o Output File/98/f Va Vb f STR outputs file + p Put v Va store-funge-space(offset+Va,v) + q Quit/98 r immediate exit, returncode = r + r Reflect/98 delta <- delta * -1 + s Store Character/98 c store-funge-space(position+delta,v) + t Split/98/c Split IP + u Stack Under Stack/98 n (en..e1) + v Go South/2D delta <- (0,1) + w Compare/98/2D a b if (a>b) ']' elsif (a x:=79, x>79 -> x:=0, y<0 -> y:=24, y>24 -> y:=0) + +for Befunge-93. Complex topologies can define their own wrapping +functions. If these functions are strictly and clearly specified in the +documentation of the Advanced Funge in question, it will save a lot of +confusion to users, and is highly recommended. + +* * * * * + +Copyright (c)2000 Chris Pressey, Cat's Eye Technologies. +Permission is granted to republish this work on the condition that the +above copyright message and this message remain included unchanged in +all copies. diff --git a/docs/catseye/doc/laheys.jpg b/docs/catseye/doc/laheys.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fe78c7f31f82f9279d24a330e98b47eb8086c289 GIT binary patch literal 18389 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 literal 0 HcmV?d00001 diff --git a/docs/catseye/doc/wrap.jpg b/docs/catseye/doc/wrap.jpg new file mode 100644 index 0000000000000000000000000000000000000000..740fd8fc570bd65bc16295c64ff5353972f471b0 GIT binary patch literal 3615 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 W_l2^6YMi#TRM|KVi@;x^QT_wZ$b2yX literal 0 HcmV?d00001 diff --git a/docs/catseye/library/HRTI.markdown b/docs/catseye/library/HRTI.markdown new file mode 100644 index 0000000..311a7f0 --- /dev/null +++ b/docs/catseye/library/HRTI.markdown @@ -0,0 +1,33 @@ +High-Resolution Timer Interface +=============================== + +### Fingerprint 0x48525449 ('HRTI') + +Under development. + +The HRTI fingerprint allows a Funge program to measure elapsed time much +more finely than the clock values returned by `y`. + +After successfully loading HRTI, the instructions `E`, `G`, `M`, `S`, +and `T` take on new semantics. + +`G` 'Granularity' pushes the smallest clock tick the underlying system +can reliably handle, measured in microseconds. + +`M` 'Mark' designates the timer as having been read by the IP with this +ID at this instance in time. + +`T` 'Timer' pushes the number of microseconds elapsed since the last +time an IP with this ID marked the timer. If there is no previous mark, +acts like `r`. + +`E` 'Erase mark' erases the last timer mark by this IP (such that `T` +above will act like `r`) + +`S` 'Second' pushes the number of microseconds elapsed since the last +whole second. + +The timer and mark-list are considered global and static, shared amongst +all IP's, in order to retain tame behaviour. + +This timer is not affected by 'time travel' contrivances. diff --git a/docs/catseye/library/MODE.markdown b/docs/catseye/library/MODE.markdown new file mode 100644 index 0000000..3cde7a9 --- /dev/null +++ b/docs/catseye/library/MODE.markdown @@ -0,0 +1,29 @@ +Funge-98 Standard Modes +======================= + +### Fingerprint 0x4d4f4445 ('MODE') + +Currently under development. + +After successfully loading MODE, the instructions `H`, `I`, `Q`, and `S` +take on new semantics, which alter four new states or 'modes' of the +current IP. + +`H` toggles an internal flag called hovermode on and off. In hovermode, +the instructions `>`, `<`, `^`, `v`, `|`, and `_` treat the IP's delta +relatively - instead of setting the dx to 0 and dy to -1, `^` would +instead simply subtract 1 from dy. + +The `I` "Toggle Invertmode" instruction toggles an internal flag called +*invertmode*. When invertmode is active, cells are **pushed** on the +stack onto the **bottom** instead of the top. + +The `Q` "Toggle Queuemode" instruction toggles an internal flag called +*queuemode*. When queuemode is active, cells are **popped** off the +stack from the **bottom** instead of the top. + +`S` toggles switchmode on and off. In switchmode, the pairs of +instructions `[` and `]`, `{` and `}`, and `(` and `)` are treated as +switches. When one is executed, the cell it is located in is immediately +overwritten with the other instruction of the pair, providing a +switching mechanism and a way to seperate coincident IP's. diff --git a/docs/catseye/library/MODU.markdown b/docs/catseye/library/MODU.markdown new file mode 100644 index 0000000..ae25ee0 --- /dev/null +++ b/docs/catseye/library/MODU.markdown @@ -0,0 +1,17 @@ +Modulo Arithmetic Extension +=========================== + +### Fingerprint 0x4d4f4455 ('MODU') + +Under development. + +The MODU fingerprint implements some of the finer, less-well-agreed-upon +points of modulo arithmetic. With positive arguments, these instructions +work exactly the same as `%` does. However, when negative values are +involved, they all work differently: + +`M`: signed-result modulo: + +`U`: Sam Holden's unsigned-result modulo + +`R`: C-language integer remainder diff --git a/docs/catseye/library/NULL.markdown b/docs/catseye/library/NULL.markdown new file mode 100644 index 0000000..7b57788 --- /dev/null +++ b/docs/catseye/library/NULL.markdown @@ -0,0 +1,10 @@ +Funge-98 Null Fingerprint +========================= + +### Fingerprint 0x4e554c4c (Null) + +After successfully loading fingerprint 0x4e554c4c, all 26 instructions +`A` to `Z` take on the semantics of `r`. + +This can be loaded before loading a regular transparent fingerprint to +make it act opaquely. diff --git a/docs/catseye/library/ORTH.markdown b/docs/catseye/library/ORTH.markdown new file mode 100644 index 0000000..42a3355 --- /dev/null +++ b/docs/catseye/library/ORTH.markdown @@ -0,0 +1,51 @@ +Orthogonal Easement Library +=========================== + +### Fingerprint 0x4f525448 ('ORTH') + +Under development. + +The ORTH fingerprint is designed to ease transition between the +Orthogonal programming language and Befunge-98 (or higher dimension +Funges.) Even if transition from Orthogonal is not an issue, the ORTH +library contains some potentially interesting instructions not in +standard Funge-98. + +After successfully loading ORTH, the instructions `A`, `E`, `G`, `O`, +`P`, `S`, `V`, `W`, `X`, `Y`, and `Z` take on new semantics. The +following table, which can be used to translate Orthogonal to Funge-98 +and back, includes which Orthogonal instructions they emulate: + + Funge Orthogonal Semantic + + + + add + * * multiply + - - subtract + / / divide + % % modulo (positive values only) + >>> A & bitwise AND + >>> O | bitwise OR + >>> E ^ bitwise EXOR + ! ! logical negate + \ ~ swap + : @ duplicate + $ $ pop + < L go west + > H go east + ^ K go north + v J go south + ] cw rotate right + [ ccw rotate left + r rev reverse + >>> X x change x + >>> Y y change y + >>> V dx change dx + >>> W dy change dy + >>> G = ortho get + >>> P # ortho put + >>> Z ? ramp if zero + , c output character + >>> S s output string + . d output decimal + z nop no operation + q ret quit diff --git a/docs/catseye/library/PERL.markdown b/docs/catseye/library/PERL.markdown new file mode 100644 index 0000000..6cecde6 --- /dev/null +++ b/docs/catseye/library/PERL.markdown @@ -0,0 +1,25 @@ +Generic Interface to the Perl Language +====================================== + +### Fingerprint 0x5045524c ('PERL') + +Under development. + +The PERL fingerprint is designed to provide a basic, no-frills interface +to the Perl language. + +After successfully loading PERL, the instructions `E`, `I`, and `S` take +on new semantics. + +`S` ('Shelled') pushes a 0 on the stack if the Perl language is already +loaded (e.g. the interpreter is written in Perl). It pushes a 1 on the +stack otherwise, indicating that the Perl language will be shelled when +needed. + +`E` ('Eval') pops a 0gnirts string and performs a Perl `eval()` on it, +possibly (or not) shelling Perl as indicated by S above. The result of +the call is pushed as a 0gnirts string back onto the stack. + +`I` ('Int Eval') acts the same as `E`, except that the result of the +call is converted to an integer and pushed as a single cell onto the +stack. diff --git a/docs/catseye/library/REFC.markdown b/docs/catseye/library/REFC.markdown new file mode 100644 index 0000000..d1dfe28 --- /dev/null +++ b/docs/catseye/library/REFC.markdown @@ -0,0 +1,24 @@ +Referenced Cells Extension +========================== + +### Fingerprint 0x52454643 ('REFC') + +Under development. + +The REFC fingerprint allows vectors to be encoded into and decoded from +single scalar cell values. + +After successfully loading REFC, the instructions `D` and `R` take on +new semantics. + +`R` 'Reference' pops a vector off the stack, and pushes a scalar value +back onto the stack, unique within an internal list of references, which +refers to that vector. + +`D` 'Dereference' pops a scalar value off the stack, and pushes the +vector back onto the stack which corresponds to that unique reference +value. + +The internal list of references is considered shared among all IP's, so +a global static can be used to store this list, so that this extension +remains tame. diff --git a/docs/catseye/library/ROMA.markdown b/docs/catseye/library/ROMA.markdown new file mode 100644 index 0000000..9e94858 --- /dev/null +++ b/docs/catseye/library/ROMA.markdown @@ -0,0 +1,19 @@ +Funge-98 Roman Numerals +======================= + +### Fingerprint 0x524f4d41 ('ROMA') + +After successfully loading ROMA, the instructions `C`, `D`, `I`, `L`, +`M`, `V`, and `X` take on new semantics. + +- `C` pushes 100 onto the stack. +- `D` pushes 500 onto the stack. +- `I` pushes 1 onto the stack. +- `L` pushes 50 onto the stack. +- `M` pushes 1000 onto the stack. +- `V` pushes 5 onto the stack. +- `X` pushes 10 onto the stack. + +Note that these are just digits, you still have to do the arithmetic +yourself. Executing `MCMLXXXIV` will not leave 1984 on the stack. But +executing `MCM\-+LXXX+++IV\-++` should. diff --git a/docs/catseye/library/TOYS.markdown b/docs/catseye/library/TOYS.markdown new file mode 100644 index 0000000..2440f85 --- /dev/null +++ b/docs/catseye/library/TOYS.markdown @@ -0,0 +1,118 @@ +Funge-98 Standard Toys +====================== + +### Fingerprint 0x544f5953 ('TOYS') + +Under development. + +After successfully loading TOYS, the instructions `A`, `B`, `C`, `D`, +`E`, `F`, `G`, `H`, `I`, `J`, `K`, `L`, `M`, `N`, `O`, `P`, `Q`, `R`, +`S`, `T`, `U`, `V`, `W`, `X`, `Y`, and `Z` take on new semantics (and +INTERCAL-esque names). + +`C` ('bracelet') pops three vectors off the stack and performs a +low-order copy of Funge-Space. + +`K` ('scissors') pops three vectors off the stack and performs a +high-order copy of Funge-Space. + +`M` ('kittycat') pops three vectors off the stack and performs a +low-order move (copy & erase original) of Funge-Space. + +`V` ('dixiecup') pops three vectors off the stack and performs a +high-order move of Funge-Space. + +`S` ('chicane') pops two vectors off the stack, then a cell, then fills +that area of Funge-Space homogenously with that cell's value. + +Which order a copy or move takes is important if you copy or move to an +overlapping area. Ensure when implementing these that the order is +preserved in all dimensions. + +The first two vectors are like the arguments to `o`. In all except `S`, +the third is the destination, relative to the origin. + +`J` ('fishhook') pops a value off the stack, and causes the current +column (y coordinate) of Funge-space to be translated north (if value is +negative) or south (if positive) that many rows. + +`O` ('boulder') pops a value off the stack, and causes the current row +(x coordinate) of Funge-space to be translated west (if value is +negative) or east (if positive) that many columns. + +`L` ('corner') works like `'` except it picks up the cell to the "left" +of the IP's line and does not skip over anything. (Historians may note +that this works like "Get Left Hand" did in Befunge-97.) The cell to the +"left" of the IP is the IP's position, plus its delta rotated -90 +degrees about the Z axis (a la `[`) + +`R` ('can opener') is a corresponding instruction that mirrors `L` and +works to the right, rotated 90 degrees about the Z axis ("Get Right +Hand" from Befunge-97) + +`I` ('doric column') pops a value off the stack, increments it, and +pushes it back onto the stack. + +`D` ('toilet seat') pops a value off the stack, decrements it, and +pushes it back onto the stack. + +`N` ('lightning bolt') pops a value off the stack, negates it, and +pushes it back onto the stack. + +`H` ('pair of stilts') pops a value *b* off the stack, then a value *a*, +then binary-shifts *a* *b* places left if *b* is positive, or |*b*| +places right if *b* is negative. + +`A` ('gable') pops a cell *n* off the stack, then another cell, then +pushes *n* copies of that cell onto the stack. + +`B` ('pair of shoes') pops two cells off the stack and pushes the result +of a "butterfly" bit operation. + +`E` ('pitchfork head') pops all values off the stack and pushes their +sum back onto it. + +`P` ('mailbox') pops all values off the stack and pushes their product +back onto it. + +`F` ('calipers') pops a vector, then a value *i*. Treating the rest of +the stack as *j* groups of *i* cells each, it writes this 2D matrix into +Funge-space in row-major order, with it's least point as the given +vector. + +`G` ('counterclockwise') pops a vector, then a value *i*. It then pushes +onto the stack *j* groups of *i* cells each which it retrieves as a 2D +matrix in Funge-space in row-major order, the least point of which being +the vector supplied to it. + +`Q` ('necklace') pops a value off the stack and places it into the cell +directly behind the IP (kind of like `s`, except behind, and no cell is +skipped over.) + +`T` ('barstool') pops a dimension number off the stack; if it's a 0, +acts like `_`; if it's 1, acts like `|`; if it's 2, acts like `m`; etc. +depending on the number of available dimensions as appropriate of +course. + +`U` ('tumbler') is like `?` but one-shot. When executed, it randomly +transmutes into one of `<`, `>`, `^`, `v`, `h`, or `l`, depending on the +number of available dimensions as appropriate of course. + +`W` ('television antenna') pops a vector off the stack, then a value. If +the cell at that vector (plus the storage offset) in Funge-Space (a la +`g`) is equal to that value, nothing happens. If the cell in space is +less than the value, it pushes the value and the vector (inverted) back +onto the stack, and backs up the IP (subtracts the IP's delta from it's +position, sort of a 'wait to try again' condition, useful mainly in +Concurrent Funge.) If the cell in space is greater than that value, acts +like `r`. + +`X` ('buried treasure') increments the IP's x coordinate. + +`Y` ('slingshot') increments the IP's y coordinate. + +`Z` ('barn door') increments the IP's z coordinate. + +These three instructions are useful at the end of a line, to indicate +"proceed to next line then wrap". Note the IP's delta motion is still +fully in effect. diff --git a/docs/catseye/library/TURT.markdown b/docs/catseye/library/TURT.markdown new file mode 100644 index 0000000..80f65f3 --- /dev/null +++ b/docs/catseye/library/TURT.markdown @@ -0,0 +1,50 @@ +Simple Turtle Graphics Library +============================== + +### Fingerprint 0x54555254 ('TURT') + +Under development. + +The TURT fingerprint provides a simple interface to a simple "drawing +turtle-robot simulator". + +After successfully loading TURT, several instructions take on new +semantics. + +These instructions pop one value off the stack: + +- `L` 'Turn Left' (angle in degrees) +- `R` 'Turn Right' (angle in degrees) +- `H` 'Set Heading' (angle in degrees, relative to 0deg, east) +- `F` 'Forward' (distance in pixels) +- `B` 'Back' (distance in pixels) +- `P` 'Pen Position' (0 = up, 1 = down) +- `C` 'Pen Colour' (24-bit RGB) +- `N` 'Clear Paper with Colour' (24-bit RGB) +- `D` 'Show Display' (0 = no, 1 = yes) + +These pop two values each: + +- `T` 'Teleport' (x, y coords relative to origin; 00T = home) + +These push one value each: + +- `E` 'Query Pen' (0 = up, 1 = down) +- `A` 'Query Heading' (positive angle relative to east) + +These push two values each: + +- `Q` 'Query Position' (x, y coordinates) + +These push four values each: + +- `U` 'Query Bounds' (two pairs of x, y coordinates) + +And these don't even use the stack: + +- `I` 'Print current Drawing' (if possible) + +To keep this fingerprint tame, a single Turtle and display is defined to +be shared amongst all IP's. The turtle is not defined to wrap if it goes +out of bounds (after all this interface might just as well be used to +drive a **real** turtle robot.) From 8774115eedb5e3a7b43a7d4915e84d765b54579f Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 16 Oct 2022 15:31:00 +0200 Subject: [PATCH 04/35] add section with unplanned fingerprints --- README.MD | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.MD b/README.MD index 92744b0..5865fd0 100644 --- a/README.MD +++ b/README.MD @@ -36,6 +36,9 @@ Additionally, the following fingerprints are currently supported (more to come): - [ORTH](./docs/catseye/library/ORTH.markdown) - [ROMA](./docs/catseye/library/ROMA.markdown) +Fingerprints that are NOT planned to be ever implemented: +- [PERL](./docs/catseye/library/PERL.markdown) -- Would need to hook into the system-installed perl, highly unsafe + ### Version Release Checklist - Update the version number inside the [pom](./pom.xml) From 605340179a7f526b5b038b839b51aeb850cfbb4f Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 16 Oct 2022 18:31:09 +0200 Subject: [PATCH 05/35] rework CLI parsing, and implement sandbox security features --- README.MD | 41 +++++-- pom.xml | 6 + .../java/com/falsepattern/jfunge/Main.java | 109 ++++++++++++------ .../jfunge/interpreter/ExecutionContext.java | 19 +++ .../jfunge/interpreter/FeatureSet.java | 15 +++ .../jfunge/interpreter/Interpreter.java | 94 +++++++++++++-- .../interpreter/instructions/Funge98.java | 43 ++++++- .../jfunge/storage/TestInterpreter.java | 18 ++- 8 files changed, 286 insertions(+), 59 deletions(-) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java diff --git a/README.MD b/README.MD index 5865fd0..55cd14e 100644 --- a/README.MD +++ b/README.MD @@ -3,16 +3,37 @@ A standard-conforming Funge-98 interpreter. ### Usage - -`jfunge [file] [--3d] [--version] [--license]` - -| argument | usage | -|----------------------|---------------------------------------------------------------------------------------------------------------------------------| -| `file` | The file to load into the interpreter | -| `--3d` | Enable 3D (Trefunge) mode. By default, the interpreter emulates 2D Befunge for compatibility. | -| `--version` | Prints the current program version, along with the handprint and version given by befunge's y instruction | -| `--license` | Prints the license of the program. | -| `--maxiter ` | The maximum number of iterations the program can run for. Anything less than 1 will run until the program terminates naturally. | +``` +usage: jfunge [-f | --help | --license | --version] [-i ] + [--maxiter ] [-o ] [--syscall] [-t] [--trefunge] +JFunge, a Funge98 interpeter for java. + -f,--file The file to load into the interpreter at the origin + on startup. + --help Displays this help page + -i,--readperm Enables read access to the specified file or + directory (i instruction). Specify / to allow read + access to every file on the system (dangerous). Can + specify multiple files/folders. + --license Prints the license of the program. + --maxiter The maximum number of iterations the program can + run for. Anything less than 1 will run until the + program terminates by itself. Default is unlimited. + -o,--writeperm Enables write access to the specified file or + directory (o instruction). Specify / to allow write + access to every file on the system (dangerous). Can + specify multiple files/folders. + --syscall Enables the syscall feature (= instruction). This + is a very dangerous permission to grant, it can + call any arbitrary program on your system. + -t,--concurrent Enables the Concurrent Funge extension (t + instruction). Buggy programs can potentially + forkbomb the interpreter. + --trefunge Enable 3D (Trefunge) mode. By default, the + interpreter emulates 2D Befunge for compatibility. + --version Prints the current program version, along with the + handprint and version given by befunge's y + instruction. +``` ### Compatibility diff --git a/pom.xml b/pom.xml index 024520f..01e05e1 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,7 @@ 1.18.22 3.0.3 1.10.2 + 1.5.0 UTF-8 mavenpattern https://mvn.falsepattern.com/releases/ @@ -40,6 +41,11 @@ joml ${joml.version} + + commons-cli + commons-cli + ${commons-cli.version} + org.junit.jupiter junit-jupiter-engine diff --git a/src/main/java/com/falsepattern/jfunge/Main.java b/src/main/java/com/falsepattern/jfunge/Main.java index 92bdd7e..d5dc9f9 100644 --- a/src/main/java/com/falsepattern/jfunge/Main.java +++ b/src/main/java/com/falsepattern/jfunge/Main.java @@ -1,57 +1,98 @@ package com.falsepattern.jfunge; +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; +import org.apache.commons.cli.OptionGroup; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; public class Main { - public static void main(String[] args) throws IOException { - List argsList = new ArrayList<>(Arrays.asList(args)); - if (args.length < 1) { - System.out.println("Usage: jfunge [file] [--3d] [--version] [--license] [--maxiter ]\nfile The file to load into the interpreter\n--3d Enable 3D (Trefunge) mode. By default, the interpreter acts as if it was 2D for compatibility reasons.\n--version Prints the current program version, along with the handprint and version given by befunge's y instruction\n--license Prints the license text of the program.\n--maxiter The maximum number of iterations the program can run for. Anything less than 1 will run until the program terminates naturally."); + public static void main(String[] args) throws IOException, ParseException { + val options = new Options(); + val masterGroup = new OptionGroup(); + masterGroup.setRequired(false); + masterGroup.addOption(Option.builder("f") + .longOpt("file") + .hasArg(true) + .argName("file") + .numberOfArgs(1) + .desc("The file to load into the interpreter at the origin on startup.") + .build()); + masterGroup.addOption(Option.builder() + .longOpt("version") + .desc("Prints the current program version, along with the handprint and version given by befunge's y instruction.") + .build()); + masterGroup.addOption(Option.builder() + .longOpt("license") + .desc("Prints the license of the program.") + .build()); + masterGroup.addOption(Option.builder() + .longOpt("help") + .desc("Displays this help page") + .build()); + options.addOptionGroup(masterGroup); + options.addOption(null, "trefunge", false, "Enable 3D (Trefunge) mode. By default, the interpreter emulates 2D Befunge for compatibility."); + options.addOption("t", "concurrent", false, "Enables the Concurrent Funge extension (t instruction). Buggy programs can potentially forkbomb the interpreter."); + options.addOption(null, "syscall", false, "Enables the syscall feature (= instruction). This is a very dangerous permission to grant, it can call any arbitrary program on your system."); + options.addOption(Option.builder("i") + .longOpt("readperm") + .hasArg(true) + .argName("file") + .numberOfArgs(-2) + .desc("Enables read access to the specified file or directory (i instruction). Specify / to allow read access to every file on the system (dangerous). Can specify multiple files/folders.") + .build()); + options.addOption(Option.builder("o") + .longOpt("writeperm") + .hasArg(true) + .argName("file") + .numberOfArgs(-2) + .desc("Enables write access to the specified file or directory (o instruction). Specify / to allow write access to every file on the system (dangerous). Can specify multiple files/folders.") + .build()); + options.addOption(Option.builder() + .longOpt("maxiter") + .hasArg(true) + .argName("iterations") + .numberOfArgs(1) + .desc("The maximum number of iterations the program can run for. Anything less than 1 will run until the program terminates by itself. Default is unlimited.") + .build()); + val parser = new DefaultParser(); + val cmd = parser.parse(options, args); + if (cmd.hasOption("help")) { + val formatter = new HelpFormatter(); + formatter.printHelp(80, "jfunge", "JFunge, a Funge98 interpeter for java.", options, null, true); + return; } - if (argsList.remove("--license")) { + if (cmd.hasOption("license")) { System.out.println(Globals.LICENSE); + return; } - if (argsList.remove("--version")) { + if (cmd.hasOption("version")) { System.out.println("Version: " + Globals.VERSION); System.out.println("Handprint: 0x" + Integer.toHexString(Globals.HANDPRINT)); System.out.println("FungeVersion: 0x" + Integer.toHexString(Globals.FUNGE_VERSION)); - } - val trefunge = argsList.remove("--3d"); - var maxIter = 0L; - if (argsList.contains("--maxiter")) { - val iterIndex = argsList.indexOf("--maxiter") + 1; - if (argsList.size() <= iterIndex) { - System.err.println("Please specify a number after --maxiter!"); - System.exit(-1); - } - val maxIterText = argsList.remove(iterIndex); - try { - maxIter = Long.parseLong(maxIterText); - } catch (NumberFormatException e) { - System.err.println(maxIterText + " is an invalid number!"); - } - argsList.remove("--maxiter"); - } - if (argsList.size() == 0) { - System.out.println("No file specified."); return; } - val file = argsList.remove(0); - if (argsList.size() != 0) { - System.out.println("Extraneous arguments ignored:"); - argsList.forEach(System.out::println); - System.out.println(); + if (!cmd.hasOption("f")) { + System.out.println("No file specified. See --help"); + return; } + val file = cmd.getOptionValue("f"); + val featureSet = FeatureSet.builder(); + featureSet.trefunge(cmd.hasOption("trefunge")); + featureSet.concurrent(cmd.hasOption("t")); + featureSet.allowedInputFiles(cmd.getOptionValues("i")); + featureSet.allowedOutputFiles(cmd.getOptionValues("o")); + featureSet.maxIter(cmd.hasOption("maxiter") ? Integer.parseInt(cmd.getOptionValue("maxiter")) : 0); byte[] program; if (file.equals("-")) { val in = System.in; @@ -65,7 +106,7 @@ public static void main(String[] args) throws IOException { } else { program = Files.readAllBytes(Paths.get(file)); } - int returnCode = Interpreter.executeProgram(trefunge, args, program, maxIter, System.in, System.out, Interpreter.DEFAULT_FILE_IO_SUPPLIER); + int returnCode = Interpreter.executeProgram(args, program, System.in, System.out, Interpreter.DEFAULT_FILE_IO_SUPPLIER, featureSet.build()); System.out.flush(); System.exit(returnCode); } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java b/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java index 0162e96..ee8024e 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java @@ -3,6 +3,7 @@ import com.falsepattern.jfunge.ip.InstructionPointer; import com.falsepattern.jfunge.storage.FungeSpace; +import java.io.IOException; import java.io.OutputStream; import java.util.List; import java.util.Map; @@ -39,4 +40,22 @@ public interface ExecutionContext { byte[] readFile(String file); boolean writeFile(String file, byte[] data); + + int envFlags(); + + default boolean concurrentAllowed() { + return (envFlags() & 0x01) != 0; + } + + default boolean fileInputAllowed(String path) throws IOException { + return (envFlags() & 0x02) != 0; + } + + default boolean fileOutputAllowed(String path) throws IOException { + return (envFlags() & 0x04) != 0; + } + + default boolean syscallAllowed() { + return (envFlags() & 0x08) != 0; + } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java b/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java new file mode 100644 index 0000000..9b58fc1 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java @@ -0,0 +1,15 @@ +package com.falsepattern.jfunge.interpreter; + +import lombok.AllArgsConstructor; +import lombok.Builder; + +@Builder +@AllArgsConstructor +public class FeatureSet { + public final boolean trefunge; + public final boolean sysCall; + public final String[] allowedInputFiles; + public final String[] allowedOutputFiles; + public final boolean concurrent; + public final long maxIter; +} diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java index 4c5266a..7fcb0f4 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java @@ -6,6 +6,7 @@ import com.falsepattern.jfunge.ip.InstructionPointer; import com.falsepattern.jfunge.storage.FungeSpace; import lombok.Getter; +import lombok.SneakyThrows; import lombok.experimental.Accessors; import lombok.val; import lombok.var; @@ -14,6 +15,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; @@ -56,6 +58,15 @@ public boolean writeFile(String file, byte[] data) throws IOException { @Getter private final int dimensions; + @Getter + private final int envFlags; + + private final boolean unrestrictedInput; + private final Path[] allowedInputPaths; + + private final boolean unrestrictedOutput; + private final Path[] allowedOutputPaths; + private Integer exitCode = null; private InstructionPointer currentIP = null; @@ -66,10 +77,9 @@ public boolean writeFile(String file, byte[] data) throws IOException { private int inputStagger; - - public Interpreter(boolean trefunge, String[] args, InputStream input, OutputStream output, FileIOSupplier fileIOSupplier) { + public Interpreter(String[] args, InputStream input, OutputStream output, FileIOSupplier fileIOSupplier, FeatureSet featureSet) { this.args = Arrays.asList(args); - dimensions = trefunge ? 3 : 2; + dimensions = featureSet.trefunge ? 3 : 2; baseInstructionManager.loadInstructionSet(Funge98.INSTANCE); this.input = input; this.output = output; @@ -77,20 +87,63 @@ public Interpreter(boolean trefunge, String[] args, InputStream input, OutputStr val ip = new InstructionPointer(); ip.UUID = nextUUID++; IPs.add(ip); + int env = 0; + if (featureSet.concurrent) { + env |= 1; + } + if (featureSet.allowedInputFiles != null && featureSet.allowedInputFiles.length != 0) { + if (Arrays.asList(featureSet.allowedInputFiles).contains("/")) { + unrestrictedInput = true; + allowedInputPaths = null; + } else { + unrestrictedInput = false; + allowedInputPaths = toPaths(featureSet.allowedInputFiles); + } + env |= 2; + } else { + unrestrictedInput = false; + allowedInputPaths = null; + } + if (featureSet.allowedOutputFiles != null && featureSet.allowedOutputFiles.length != 0) { + if (Arrays.asList(featureSet.allowedOutputFiles).contains("/")) { + unrestrictedOutput = true; + allowedOutputPaths = null; + } else { + unrestrictedOutput = false; + allowedOutputPaths = toPaths(featureSet.allowedOutputFiles); + } + env |= 4; + } else { + unrestrictedOutput = false; + allowedOutputPaths = null; + } + if (featureSet.sysCall) { + env |= 8; + } + envFlags = env; } - public static int executeProgram(boolean trefunge, String[] args, byte[] program, long iterLimit, InputStream input, OutputStream output, FileIOSupplier fileIOSupplier) { - val interpreter = new Interpreter(trefunge, args, input, output, fileIOSupplier); - interpreter.fungeSpace().loadFileAt(0, 0, 0, program, trefunge); + @SneakyThrows + private static Path[] toPaths(String[] files) { + List list = new ArrayList<>(); + for (String file : files) { + list.add(Paths.get(file).toRealPath()); + } + return list.toArray(new Path[0]); + } + + public static int executeProgram(String[] args, byte[] program, InputStream input, OutputStream output, FileIOSupplier fileIOSupplier, FeatureSet featureSet) { + val interpreter = new Interpreter(args, input, output, fileIOSupplier, featureSet); + interpreter.fungeSpace().loadFileAt(0, 0, 0, program, featureSet.trefunge); //Init step { val ip = interpreter.IPs.get(0); ip.position.sub(ip.delta); interpreter.step(ip); } - if (iterLimit > 0) { + if (featureSet.maxIter > 0) { long step = 0; - while (!interpreter.stopped() && step < iterLimit) { + while (!interpreter.stopped() && step < featureSet.maxIter) { interpreter.tick(); step++; } @@ -246,6 +299,31 @@ public boolean writeFile(String file, byte[] data) { } } + @Override + public boolean fileInputAllowed(String file) throws IOException { + if ((envFlags & 0x02) == 0) { + return false; + } + if (unrestrictedInput) return true; + val path = Paths.get(file).toRealPath(); + return Arrays.stream(allowedInputPaths).anyMatch(path::startsWith); + } + + @Override + public boolean fileOutputAllowed(String file) throws IOException { + if ((envFlags & 0x04) == 0) { + return false; + } + if (unrestrictedOutput) return true; + val path = Paths.get(file).toRealPath(); + return Arrays.stream(allowedOutputPaths).anyMatch(path::startsWith); + } + + @Override + public boolean syscallAllowed() { + return (envFlags & 0x08) != 0; + } + public void tick() { currentIP = null; for (int i = 0; i < IPs.size(); i++) { 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 b837dbd..0d6d049 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 org.joml.Vector3i; import java.io.File; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.Map; @@ -522,7 +523,7 @@ public static void sysInfo(ExecutionContext ctx) { //6 separator s.push(File.separatorChar); //5 operating paradigm - s.push(1); + s.push((ctx.envFlags() & 0x08) != 0 ? 1 : 0); //4 version s.push(Globals.FUNGE_VERSION); //3 handprint @@ -530,7 +531,7 @@ public static void sysInfo(ExecutionContext ctx) { //2 bpc s.push(4); //1 flags - s.push(0b00001111); + s.push(ctx.envFlags()); if (n > 0) { int curr = s.pick(n - 1); for (int i = s.size(); i >= tossSize; i--) { @@ -548,12 +549,21 @@ public static void quit(ExecutionContext ctx) { @Instr('t') public static void split(ExecutionContext ctx) { + if (!ctx.concurrentAllowed()) { + System.err.println("Program tried to execute concurrent funge when it was disabled!"); + reflect(ctx); + return; + } val clone = ctx.cloneIP(); clone.delta.mul(-1); } @Instr('i') public static void input(ExecutionContext ctx) { + if ((ctx.envFlags() & 0x02) == 0) { + reflect(ctx); + return; + } val s = ctx.IP().stackStack.TOSS(); val filename = s.popString(); val flags = s.pop(); @@ -564,6 +574,16 @@ public static void input(ExecutionContext ctx) { pos.set(s.pop2(), 0); } pos.add(ctx.IP().storageOffset); + try { + if (!ctx.fileInputAllowed(filename)) { + System.err.println("Code tried to read file not on whitelist: " + filename); + reflect(ctx); + return; + } + } catch (IOException e) { + reflect(ctx); + return; + } val file = ctx.readFile(filename); if (file == null) { ctx.interpret('r'); @@ -582,6 +602,10 @@ public static void input(ExecutionContext ctx) { @Instr('o') public static void output(ExecutionContext ctx) { + if ((ctx.envFlags() & 0x04) == 0) { + reflect(ctx); + return; + } val s = ctx.IP().stackStack.TOSS(); val filename = s.popString(); val flags = s.pop(); @@ -595,6 +619,16 @@ public static void output(ExecutionContext ctx) { delta.set(s.pop2(), 1); } pos.add(ctx.IP().storageOffset); + try { + if (!ctx.fileOutputAllowed(filename)) { + System.err.println("Code tried to write file not on whitelist: " + filename); + reflect(ctx); + return; + } + } catch (IOException e) { + 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'); @@ -641,6 +675,11 @@ public static void readChar(ExecutionContext ctx) { @Instr('=') public static void sysCall(ExecutionContext ctx) { + if (!ctx.syscallAllowed()) { + System.err.println("Program tried to syscall while it was disabled!"); + reflect(ctx); + return; + } val command = ctx.IP().stackStack.TOSS().popString(); try { val proc = Runtime.getRuntime().exec(command); diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java index f19c7db..64fb219 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java @@ -1,5 +1,6 @@ package com.falsepattern.jfunge.storage; +import com.falsepattern.jfunge.interpreter.FeatureSet; import com.falsepattern.jfunge.interpreter.Interpreter; import lombok.val; import lombok.var; @@ -68,8 +69,8 @@ private static byte[] readProgram(String path) { return program.toByteArray(); } - private static int interpret(String[] args, byte[] code, int iterLimit, InputStream input, OutputStream output) { - return Assertions.assertDoesNotThrow(() -> Interpreter.executeProgram(false, args, code, iterLimit, input, output, fakeSupplier)); + private static int interpret(String[] args, byte[] code, InputStream input, OutputStream output, FeatureSet featureSet) { + return Assertions.assertDoesNotThrow(() -> Interpreter.executeProgram(args, code, input, output, fakeSupplier, featureSet)); } private static InputStream nullStream() { @@ -81,7 +82,14 @@ public void testMycology() { val checkingOutput = new ByteArrayOutputStream(); val output = new TeeOutputStream(checkingOutput, System.out); val program = readProgram("/mycology.b98"); - val returnCode = interpret(new String[]{"mycology.b98"}, program, 300000, nullStream(), output); + val featureSet = FeatureSet.builder() + .allowedInputFiles(new String[]{"/"}) + .allowedOutputFiles(new String[]{"/"}) + .sysCall(false) + .concurrent(true) + .maxIter(300000L) + .build(); + val returnCode = interpret(new String[]{"mycology.b98"}, program, nullStream(), output, featureSet); val txt = checkingOutput.toString(); String currentlyActiveFingerprint = null; boolean fingerprintHadError = false; @@ -121,7 +129,7 @@ public void testMycology() { public void testSemicolonAtStart() { System.out.println("Testing edge case ;;.@"); val output = new ByteArrayOutputStream(); - val returnCode = interpret(new String[0], ";;.@".getBytes(StandardCharsets.UTF_8), 50, nullStream(), output); + 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); @@ -131,7 +139,7 @@ public void testSemicolonAtStart() { 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), 50, nullStream(), output); + 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 aca72e38f7cc4a3f60a0c41fd8d53e0cf5294de3 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 16 Oct 2022 18:57:48 +0200 Subject: [PATCH 06/35] implement per-context global state for tame fingerprints --- .../jfunge/interpreter/ExecutionContext.java | 6 ++++ .../jfunge/interpreter/Interpreter.java | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java b/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java index ee8024e..38c32a1 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java @@ -17,6 +17,12 @@ public interface ExecutionContext { FungeSpace fungeSpace(); + T getGlobal(int finger, String key); + + void putGlobal(int finger, String key, T value); + + boolean hasGlobal(int finger, String key); + int dimensions(); boolean stopped(); diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java index 7fcb0f4..cc40b15 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java @@ -5,6 +5,8 @@ import com.falsepattern.jfunge.interpreter.instructions.InstructionManager; import com.falsepattern.jfunge.ip.InstructionPointer; import com.falsepattern.jfunge.storage.FungeSpace; +import gnu.trove.map.TIntObjectMap; +import gnu.trove.map.hash.TIntObjectHashMap; import lombok.Getter; import lombok.SneakyThrows; import lombok.experimental.Accessors; @@ -20,6 +22,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -67,6 +70,8 @@ public boolean writeFile(String file, byte[] data) throws IOException { private final boolean unrestrictedOutput; private final Path[] allowedOutputPaths; + private final TIntObjectMap> globals = new TIntObjectHashMap<>(); + private Integer exitCode = null; private InstructionPointer currentIP = null; @@ -174,6 +179,31 @@ public InstructionPointer cloneIP() { return clone; } + private Map getMap(int key) { + if (globals.containsKey(key)) { + return globals.get(key); + } else { + val map = new HashMap(); + globals.put(key, map); + return map; + } + } + + @Override + public T getGlobal(int finger, String key) { + return (T) getMap(finger).getOrDefault(key, null); + } + + @Override + public void putGlobal(int finger, String key, T value) { + getMap(finger).put(key, value); + } + + @Override + public boolean hasGlobal(int finger, String key) { + return getMap(finger).containsKey(key); + } + @Override public boolean stopped() { if (IPs.size() == 0) { From 9846a64ce568150f31e9cdc0a754da9fe6b54a35 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 16 Oct 2022 18:58:10 +0200 Subject: [PATCH 07/35] implement REFC --- README.MD | 1 + .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/REFC.java | 81 +++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java diff --git a/README.MD b/README.MD index 55cd14e..b241fcd 100644 --- a/README.MD +++ b/README.MD @@ -55,6 +55,7 @@ Additionally, the following fingerprints are currently supported (more to come): - [MODU](./docs/catseye/library/MODU.markdown) - [NULL](./docs/catseye/library/NULL.markdown) - [ORTH](./docs/catseye/library/ORTH.markdown) +- [REFC](./docs/catseye/library/REFC.markdown) - [ROMA](./docs/catseye/library/ROMA.markdown) Fingerprints that are NOT planned to be ever implemented: 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 0d6d049..cf7d84c 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -7,6 +7,7 @@ import com.falsepattern.jfunge.interpreter.instructions.fingerprints.MODU; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.NULL; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.ORTH; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.REFC; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.ROMA; import com.falsepattern.jfunge.ip.Stack; import gnu.trove.map.TIntObjectMap; @@ -38,6 +39,7 @@ public class Funge98 implements InstructionSet { addFingerprint(MODU.INSTANCE); addFingerprint(NULL.INSTANCE); addFingerprint(ORTH.INSTANCE); + addFingerprint(REFC.INSTANCE); addFingerprint(ROMA.INSTANCE); } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java new file mode 100644 index 0000000..ca0a701 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java @@ -0,0 +1,81 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import gnu.trove.map.TIntObjectMap; +import gnu.trove.map.TObjectIntMap; +import gnu.trove.map.hash.TIntObjectHashMap; +import gnu.trove.map.hash.TObjectIntHashMap; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.val; +import org.joml.Vector2i; +import org.joml.Vector3i; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class REFC implements Fingerprint { + public static final REFC INSTANCE = new REFC(); + + private static Vectors getGlobal(ExecutionContext ctx) { + if (!ctx.hasGlobal(INSTANCE.code(), "references")) { + ctx.putGlobal(INSTANCE.code(), "references", new Vectors()); + } + return ctx.getGlobal(INSTANCE.code(), "references"); + } + + @Instr('R') + public static void reference(ExecutionContext ctx) { + val vecs = getGlobal(ctx); + val vec = new Vector3i(); + val toss = ctx.IP().stackStack.TOSS(); + if (ctx.dimensions() == 3) { + toss.pop3(vec); + } else { + val vec2 = toss.pop2(); + vec.x = vec2.x; + vec.y = vec2.y; + } + toss.push(vecs.reference(vec)); + } + + @Instr('D') + public static void dereference(ExecutionContext ctx) { + val vecs = getGlobal(ctx); + val toss = ctx.IP().stackStack.TOSS(); + Vector3i vec = vecs.dereference(toss.pop()); + if (ctx.dimensions() == 3) { + toss.push3(vec); + } else { + toss.push2(new Vector2i(vec.x, vec.y)); + } + } + + public static class Vectors { + private final TIntObjectMap references = new TIntObjectHashMap<>(); + private final TObjectIntMap dereferences = new TObjectIntHashMap<>(); + private static int counter = 0; + + public int reference(Vector3i vec) { + if (dereferences.containsKey(vec)) { + return dereferences.get(vec); + } else { + int ref = counter++; + references.put(ref, vec); + dereferences.put(vec, ref); + return ref; + } + } + + public Vector3i dereference(int index) { + if (references.containsKey(index)) { + return references.get(index); + } else { + return null; + } + } + } + + @Override + public int code() { + return 0x52454643; + } +} From 63ff513588980f609a7751912da8072f58511125 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 16 Oct 2022 21:06:57 +0200 Subject: [PATCH 08/35] implement HRTI --- README.MD | 1 + .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/HRTI.java | 69 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/HRTI.java diff --git a/README.MD b/README.MD index b241fcd..b30c955 100644 --- a/README.MD +++ b/README.MD @@ -51,6 +51,7 @@ The interpreter supports the following Funge-98 specification extensions: - Optional Trefunge mode (experimental) Additionally, the following fingerprints are currently supported (more to come): +- [HRTI](./docs/catseye/library/HRTI.markdown) - [MODE](./docs/catseye/library/MODE.markdown) - [MODU](./docs/catseye/library/MODU.markdown) - [NULL](./docs/catseye/library/NULL.markdown) 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 cf7d84c..e1c9993 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -3,6 +3,7 @@ import com.falsepattern.jfunge.Globals; import com.falsepattern.jfunge.interpreter.ExecutionContext; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.Fingerprint; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.HRTI; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.MODE; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.MODU; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.NULL; @@ -35,6 +36,7 @@ public class Funge98 implements InstructionSet { private static final TIntObjectMap fingerprints = new TIntObjectHashMap<>(); static { + addFingerprint(HRTI.INSTANCE); addFingerprint(MODE.INSTANCE); addFingerprint(MODU.INSTANCE); addFingerprint(NULL.INSTANCE); 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 new file mode 100644 index 0000000..312f5da --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/HRTI.java @@ -0,0 +1,69 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import gnu.trove.map.TIntLongMap; +import gnu.trove.map.hash.TIntLongHashMap; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.val; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class HRTI implements Fingerprint { + public static final HRTI INSTANCE = new HRTI(); + + @Override + public int code() { + return 0x48525449; + } + + private static TIntLongMap getMarkMap(ExecutionContext ctx) { + if (!ctx.hasGlobal(INSTANCE.code(), "marks")) { + ctx.putGlobal(INSTANCE.code(), "marks", new TIntLongHashMap()); + } + return ctx.getGlobal(INSTANCE.code(), "marks"); + } + + @Instr('G') + public static void granularity(ExecutionContext ctx) { + ctx.IP().stackStack.TOSS().push(1); + } + + @Instr('M') + public static void mark(ExecutionContext ctx) { + val marks = getMarkMap(ctx); + marks.put(ctx.IP().UUID, System.nanoTime()); + } + + @Instr('T') + public static void timer(ExecutionContext ctx) { + val marks = getMarkMap(ctx); + val ip = ctx.IP(); + if (!marks.containsKey(ip.UUID)) { + ctx.interpret('r'); + return; + } + ip.stackStack.TOSS().push((int)((System.nanoTime() - marks.get(ip.UUID)) / 1000L)); + } + + /* + TODO Erase behaviour without marking first is undocumented in the official spec, and untested in mycology. + Assuming that fall-through behaviour is desired. Commented out code has a reflect implementation. Need to add some sort of toggle for this. + */ + // + //TODO Comm + @Instr('E') + public static void erase(ExecutionContext ctx) { + val marks = getMarkMap(ctx); + val ip = ctx.IP(); +// if (!marks.containsKey(ip.UUID)) { +// ctx.interpret('r'); +// return; +// } + marks.remove(ip.UUID); + } + + @Instr('S') + public static void second(ExecutionContext ctx) { + ctx.IP().stackStack.TOSS().push((int)((System.nanoTime() % 1000000000L) / 1000L)); + } +} From 85b704b5aeaa43d9e4f5c8f020eaaaca8bc80265 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sun, 16 Oct 2022 21:07:13 +0200 Subject: [PATCH 09/35] update mycology with the HRTI PR --- src/test/resources/mycology.b98 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/resources/mycology.b98 b/src/test/resources/mycology.b98 index 8bede96..3f9c2c2 100644 --- a/src/test/resources/mycology.b98 +++ b/src/test/resources/mycology.b98 @@ -234,9 +234,9 @@ n >na"stcelfer G :DAB"; >0a"detcelfer TTM :DAB"a > ay v >#^G0" sa ytiralunarg kcolc sevig G :FEDNU">:#,_$.0a"sdnocesorcim">:#,_$#vT0a"M erofeb dellac nehw detcelfer evah dluohs T :DAB"^$ ^"HRTI"0< v_v#!p00:-1<*ff .$_,#! #:<"UNDEF: T after M pushed "0T^# ;> a"M erofeb dellac nehw detcelfer T :DOOG">:#,_#^S0" dehsup S :FEDNU">:#,_$.a,#^M#;< v >;>yynyn00g^; #vT 0" dehsup ,sy 576 retfa ,T dnoces a dna">:#,_$.a,#vE #vT0a"tcelfer t'nseod TE :DAB" ^ -v )g22g33,a < ;>n^; $_,#! #:<"GOOD: ET reflected"a0># #<0a"stcelfer E :DAB" ^ - v - H >0a"tsaehtron seog v< >H retfa ^ :DAB"v vv $_,#! #:n^;"d not reflect"Ev#$_,#! #:<"GOOD: ET reflected"a0># #<0a"stcelfer E :DAB" ^ + >;#"UNDEF: E without M "<;>:#,_$v ;"reflected"< v + ^ < H >0a"tsaehtron seog v< >H retfa ^ :DAB"v vv $_,#! #:0a"pu thgiarts seog v< >H retfa ^ :DAB" v _0a"thgir seog _0H :DAB" v"BAD: H1_ goes left"a0_ >"domh"v > ay vvv;>#v#vH >v v< #; #< vHH v "BAD: H1| goes straight up"a0 <0 ' " v># # >0a"stcelfer H :DAB" ^ >:#,_$vH^a"edomrevoh ni krow ot mees v^<> :DOOG" H >:#,_ H | H< : ^;^"BAD: S reflects"a0<>:#,_$v; 1^ '>"ws "v From 654fcc3a75005453385421ec7a7e004e857696a6 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 20 Oct 2022 19:53:24 +0200 Subject: [PATCH 10/35] update to latest mycology test suite --- src/test/resources/mycology.b98 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/resources/mycology.b98 b/src/test/resources/mycology.b98 index 3f9c2c2..3f4c395 100644 --- a/src/test/resources/mycology.b98 +++ b/src/test/resources/mycology.b98 @@ -234,9 +234,9 @@ n >na"stcelfer G :DAB"; >0a"detcelfer TTM :DAB"a > ay v >#^G0" sa ytiralunarg kcolc sevig G :FEDNU">:#,_$.0a"sdnocesorcim">:#,_$#vT0a"M erofeb dellac nehw detcelfer evah dluohs T :DAB"^$ ^"HRTI"0< v_v#!p00:-1<*ff .$_,#! #:<"UNDEF: T after M pushed "0T^# ;> a"M erofeb dellac nehw detcelfer T :DOOG">:#,_#^S0" dehsup S :FEDNU">:#,_$.a,#^M#;< v >;>yynyn00g^; #vT 0" dehsup ,sy 576 retfa ,T dnoces a dna">:#,_$.a,#vE #vT0a"tcelfer t'nseod TE :DAB" ^ -v )g22g33,a < < v"di";>n^;"d not reflect"Ev#$_,#! #:<"GOOD: ET reflected"a0># #<0a"stcelfer E :DAB" ^ - >;#"UNDEF: E without M "<;>:#,_$v ;"reflected"< v - ^ < H >0a"tsaehtron seog v< >H retfa ^ :DAB"v vv $_,#! #:n^;"not reflect"a0Ev#$_,#! #:<"GOOD: ET reflected"a0># #<0a"stcelfer E :DAB" ^ + >;#"UNDEF: E without M "<;>:#,_$v ;"reflected"a0< v + ^ ;> ^; < H >0a"tsaehtron seog v< >H retfa ^ :DAB"v vv $_,#! #:0a"pu thgiarts seog v< >H retfa ^ :DAB" v _0a"thgir seog _0H :DAB" v"BAD: H1_ goes left"a0_ >"domh"v > ay vvv;>#v#vH >v v< #; #< vHH v "BAD: H1| goes straight up"a0 <0 ' " v># # >0a"stcelfer H :DAB" ^ >:#,_$vH^a"edomrevoh ni krow ot mees v^<> :DOOG" H >:#,_ H | H< : ^;^"BAD: S reflects"a0<>:#,_$v; 1^ '>"ws "v From 4eb311b447cce13801e2720021496763cf3cc158 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 20 Oct 2022 19:55:09 +0200 Subject: [PATCH 11/35] implement PERL --- README.MD | 5 +- .../java/com/falsepattern/jfunge/Main.java | 5 + .../jfunge/interpreter/ExecutionContext.java | 2 + .../jfunge/interpreter/FeatureSet.java | 2 + .../jfunge/interpreter/Interpreter.java | 13 +++ .../interpreter/instructions/Funge98.java | 4 +- .../instructions/fingerprints/PERL.java | 104 ++++++++++++++++++ .../jfunge/storage/TestInterpreter.java | 1 + 8 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/PERL.java diff --git a/README.MD b/README.MD index b30c955..84a1279 100644 --- a/README.MD +++ b/README.MD @@ -56,13 +56,10 @@ Additionally, the following fingerprints are currently supported (more to come): - [MODU](./docs/catseye/library/MODU.markdown) - [NULL](./docs/catseye/library/NULL.markdown) - [ORTH](./docs/catseye/library/ORTH.markdown) +- [PERL](./docs/catseye/library/PERL.markdown) - [REFC](./docs/catseye/library/REFC.markdown) - [ROMA](./docs/catseye/library/ROMA.markdown) -Fingerprints that are NOT planned to be ever implemented: -- [PERL](./docs/catseye/library/PERL.markdown) -- Would need to hook into the system-installed perl, highly unsafe - - ### Version Release Checklist - Update the version number inside the [pom](./pom.xml) - Update the version string and FUNGE_VERSION inside [Globals](./src/main/java/com/falsepattern/jfunge/Globals.java) diff --git a/src/main/java/com/falsepattern/jfunge/Main.java b/src/main/java/com/falsepattern/jfunge/Main.java index d5dc9f9..c6420bf 100644 --- a/src/main/java/com/falsepattern/jfunge/Main.java +++ b/src/main/java/com/falsepattern/jfunge/Main.java @@ -65,6 +65,10 @@ public static void main(String[] args) throws IOException, ParseException { .numberOfArgs(1) .desc("The maximum number of iterations the program can run for. Anything less than 1 will run until the program terminates by itself. Default is unlimited.") .build()); + options.addOption(Option.builder() + .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()); val parser = new DefaultParser(); val cmd = parser.parse(options, args); if (cmd.hasOption("help")) { @@ -92,6 +96,7 @@ public static void main(String[] args) throws IOException, ParseException { featureSet.concurrent(cmd.hasOption("t")); featureSet.allowedInputFiles(cmd.getOptionValues("i")); featureSet.allowedOutputFiles(cmd.getOptionValues("o")); + featureSet.perl(cmd.hasOption("perl")); featureSet.maxIter(cmd.hasOption("maxiter") ? Integer.parseInt(cmd.getOptionValue("maxiter")) : 0); byte[] program; if (file.equals("-")) { diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java b/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java index 38c32a1..7da76cb 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java @@ -64,4 +64,6 @@ default boolean fileOutputAllowed(String path) throws IOException { default boolean syscallAllowed() { return (envFlags() & 0x08) != 0; } + + boolean fingerprintAllowed(int code); } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java b/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java index 9b58fc1..f54d2b2 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java @@ -12,4 +12,6 @@ public class FeatureSet { public final String[] allowedOutputFiles; public final boolean concurrent; public final long maxIter; + + public final boolean perl; } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java index cc40b15..3bb52c0 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java @@ -3,8 +3,11 @@ import com.falsepattern.jfunge.interpreter.instructions.Funge98; 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.ip.InstructionPointer; import com.falsepattern.jfunge.storage.FungeSpace; +import gnu.trove.list.TIntList; +import gnu.trove.list.array.TIntArrayList; import gnu.trove.map.TIntObjectMap; import gnu.trove.map.hash.TIntObjectHashMap; import lombok.Getter; @@ -49,6 +52,7 @@ public boolean writeFile(String file, byte[] data) throws IOException { private final InputStream input; + private final TIntList fingerprintBlackList = new TIntArrayList(); @Getter private final OutputStream output; @@ -126,6 +130,10 @@ public Interpreter(String[] args, InputStream input, OutputStream output, FileIO env |= 8; } envFlags = env; + + if (!featureSet.perl) { + fingerprintBlackList.add(PERL.INSTANCE.code()); + } } @SneakyThrows @@ -354,6 +362,11 @@ public boolean syscallAllowed() { return (envFlags & 0x08) != 0; } + @Override + public boolean fingerprintAllowed(int code) { + return !fingerprintBlackList.contains(code); + } + public void tick() { currentIP = null; for (int i = 0; i < IPs.size(); i++) { 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 e1c9993..72fb69b 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.MODU; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.NULL; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.ORTH; +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.ip.Stack; @@ -41,6 +42,7 @@ public class Funge98 implements InstructionSet { addFingerprint(MODU.INSTANCE); addFingerprint(NULL.INSTANCE); addFingerprint(ORTH.INSTANCE); + addFingerprint(PERL.INSTANCE); addFingerprint(REFC.INSTANCE); addFingerprint(ROMA.INSTANCE); } @@ -63,7 +65,7 @@ private static int getFingerCode(Stack s) { public static void loadFinger(ExecutionContext ctx) { val s = ctx.IP().stackStack.TOSS(); val code = getFingerCode(s); - if (fingerprints.containsKey(code)) { + if (fingerprints.containsKey(code) && ctx.fingerprintAllowed(code)) { s.push(code); s.push(1); ctx.IP().instructionManager.loadInstructionSet(fingerprints.get(code)); diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/PERL.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/PERL.java new file mode 100644 index 0000000..e1ae37c --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/PERL.java @@ -0,0 +1,104 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Funge98; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.val; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.UUID; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class PERL implements Fingerprint { + public static final PERL INSTANCE = new PERL(); + + private static String executePerl(ExecutionContext ctx) { + String name; + do { + name = UUID.randomUUID() + ".pl"; + } while (Files.exists(Paths.get(name))); + try { + try (val os = Files.newOutputStream(Paths.get(name), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) { + os.write("print eval(".getBytes(StandardCharsets.UTF_8)); + os.write(ctx.IP().stackStack.TOSS().popString().getBytes(StandardCharsets.UTF_8)); + os.write(");".getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + e.printStackTrace(); + Funge98.reflect(ctx); + return null; + } + try { + val process = Runtime.getRuntime().exec(new String[]{"perl", name}); + int returnCode; + while (true) { + try { + returnCode = process.waitFor(); + break; + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + if (returnCode != 0) { + return Integer.toString(returnCode); + } + val res = new ByteArrayOutputStream(); + val input = process.getInputStream(); + val buf = new byte[256]; + int read; + while ((read = input.read(buf)) > 0) { + res.write(buf, 0, read); + } + return res.toString("UTF-8"); + } catch (IOException e) { + e.printStackTrace(); + Funge98.reflect(ctx); + return null; + } + } finally { + try { + Files.deleteIfExists(Paths.get(name)); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + @Instr('E') + public static void exec(ExecutionContext ctx) { + String res = executePerl(ctx); + if (res != null) { + ctx.IP().stackStack.TOSS().pushString(res); + } + } + + @Instr('S') + public static void getShelled(ExecutionContext ctx) { + ctx.IP().stackStack.TOSS().push(1); + } + + @Instr('I') + public static void execInt(ExecutionContext ctx) { + String res = executePerl(ctx); + if (res != null) { + int intRes; + try { + intRes = Integer.parseInt(res); + } catch (NumberFormatException e) { + Funge98.reflect(ctx); + return; + } + ctx.IP().stackStack.TOSS().push(intRes); + } + } + + @Override + public int code() { + return 0x5045524c; + } +} diff --git a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java index 64fb219..0641917 100644 --- a/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java +++ b/src/test/java/com/falsepattern/jfunge/storage/TestInterpreter.java @@ -87,6 +87,7 @@ public void testMycology() { .allowedOutputFiles(new String[]{"/"}) .sysCall(false) .concurrent(true) + .perl(true) .maxIter(300000L) .build(); val returnCode = interpret(new String[]{"mycology.b98"}, program, nullStream(), output, featureSet); From af3f8b84c0ce8b57ca5d890762cee4a9d0e17bc0 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 20 Oct 2022 21:01:52 +0200 Subject: [PATCH 12/35] move some stuff into interfaces for future javadoc --- .../jfunge/interpreter/ExecutionContext.java | 18 +- .../jfunge/interpreter/Interpreter.java | 75 +++---- .../interpreter/instructions/Funge98.java | 193 +++++++++--------- .../instructions/fingerprints/HRTI.java | 12 +- .../instructions/fingerprints/MODE.java | 26 +-- .../instructions/fingerprints/ORTH.java | 54 ++--- .../instructions/fingerprints/PERL.java | 8 +- .../instructions/fingerprints/REFC.java | 16 +- .../instructions/fingerprints/ROMA.java | 14 +- .../java/com/falsepattern/jfunge/ip/IP.java | 67 ++++++ .../com/falsepattern/jfunge/ip/IStack.java | 96 +++++++++ .../falsepattern/jfunge/ip/IStackStack.java | 18 ++ .../com/falsepattern/jfunge/ip/Stack.java | 131 ------------ .../ip/{ => impl}/InstructionPointer.java | 41 ++-- .../falsepattern/jfunge/ip/impl/Stack.java | 65 ++++++ .../jfunge/ip/{ => impl}/StackStack.java | 54 ++--- 16 files changed, 515 insertions(+), 373 deletions(-) create mode 100644 src/main/java/com/falsepattern/jfunge/ip/IP.java create mode 100644 src/main/java/com/falsepattern/jfunge/ip/IStack.java create mode 100644 src/main/java/com/falsepattern/jfunge/ip/IStackStack.java delete mode 100644 src/main/java/com/falsepattern/jfunge/ip/Stack.java rename src/main/java/com/falsepattern/jfunge/ip/{ => impl}/InstructionPointer.java (68%) create mode 100644 src/main/java/com/falsepattern/jfunge/ip/impl/Stack.java rename src/main/java/com/falsepattern/jfunge/ip/{ => impl}/StackStack.java (64%) diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java b/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java index 7da76cb..1aa38d7 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java @@ -1,7 +1,9 @@ package com.falsepattern.jfunge.interpreter; -import com.falsepattern.jfunge.ip.InstructionPointer; +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; @@ -9,11 +11,11 @@ import java.util.Map; public interface ExecutionContext { - InstructionPointer[] allIPs(); + IP[] allIPs(); - InstructionPointer IP(); + IP IP(); - InstructionPointer cloneIP(); + IP cloneIP(); FungeSpace fungeSpace(); @@ -33,7 +35,7 @@ public interface ExecutionContext { void interpret(int code); - void step(InstructionPointer ip); + void step(IP ip); List args(); @@ -66,4 +68,10 @@ default boolean syscallAllowed() { } boolean fingerprintAllowed(int code); + + //Common shorthands + + default IStack stack() { + return IP().stackStack().TOSS(); + } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java index 3bb52c0..9728058 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java @@ -4,7 +4,8 @@ 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.ip.InstructionPointer; +import com.falsepattern.jfunge.ip.IP; +import com.falsepattern.jfunge.ip.impl.InstructionPointer; import com.falsepattern.jfunge.storage.FungeSpace; import gnu.trove.list.TIntList; import gnu.trove.list.array.TIntArrayList; @@ -46,7 +47,7 @@ public boolean writeFile(String file, byte[] data) throws IOException { @Getter private final FungeSpace fungeSpace = new FungeSpace(' '); - private final List IPs = new ArrayList<>(); + private final List IPs = new ArrayList<>(); private final InstructionManager baseInstructionManager = new InstructionManager(); @@ -78,9 +79,9 @@ public boolean writeFile(String file, byte[] data) throws IOException { private Integer exitCode = null; - private InstructionPointer currentIP = null; + private IP currentIP = null; - private InstructionPointer clone = null; + private IP clone = null; private int nextUUID = 0; @@ -93,8 +94,8 @@ public Interpreter(String[] args, InputStream input, OutputStream output, FileIO this.input = input; this.output = output; this.fileIOSupplier = fileIOSupplier; - val ip = new InstructionPointer(); - ip.UUID = nextUUID++; + val ip = (IP) new InstructionPointer(); + ip.UUID(nextUUID++); IPs.add(ip); int env = 0; if (featureSet.concurrent) { @@ -151,7 +152,7 @@ public static int executeProgram(String[] args, byte[] program, InputStream inpu //Init step { val ip = interpreter.IPs.get(0); - ip.position.sub(ip.delta); + ip.position().sub(ip.delta()); interpreter.step(ip); } if (featureSet.maxIter > 0) { @@ -171,19 +172,19 @@ public static int executeProgram(String[] args, byte[] program, InputStream inpu } @Override - public InstructionPointer[] allIPs() { - return IPs.toArray(new InstructionPointer[0]); + public IP[] allIPs() { + return IPs.toArray(new IP[0]); } @Override - public InstructionPointer IP() { + public IP IP() { return currentIP; } @Override - public InstructionPointer cloneIP() { + public IP cloneIP() { clone = currentIP.deepCopy(); - clone.UUID = nextUUID++; + clone.UUID(nextUUID++); return clone; } @@ -233,12 +234,12 @@ public int exitCode() { @Override public void interpret(int opcode) { if (opcode == '"') { - IP().stringMode = !IP().stringMode; - } else if (IP().stringMode) { - IP().stackStack.TOSS().push(opcode); + IP().stringMode(!IP().stringMode()); + } else if (IP().stringMode()) { + IP().stackStack().TOSS().push(opcode); } else { Instruction instr; - if ((instr = IP().instructionManager.fetch(opcode)) != null || (instr = baseInstructionManager.fetch(opcode)) != null) { + if ((instr = IP().instructionManager().fetch(opcode)) != null || (instr = baseInstructionManager.fetch(opcode)) != null) { instr.process(this); } else { if (opcode == 'r') @@ -249,29 +250,29 @@ public void interpret(int opcode) { } - private boolean wrappingStep(InstructionPointer ip) { - ip.position.add(ip.delta); - if (!fungeSpace().bounds().inBounds(ip.position)) { - ip.delta.mul(-1); + private boolean wrappingStep(IP ip) { + ip.step(); + if (!fungeSpace().bounds().inBounds(ip.position())) { + ip.reflect(); do { - ip.position.add(ip.delta); - } while (!fungeSpace().bounds().inBounds(ip.position)); + ip.step(); + } while (!fungeSpace().bounds().inBounds(ip.position())); do { - ip.position.add(ip.delta); - } while (fungeSpace().bounds().inBounds(ip.position)); - ip.delta.mul(-1); + ip.step(); + } while (fungeSpace().bounds().inBounds(ip.position())); + ip.reflect(); do { - ip.position.add(ip.delta); - } while (!fungeSpace().bounds().inBounds(ip.position)); + ip.step(); + } while (!fungeSpace().bounds().inBounds(ip.position())); return true; } return false; } - public void step(InstructionPointer ip) { + public void step(IP ip) { int p; - if (ip.stringMode) { - p = fungeSpace.get(ip.position); + if (ip.stringMode()) { + p = fungeSpace.get(ip.position()); if (p != ' ') { wrappingStep(ip); return; @@ -280,19 +281,19 @@ public void step(InstructionPointer ip) { int flipCount = 0; do { flipCount += wrappingStep(ip) ? 1 : 0; - p = fungeSpace.get(ip.position); - if (!ip.stringMode) { + p = fungeSpace.get(ip.position()); + if (!ip.stringMode()) { while (p == ';') { do { flipCount += wrappingStep(ip) ? 1 : 0; - p = fungeSpace.get(ip.position); + p = fungeSpace.get(ip.position()); } while (p != ';'); flipCount += wrappingStep(ip) ? 1 : 0; - p = fungeSpace.get(ip.position); + p = fungeSpace.get(ip.position()); } } if (flipCount == 1000) { - throw new IllegalStateException("IP " + ip.UUID + " is stuck on a blank strip!\nPos: " + ip.position + ", Delta: " + ip.delta + (ip.position.equals(0, 0, 0) && ip.UUID == 0 ? "\nIs the file empty?" : "")); + throw new IllegalStateException("IP " + ip.UUID() + " is stuck on a blank strip!\nPos: " + ip.position() + ", Delta: " + ip.delta() + (ip.position().equals(0, 0, 0) && ip.UUID() == 0 ? "\nIs the file empty?" : "")); } } while (p == ' '); } @@ -371,12 +372,12 @@ public void tick() { currentIP = null; for (int i = 0; i < IPs.size(); i++) { currentIP = IPs.get(i); - if (IP().isDead()) { + if (IP().dead()) { IPs.remove(i); i--; continue; } - interpret(fungeSpace().get(IP().position)); + 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 72fb69b..6c82ab9 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -11,7 +11,8 @@ 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.ip.Stack; +import com.falsepattern.jfunge.ip.IStack; +import com.falsepattern.jfunge.ip.impl.Stack; import gnu.trove.map.TIntObjectMap; import gnu.trove.map.hash.TIntObjectHashMap; import lombok.AccessLevel; @@ -51,7 +52,7 @@ private static void addFingerprint(Fingerprint print) { fingerprints.put(print.code(), print); } - private static int getFingerCode(Stack s) { + private static int getFingerCode(IStack s) { val n = s.pop(); var sum = 0; for (var i = 0; i < n; i++) { @@ -63,12 +64,12 @@ private static int getFingerCode(Stack s) { @Instr('(') public static void loadFinger(ExecutionContext ctx) { - val s = ctx.IP().stackStack.TOSS(); - val code = getFingerCode(s); + val stack = ctx.stack(); + val code = getFingerCode(stack); if (fingerprints.containsKey(code) && ctx.fingerprintAllowed(code)) { - s.push(code); - s.push(1); - ctx.IP().instructionManager.loadInstructionSet(fingerprints.get(code)); + stack.push(code); + stack.push(1); + ctx.IP().instructionManager().loadInstructionSet(fingerprints.get(code)); } else { ctx.interpret('r'); } @@ -76,9 +77,9 @@ public static void loadFinger(ExecutionContext ctx) { @Instr(')') public static void unloadFinger(ExecutionContext ctx) { - val code = getFingerCode(ctx.IP().stackStack.TOSS()); + val code = getFingerCode(ctx.stack()); if (fingerprints.containsKey(code)) { - ctx.IP().instructionManager.unloadInstructionSet(fingerprints.get(code)); + ctx.IP().instructionManager().unloadInstructionSet(fingerprints.get(code)); } else { ctx.interpret('r'); } @@ -86,18 +87,18 @@ public static void unloadFinger(ExecutionContext ctx) { @Instr('>') public static void east(ExecutionContext ctx) { - ctx.IP().delta.set(1, 0, 0); + ctx.IP().delta().set(1, 0, 0); } @Instr('v') public static void south(ExecutionContext ctx) { - ctx.IP().delta.set(0, 1, 0); + ctx.IP().delta().set(0, 1, 0); } @Instr('h') public static void high(ExecutionContext ctx) { if (ctx.dimensions() == 3) - ctx.IP().delta.set(0, 0, 1); + ctx.IP().delta().set(0, 0, 1); else ctx.interpret('r'); } @@ -105,7 +106,7 @@ public static void high(ExecutionContext ctx) { @Instr('l') public static void low(ExecutionContext ctx) { if (ctx.dimensions() == 3) - ctx.IP().delta.set(0, 0, -1); + ctx.IP().delta().set(0, 0, -1); else ctx.interpret('r'); } @@ -154,57 +155,57 @@ public static void away(ExecutionContext ctx) { @Instr('<') public static void west(ExecutionContext ctx) { - ctx.IP().delta.set(-1, 0, 0); + ctx.IP().delta().set(-1, 0, 0); } @Instr('^') public static void north(ExecutionContext ctx) { - ctx.IP().delta.set(0, -1, 0); + ctx.IP().delta().set(0, -1, 0); } @Instr('[') public static void turnLeft(ExecutionContext ctx) { - val d = ctx.IP().delta; + val d = ctx.IP().delta(); d.set(d.y, -d.x, d.z); } @Instr(']') public static void turnRight(ExecutionContext ctx) { - val d = ctx.IP().delta; + val d = ctx.IP().delta(); d.set(-d.y, d.x, d.z); } @Instr('#') public static void trampoline(ExecutionContext ctx) { - ctx.IP().position.add(ctx.IP().delta); + ctx.IP().step(); } @Instr('j') public static void jumpNTimes(ExecutionContext ctx) { - int n = ctx.IP().stackStack.TOSS().pop(); - ctx.IP().position.add(new Vector3i(ctx.IP().delta).mul(n)); + int n = ctx.stack().pop(); + ctx.IP().position().add(new Vector3i(ctx.IP().delta()).mul(n)); } @Instr('x') public static void absoluteDelta(ExecutionContext ctx) { - val s = ctx.IP().stackStack.TOSS(); + val stack = ctx.stack(); if (ctx.dimensions() == 3) { - s.pop3(ctx.IP().delta); + stack.pop3(ctx.IP().delta()); } else { - val tmp = s.pop2(); - ctx.IP().delta.set(tmp.x, tmp.y, 0); + val tmp = stack.pop2(); + ctx.IP().delta().set(tmp.x, tmp.y, 0); } } @Instr('k') public static void doNTimes(ExecutionContext ctx) { - val s = ctx.IP().stackStack.TOSS(); + val s = ctx.stack(); int n = s.pop(); if (n > 0) { - val snapshot = new Vector3i(ctx.IP().position); + val snapshot = new Vector3i(ctx.IP().position()); ctx.step(ctx.IP()); - int i = ctx.fungeSpace().get(ctx.IP().position); - ctx.IP().position.set(snapshot); + int i = ctx.fungeSpace().get(ctx.IP().position()); + ctx.IP().position().set(snapshot); for (int j = 0; j < n; j++) { ctx.interpret(i); } @@ -216,19 +217,19 @@ public static void doNTimes(ExecutionContext ctx) { @SneakyThrows @Instr('.') public static void printNumber(ExecutionContext ctx) { - ctx.output().write(Integer.toString(ctx.IP().stackStack.TOSS().pop()).getBytes(StandardCharsets.UTF_8)); + ctx.output().write(Integer.toString(ctx.stack().pop()).getBytes(StandardCharsets.UTF_8)); ctx.output().write(' '); } @SneakyThrows @Instr(',') public static void printChar(ExecutionContext ctx) { - ctx.output().write(ctx.IP().stackStack.TOSS().pop()); + ctx.output().write(ctx.stack().pop()); } @Instr('r') public static void reflect(ExecutionContext ctx) { - ctx.IP().delta.mul(-1); + ctx.IP().reflect(); } @Instr('@') @@ -238,28 +239,28 @@ public static void die(ExecutionContext ctx) { @Instr('$') public static void pop(ExecutionContext ctx) { - ctx.IP().stackStack.TOSS().pop(); + ctx.stack().pop(); } @Instr('n') public static void clearStack(ExecutionContext ctx) { - ctx.IP().stackStack.TOSS().clear(); + ctx.stack().clear(); } @Instr('_') public static void branchEastWest(ExecutionContext ctx) { - ctx.interpret(ctx.IP().stackStack.TOSS().pop() == 0 ? '>' : '<'); + ctx.interpret(ctx.stack().pop() == 0 ? '>' : '<'); } @Instr('|') public static void branchNorthSouth(ExecutionContext ctx) { - ctx.interpret(ctx.IP().stackStack.TOSS().pop() == 0 ? 'v' : '^'); + ctx.interpret(ctx.stack().pop() == 0 ? 'v' : '^'); } @Instr('m') public static void branchHighLow(ExecutionContext ctx) { if (ctx.dimensions() == 3) { - ctx.interpret(ctx.IP().stackStack.TOSS().pop() == 0 ? 'h' : 'l'); + ctx.interpret(ctx.stack().pop() == 0 ? 'h' : 'l'); } else { ctx.interpret('r'); } @@ -267,20 +268,20 @@ public static void branchHighLow(ExecutionContext ctx) { @Instr('\'') public static void getNext(ExecutionContext ctx) { - int i = ctx.fungeSpace().get(new Vector3i(ctx.IP().position).add(ctx.IP().delta)); - ctx.IP().stackStack.TOSS().push(i); + int i = ctx.fungeSpace().get(new Vector3i(ctx.IP().position()).add(ctx.IP().delta())); + ctx.stack().push(i); ctx.interpret('#'); } @Instr('s') public static void putNext(ExecutionContext ctx) { - ctx.fungeSpace().set(new Vector3i(ctx.IP().position).add(ctx.IP().delta), ctx.IP().stackStack.TOSS().pop()); + ctx.fungeSpace().set(new Vector3i(ctx.IP().position()).add(ctx.IP().delta()), ctx.stack().pop()); ctx.interpret('#'); } @Instr('w') public static void conditionalTurn(ExecutionContext ctx) { - val s = ctx.IP().stackStack.TOSS(); + val s = ctx.stack(); val b = s.pop(); val a = s.pop(); if (a > b) { @@ -339,28 +340,28 @@ public static void swap(ExecutionContext ctx) { @Instr('p') public static void put(ExecutionContext ctx) { val ip = ctx.IP(); - val toss = ip.stackStack.TOSS(); + val toss = ip.stackStack().TOSS(); if (ctx.dimensions() == 3) { - val vec = toss.pop3().add(ip.storageOffset); + val vec = toss.pop3().add(ip.storageOffset()); val i = toss.pop(); ctx.fungeSpace().set(vec, i); } else { - val vec = toss.pop2().add(ip.storageOffset.x, ip.storageOffset.y); + val vec = toss.pop2().add(ip.storageOffset2()); val i = toss.pop(); - ctx.fungeSpace().set(vec.x, vec.y, ip.storageOffset.z, i); + ctx.fungeSpace().set(vec.x, vec.y, ip.storageOffset().z, i); } } @Instr('g') public static void get(ExecutionContext ctx) { val ip = ctx.IP(); - val toss = ip.stackStack.TOSS(); + val stack = ctx.stack(); if (ctx.dimensions() == 3) { - val vec = toss.pop3().add(ip.storageOffset); - toss.push(ctx.fungeSpace().get(vec)); + val vec = stack.pop3().add(ip.storageOffset()); + stack.push(ctx.fungeSpace().get(vec)); } else { - val vec = toss.pop2().add(ip.storageOffset.x, ip.storageOffset.y); - toss.push(ctx.fungeSpace().get(vec.x, vec.y, ip.storageOffset.z)); + val vec = stack.pop2().add(ip.storageOffset2()); + stack.push(ctx.fungeSpace().get(vec.x, vec.y, ip.storageOffset().z)); } } @@ -380,12 +381,12 @@ public static void duplicate(ExecutionContext ctx) { @Instr('{') public static void blockStart(ExecutionContext ctx) { - if (!ctx.IP().stackStack.pushStackStack()) { + if (!ctx.IP().stackStack().push()) { ctx.interpret('r'); return; } - val SOSS = ctx.IP().stackStack.SOSS().get(); - val TOSS = ctx.IP().stackStack.TOSS(); + val SOSS = ctx.IP().stackStack().SOSS().get(); + val TOSS = ctx.stack(); int n = SOSS.pop(); if (n > 0) { val tmp = new Stack(); @@ -402,21 +403,21 @@ public static void blockStart(ExecutionContext ctx) { } } if (ctx.dimensions() == 3) { - SOSS.push3(ctx.IP().storageOffset); + SOSS.push3(ctx.IP().storageOffset()); } else { - SOSS.push2(new Vector2i(ctx.IP().storageOffset.x, ctx.IP().storageOffset.y)); + SOSS.push2(new Vector2i(ctx.IP().storageOffset().x, ctx.IP().storageOffset().y)); } - val snapshot = new Vector3i(ctx.IP().position); + val snapshot = new Vector3i(ctx.IP().position()); ctx.step(ctx.IP()); - ctx.IP().storageOffset.set(ctx.IP().position); - ctx.IP().position.set(snapshot); + ctx.IP().storageOffset().set(ctx.IP().position()); + ctx.IP().position().set(snapshot); } @Instr('}') public static void blockEnd(ExecutionContext ctx) { - val TOSS = ctx.IP().stackStack.TOSS(); - val SOSSt = ctx.IP().stackStack.SOSS(); - if (!SOSSt.isPresent() || !ctx.IP().stackStack.popStackStack()) { + val TOSS = ctx.stack(); + val SOSSt = ctx.IP().stackStack().SOSS(); + if (!SOSSt.isPresent() || !ctx.IP().stackStack().pop()) { ctx.interpret('r'); return; } @@ -424,13 +425,13 @@ public static void blockEnd(ExecutionContext ctx) { int n = TOSS.pop(); if (ctx.dimensions() == 3) { - SOSS.pop3(ctx.IP().storageOffset); + SOSS.pop3(ctx.IP().storageOffset()); } else { val tmp2 = SOSS.pop2(); - ctx.IP().storageOffset.x = tmp2.x; - ctx.IP().storageOffset.y = tmp2.y; + ctx.IP().storageOffset().x = tmp2.x; + ctx.IP().storageOffset().y = tmp2.y; } - val tmp = new Stack(); + val tmp = (IStack) new Stack(); if (n > 0) { for (int i = 0; i < n; i++) { tmp.push(TOSS.pop()); @@ -448,8 +449,8 @@ public static void blockEnd(ExecutionContext ctx) { @Instr('u') public static void stackUnderStack(ExecutionContext ctx) { - val TOSS = ctx.IP().stackStack.TOSS(); - val SOSSt = ctx.IP().stackStack.SOSS(); + val TOSS = ctx.stack(); + val SOSSt = ctx.IP().stackStack().SOSS(); if (!SOSSt.isPresent()) { ctx.interpret('r'); return; @@ -469,10 +470,10 @@ public static void stackUnderStack(ExecutionContext ctx) { @Instr('y') public static void sysInfo(ExecutionContext ctx) { - val s = ctx.IP().stackStack.TOSS(); - val tossSize = ctx.IP().stackStack.TOSS().size(); + val s = ctx.stack(); + val tossSize = ctx.stack().size(); val n = s.pop(); - val sizes = ctx.IP().stackStack.sizes(); + val sizes = ctx.IP().stackStack().stackSizes(); //20 envs for (Map.Entry entry : ctx.env().entrySet()) { String key = entry.getKey(); @@ -486,11 +487,11 @@ public static void sysInfo(ExecutionContext ctx) { s.push(0); s.push(0); //18 sizes of stack stack - for (int i = 0; i < sizes.length; i++) { - s.push(sizes[i]); + for (int size : sizes) { + s.push(size); } //17 size of stack stack - s.push(ctx.IP().stackStack.size()); + s.push(ctx.IP().stackStack().size()); //16 time val time = LocalDateTime.now(); s.push(time.getHour() * 256 * 256 + time.getMinute() * 256 + time.getSecond()); @@ -503,27 +504,27 @@ public static void sysInfo(ExecutionContext ctx) { //13 least point s.push3(new Vector3i(bounds.xMin(), bounds.yMin(), bounds.zMin())); //12 storage offset - s.push3(ctx.IP().storageOffset); + s.push3(ctx.IP().storageOffset()); //11 delta - s.push3(ctx.IP().delta); + s.push3(ctx.IP().delta()); //10 position - s.push3(ctx.IP().position); + s.push3(ctx.IP().position()); } else { //14 greatest point s.push2(new Vector2i(bounds.xMax() - bounds.xMin(), bounds.yMax() - bounds.yMin())); //13 least point s.push2(new Vector2i(bounds.xMin(), bounds.yMin())); //12 storage offset - s.push2(new Vector2i(ctx.IP().storageOffset.x, ctx.IP().storageOffset.y)); + s.push2(ctx.IP().storageOffset2()); //11 delta - s.push2(new Vector2i(ctx.IP().delta.x, ctx.IP().delta.y)); + s.push2(ctx.IP().delta2()); //10 position - s.push2(new Vector2i(ctx.IP().position.x, ctx.IP().position.y)); + s.push2(ctx.IP().position2()); } //9 teamnumber s.push(0); //8 uuid - s.push(ctx.IP().UUID); + s.push(ctx.IP().UUID()); //7 dimensions s.push(ctx.dimensions()); //6 separator @@ -549,7 +550,7 @@ public static void sysInfo(ExecutionContext ctx) { @Instr('q') public static void quit(ExecutionContext ctx) { - int q = ctx.IP().stackStack.TOSS().pop(); + int q = ctx.stack().pop(); ctx.stop(q); } @@ -561,7 +562,7 @@ public static void split(ExecutionContext ctx) { return; } val clone = ctx.cloneIP(); - clone.delta.mul(-1); + clone.delta().mul(-1); } @Instr('i') @@ -570,7 +571,7 @@ public static void input(ExecutionContext ctx) { reflect(ctx); return; } - val s = ctx.IP().stackStack.TOSS(); + val s = ctx.stack(); val filename = s.popString(); val flags = s.pop(); val pos = new Vector3i(); @@ -579,7 +580,7 @@ public static void input(ExecutionContext ctx) { } else { pos.set(s.pop2(), 0); } - pos.add(ctx.IP().storageOffset); + pos.add(ctx.IP().storageOffset()); try { if (!ctx.fileInputAllowed(filename)) { System.err.println("Code tried to read file not on whitelist: " + filename); @@ -596,7 +597,7 @@ public static void input(ExecutionContext 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); - pos.sub(ctx.IP().storageOffset); + pos.sub(ctx.IP().storageOffset()); if (ctx.dimensions() == 3) { s.push3(delta); s.push3(pos); @@ -612,7 +613,7 @@ public static void output(ExecutionContext ctx) { reflect(ctx); return; } - val s = ctx.IP().stackStack.TOSS(); + val s = ctx.stack(); val filename = s.popString(); val flags = s.pop(); val pos = new Vector3i(); @@ -624,7 +625,7 @@ public static void output(ExecutionContext ctx) { pos.set(s.pop2(), 0); delta.set(s.pop2(), 1); } - pos.add(ctx.IP().storageOffset); + pos.add(ctx.IP().storageOffset()); try { if (!ctx.fileOutputAllowed(filename)) { System.err.println("Code tried to write file not on whitelist: " + filename); @@ -666,7 +667,7 @@ public static void readInt(ExecutionContext ctx) { } } if (found) { - ctx.IP().stackStack.TOSS().push(counter); + ctx.stack().push(counter); } else { ctx.interpret('r'); } @@ -676,7 +677,7 @@ public static void readInt(ExecutionContext ctx) { @SneakyThrows public static void readChar(ExecutionContext ctx) { ctx.output().flush(); - ctx.IP().stackStack.TOSS().push(ctx.input(false)); + ctx.stack().push(ctx.input(false)); } @Instr('=') @@ -686,21 +687,21 @@ public static void sysCall(ExecutionContext ctx) { reflect(ctx); return; } - val command = ctx.IP().stackStack.TOSS().popString(); + val command = ctx.stack().popString(); try { val proc = Runtime.getRuntime().exec(command); - ctx.IP().stackStack.TOSS().push(proc.waitFor()); + ctx.stack().push(proc.waitFor()); } catch (Exception e) { - ctx.IP().stackStack.TOSS().push(-1); + ctx.stack().push(-1); } } - public static void stack(ExecutionContext ctx, Consumer runner) { - runner.accept(ctx.IP().stackStack.TOSS()); + public static void stack(ExecutionContext ctx, Consumer runner) { + runner.accept(ctx.stack()); } public static void binop(ExecutionContext ctx, BinaryOperator op) { - val TOSS = ctx.IP().stackStack.TOSS(); + val TOSS = ctx.stack(); int b = TOSS.pop(); int a = TOSS.pop(); TOSS.push(op.op(a, b)); @@ -711,11 +712,11 @@ public void load(ObjIntConsumer instructionSet) { InstructionSet.super.load(instructionSet); for (int i = 0; i <= 9; i++) { int finalI = i; - instructionSet.accept((ctx) -> ctx.IP().stackStack.TOSS().push(finalI), '0' + i); + instructionSet.accept((ctx) -> ctx.stack().push(finalI), '0' + i); } for (int i = 0; i <= 5; i++) { int finalI = i; - instructionSet.accept((ctx) -> ctx.IP().stackStack.TOSS().push(10 + finalI), 'a' + i); + instructionSet.accept((ctx) -> ctx.stack().push(10 + finalI), 'a' + i); } } 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 312f5da..2fc9cd0 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 @@ -25,24 +25,24 @@ private static TIntLongMap getMarkMap(ExecutionContext ctx) { @Instr('G') public static void granularity(ExecutionContext ctx) { - ctx.IP().stackStack.TOSS().push(1); + ctx.stack().push(1); } @Instr('M') public static void mark(ExecutionContext ctx) { val marks = getMarkMap(ctx); - marks.put(ctx.IP().UUID, System.nanoTime()); + marks.put(ctx.IP().UUID(), System.nanoTime()); } @Instr('T') public static void timer(ExecutionContext ctx) { val marks = getMarkMap(ctx); val ip = ctx.IP(); - if (!marks.containsKey(ip.UUID)) { + if (!marks.containsKey(ip.UUID())) { ctx.interpret('r'); return; } - ip.stackStack.TOSS().push((int)((System.nanoTime() - marks.get(ip.UUID)) / 1000L)); + ctx.stack().push((int)((System.nanoTime() - marks.get(ip.UUID())) / 1000L)); } /* @@ -59,11 +59,11 @@ public static void erase(ExecutionContext ctx) { // ctx.interpret('r'); // return; // } - marks.remove(ip.UUID); + marks.remove(ip.UUID()); } @Instr('S') public static void second(ExecutionContext ctx) { - ctx.IP().stackStack.TOSS().push((int)((System.nanoTime() % 1000000000L) / 1000L)); + ctx.stack().push((int)((System.nanoTime() % 1000000000L) / 1000L)); } } 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 6a1917b..e736826 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 @@ -28,25 +28,25 @@ public static void switchMode(ExecutionContext ctx) { @Instr('I') public static void invertMode(ExecutionContext ctx) { - ctx.IP().stackStack.invertMode(!ctx.IP().stackStack.invertMode()); + ctx.IP().stackStack().invertMode(!ctx.IP().stackStack().invertMode()); } @Instr('Q') public static void queueMode(ExecutionContext ctx) { - ctx.IP().stackStack.queueMode(!ctx.IP().stackStack.queueMode()); + ctx.IP().stackStack().queueMode(!ctx.IP().stackStack().queueMode()); } private static void toggleMode(ExecutionContext ctx, InstructionSet set, int bit) { val ip = ctx.IP(); - val state = ip.customStorage.get("mode"); + val state = ip.customStorage().get("mode"); var mode = (state & bit) == bit; mode = !mode; if (mode) { - ip.instructionManager.loadInstructionSet(set); + ip.instructionManager().loadInstructionSet(set); } else { - ip.instructionManager.unloadInstructionSet(set); + ip.instructionManager().unloadInstructionSet(set); } - ip.customStorage.put("mode", state ^ bit); + ip.customStorage().put("mode", state ^ bit); } @Override @@ -58,28 +58,28 @@ public int code() { public static final class HoverMode implements InstructionSet { @Instr('>') public static void east(ExecutionContext ctx) { - ctx.IP().delta.add(1, 0, 0); + ctx.IP().delta().add(1, 0, 0); } @Instr('v') public static void south(ExecutionContext ctx) { - ctx.IP().delta.add(0, 1, 0); + ctx.IP().delta().add(0, 1, 0); } @Instr('<') public static void west(ExecutionContext ctx) { - ctx.IP().delta.add(-1, 0, 0); + ctx.IP().delta().add(-1, 0, 0); } @Instr('^') public static void north(ExecutionContext ctx) { - ctx.IP().delta.add(0, -1, 0); + ctx.IP().delta().add(0, -1, 0); } @Instr('h') public static void high(ExecutionContext ctx) { if (ctx.dimensions() == 3) { - ctx.IP().delta.add(0, 0, 1); + ctx.IP().delta().add(0, 0, 1); } else { ctx.interpret('r'); } @@ -88,7 +88,7 @@ public static void high(ExecutionContext ctx) { @Instr('l') public static void low(ExecutionContext ctx) { if (ctx.dimensions() == 3) { - ctx.IP().delta.add(0, 0, -1); + ctx.IP().delta().add(0, 0, -1); } else { ctx.interpret('r'); } @@ -98,7 +98,7 @@ public static void low(ExecutionContext ctx) { @NoArgsConstructor(access = AccessLevel.PRIVATE) public static final class SwitchMode implements InstructionSet { private static void set(ExecutionContext ctx, int ch) { - ctx.fungeSpace().set(ctx.IP().position, ch); + ctx.fungeSpace().set(ctx.IP().position(), ch); } @Instr('(') diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java index c1725c9..742c123 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java @@ -32,69 +32,69 @@ public static void xor(ExecutionContext ctx) { @Instr('X') public static void setX(ExecutionContext ctx) { - ctx.IP().position.x = ctx.IP().stackStack.TOSS().pop(); + ctx.IP().position().x = ctx.stack().pop(); } @Instr('Y') public static void setY(ExecutionContext ctx) { - ctx.IP().position.y = ctx.IP().stackStack.TOSS().pop(); + ctx.IP().position().y = ctx.stack().pop(); } @Instr('V') public static void setDX(ExecutionContext ctx) { - ctx.IP().delta.x = ctx.IP().stackStack.TOSS().pop(); + ctx.IP().delta().x = ctx.stack().pop(); } @Instr('W') public static void setDY(ExecutionContext ctx) { - ctx.IP().delta.y = ctx.IP().stackStack.TOSS().pop(); + ctx.IP().delta().y = ctx.stack().pop(); } @Instr('G') public static void get(ExecutionContext ctx) { val ip = ctx.IP(); - val toss = ip.stackStack.TOSS(); + val stack = ctx.stack(); if (ctx.dimensions() == 3) { val vec = new Vector3i(); - vec.x = toss.pop(); - vec.y = toss.pop(); - vec.z = toss.pop(); - vec.add(ip.storageOffset); - toss.push(ctx.fungeSpace().get(vec)); + vec.x = stack.pop(); + vec.y = stack.pop(); + vec.z = stack.pop(); + vec.add(ip.storageOffset()); + stack.push(ctx.fungeSpace().get(vec)); } else { val vec = new Vector2i(); - vec.x = toss.pop(); - vec.y = toss.pop(); - vec.add(ip.storageOffset.x, ip.storageOffset.y); - toss.push(ctx.fungeSpace().get(vec.x, vec.y, ip.storageOffset.z)); + vec.x = stack.pop(); + vec.y = stack.pop(); + vec.add(ip.storageOffset().x, ip.storageOffset().y); + stack.push(ctx.fungeSpace().get(vec.x, vec.y, ip.storageOffset().z)); } } @Instr('P') public static void put(ExecutionContext ctx) { val ip = ctx.IP(); - val toss = ip.stackStack.TOSS(); + val stack = ctx.stack(); if (ctx.dimensions() == 3) { val vec = new Vector3i(); - vec.x = toss.pop(); - vec.y = toss.pop(); - vec.z = toss.pop(); - vec.add(ip.storageOffset); - val i = toss.pop(); + vec.x = stack.pop(); + vec.y = stack.pop(); + vec.z = stack.pop(); + vec.add(ip.storageOffset()); + val i = stack.pop(); ctx.fungeSpace().set(vec, i); } else { val vec = new Vector2i(); - vec.x = toss.pop(); - vec.y = toss.pop(); - vec.add(ip.storageOffset.x, ip.storageOffset.y); - val i = toss.pop(); - ctx.fungeSpace().set(vec.x, vec.y, ip.storageOffset.z, i); + vec.x = stack.pop(); + vec.y = stack.pop(); + vec.add(ip.storageOffset().x, ip.storageOffset().y); + val i = stack.pop(); + ctx.fungeSpace().set(vec.x, vec.y, ip.storageOffset().z, i); } } @Instr('Z') public static void rampIfZero(ExecutionContext ctx) { - if (ctx.IP().stackStack.TOSS().pop() == 0) { + if (ctx.stack().pop() == 0) { Funge98.trampoline(ctx); } } @@ -102,7 +102,7 @@ public static void rampIfZero(ExecutionContext ctx) { @SneakyThrows @Instr('S') public static void outputString(ExecutionContext ctx) { - val str = ctx.IP().stackStack.TOSS().popString(); + val str = ctx.stack().popString(); ctx.output().write(str.getBytes(StandardCharsets.UTF_8)); } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/PERL.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/PERL.java index e1ae37c..8cd1a44 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/PERL.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/PERL.java @@ -26,7 +26,7 @@ private static String executePerl(ExecutionContext ctx) { try { try (val os = Files.newOutputStream(Paths.get(name), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) { os.write("print eval(".getBytes(StandardCharsets.UTF_8)); - os.write(ctx.IP().stackStack.TOSS().popString().getBytes(StandardCharsets.UTF_8)); + os.write(ctx.stack().popString().getBytes(StandardCharsets.UTF_8)); os.write(");".getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { e.printStackTrace(); @@ -73,13 +73,13 @@ private static String executePerl(ExecutionContext ctx) { public static void exec(ExecutionContext ctx) { String res = executePerl(ctx); if (res != null) { - ctx.IP().stackStack.TOSS().pushString(res); + ctx.stack().pushString(res); } } @Instr('S') public static void getShelled(ExecutionContext ctx) { - ctx.IP().stackStack.TOSS().push(1); + ctx.stack().push(1); } @Instr('I') @@ -93,7 +93,7 @@ public static void execInt(ExecutionContext ctx) { Funge98.reflect(ctx); return; } - ctx.IP().stackStack.TOSS().push(intRes); + ctx.stack().push(intRes); } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java index ca0a701..de780dc 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java @@ -26,26 +26,26 @@ private static Vectors getGlobal(ExecutionContext ctx) { public static void reference(ExecutionContext ctx) { val vecs = getGlobal(ctx); val vec = new Vector3i(); - val toss = ctx.IP().stackStack.TOSS(); + val stack = ctx.stack(); if (ctx.dimensions() == 3) { - toss.pop3(vec); + stack.pop3(vec); } else { - val vec2 = toss.pop2(); + val vec2 = stack.pop2(); vec.x = vec2.x; vec.y = vec2.y; } - toss.push(vecs.reference(vec)); + stack.push(vecs.reference(vec)); } @Instr('D') public static void dereference(ExecutionContext ctx) { val vecs = getGlobal(ctx); - val toss = ctx.IP().stackStack.TOSS(); - Vector3i vec = vecs.dereference(toss.pop()); + val stack = ctx.stack(); + Vector3i vec = vecs.dereference(stack.pop()); if (ctx.dimensions() == 3) { - toss.push3(vec); + stack.push3(vec); } else { - toss.push2(new Vector2i(vec.x, vec.y)); + stack.push2(new Vector2i(vec.x, vec.y)); } } diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ROMA.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ROMA.java index d5990e3..cf43ffc 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ROMA.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ROMA.java @@ -10,37 +10,37 @@ public class ROMA implements Fingerprint { @Instr('I') public static void I(ExecutionContext ctx) { - ctx.IP().stackStack.TOSS().push(1); + ctx.stack().push(1); } @Instr('V') public static void V(ExecutionContext ctx) { - ctx.IP().stackStack.TOSS().push(5); + ctx.stack().push(5); } @Instr('X') public static void X(ExecutionContext ctx) { - ctx.IP().stackStack.TOSS().push(10); + ctx.stack().push(10); } @Instr('L') public static void L(ExecutionContext ctx) { - ctx.IP().stackStack.TOSS().push(50); + ctx.stack().push(50); } @Instr('C') public static void C(ExecutionContext ctx) { - ctx.IP().stackStack.TOSS().push(100); + ctx.stack().push(100); } @Instr('D') public static void D(ExecutionContext ctx) { - ctx.IP().stackStack.TOSS().push(500); + ctx.stack().push(500); } @Instr('M') public static void M(ExecutionContext ctx) { - ctx.IP().stackStack.TOSS().push(1000); + ctx.stack().push(1000); } @Override diff --git a/src/main/java/com/falsepattern/jfunge/ip/IP.java b/src/main/java/com/falsepattern/jfunge/ip/IP.java new file mode 100644 index 0000000..b9c830c --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/ip/IP.java @@ -0,0 +1,67 @@ +package com.falsepattern.jfunge.ip; + +import com.falsepattern.jfunge.Copiable; +import com.falsepattern.jfunge.interpreter.instructions.InstructionManager; +import gnu.trove.map.TObjectIntMap; +import org.joml.Vector2i; +import org.joml.Vector2ic; +import org.joml.Vector3i; + +public interface IP extends Copiable { + Vector3i position(); + Vector3i delta(); + Vector3i storageOffset(); + IStackStack stackStack(); + InstructionManager instructionManager(); + TObjectIntMap customStorage(); + + void stringMode(boolean state); + boolean stringMode(); + + int UUID(); + void UUID(int newUUID); + + int nextRandom(); + + boolean dead(); + void die(); + + default void step() { + position().add(delta()); + } + + default void reflect() { + delta().mul(-1); + } + + //QoL code + default Vector2ic position2(Vector2i buf) { + buf.x = position().x; + buf.y = position().y; + return buf; + } + + default Vector2ic position2() { + return position2(new Vector2i()); + } + + default Vector2ic delta2(Vector2i buf) { + buf.x = delta().x; + buf.y = delta().y; + return buf; + } + + default Vector2ic delta2() { + return delta2(new Vector2i()); + } + + default Vector2ic storageOffset2(Vector2i buf) { + buf.x = storageOffset().x; + buf.y = storageOffset().y; + return buf; + } + + default Vector2ic storageOffset2() { + return storageOffset2(new Vector2i()); + } +} diff --git a/src/main/java/com/falsepattern/jfunge/ip/IStack.java b/src/main/java/com/falsepattern/jfunge/ip/IStack.java new file mode 100644 index 0000000..0548448 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/ip/IStack.java @@ -0,0 +1,96 @@ +package com.falsepattern.jfunge.ip; + +import com.falsepattern.jfunge.Copiable; +import lombok.SneakyThrows; +import lombok.val; +import org.joml.Vector2i; +import org.joml.Vector2ic; +import org.joml.Vector3i; +import org.joml.Vector3ic; +import org.joml.Vector4i; +import org.joml.Vector4ic; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; + +public interface IStack extends Copiable { + void push(int x); + int peek(); + int pop(); + void clear(); + int pick(int index); + int size(); + void invertMode(boolean state); + boolean invertMode(); + void queueMode(boolean state); + boolean queueMode(); + + default void push2(Vector2ic v) { + push(v.x()); + push(v.y()); + } + + default Vector2i pop2(Vector2i v) { + v.y = pop(); + v.x = pop(); + return v; + } + + default Vector2i pop2() { + return pop2(new Vector2i()); + } + + default void push3(Vector3ic v) { + push(v.x()); + push(v.y()); + push(v.z()); + } + + default Vector3i pop3(Vector3i v) { + v.z = pop(); + v.y = pop(); + v.x = pop(); + return v; + } + + default Vector3i pop3() { + return pop3(new Vector3i()); + } + + default void push4(Vector4ic v) { + push(v.x()); + push(v.y()); + push(v.z()); + push(v.w()); + } + + default Vector4i pop4(Vector4i v) { + v.w = pop(); + v.z = pop(); + v.y = pop(); + v.x = pop(); + return v; + } + + default Vector4i pop4() { + return pop4(new Vector4i()); + } + + default void pushString(String text) { + val chars = text.getBytes(StandardCharsets.UTF_8); + push(0); + for (int i = chars.length - 1; i >= 0; i--) { + push(chars[i]); + } + } + + @SneakyThrows + default String popString() { + val data = new ByteArrayOutputStream(); + byte b; + while ((b = (byte) pop()) != 0) { + data.write(b); + } + return data.toString("UTF-8"); + } +} diff --git a/src/main/java/com/falsepattern/jfunge/ip/IStackStack.java b/src/main/java/com/falsepattern/jfunge/ip/IStackStack.java new file mode 100644 index 0000000..000993f --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/ip/IStackStack.java @@ -0,0 +1,18 @@ +package com.falsepattern.jfunge.ip; + +import com.falsepattern.jfunge.Copiable; + +import java.util.Optional; + +public interface IStackStack extends Copiable { + IStack TOSS(); + Optional SOSS(); + boolean push(); + boolean pop(); + int size(); + int[] stackSizes(); + boolean invertMode(); + void invertMode(boolean state); + boolean queueMode(); + void queueMode(boolean state); +} diff --git a/src/main/java/com/falsepattern/jfunge/ip/Stack.java b/src/main/java/com/falsepattern/jfunge/ip/Stack.java deleted file mode 100644 index 3f01aa9..0000000 --- a/src/main/java/com/falsepattern/jfunge/ip/Stack.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.falsepattern.jfunge.ip; - -import com.falsepattern.jfunge.Copiable; -import gnu.trove.list.TIntList; -import gnu.trove.list.array.TIntArrayList; -import lombok.val; -import org.joml.Vector2i; -import org.joml.Vector2ic; -import org.joml.Vector3i; -import org.joml.Vector3ic; -import org.joml.Vector4i; -import org.joml.Vector4ic; - -public class Stack implements Copiable { - private final TIntList storage; - public boolean invertMode; - public boolean queueMode; - - public Stack() { - storage = new TIntArrayList(); - } - - private Stack(Stack original) { - storage = new TIntArrayList(original.storage); - invertMode = original.invertMode; - queueMode = original.queueMode; - } - - public void push(int x) { - if (invertMode) { - storage.insert(0, x); - } else { - storage.add(x); - } - } - - public int peek() { - int s = storage.size(); - return s == 0 ? 0 : storage.get(queueMode ? 0 : s - 1); - } - - public int pop() { - int s = storage.size(); - return s == 0 ? 0 : storage.removeAt(queueMode ? 0 : s - 1); - } - - public void push2(Vector2ic v) { - storage.add(v.x()); - storage.add(v.y()); - } - - public Vector2i pop2(Vector2i v) { - v.y = pop(); - v.x = pop(); - return v; - } - - public Vector2i pop2() { - return pop2(new Vector2i()); - } - - public void push3(Vector3ic v) { - storage.add(v.x()); - storage.add(v.y()); - storage.add(v.z()); - } - - public Vector3i pop3(Vector3i v) { - v.z = pop(); - v.y = pop(); - v.x = pop(); - return v; - } - - public Vector3i pop3() { - return pop3(new Vector3i()); - } - - public void push4(Vector4ic v) { - storage.add(v.x()); - storage.add(v.y()); - storage.add(v.z()); - storage.add(v.w()); - } - - public Vector4i pop4(Vector4i v) { - v.w = pop(); - v.z = pop(); - v.y = pop(); - v.x = pop(); - return v; - } - - public Vector4i pop4() { - return pop4(new Vector4i()); - } - - public void clear() { - storage.clear(); - } - - public int pick(int index) { - return storage.get(storage.size() - 1 - index); - } - - public void pushString(String text) { - val chars = text.toCharArray(); - push(0); - for (int i = chars.length - 1; i >= 0; i--) { - push(chars[i]); - } - } - - public String popString() { - val sb = new StringBuilder(); - char c; - while ((c = (char) pop()) != 0) { - sb.append(c); - } - return sb.toString(); - } - - public int size() { - return storage.size(); - } - - @Override - public Stack deepCopy() { - return new Stack(this); - } -} diff --git a/src/main/java/com/falsepattern/jfunge/ip/InstructionPointer.java b/src/main/java/com/falsepattern/jfunge/ip/impl/InstructionPointer.java similarity index 68% rename from src/main/java/com/falsepattern/jfunge/ip/InstructionPointer.java rename to src/main/java/com/falsepattern/jfunge/ip/impl/InstructionPointer.java index 9144dcf..025981a 100644 --- a/src/main/java/com/falsepattern/jfunge/ip/InstructionPointer.java +++ b/src/main/java/com/falsepattern/jfunge/ip/impl/InstructionPointer.java @@ -1,28 +1,43 @@ -package com.falsepattern.jfunge.ip; +package com.falsepattern.jfunge.ip.impl; -import com.falsepattern.jfunge.Copiable; import com.falsepattern.jfunge.interpreter.instructions.InstructionManager; +import com.falsepattern.jfunge.ip.IP; import gnu.trove.map.TObjectIntMap; import gnu.trove.map.hash.TObjectIntHashMap; import lombok.Getter; +import lombok.Setter; import lombok.SneakyThrows; +import lombok.experimental.Accessors; import org.joml.Vector3i; import java.security.SecureRandom; -public class InstructionPointer implements Copiable { - public final Vector3i position; - public final Vector3i delta; - public final Vector3i storageOffset; - public final StackStack stackStack; - public final InstructionManager instructionManager; - public final TObjectIntMap customStorage; - private final SecureRandom rng; - public boolean stringMode = false; - public int UUID; +@Accessors(fluent = true, + chain = false) +public class InstructionPointer implements IP { + @Getter + private final Vector3i position; + @Getter + private final Vector3i delta; + @Getter + private final Vector3i storageOffset; + @Getter + private final StackStack stackStack; + @Getter + private final InstructionManager instructionManager; + @Getter + private final TObjectIntMap customStorage; + @Setter + @Getter + private boolean stringMode = false; + @Setter + @Getter + private int UUID; @Getter private boolean dead = false; + private final SecureRandom rng; + @SneakyThrows public InstructionPointer() { position = new Vector3i(); @@ -49,10 +64,12 @@ private InstructionPointer(InstructionPointer original) { UUID = 0; } + @Override public void die() { dead = true; } + @Override public int nextRandom() { return rng.nextInt(); } diff --git a/src/main/java/com/falsepattern/jfunge/ip/impl/Stack.java b/src/main/java/com/falsepattern/jfunge/ip/impl/Stack.java new file mode 100644 index 0000000..801a7c9 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/ip/impl/Stack.java @@ -0,0 +1,65 @@ +package com.falsepattern.jfunge.ip.impl; + +import com.falsepattern.jfunge.ip.IStack; +import gnu.trove.list.TIntList; +import gnu.trove.list.array.TIntArrayList; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +@Accessors(fluent = true, + chain = false) +public class Stack implements IStack { + private final TIntList storage; + @Setter + @Getter + private boolean invertMode; + @Setter + @Getter + private boolean queueMode; + + public Stack() { + storage = new TIntArrayList(); + } + + private Stack(Stack original) { + storage = new TIntArrayList(original.storage); + invertMode = original.invertMode; + queueMode = original.queueMode; + } + + public void push(int x) { + if (invertMode) { + storage.insert(0, x); + } else { + storage.add(x); + } + } + + public int peek() { + int s = storage.size(); + return s == 0 ? 0 : storage.get(queueMode ? 0 : s - 1); + } + + public int pop() { + int s = storage.size(); + return s == 0 ? 0 : storage.removeAt(queueMode ? 0 : s - 1); + } + + public void clear() { + storage.clear(); + } + + public int pick(int index) { + return storage.get(storage.size() - 1 - index); + } + + public int size() { + return storage.size(); + } + + @Override + public Stack deepCopy() { + return new Stack(this); + } +} diff --git a/src/main/java/com/falsepattern/jfunge/ip/StackStack.java b/src/main/java/com/falsepattern/jfunge/ip/impl/StackStack.java similarity index 64% rename from src/main/java/com/falsepattern/jfunge/ip/StackStack.java rename to src/main/java/com/falsepattern/jfunge/ip/impl/StackStack.java index fb45b6f..f158498 100644 --- a/src/main/java/com/falsepattern/jfunge/ip/StackStack.java +++ b/src/main/java/com/falsepattern/jfunge/ip/impl/StackStack.java @@ -1,6 +1,7 @@ -package com.falsepattern.jfunge.ip; +package com.falsepattern.jfunge.ip.impl; -import com.falsepattern.jfunge.Copiable; +import com.falsepattern.jfunge.ip.IStack; +import com.falsepattern.jfunge.ip.IStackStack; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -14,13 +15,14 @@ import java.util.Optional; @NoArgsConstructor -@Accessors(fluent = true) -public class StackStack implements Copiable { - private final Deque stackStack = new ArrayDeque<>(); +@Accessors(fluent = true, + chain = false) +public class StackStack implements IStackStack { + private final Deque stackStack = new ArrayDeque<>(); @Getter @Setter(AccessLevel.PRIVATE) @NonNull - private Stack TOSS = new Stack(); + private IStack TOSS = new Stack(); @Getter private boolean invertMode; @@ -29,42 +31,47 @@ public class StackStack implements Copiable { private StackStack(StackStack original) { TOSS(original.TOSS().deepCopy()); - original.stackStack.forEach((stack) -> { - stackStack.add(stack.deepCopy()); - }); + original.stackStack.forEach((stack) -> stackStack.add(stack.deepCopy())); invertMode = original.invertMode; queueMode = original.queueMode; } - public Optional SOSS() { + public Optional SOSS() { return Optional.ofNullable(stackStack.peek()); } - public boolean pushStackStack() { + public boolean push() { try { stackStack.push(TOSS); TOSS(new Stack()); - TOSS().invertMode = invertMode; - TOSS().queueMode = queueMode; + TOSS().invertMode(invertMode); + TOSS().queueMode(queueMode); return true; } catch (Exception e) { return false; } } + public boolean pop() { + if (stackStack.size() == 0) + return false; + TOSS(stackStack.pop()); + return true; + } + public void invertMode(boolean state) { invertMode = state; - TOSS().invertMode = state; - for (Stack stack : stackStack) { - stack.invertMode = state; + TOSS().invertMode(state); + for (val stack : stackStack) { + stack.invertMode(state); } } public void queueMode(boolean state) { queueMode = state; - TOSS().queueMode = state; - for (Stack stack : stackStack) { - stack.queueMode = state; + TOSS().queueMode(state); + for (val stack : stackStack) { + stack.queueMode(state); } } @@ -72,7 +79,7 @@ public int size() { return stackStack.size() + 1; } - public int[] sizes() { + public int[] stackSizes() { val sizes = new int[stackStack.size() + 1]; sizes[0] = TOSS().size(); int i = 1; @@ -82,13 +89,6 @@ public int[] sizes() { return sizes; } - public boolean popStackStack() { - if (stackStack.size() == 0) - return false; - TOSS(stackStack.pop()); - return true; - } - @Override public StackStack deepCopy() { return new StackStack(this); From b0275242ac1001f01d821ec78887faac94b1a165 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Fri, 21 Oct 2022 19:28:47 +0200 Subject: [PATCH 13/35] QoL methods for dimension-aware spatial vector push/pop --- .../com/falsepattern/jfunge/ip/IStack.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/main/java/com/falsepattern/jfunge/ip/IStack.java b/src/main/java/com/falsepattern/jfunge/ip/IStack.java index 0548448..7a6ef6e 100644 --- a/src/main/java/com/falsepattern/jfunge/ip/IStack.java +++ b/src/main/java/com/falsepattern/jfunge/ip/IStack.java @@ -76,6 +76,43 @@ default Vector4i pop4() { return pop4(new Vector4i()); } + default void pushVecDimProof(int dimensions, Vector3i buf) { + switch (dimensions) { + default: + throw new IllegalStateException("pushVecDimProof only works with parameters 1-3"); + case 1: + push(buf.x); + break; + case 2: + push(buf.x); + push(buf.y); + break; + case 3: + push(buf.x); + push(buf.y); + push(buf.z); + break; + } + } + + default Vector3i popVecDimProof(int dimensions, Vector3i buf) { + switch (dimensions) { + default: + throw new IllegalStateException("popVecDimProof only works with parameters 1-3"); + case 3: + buf.z = pop(); + case 2: + buf.y = pop(); + case 1: + buf.x = pop(); + } + return buf; + } + + default Vector3i popVecDimProof(int dimensions) { + return popVecDimProof(dimensions, new Vector3i()); + } + default void pushString(String text) { val chars = text.getBytes(StandardCharsets.UTF_8); push(0); From e3b0dcb60391b20ae7ae66a45acc4756e817d012 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 20 Oct 2022 21:36:42 +0200 Subject: [PATCH 14/35] implement TOYS --- .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/TOYS.java | 351 ++++++++++++++++++ 2 files changed, 353 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/TOYS.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 6c82ab9..104bc8e 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -11,6 +11,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.TOYS; import com.falsepattern.jfunge.ip.IStack; import com.falsepattern.jfunge.ip.impl.Stack; import gnu.trove.map.TIntObjectMap; @@ -46,6 +47,7 @@ public class Funge98 implements InstructionSet { addFingerprint(PERL.INSTANCE); addFingerprint(REFC.INSTANCE); addFingerprint(ROMA.INSTANCE); + addFingerprint(TOYS.INSTANCE); } private static void addFingerprint(Fingerprint print) { 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 new file mode 100644 index 0000000..6805f60 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/TOYS.java @@ -0,0 +1,351 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.storage.FungeSpace; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.val; +import org.joml.Vector2i; +import org.joml.Vector3i; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TOYS implements Fingerprint { + public static final TOYS INSTANCE = new TOYS(); + + private interface TransferOperation { + void operate(FungeSpace space, Vector3i src, Vector3i dst, int x, int y, int z); + } + + private interface IterationOperation { + void operate(FungeSpace space, Vector3i dst, Vector3i delta, Vector3i src, TransferOperation op); + } + + private static void copy(FungeSpace space, Vector3i src, Vector3i dst, int x, int y, int z) { + space.set(dst.x + x, dst.y + y, dst.z + z, space.get(src.x + x, src.y + y, src.z + z)); + } + + private static void move(FungeSpace space, Vector3i src, Vector3i dst, int x, int y, int z) { + copy(space, src, dst, x, y, z); + space.set(src.x + x, src.y + y, src.z + z, ' '); + } + + private static void lowOrder(FungeSpace space, Vector3i dst, Vector3i delta, Vector3i src, TransferOperation op) { + for (int z = 0; z < delta.z; z++) + for (int y = 0; y < delta.y; y++) + for (int x = 0; x < delta.x; x++) + op.operate(space, src, dst, x, y, z); + } + + private static void highOrder(FungeSpace space, Vector3i dst, Vector3i delta, Vector3i src, TransferOperation op) { + for (int z = delta.z - 1; z >= 0; z--) + for (int y = delta.y - 1; y >= 0; y--) + for (int x = delta.x - 1; x >= 0; x--) + op.operate(space, src, dst, x, y, z); + } + + private static void executeOperation(ExecutionContext ctx, IterationOperation iter, TransferOperation op) { + val dst = new Vector3i(); + val delta = new Vector3i(); + val src = new Vector3i(); + val stack = ctx.stack(); + if (ctx.dimensions() == 3) { + stack.pop3(dst); + stack.pop3(delta); + stack.pop3(src); + } else { + val buf = new Vector2i(); + dst.set(stack.pop2(buf), 0); + delta.set(stack.pop2(buf), 1); + src.set(stack.pop2(buf), 0); + } + val space = ctx.fungeSpace(); + iter.operate(space, dst, delta, src, op); + } + + @Instr('C') + public static void lowOrderCopy(ExecutionContext ctx) { + executeOperation(ctx, TOYS::lowOrder, TOYS::copy); + } + + @Instr('K') + public static void highOrderCopy(ExecutionContext ctx) { + executeOperation(ctx, TOYS::highOrder, TOYS::copy); + } + + @Instr('M') + public static void lowOrderMove(ExecutionContext ctx) { + executeOperation(ctx, TOYS::lowOrder, TOYS::move); + } + + @Instr('V') + public static void highOrderMove(ExecutionContext ctx) { + executeOperation(ctx, TOYS::highOrder, TOYS::move); + } + + @Instr('S') + public static void fill(ExecutionContext ctx) { + val dst = new Vector3i(); + val delta = new Vector3i(); + val stack = ctx.stack(); + if (ctx.dimensions() == 3) { + stack.pop3(dst); + stack.pop3(delta); + } else { + val buf = new Vector2i(); + dst.set(stack.pop2(buf), 0); + delta.set(stack.pop2(buf), 1); + } + val cellValue = stack.pop(); + val space = ctx.fungeSpace(); + lowOrder(space, dst, delta, null, (space1, src, dst1, x, y, z) -> space.set(dst1.x + x, dst1.y + y, dst1.z + z, cellValue)); + } + + @Instr('J') + public static void shiftY(ExecutionContext ctx) { + int shift = ctx.stack().pop(); + if (shift != 0) { + val pos = ctx.IP().position(); + val x = pos.x(); + val z = pos.z(); + val space = ctx.fungeSpace(); + val bounds = space.bounds(); + if (shift < 0) { + for (int y = bounds.yMin(); y <= bounds.yMax() - shift; y++) { + space.set(x, y + shift, z, space.get(x, y, z)); + } + } else { + for (int y = bounds.yMax(); y >= bounds.yMin() - shift; y--) { + space.set(x, y + shift, z, space.get(x, y, z)); + } + } + } + } + + @Instr('O') + public static void shiftX(ExecutionContext ctx) { + int shift = ctx.stack().pop(); + if (shift != 0) { + val pos = ctx.IP().position(); + val y = pos.y(); + val z = pos.z(); + val space = ctx.fungeSpace(); + val bounds = space.bounds(); + if (shift < 0) { + for (int x = bounds.xMin(); x <= bounds.xMax() - shift; x++) { + space.set(x + shift, y, z, space.get(x, y, z)); + } + } else { + for (int x = bounds.xMax(); x >= bounds.xMin() - shift; x--) { + space.set(x + shift, y, z, space.get(x, y, z)); + } + } + } + } + + @Instr('L') + public static void getLeft(ExecutionContext ctx) { + val pos = new Vector3i(ctx.IP().position()); + ctx.interpret('['); + ctx.interpret('\''); + ctx.interpret(']'); + ctx.IP().position().set(pos); + } + + @Instr('R') + public static void getRight(ExecutionContext ctx) { + val pos = new Vector3i(ctx.IP().position()); + ctx.interpret(']'); + ctx.interpret('\''); + ctx.interpret('['); + ctx.IP().position().set(pos); + } + + @Instr('I') + public static void increment(ExecutionContext ctx) { + val stack = ctx.stack(); + stack.push(stack.pop() + 1); + } + + @Instr('D') + public static void decrement(ExecutionContext ctx) { + val stack = ctx.stack(); + stack.push(stack.pop() - 1); + } + + @Instr('N') + public static void negate(ExecutionContext ctx) { + val stack = ctx.stack(); + stack.push(~stack.pop()); + } + + @Instr('H') + public static void shift(ExecutionContext ctx) { + val stack = ctx.stack(); + val b = stack.pop(); + val a = stack.pop(); + if (b >= 0) { + stack.push(a << b); + } else { + //UNDEF signed right shift because unsigned shift fails mycology check for some reason + //TODO inspect the check inside mycology + stack.push(a >> (-b)); + } + } + + @Instr('A') + public static void replicate(ExecutionContext ctx) { + val stack = ctx.stack(); + val n = stack.pop(); + val x = stack.pop(); + for (int i = 0; i < n; i++) { + stack.push(x); + } + } + + //UNDEF I don't know what "butterfly operation" means, so I just checked how rcfunge does it :shrug: + @Instr('B') + public static void butterfly(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + val b = stack.pop(); + stack.push(a + b); + stack.push(a - b); + } + + @Instr('E') + public static void sum(ExecutionContext ctx) { + val stack = ctx.stack(); + int sum = 0; + while (stack.size() > 0) { + sum += stack.pop(); + } + stack.push(sum); + } + + @Instr('P') + public static void product(ExecutionContext ctx) { + val stack = ctx.stack(); + int prod = 1; + while (stack.size() > 0) { + prod *= stack.pop(); + } + stack.push(prod); + } + + private static void operateMatrix(ExecutionContext ctx, boolean get) { + val stack = ctx.stack(); + val pos = new Vector3i(); + if (ctx.dimensions() == 3) { + stack.pop3(pos); + } else { + pos.set(stack.pop2(), 0); + } + //UNDEF F and G pop i and j in the style of a vector (first Y then X) + val scale = stack.pop2(); + val space = ctx.fungeSpace(); + if (get) { + for (int i = scale.y - 1; i >= 0; i--) { + for (int j = scale.x - 1; j >= 0; j--) { + stack.push(space.get(pos.x + j, pos.y + i, pos.z)); + } + } + } else { + for (int i = 0; i < scale.y; i++) { + for (int j = 0; j < scale.x; j++) { + space.set(pos.x + j, pos.y + i, pos.z, stack.pop()); + } + } + } + } + + @Instr('F') + public static void putMatrix(ExecutionContext ctx) { + operateMatrix(ctx, false); + } + + @Instr('G') + public static void getMatrix(ExecutionContext ctx) { + operateMatrix(ctx, true); + } + + @Instr('Q') + public static void storeBehind(ExecutionContext ctx) { + val pos = new Vector3i(ctx.IP().position()); + ctx.interpret('r'); + ctx.interpret('s'); + ctx.interpret('r'); + ctx.IP().position().set(pos); + } + + @Instr('T') + public static void dimBranch(ExecutionContext ctx) { + int dim = ctx.stack().pop(); + switch (dim) { + case 0: + ctx.interpret('_'); + break; + case 1: + ctx.interpret('|'); + break; + case 2: + if (ctx.dimensions() != 3) { + ctx.interpret('r'); + } else { + ctx.interpret('m'); + } + break; + } + } + + private static final int[] branchDirs = new int[]{'<', '>', '^', 'v', 'h', 'l'}; + + @Instr('U') + public static void transmutableBranch(ExecutionContext ctx) { + int random = ctx.IP().nextRandom() & 0xffff; + random %= ctx.dimensions() * 2; + val dir = branchDirs[random]; + val pos = ctx.IP().position(); + ctx.fungeSpace().set(pos.x, pos.y, pos.z, dir); + ctx.interpret(dir); + } + + @Instr('W') + public static void waitForValue(ExecutionContext ctx) { + val stack = ctx.stack(); + val pos = stack.popVecDimProof(ctx.dimensions()); + int value = stack.pop(); + val space = ctx.fungeSpace(); + int valueAtCell = space.get(new Vector3i(pos).add(ctx.IP().storageOffset())); + if (valueAtCell < value) { + stack.push(value); + stack.pushVecDimProof(ctx.dimensions(), pos); + ctx.IP().position().sub(ctx.IP().delta()); + } else if (valueAtCell > value) { + ctx.interpret('r'); + } + } + + @Instr('X') + public static void incrementX(ExecutionContext ctx) { + ctx.IP().position().x++; + } + + @Instr('Y') + public static void incrementY(ExecutionContext ctx) { + ctx.IP().position().y++; + } + + @Instr('Z') + public static void incrementZ(ExecutionContext ctx) { + if (ctx.dimensions() == 3) { + ctx.IP().position().z++; + } else { + ctx.interpret('r'); + } + } + + @Override + public int code() { + return 0x544f5953; + } +} From 565d7bc30044ea34f277bdebd20886e2cb63ba9f Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Fri, 21 Oct 2022 19:33:45 +0200 Subject: [PATCH 15/35] [ci skip] update readme --- README.MD | 1 + 1 file changed, 1 insertion(+) diff --git a/README.MD b/README.MD index 84a1279..6679c44 100644 --- a/README.MD +++ b/README.MD @@ -59,6 +59,7 @@ Additionally, the following fingerprints are currently supported (more to come): - [PERL](./docs/catseye/library/PERL.markdown) - [REFC](./docs/catseye/library/REFC.markdown) - [ROMA](./docs/catseye/library/ROMA.markdown) +- [TOYS](./docs/catseye/library/TOYS.markdown) ### Version Release Checklist - Update the version number inside the [pom](./pom.xml) From de1225dea014f267be8a4ff68f5d7f4177987607 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Fri, 21 Oct 2022 19:53:51 +0200 Subject: [PATCH 16/35] float support for stacks --- .../com/falsepattern/jfunge/ip/IStack.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/main/java/com/falsepattern/jfunge/ip/IStack.java b/src/main/java/com/falsepattern/jfunge/ip/IStack.java index 7a6ef6e..7250c79 100644 --- a/src/main/java/com/falsepattern/jfunge/ip/IStack.java +++ b/src/main/java/com/falsepattern/jfunge/ip/IStack.java @@ -3,10 +3,16 @@ import com.falsepattern.jfunge.Copiable; import lombok.SneakyThrows; import lombok.val; +import org.joml.Vector2f; +import org.joml.Vector2fc; import org.joml.Vector2i; import org.joml.Vector2ic; +import org.joml.Vector3f; +import org.joml.Vector3fc; import org.joml.Vector3i; import org.joml.Vector3ic; +import org.joml.Vector4f; +import org.joml.Vector4fc; import org.joml.Vector4i; import org.joml.Vector4ic; @@ -130,4 +136,63 @@ default String popString() { } return data.toString("UTF-8"); } + + default void pushF(float val) { + push(Float.floatToRawIntBits(val)); + } + + default float popF() { + return Float.intBitsToFloat(pop()); + } + + default void pushF2(Vector2fc v) { + pushF(v.x()); + pushF(v.y()); + } + + default Vector2f popF2(Vector2f v) { + v.y = popF(); + v.x = popF(); + return v; + } + + default Vector2f popF2() { + return popF2(new Vector2f()); + } + + default void pushF3(Vector3fc v) { + pushF(v.x()); + pushF(v.y()); + pushF(v.z()); + } + + default Vector3f popF3(Vector3f v) { + v.z = popF(); + v.y = popF(); + v.x = popF(); + return v; + } + + default Vector3f popF3() { + return popF3(new Vector3f()); + } + + default void pushF4(Vector4fc v) { + pushF(v.x()); + pushF(v.y()); + pushF(v.z()); + pushF(v.w()); + } + + default Vector4f popF4(Vector4f v) { + v.w = popF(); + v.z = popF(); + v.y = popF(); + v.x = popF(); + return v; + } + + default Vector4f popF4() { + return popF4(new Vector4f()); + } } From b5aaa73c674976849b4ed37c2ee78db57068872d Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 22 Oct 2022 15:22:31 +0200 Subject: [PATCH 17/35] better caching logic for common datatypes --- .../falsepattern/jfunge/util/MemoryStack.java | 57 +++++++++++++++++++ .../jfunge/util/RewindableCachingStorage.java | 53 +++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/util/MemoryStack.java create mode 100644 src/main/java/com/falsepattern/jfunge/util/RewindableCachingStorage.java diff --git a/src/main/java/com/falsepattern/jfunge/util/MemoryStack.java b/src/main/java/com/falsepattern/jfunge/util/MemoryStack.java new file mode 100644 index 0000000..befddb5 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/util/MemoryStack.java @@ -0,0 +1,57 @@ +package com.falsepattern.jfunge.util; + +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.joml.Vector3i; + +public class MemoryStack implements AutoCloseable{ + private static final ThreadLocal THREAD_LOCAL = ThreadLocal.withInitial(MemoryStack::new); + + private int depth = 0; + + private final RewindableCachingStorage mat4f = new RewindableCachingStorage<>(Matrix4f::new, + Matrix4f::identity); + private final RewindableCachingStorage vec3i = new RewindableCachingStorage<>(Vector3i::new, + Vector3i::zero); + private final RewindableCachingStorage vec3f = new RewindableCachingStorage<>(Vector3f::new, + Vector3f::zero); + + public static MemoryStack stackPush() { + return THREAD_LOCAL.get().push(); + } + + public Matrix4f mat4f() { + return mat4f.alloc(); + } + + public Vector3i vec3i() { + return vec3i.alloc(); + } + + public Vector3f vec3f() { + return vec3f.alloc(); + } + + public MemoryStack push() { + mat4f.mark(); + vec3i.mark(); + vec3f.mark(); + depth++; + return this; + } + + public void pop() { + if (depth == 0) { + throw new IllegalStateException("Cannot pop stack of depth 0!"); + } + depth--; + mat4f.unmark(); + vec3i.unmark(); + vec3f.unmark(); + } + + @Override + public void close() { + pop(); + } +} diff --git a/src/main/java/com/falsepattern/jfunge/util/RewindableCachingStorage.java b/src/main/java/com/falsepattern/jfunge/util/RewindableCachingStorage.java new file mode 100644 index 0000000..fde8b8c --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/util/RewindableCachingStorage.java @@ -0,0 +1,53 @@ +package com.falsepattern.jfunge.util; + +import gnu.trove.stack.TIntStack; +import gnu.trove.stack.array.TIntArrayStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class RewindableCachingStorage { + private static final int EXTRA_CAPACITY = 16; + + private final Supplier creator; + private final Consumer reseter; + + private final List storage = new ArrayList<>(); + private final TIntStack marks = new TIntArrayStack(); + private int currentMark = 0; + private int currentIndex = 0; + + + public RewindableCachingStorage(Supplier creator, Consumer reseter) { + this.creator = creator; + this.reseter = reseter; + } + + public T alloc() { + T instance; + if (storage.size() == currentIndex) { + instance = creator.get(); + storage.add(instance); + currentIndex++; + } else { + instance = storage.get(currentIndex++); + reseter.accept(instance); + } + return instance; + } + + public void mark() { + marks.push(currentMark); + currentMark = currentIndex; + } + + public void unmark() { + while (storage.size() - EXTRA_CAPACITY > currentIndex) { + storage.remove(storage.size() - 1); + } + currentIndex = currentMark; + currentMark = marks.pop(); + } +} From a4379305e7cb54a8da870e6c8fb4eea52cb2aa13 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 22 Oct 2022 15:22:49 +0200 Subject: [PATCH 18/35] implement FPSP and 3DSP --- .../interpreter/instructions/Funge98.java | 4 + .../instructions/fingerprints/FPSP.java | 153 ++++++++++++++ .../instructions/fingerprints/_3DSP.java | 199 ++++++++++++++++++ 3 files changed, 356 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FPSP.java create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/_3DSP.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 104bc8e..5e17c4c 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -2,6 +2,7 @@ import com.falsepattern.jfunge.Globals; import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPSP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.Fingerprint; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.HRTI; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.MODE; @@ -12,6 +13,7 @@ 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._3DSP; import com.falsepattern.jfunge.ip.IStack; import com.falsepattern.jfunge.ip.impl.Stack; import gnu.trove.map.TIntObjectMap; @@ -39,6 +41,8 @@ public class Funge98 implements InstructionSet { private static final TIntObjectMap fingerprints = new TIntObjectHashMap<>(); static { + addFingerprint(_3DSP.INSTANCE); + addFingerprint(FPSP.INSTANCE); addFingerprint(HRTI.INSTANCE); addFingerprint(MODE.INSTANCE); addFingerprint(MODU.INSTANCE); 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 new file mode 100644 index 0000000..136da95 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FPSP.java @@ -0,0 +1,153 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import gnu.trove.function.TFloatFunction; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import lombok.val; +import org.joml.Math; + +import java.nio.charset.StandardCharsets; +import java.text.DecimalFormat; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class FPSP implements Fingerprint { + public static final FPSP INSTANCE = new FPSP(); + @Override + public int code() { + return 0x46505350; + } + + private interface BinOp { + float op(float a, float b); + } + + private static void binop(ExecutionContext ctx, BinOp op) { + val stack = ctx.stack(); + val b = stack.popF(); + val a = stack.popF(); + stack.pushF(op.op(a, b)); + } + + private static void unop(ExecutionContext ctx, TFloatFunction op) { + val stack = ctx.stack(); + stack.pushF(op.execute(stack.popF())); + } + + @Instr('A') + public static void add(ExecutionContext ctx) { + binop(ctx, Float::sum); + } + + @Instr('B') + public static void sin(ExecutionContext ctx) { + unop(ctx, Math::sin); + } + + @Instr('C') + public static void cos(ExecutionContext ctx) { + unop(ctx, Math::cos); + } + + @Instr('D') + public static void div(ExecutionContext ctx) { + binop(ctx, (a, b) -> a / b); + } + + @Instr('E') + public static void aSin(ExecutionContext ctx) { + unop(ctx, Math::asin); + } + + @Instr('F') + public static void iToF(ExecutionContext ctx) { + val stack = ctx.stack(); + stack.pushF(stack.pop()); + } + + @Instr('G') + public static void atan(ExecutionContext ctx) { + unop(ctx, (x) -> (float) java.lang.Math.atan(x)); + } + + @Instr('H') + public static void acos(ExecutionContext ctx) { + unop(ctx, Math::acos); + } + + @Instr('I') + public static void fToI(ExecutionContext ctx) { + val stack = ctx.stack(); + stack.push((int)stack.popF()); + } + + @Instr('K') + public static void logE(ExecutionContext ctx) { + unop(ctx, (x) -> (float) java.lang.Math.log(x)); + } + + @Instr('L') + public static void log10(ExecutionContext ctx) { + unop(ctx, (x) -> (float) java.lang.Math.log10(x)); + } + + @Instr('M') + public static void mul(ExecutionContext ctx) { + binop(ctx, (a, b) -> a * b); + } + + @Instr('N') + public static void negate(ExecutionContext ctx) { + unop(ctx, (x) -> -x); + } + + private static final DecimalFormat PRINT_FORMAT = new DecimalFormat("0.###### "); + + @SneakyThrows + @Instr('P') + public static void print(ExecutionContext ctx) { + ctx.output().write((PRINT_FORMAT.format(ctx.stack().popF())).getBytes(StandardCharsets.UTF_8)); + } + + @Instr('Q') + public static void sqrt(ExecutionContext ctx) { + unop(ctx, Math::sqrt); + } + + @Instr('R') + public static void parseFloat(ExecutionContext ctx) { + val stack = ctx.stack(); + val str = stack.popString(); + try { + stack.pushF(Float.parseFloat(str)); + } catch (NumberFormatException e) { + ctx.interpret('r'); + } + } + + @Instr('S') + public static void sub(ExecutionContext ctx) { + binop(ctx, (a, b) -> a - b); + } + + @Instr('T') + public static void tan(ExecutionContext ctx) { + unop(ctx, Math::tan); + } + + @Instr('V') + public static void abs(ExecutionContext ctx) { + unop(ctx, Math::abs); + } + + @Instr('X') + public static void exp(ExecutionContext ctx) { + unop(ctx, (x) -> (float) Math.exp(x)); + } + + @Instr('Y') + public static void pow(ExecutionContext ctx) { + binop(ctx, (a, b) -> (float) java.lang.Math.pow(a, b)); + } +} 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 new file mode 100644 index 0000000..3c8f72d --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/_3DSP.java @@ -0,0 +1,199 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.util.MemoryStack; +import lombok.AccessLevel; +import lombok.Cleanup; +import lombok.NoArgsConstructor; +import lombok.val; +import org.joml.Math; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.joml.Vector3fc; +import org.joml.Vector3i; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class _3DSP implements Fingerprint { + public static final _3DSP INSTANCE = new _3DSP(); + + @Override + public int code() { + return 0x33445350; + } + + private interface Op { + void operate(Vector3fc a, Vector3fc b, Vector3f result); + } + + 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(); + 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))); + } + } + } + + private static void binOp(ExecutionContext ctx, Op op) { + @Cleanup val mem = MemoryStack.stackPush(); + val stack = ctx.stack(); + val b = stack.popF3(mem.vec3f()); + val a = stack.popF3(mem.vec3f()); + val res = mem.vec3f(); + op.operate(a, b, res); + stack.pushF3(res); + } + + @Instr('A') + public static void add(ExecutionContext ctx) { + binOp(ctx, (a, b, res) -> res.set(a).add(b)); + } + + @Instr('B') + public static void sub(ExecutionContext ctx) { + binOp(ctx, (a, b, res) -> res.set(a).sub(b)); + } + + @Instr('C') + public static void cross(ExecutionContext ctx) { + binOp(ctx, (a, b, res) -> res.set(a).cross(b)); + } + + @Instr('D') + public static void dot(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val stack = ctx.stack(); + val b = stack.popF3(mem.vec3f()); + val a = stack.popF3(mem.vec3f()); + stack.pushF(a.dot(b)); + } + + @Instr('L') + public static void length(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val v = ctx.stack().popF3(mem.vec3f()); + ctx.stack().pushF(v.length()); + } + + @Instr('M') + public static void mul(ExecutionContext ctx) { + binOp(ctx, (a, b, res) -> res.set(a).mul(b)); + } + + @Instr('N') + public static void normalize(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val v = ctx.stack().popF3(mem.vec3f()); + v.normalize(); + ctx.stack().pushF3(v); + } + + @Instr('P') + public static void copyMatrix(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val stack = ctx.stack(); + val source = stack.popVecDimProof(ctx.dimensions(), mem.vec3i()); + val target = stack.popVecDimProof(ctx.dimensions(), mem.vec3i()); + val matrix = getMatrix(ctx, source, mem.mat4f()); + putMatrix(ctx, target, matrix); + } + + @Instr('R') + public static void genRotMatrix(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val stack = ctx.stack(); + val angle = stack.popF(); + val axis = stack.pop(); + val pos = stack.popVecDimProof(ctx.dimensions(), mem.vec3i()); + if (axis <= 0 || axis >= 4) { + ctx.interpret('r'); + return; + } + val matrix = mem.mat4f(); + matrix.rotation(Math.toRadians(angle), axis == 1 ? 1 : 0, axis == 2 ? 1 : 0, axis == 3 ? 1 : 0); + putMatrix(ctx, pos, matrix); + } + + @Instr('S') + public static void genScaleMatrix(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val stack = ctx.stack(); + val scale = stack.popF3(mem.vec3f()); + val pos = stack.popVecDimProof(ctx.dimensions(), mem.vec3i()); + val matrix = mem.mat4f(); + matrix.scaling(scale); + putMatrix(ctx, pos, matrix); + } + + @Instr('T') + public static void genTranslationMatrix(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val stack = ctx.stack(); + val translation = stack.popF3(mem.vec3f()); + val pos = stack.popVecDimProof(ctx.dimensions(), mem.vec3i()); + val matrix = mem.mat4f(); + matrix.translation(translation); + putMatrix(ctx, pos, matrix); + } + + @Instr('U') + public static void duplicateVector(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val stack = ctx.stack(); + val vec = stack.popF3(mem.vec3f()); + stack.pushF3(vec); + stack.pushF3(vec); + } + + @Instr('V') + public static void mapTo2D(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val stack = ctx.stack(); + val vec = stack.popF3(mem.vec3f()); + if (vec.z == 0) { + vec.z = 1; + } + stack.pushF(vec.x / vec.z); + stack.pushF(vec.y / vec.z); + } + + @Instr('X') + public static void transformVector(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val stack = ctx.stack(); + val matrix = getMatrix(ctx, stack.popVecDimProof(ctx.dimensions(), mem.vec3i()), mem.mat4f()); + val vector = stack.popF3(); + matrix.transformPosition(vector); + stack.pushF3(vector); + } + + @Instr('Y') + public static void multiplyMatrices(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val stack = ctx.stack(); + val tmp = mem.vec3i(); + val matB = getMatrix(ctx, stack.popVecDimProof(ctx.dimensions(), tmp), mem.mat4f()); + val matA = getMatrix(ctx, stack.popVecDimProof(ctx.dimensions(), tmp), mem.mat4f()); + putMatrix(ctx, stack.popVecDimProof(ctx.dimensions(), tmp), matB.mul(matA)); + } + + @Instr('Z') + public static void scaleVector(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val stack = ctx.stack(); + val vec = stack.popF3(mem.vec3f()); + vec.mul(stack.popF()); + stack.pushF3(vec); + } +} From 24d7817e08e2894e12fe597c3315e578d3c36bf5 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 22 Oct 2022 15:51:14 +0200 Subject: [PATCH 19/35] use memorystack system for the rest of the code too for performance --- .../interpreter/instructions/Funge98.java | 126 ++++++------------ .../instructions/fingerprints/ORTH.java | 14 +- .../instructions/fingerprints/REFC.java | 22 +-- .../instructions/fingerprints/TOYS.java | 64 +++++---- .../instructions/fingerprints/_3DSP.java | 2 +- .../com/falsepattern/jfunge/ip/IStack.java | 29 +--- .../falsepattern/jfunge/util/MemoryStack.java | 9 ++ 7 files changed, 96 insertions(+), 170 deletions(-) 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 5e17c4c..81e4289 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -16,15 +16,15 @@ import com.falsepattern.jfunge.interpreter.instructions.fingerprints._3DSP; import com.falsepattern.jfunge.ip.IStack; import com.falsepattern.jfunge.ip.impl.Stack; +import com.falsepattern.jfunge.util.MemoryStack; import gnu.trove.map.TIntObjectMap; import gnu.trove.map.hash.TIntObjectHashMap; import lombok.AccessLevel; +import lombok.Cleanup; import lombok.NoArgsConstructor; import lombok.SneakyThrows; import lombok.val; import lombok.var; -import org.joml.Vector2i; -import org.joml.Vector3i; import java.io.File; import java.io.IOException; @@ -188,27 +188,24 @@ public static void trampoline(ExecutionContext ctx) { @Instr('j') public static void jumpNTimes(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); int n = ctx.stack().pop(); - ctx.IP().position().add(new Vector3i(ctx.IP().delta()).mul(n)); + ctx.IP().position().add(mem.vec3i().set(ctx.IP().delta()).mul(n)); } @Instr('x') public static void absoluteDelta(ExecutionContext ctx) { val stack = ctx.stack(); - if (ctx.dimensions() == 3) { - stack.pop3(ctx.IP().delta()); - } else { - val tmp = stack.pop2(); - ctx.IP().delta().set(tmp.x, tmp.y, 0); - } + stack.popVecDimProof(ctx.dimensions(), ctx.IP().delta()); } @Instr('k') public static void doNTimes(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); val s = ctx.stack(); int n = s.pop(); if (n > 0) { - val snapshot = new Vector3i(ctx.IP().position()); + val snapshot = mem.vec3i().set(ctx.IP().position()); ctx.step(ctx.IP()); int i = ctx.fungeSpace().get(ctx.IP().position()); ctx.IP().position().set(snapshot); @@ -274,14 +271,16 @@ public static void branchHighLow(ExecutionContext ctx) { @Instr('\'') public static void getNext(ExecutionContext ctx) { - int i = ctx.fungeSpace().get(new Vector3i(ctx.IP().position()).add(ctx.IP().delta())); + @Cleanup val mem = MemoryStack.stackPush(); + int i = ctx.fungeSpace().get(mem.vec3i().set(ctx.IP().position()).add(ctx.IP().delta())); ctx.stack().push(i); ctx.interpret('#'); } @Instr('s') public static void putNext(ExecutionContext ctx) { - ctx.fungeSpace().set(new Vector3i(ctx.IP().position()).add(ctx.IP().delta()), ctx.stack().pop()); + @Cleanup val mem = MemoryStack.stackPush(); + ctx.fungeSpace().set(mem.vec3i().set(ctx.IP().position()).add(ctx.IP().delta()), ctx.stack().pop()); ctx.interpret('#'); } @@ -345,30 +344,18 @@ public static void swap(ExecutionContext ctx) { @Instr('p') public static void put(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); val ip = ctx.IP(); - val toss = ip.stackStack().TOSS(); - if (ctx.dimensions() == 3) { - val vec = toss.pop3().add(ip.storageOffset()); - val i = toss.pop(); - ctx.fungeSpace().set(vec, i); - } else { - val vec = toss.pop2().add(ip.storageOffset2()); - val i = toss.pop(); - ctx.fungeSpace().set(vec.x, vec.y, ip.storageOffset().z, i); - } + val stack = ip.stackStack().TOSS(); + ctx.fungeSpace().set(stack.popVecDimProof(ctx.dimensions(), mem.vec3i()).add(ip.storageOffset()), stack.pop()); } @Instr('g') public static void get(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); val ip = ctx.IP(); val stack = ctx.stack(); - if (ctx.dimensions() == 3) { - val vec = stack.pop3().add(ip.storageOffset()); - stack.push(ctx.fungeSpace().get(vec)); - } else { - val vec = stack.pop2().add(ip.storageOffset2()); - stack.push(ctx.fungeSpace().get(vec.x, vec.y, ip.storageOffset().z)); - } + stack.push(ctx.fungeSpace().get(stack.popVecDimProof(ctx.dimensions(), mem.vec3i()).add(ip.storageOffset()))); } @Instr('!') @@ -387,6 +374,7 @@ public static void duplicate(ExecutionContext ctx) { @Instr('{') public static void blockStart(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); if (!ctx.IP().stackStack().push()) { ctx.interpret('r'); return; @@ -408,12 +396,8 @@ public static void blockStart(ExecutionContext ctx) { SOSS.push(0); } } - if (ctx.dimensions() == 3) { - SOSS.push3(ctx.IP().storageOffset()); - } else { - SOSS.push2(new Vector2i(ctx.IP().storageOffset().x, ctx.IP().storageOffset().y)); - } - val snapshot = new Vector3i(ctx.IP().position()); + SOSS.pushVecDimProof(ctx.dimensions(), ctx.IP().storageOffset()); + val snapshot = mem.vec3i().set(ctx.IP().position()); ctx.step(ctx.IP()); ctx.IP().storageOffset().set(ctx.IP().position()); ctx.IP().position().set(snapshot); @@ -430,13 +414,7 @@ public static void blockEnd(ExecutionContext ctx) { val SOSS = SOSSt.get(); int n = TOSS.pop(); - if (ctx.dimensions() == 3) { - SOSS.pop3(ctx.IP().storageOffset()); - } else { - val tmp2 = SOSS.pop2(); - ctx.IP().storageOffset().x = tmp2.x; - ctx.IP().storageOffset().y = tmp2.y; - } + SOSS.popVecDimProof(ctx.dimensions(), ctx.IP().storageOffset()); val tmp = (IStack) new Stack(); if (n > 0) { for (int i = 0; i < n; i++) { @@ -476,6 +454,7 @@ public static void stackUnderStack(ExecutionContext ctx) { @Instr('y') public static void sysInfo(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); val s = ctx.stack(); val tossSize = ctx.stack().size(); val n = s.pop(); @@ -504,29 +483,16 @@ public static void sysInfo(ExecutionContext ctx) { //15 date s.push((time.getYear() - 1900) * 256 * 256 + time.getMonthValue() * 256 + time.getDayOfMonth()); val bounds = ctx.fungeSpace().bounds(); - if (ctx.dimensions() == 3) { - //14 greatest point - s.push3(new Vector3i(bounds.xMax() - bounds.xMin(), bounds.yMax() - bounds.yMin(), bounds.zMax() - bounds.zMin())); - //13 least point - s.push3(new Vector3i(bounds.xMin(), bounds.yMin(), bounds.zMin())); - //12 storage offset - s.push3(ctx.IP().storageOffset()); - //11 delta - s.push3(ctx.IP().delta()); - //10 position - s.push3(ctx.IP().position()); - } else { - //14 greatest point - s.push2(new Vector2i(bounds.xMax() - bounds.xMin(), bounds.yMax() - bounds.yMin())); - //13 least point - s.push2(new Vector2i(bounds.xMin(), bounds.yMin())); - //12 storage offset - s.push2(ctx.IP().storageOffset2()); - //11 delta - s.push2(ctx.IP().delta2()); - //10 position - s.push2(ctx.IP().position2()); - } + //14 greatest point + s.pushVecDimProof(ctx.dimensions(), mem.vec3i().set(bounds.xMax() - bounds.xMin(), bounds.yMax() - bounds.yMin(), bounds.zMax() - bounds.zMin())); + //13 least point + s.pushVecDimProof(ctx.dimensions(), mem.vec3i().set(bounds.xMin(), bounds.yMin(), bounds.zMin())); + //12 storage offset + s.pushVecDimProof(ctx.dimensions(), ctx.IP().storageOffset()); + //11 delta + s.pushVecDimProof(ctx.dimensions(), ctx.IP().delta()); + //10 position + s.pushVecDimProof(ctx.dimensions(), ctx.IP().position()); //9 teamnumber s.push(0); //8 uuid @@ -573,6 +539,7 @@ public static void split(ExecutionContext ctx) { @Instr('i') public static void input(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); if ((ctx.envFlags() & 0x02) == 0) { reflect(ctx); return; @@ -580,12 +547,7 @@ public static void input(ExecutionContext ctx) { val s = ctx.stack(); val filename = s.popString(); val flags = s.pop(); - val pos = new Vector3i(); - if (ctx.dimensions() == 3) { - s.pop3(pos); - } else { - pos.set(s.pop2(), 0); - } + val pos = s.popVecDimProof(ctx.dimensions(), mem.vec3i()); pos.add(ctx.IP().storageOffset()); try { if (!ctx.fileInputAllowed(filename)) { @@ -604,17 +566,13 @@ public static void input(ExecutionContext ctx) { } 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); pos.sub(ctx.IP().storageOffset()); - if (ctx.dimensions() == 3) { - s.push3(delta); - s.push3(pos); - } else { - s.push2(new Vector2i(delta.x, delta.y)); - s.push2(new Vector2i(pos.x, pos.y)); - } + s.pushVecDimProof(ctx.dimensions(), delta); + s.pushVecDimProof(ctx.dimensions(), pos); } @Instr('o') public static void output(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); if ((ctx.envFlags() & 0x04) == 0) { reflect(ctx); return; @@ -622,14 +580,10 @@ public static void output(ExecutionContext ctx) { val s = ctx.stack(); val filename = s.popString(); val flags = s.pop(); - val pos = new Vector3i(); - val delta = new Vector3i(); - if (ctx.dimensions() == 3) { - s.pop3(pos); - s.pop3(delta); - } else { - pos.set(s.pop2(), 0); - delta.set(s.pop2(), 1); + val pos = s.popVecDimProof(ctx.dimensions(), mem.vec3i()); + val delta = s.popVecDimProof(ctx.dimensions(), mem.vec3i()); + if (ctx.dimensions() != 3) { + delta.z = 1; } pos.add(ctx.IP().storageOffset()); try { diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java index 742c123..55ff667 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java @@ -2,12 +2,12 @@ import com.falsepattern.jfunge.interpreter.ExecutionContext; import com.falsepattern.jfunge.interpreter.instructions.Funge98; +import com.falsepattern.jfunge.util.MemoryStack; import lombok.AccessLevel; +import lombok.Cleanup; import lombok.NoArgsConstructor; import lombok.SneakyThrows; import lombok.val; -import org.joml.Vector2i; -import org.joml.Vector3i; import java.nio.charset.StandardCharsets; @@ -52,17 +52,18 @@ public static void setDY(ExecutionContext ctx) { @Instr('G') public static void get(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); val ip = ctx.IP(); val stack = ctx.stack(); if (ctx.dimensions() == 3) { - val vec = new Vector3i(); + val vec = mem.vec3i(); vec.x = stack.pop(); vec.y = stack.pop(); vec.z = stack.pop(); vec.add(ip.storageOffset()); stack.push(ctx.fungeSpace().get(vec)); } else { - val vec = new Vector2i(); + val vec = mem.vec2i(); vec.x = stack.pop(); vec.y = stack.pop(); vec.add(ip.storageOffset().x, ip.storageOffset().y); @@ -72,10 +73,11 @@ public static void get(ExecutionContext ctx) { @Instr('P') public static void put(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); val ip = ctx.IP(); val stack = ctx.stack(); if (ctx.dimensions() == 3) { - val vec = new Vector3i(); + val vec = mem.vec3i(); vec.x = stack.pop(); vec.y = stack.pop(); vec.z = stack.pop(); @@ -83,7 +85,7 @@ public static void put(ExecutionContext ctx) { val i = stack.pop(); ctx.fungeSpace().set(vec, i); } else { - val vec = new Vector2i(); + val vec = mem.vec2i(); vec.x = stack.pop(); vec.y = stack.pop(); vec.add(ip.storageOffset().x, ip.storageOffset().y); diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java index de780dc..7ac9d18 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java @@ -1,14 +1,15 @@ package com.falsepattern.jfunge.interpreter.instructions.fingerprints; import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.util.MemoryStack; import gnu.trove.map.TIntObjectMap; import gnu.trove.map.TObjectIntMap; import gnu.trove.map.hash.TIntObjectHashMap; import gnu.trove.map.hash.TObjectIntHashMap; import lombok.AccessLevel; +import lombok.Cleanup; import lombok.NoArgsConstructor; import lombok.val; -import org.joml.Vector2i; import org.joml.Vector3i; @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -24,16 +25,10 @@ private static Vectors getGlobal(ExecutionContext ctx) { @Instr('R') public static void reference(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); val vecs = getGlobal(ctx); - val vec = new Vector3i(); val stack = ctx.stack(); - if (ctx.dimensions() == 3) { - stack.pop3(vec); - } else { - val vec2 = stack.pop2(); - vec.x = vec2.x; - vec.y = vec2.y; - } + val vec = stack.popVecDimProof(ctx.dimensions(), mem.vec3i()); stack.push(vecs.reference(vec)); } @@ -41,12 +36,8 @@ public static void reference(ExecutionContext ctx) { public static void dereference(ExecutionContext ctx) { val vecs = getGlobal(ctx); val stack = ctx.stack(); - Vector3i vec = vecs.dereference(stack.pop()); - if (ctx.dimensions() == 3) { - stack.push3(vec); - } else { - stack.push2(new Vector2i(vec.x, vec.y)); - } + val vec = vecs.dereference(stack.pop()); + stack.pushVecDimProof(ctx.dimensions(), vec); } public static class Vectors { @@ -55,6 +46,7 @@ public static class Vectors { private static int counter = 0; public int reference(Vector3i vec) { + vec = new Vector3i(vec); if (dereferences.containsKey(vec)) { return dereferences.get(vec); } else { 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 6805f60..4e4dbfe 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 @@ -2,10 +2,11 @@ import com.falsepattern.jfunge.interpreter.ExecutionContext; 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.Vector2i; import org.joml.Vector3i; @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -44,19 +45,16 @@ private static void highOrder(FungeSpace space, Vector3i dst, Vector3i delta, Ve } private static void executeOperation(ExecutionContext ctx, IterationOperation iter, TransferOperation op) { - val dst = new Vector3i(); - val delta = new Vector3i(); - val src = new Vector3i(); + @Cleanup val mem = MemoryStack.stackPush(); + val dst = mem.vec3i(); + val delta = mem.vec3i(); + val src = mem.vec3i(); val stack = ctx.stack(); - if (ctx.dimensions() == 3) { - stack.pop3(dst); - stack.pop3(delta); - stack.pop3(src); - } else { - val buf = new Vector2i(); - dst.set(stack.pop2(buf), 0); - delta.set(stack.pop2(buf), 1); - src.set(stack.pop2(buf), 0); + stack.popVecDimProof(ctx.dimensions(), dst); + stack.popVecDimProof(ctx.dimensions(), delta); + stack.popVecDimProof(ctx.dimensions(), src); + if (ctx.dimensions() != 3) { + delta.z = 1; } val space = ctx.fungeSpace(); iter.operate(space, dst, delta, src, op); @@ -84,16 +82,14 @@ public static void highOrderMove(ExecutionContext ctx) { @Instr('S') public static void fill(ExecutionContext ctx) { - val dst = new Vector3i(); - val delta = new Vector3i(); + @Cleanup val mem = MemoryStack.stackPush(); + val dst = mem.vec3i(); + val delta = mem.vec3i(); val stack = ctx.stack(); - if (ctx.dimensions() == 3) { - stack.pop3(dst); - stack.pop3(delta); - } else { - val buf = new Vector2i(); - dst.set(stack.pop2(buf), 0); - delta.set(stack.pop2(buf), 1); + stack.popVecDimProof(ctx.dimensions(), dst); + stack.popVecDimProof(ctx.dimensions(), delta); + if (ctx.dimensions() != 3) { + delta.z = 1; } val cellValue = stack.pop(); val space = ctx.fungeSpace(); @@ -144,7 +140,8 @@ public static void shiftX(ExecutionContext ctx) { @Instr('L') public static void getLeft(ExecutionContext ctx) { - val pos = new Vector3i(ctx.IP().position()); + @Cleanup val mem = MemoryStack.stackPush(); + val pos = mem.vec3i().set(ctx.IP().position()); ctx.interpret('['); ctx.interpret('\''); ctx.interpret(']'); @@ -153,7 +150,8 @@ public static void getLeft(ExecutionContext ctx) { @Instr('R') public static void getRight(ExecutionContext ctx) { - val pos = new Vector3i(ctx.IP().position()); + @Cleanup val mem = MemoryStack.stackPush(); + val pos = mem.vec3i().set(ctx.IP().position()); ctx.interpret(']'); ctx.interpret('\''); ctx.interpret('['); @@ -233,15 +231,11 @@ public static void product(ExecutionContext ctx) { } private static void operateMatrix(ExecutionContext ctx, boolean get) { + @Cleanup val mem = MemoryStack.stackPush(); val stack = ctx.stack(); - val pos = new Vector3i(); - if (ctx.dimensions() == 3) { - stack.pop3(pos); - } else { - pos.set(stack.pop2(), 0); - } + val pos = stack.popVecDimProof(ctx.dimensions(), mem.vec3i()); //UNDEF F and G pop i and j in the style of a vector (first Y then X) - val scale = stack.pop2(); + val scale = stack.pop2(mem.vec2i()); val space = ctx.fungeSpace(); if (get) { for (int i = scale.y - 1; i >= 0; i--) { @@ -270,7 +264,8 @@ public static void getMatrix(ExecutionContext ctx) { @Instr('Q') public static void storeBehind(ExecutionContext ctx) { - val pos = new Vector3i(ctx.IP().position()); + @Cleanup val mem = MemoryStack.stackPush(); + val pos = mem.vec3i().set(ctx.IP().position()); ctx.interpret('r'); ctx.interpret('s'); ctx.interpret('r'); @@ -311,11 +306,12 @@ public static void transmutableBranch(ExecutionContext ctx) { @Instr('W') public static void waitForValue(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); val stack = ctx.stack(); - val pos = stack.popVecDimProof(ctx.dimensions()); + val pos = stack.popVecDimProof(ctx.dimensions(), mem.vec3i()); int value = stack.pop(); val space = ctx.fungeSpace(); - int valueAtCell = space.get(new Vector3i(pos).add(ctx.IP().storageOffset())); + int valueAtCell = space.get(mem.vec3i().set(pos).add(ctx.IP().storageOffset())); if (valueAtCell < value) { stack.push(value); stack.pushVecDimProof(ctx.dimensions(), pos); 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 3c8f72d..66ca391 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 @@ -173,7 +173,7 @@ public static void transformVector(ExecutionContext ctx) { @Cleanup val mem = MemoryStack.stackPush(); val stack = ctx.stack(); val matrix = getMatrix(ctx, stack.popVecDimProof(ctx.dimensions(), mem.vec3i()), mem.mat4f()); - val vector = stack.popF3(); + val vector = stack.popF3(mem.vec3f()); matrix.transformPosition(vector); stack.pushF3(vector); } diff --git a/src/main/java/com/falsepattern/jfunge/ip/IStack.java b/src/main/java/com/falsepattern/jfunge/ip/IStack.java index 7250c79..cc4b463 100644 --- a/src/main/java/com/falsepattern/jfunge/ip/IStack.java +++ b/src/main/java/com/falsepattern/jfunge/ip/IStack.java @@ -42,10 +42,6 @@ default Vector2i pop2(Vector2i v) { return v; } - default Vector2i pop2() { - return pop2(new Vector2i()); - } - default void push3(Vector3ic v) { push(v.x()); push(v.y()); @@ -59,10 +55,6 @@ default Vector3i pop3(Vector3i v) { return v; } - default Vector3i pop3() { - return pop3(new Vector3i()); - } - default void push4(Vector4ic v) { push(v.x()); push(v.y()); @@ -78,10 +70,6 @@ default Vector4i pop4(Vector4i v) { return v; } - default Vector4i pop4() { - return pop4(new Vector4i()); - } - default void pushVecDimProof(int dimensions, Vector3i buf) { switch (dimensions) { default: @@ -102,6 +90,7 @@ default void pushVecDimProof(int dimensions, Vector3i buf) { } default Vector3i popVecDimProof(int dimensions, Vector3i buf) { + buf.set(0, 0, 0); switch (dimensions) { default: throw new IllegalStateException("popVecDimProof only works with parameters 1-3"); @@ -115,10 +104,6 @@ default Vector3i popVecDimProof(int dimensions, Vector3i buf) { return buf; } - default Vector3i popVecDimProof(int dimensions) { - return popVecDimProof(dimensions, new Vector3i()); - } - default void pushString(String text) { val chars = text.getBytes(StandardCharsets.UTF_8); push(0); @@ -156,10 +141,6 @@ default Vector2f popF2(Vector2f v) { return v; } - default Vector2f popF2() { - return popF2(new Vector2f()); - } - default void pushF3(Vector3fc v) { pushF(v.x()); pushF(v.y()); @@ -173,10 +154,6 @@ default Vector3f popF3(Vector3f v) { return v; } - default Vector3f popF3() { - return popF3(new Vector3f()); - } - default void pushF4(Vector4fc v) { pushF(v.x()); pushF(v.y()); @@ -191,8 +168,4 @@ default Vector4f popF4(Vector4f v) { v.x = popF(); return v; } - - default Vector4f popF4() { - return popF4(new Vector4f()); - } } diff --git a/src/main/java/com/falsepattern/jfunge/util/MemoryStack.java b/src/main/java/com/falsepattern/jfunge/util/MemoryStack.java index befddb5..4b411d7 100644 --- a/src/main/java/com/falsepattern/jfunge/util/MemoryStack.java +++ b/src/main/java/com/falsepattern/jfunge/util/MemoryStack.java @@ -1,6 +1,7 @@ package com.falsepattern.jfunge.util; import org.joml.Matrix4f; +import org.joml.Vector2i; import org.joml.Vector3f; import org.joml.Vector3i; @@ -15,6 +16,8 @@ public class MemoryStack implements AutoCloseable{ Vector3i::zero); private final RewindableCachingStorage vec3f = new RewindableCachingStorage<>(Vector3f::new, Vector3f::zero); + private final RewindableCachingStorage vec2i = new RewindableCachingStorage<>(Vector2i::new, + Vector2i::zero); public static MemoryStack stackPush() { return THREAD_LOCAL.get().push(); @@ -32,10 +35,15 @@ public Vector3f vec3f() { return vec3f.alloc(); } + public Vector2i vec2i() { + return vec2i.alloc(); + } + public MemoryStack push() { mat4f.mark(); vec3i.mark(); vec3f.mark(); + vec2i.mark(); depth++; return this; } @@ -48,6 +56,7 @@ public void pop() { mat4f.unmark(); vec3i.unmark(); vec3f.unmark(); + vec2i.unmark(); } @Override From 815ee30a820e646bcfac366a124e76d30c1310b2 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 22 Oct 2022 15:52:46 +0200 Subject: [PATCH 20/35] move fingerprint interface --- .../jfunge/interpreter/instructions/Fingerprint.java | 5 +++++ .../jfunge/interpreter/instructions/Funge98.java | 1 - .../jfunge/interpreter/instructions/fingerprints/FPSP.java | 1 + .../interpreter/instructions/fingerprints/Fingerprint.java | 7 ------- .../jfunge/interpreter/instructions/fingerprints/HRTI.java | 1 + .../jfunge/interpreter/instructions/fingerprints/MODE.java | 1 + .../jfunge/interpreter/instructions/fingerprints/MODU.java | 1 + .../jfunge/interpreter/instructions/fingerprints/NULL.java | 1 + .../jfunge/interpreter/instructions/fingerprints/ORTH.java | 1 + .../jfunge/interpreter/instructions/fingerprints/PERL.java | 1 + .../jfunge/interpreter/instructions/fingerprints/REFC.java | 1 + .../jfunge/interpreter/instructions/fingerprints/ROMA.java | 1 + .../jfunge/interpreter/instructions/fingerprints/TOYS.java | 1 + .../interpreter/instructions/fingerprints/_3DSP.java | 1 + 14 files changed, 16 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/Fingerprint.java delete mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/Fingerprint.java diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Fingerprint.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Fingerprint.java new file mode 100644 index 0000000..94d8802 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Fingerprint.java @@ -0,0 +1,5 @@ +package com.falsepattern.jfunge.interpreter.instructions; + +public interface Fingerprint extends InstructionSet { + int code(); +} 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 81e4289..839102c 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -3,7 +3,6 @@ import com.falsepattern.jfunge.Globals; import com.falsepattern.jfunge.interpreter.ExecutionContext; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPSP; -import com.falsepattern.jfunge.interpreter.instructions.fingerprints.Fingerprint; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.HRTI; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.MODE; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.MODU; 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 136da95..501ecec 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 @@ -1,6 +1,7 @@ package com.falsepattern.jfunge.interpreter.instructions.fingerprints; import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; import gnu.trove.function.TFloatFunction; import lombok.AccessLevel; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/Fingerprint.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/Fingerprint.java deleted file mode 100644 index b32cc85..0000000 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/Fingerprint.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.falsepattern.jfunge.interpreter.instructions.fingerprints; - -import com.falsepattern.jfunge.interpreter.instructions.InstructionSet; - -public interface Fingerprint extends InstructionSet { - int code(); -} 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 2fc9cd0..69ad95a 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 @@ -1,6 +1,7 @@ package com.falsepattern.jfunge.interpreter.instructions.fingerprints; import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; import gnu.trove.map.TIntLongMap; import gnu.trove.map.hash.TIntLongHashMap; import lombok.AccessLevel; 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 e736826..3fcb3dc 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 @@ -1,6 +1,7 @@ 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.Funge98; import com.falsepattern.jfunge.interpreter.instructions.InstructionSet; import lombok.AccessLevel; diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/MODU.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/MODU.java index e5ea019..25af4e7 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/MODU.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/MODU.java @@ -1,6 +1,7 @@ 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.Funge98; import lombok.AccessLevel; import lombok.NoArgsConstructor; 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 a1a25b3..b6f433b 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 @@ -1,5 +1,6 @@ package com.falsepattern.jfunge.interpreter.instructions.fingerprints; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; import com.falsepattern.jfunge.interpreter.instructions.Instruction; import lombok.AccessLevel; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java index 55ff667..f691615 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ORTH.java @@ -1,6 +1,7 @@ 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.Funge98; import com.falsepattern.jfunge.util.MemoryStack; import lombok.AccessLevel; diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/PERL.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/PERL.java index 8cd1a44..9cc4120 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/PERL.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/PERL.java @@ -1,6 +1,7 @@ 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.Funge98; import lombok.AccessLevel; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java index 7ac9d18..2044f54 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/REFC.java @@ -1,6 +1,7 @@ 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 gnu.trove.map.TIntObjectMap; import gnu.trove.map.TObjectIntMap; diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ROMA.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ROMA.java index cf43ffc..0a37062 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ROMA.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/ROMA.java @@ -1,6 +1,7 @@ package com.falsepattern.jfunge.interpreter.instructions.fingerprints; import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; import lombok.AccessLevel; import lombok.NoArgsConstructor; 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 4e4dbfe..77269f5 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 @@ -1,6 +1,7 @@ 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; 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 66ca391..e7aeb74 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 @@ -1,6 +1,7 @@ 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; From c0c1ec3bc64f3940dea19e0e1a281b8bbc47cf35 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 22 Oct 2022 15:53:42 +0200 Subject: [PATCH 21/35] [ci skip] update fingerprint list --- README.MD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.MD b/README.MD index 6679c44..1c8d8a9 100644 --- a/README.MD +++ b/README.MD @@ -51,6 +51,8 @@ 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) +- [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) From a010fc6530ad6269ba5a85f6d41119030db2cd0a Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 22 Oct 2022 20:58:37 +0200 Subject: [PATCH 22/35] rebrand handprint to "JFUN" --- src/main/java/com/falsepattern/jfunge/Globals.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/falsepattern/jfunge/Globals.java b/src/main/java/com/falsepattern/jfunge/Globals.java index ad26242..3750ef5 100644 --- a/src/main/java/com/falsepattern/jfunge/Globals.java +++ b/src/main/java/com/falsepattern/jfunge/Globals.java @@ -3,6 +3,6 @@ public class Globals { public static final String VERSION = "1.0.1"; public static final int FUNGE_VERSION = 1 * 256 * 256 + 0 * 256 + 1; - public static final int HANDPRINT = 0xfa15e9a7; //"falsepat" + public static final int HANDPRINT = 0x74_70_85_78; //"JFUN" public static final String LICENSE = "JFunge - A standard-conforming Befunge-98 and Trefunge-98 interpreter\n" + "Copyright (C) 2022 FalsePattern\n" + "\n" + "This program is free software: you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation, either version 3 of the License, or\n" + "(at your option) any later version.\n" + "\n" + "This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with this program. If not, see ."; } From a6fd146131362098511b89a2092777a06f6e24ff Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 22 Oct 2022 21:12:49 +0200 Subject: [PATCH 23/35] embed license inside the jar --- COPYING | 675 +----------------- LICENSE | 16 +- .../java/com/falsepattern/jfunge/Globals.java | 1 - .../java/com/falsepattern/jfunge/Main.java | 15 +- src/main/resources/COPYING | 674 +++++++++++++++++ src/main/resources/LICENSE | 15 + 6 files changed, 704 insertions(+), 692 deletions(-) mode change 100644 => 120000 COPYING mode change 100644 => 120000 LICENSE create mode 100644 src/main/resources/COPYING create mode 100644 src/main/resources/LICENSE diff --git a/COPYING b/COPYING deleted file mode 100644 index f288702..0000000 --- a/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - 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/COPYING b/COPYING new file mode 120000 index 0000000..250de62 --- /dev/null +++ b/COPYING @@ -0,0 +1 @@ +src/main/resources/COPYING \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index fc14f61..0000000 --- a/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -JFunge - A standard-conforming Befunge-98 and Trefunge-98 interpreter -Copyright (C) 2022 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/LICENSE b/LICENSE new file mode 120000 index 0000000..5cf0161 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +src/main/resources/LICENSE \ No newline at end of file diff --git a/src/main/java/com/falsepattern/jfunge/Globals.java b/src/main/java/com/falsepattern/jfunge/Globals.java index 3750ef5..ead82a3 100644 --- a/src/main/java/com/falsepattern/jfunge/Globals.java +++ b/src/main/java/com/falsepattern/jfunge/Globals.java @@ -4,5 +4,4 @@ public class Globals { public static final String VERSION = "1.0.1"; public static final int FUNGE_VERSION = 1 * 256 * 256 + 0 * 256 + 1; public static final int HANDPRINT = 0x74_70_85_78; //"JFUN" - public static final String LICENSE = "JFunge - A standard-conforming Befunge-98 and Trefunge-98 interpreter\n" + "Copyright (C) 2022 FalsePattern\n" + "\n" + "This program is free software: you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation, either version 3 of the License, or\n" + "(at your option) any later version.\n" + "\n" + "This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with this program. If not, see ."; } diff --git a/src/main/java/com/falsepattern/jfunge/Main.java b/src/main/java/com/falsepattern/jfunge/Main.java index c6420bf..0a7452a 100644 --- a/src/main/java/com/falsepattern/jfunge/Main.java +++ b/src/main/java/com/falsepattern/jfunge/Main.java @@ -77,8 +77,19 @@ public static void main(String[] args) throws IOException, ParseException { return; } if (cmd.hasOption("license")) { - System.out.println(Globals.LICENSE); - return; + try (val res = Main.class.getResourceAsStream("/LICENSE")) { + if (res == null) { + System.out.println("Could not read embedded license file, however, this program (JFunge) is licensed under LGPLv3."); + return; + } + val buf = new byte[256]; + int read; + while ((read = res.read(buf)) > 0) { + System.out.write(buf, 0, read); + } + System.out.println(); + return; + } } if (cmd.hasOption("version")) { System.out.println("Version: " + Globals.VERSION); diff --git a/src/main/resources/COPYING b/src/main/resources/COPYING new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/src/main/resources/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/src/main/resources/LICENSE b/src/main/resources/LICENSE new file mode 100644 index 0000000..fc14f61 --- /dev/null +++ b/src/main/resources/LICENSE @@ -0,0 +1,15 @@ +JFunge - A standard-conforming Befunge-98 and Trefunge-98 interpreter +Copyright (C) 2022 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 From 8d16897b0e7b1af050d69bd7320302d2b03a3918 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 22 Oct 2022 22:14:13 +0200 Subject: [PATCH 24/35] implement TURT --- README.MD | 1 + .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/TURT.java | 193 ++++++++++++++++++ .../falsepattern/jfunge/util/MemoryStack.java | 9 + 4 files changed, 205 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/TURT.java diff --git a/README.MD b/README.MD index 1c8d8a9..26c1956 100644 --- a/README.MD +++ b/README.MD @@ -62,6 +62,7 @@ Additionally, the following fingerprints are currently supported (more to come): - [REFC](./docs/catseye/library/REFC.markdown) - [ROMA](./docs/catseye/library/ROMA.markdown) - [TOYS](./docs/catseye/library/TOYS.markdown) +- [TURT](./docs/catseye/library/TURT.markdown) ### Version Release Checklist - Update the version number inside the [pom](./pom.xml) 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 839102c..b5f83aa 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -12,6 +12,7 @@ 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; @@ -51,6 +52,7 @@ public class Funge98 implements InstructionSet { addFingerprint(REFC.INSTANCE); addFingerprint(ROMA.INSTANCE); addFingerprint(TOYS.INSTANCE); + addFingerprint(TURT.INSTANCE); } private static void addFingerprint(Fingerprint print) { 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 new file mode 100644 index 0000000..149a9fe --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/TURT.java @@ -0,0 +1,193 @@ +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 org.joml.Math; +import org.joml.Matrix3x2f; +import org.joml.Vector2f; +import org.joml.Vector2i; + +import javax.imageio.ImageIO; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TURT implements Fingerprint { + public static final TURT INSTANCE = new TURT(); + + private static BufferedImage image; + private static Graphics2D gfx; + + private static Vector2i dimensions; + private static Vector2f position; + private static Color lineColor; + private static boolean penDown; + private static int angle; + + static { + initialize(new Vector2i(500, 500), new Vector2i(0, 0)); + } + + public static void initialize(Vector2i dimensions, Vector2i initialPosition) { + TURT.dimensions = new Vector2i(dimensions); + TURT.position = new Vector2f(initialPosition); + TURT.angle = 0; + TURT.penDown = false; + + TURT.image = new BufferedImage(dimensions.x, dimensions.y, BufferedImage.TYPE_INT_RGB); + TURT.gfx = image.createGraphics(); + gfx.setColor(Color.WHITE); + gfx.fillRect(0, 0, dimensions.x, dimensions.y); + gfx.setColor(lineColor = Color.BLACK); + } + + private static void clampPos() { + if (position.x < 0) { + position.x = 0; + } + if (position.x > dimensions.x - 1) { + position.x = dimensions.x - 1; + } + if (position.y < 0) { + position.y = 0; + } + if (position.y > dimensions.y - 1) { + position.y = dimensions.y - 1; + } + } + + @Instr('L') + public static void turnLeft(ExecutionContext ctx) { + angle = (((angle + ctx.stack().pop()) % 360) + 360) % 360; + } + + @Instr('R') + public static void turnRight(ExecutionContext ctx) { + angle = (((angle - ctx.stack().pop()) % 360) + 360) % 360; + } + + @Instr('H') + public static void setHeading(ExecutionContext ctx) { + angle = ((ctx.stack().pop() % 360) + 360) % 360; + } + + @Instr('F') + public static void forward(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val newPos = mem.vec2f().set(1, 0); + new Matrix3x2f().rotation(Math.toRadians(-angle)).transformDirection(newPos); + val step = ctx.stack().pop(); + if (penDown) { + newPos.mul(step).add(position); + gfx.drawLine((int) position.x, (int) position.y, (int) newPos.x, (int) newPos.y); + position.set(newPos); + } else { + position.add(newPos.mul(step)); + } + clampPos(); + } + + @Instr('B') + public static void back(ExecutionContext ctx) { + @Cleanup val mem = MemoryStack.stackPush(); + val newPos = mem.vec2f().set(1, 0); + new Matrix3x2f().rotation(Math.toRadians(-angle)).transformDirection(newPos); + val step = ctx.stack().pop(); + if (penDown) { + newPos.mul(-step).add(position); + gfx.drawLine((int) position.x, (int) position.y, (int) newPos.x, (int) newPos.y); + position.set(newPos); + } else { + position.add(newPos.mul(-step)); + } + clampPos(); + } + + @Instr('P') + public static void penPos(ExecutionContext ctx) { + penDown = ctx.stack().pop() != 0; + } + + @Instr('C') + public static void penColor(ExecutionContext ctx) { + val rgb = ctx.stack().pop() & 0xFFFFFF; + lineColor = new Color(rgb); + gfx.setColor(lineColor); + } + + @Instr('N') + public static void clearPaper(ExecutionContext ctx) { + val rgb = ctx.stack().pop() & 0xFFFFFF; + gfx.setColor(new Color(rgb)); + gfx.fillRect(0, 0, dimensions.x, dimensions.y); + gfx.setColor(lineColor); + } + + @Instr('D') + public static void showDisplay(ExecutionContext ctx) { + System.err.println("TODO: TURT 'D' unsupported. Popping stack, but not displaying."); + ctx.stack().pop(); + } + + @Instr('T') + public static void teleport(ExecutionContext ctx) { + val stack = ctx.stack(); + position.y = stack.pop(); + position.x = stack.pop(); + } + + @Instr('E') + public static void queryPen(ExecutionContext ctx) { + ctx.stack().push(penDown ? 1 : 0); + } + + @Instr('A') + public static void queryHeading(ExecutionContext ctx) { + ctx.stack().push(angle); + } + + @Instr('Q') + public static void queryPosition(ExecutionContext ctx) { + val stack = ctx.stack(); + stack.push((int) position.x); + stack.push((int) position.y); + } + + @Instr('U') + public static void queryBounds(ExecutionContext ctx) { + val stack = ctx.stack(); + stack.push(0); + stack.push(0); + stack.push(dimensions.x); + stack.push(dimensions.y); + } + + @Instr('I') + public static void printDrawing(ExecutionContext ctx) { + File file; + int counter = 0; + do { + file = Paths.get(".", "turtle" + (counter++) + ".png").toFile(); + } while (file.exists()); + try { + ImageIO.write(image, "PNG", file); + } catch (IOException e) { + e.printStackTrace(); + ctx.interpret('r'); + } + } + + @Override + public int code() { + return 0x54555254; + } +} diff --git a/src/main/java/com/falsepattern/jfunge/util/MemoryStack.java b/src/main/java/com/falsepattern/jfunge/util/MemoryStack.java index 4b411d7..cbd41ad 100644 --- a/src/main/java/com/falsepattern/jfunge/util/MemoryStack.java +++ b/src/main/java/com/falsepattern/jfunge/util/MemoryStack.java @@ -1,6 +1,7 @@ package com.falsepattern.jfunge.util; import org.joml.Matrix4f; +import org.joml.Vector2f; import org.joml.Vector2i; import org.joml.Vector3f; import org.joml.Vector3i; @@ -18,6 +19,8 @@ public class MemoryStack implements AutoCloseable{ Vector3f::zero); private final RewindableCachingStorage vec2i = new RewindableCachingStorage<>(Vector2i::new, Vector2i::zero); + private final RewindableCachingStorage vec2f = new RewindableCachingStorage<>(Vector2f::new, + Vector2f::zero); public static MemoryStack stackPush() { return THREAD_LOCAL.get().push(); @@ -39,11 +42,16 @@ public Vector2i vec2i() { return vec2i.alloc(); } + public Vector2f vec2f() { + return vec2f.alloc(); + } + public MemoryStack push() { mat4f.mark(); vec3i.mark(); vec3f.mark(); vec2i.mark(); + vec2f.mark(); depth++; return this; } @@ -57,6 +65,7 @@ public void pop() { vec3i.unmark(); vec3f.unmark(); vec2i.unmark(); + vec2f.unmark(); } @Override From e6d940d2872a14a4b5ba3c8425b02b3caeac8268 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Sat, 10 Dec 2022 20:56:10 +0100 Subject: [PATCH 25/35] upgrade to utility class --- .../jfunge/interpreter/LambdaHelper.java | 45 +++++++++++++++++++ .../instructions/InstructionSet.java | 37 ++++++++------- 2 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/LambdaHelper.java diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/LambdaHelper.java b/src/main/java/com/falsepattern/jfunge/interpreter/LambdaHelper.java new file mode 100644 index 0000000..d99d031 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/LambdaHelper.java @@ -0,0 +1,45 @@ +package com.falsepattern.jfunge.interpreter; + +import lombok.*; +import lombok.experimental.*; + +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.util.Arrays; + +@UtilityClass +public final class LambdaHelper { + public static MethodHandle newLambdaMetaFactory(Class functionalInterface, Method staticMethod) { + try { + val interfaceMethod = Arrays.stream(functionalInterface.getDeclaredMethods()) + .filter(method1 -> !method1.isDefault()) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(functionalInterface.getName() + " does not have a single abstract method")); + val interfaceMethodName = interfaceMethod.getName(); + val factoryType = MethodType.methodType(functionalInterface); + val interfaceMethodType = MethodType.methodType(interfaceMethod.getReturnType(), + interfaceMethod.getParameterTypes()); + + // Get the method handle for the static method + val lookup = MethodHandles.lookup(); + val implementation = lookup.unreflect(staticMethod); + val dynamicMethodType = implementation.type(); + + return LambdaMetafactory + .metafactory(lookup, + interfaceMethodName, + factoryType, + interfaceMethodType, + implementation, + dynamicMethodType) + .getTarget(); + } catch (Throwable throwable) { + val errStr = "Failed to bind method: %s to functional interface: %s"; + throw new RuntimeException(String.format(errStr, staticMethod.getName(), functionalInterface.getName()), + throwable); + } + } +} 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 8023f8e..a97382d 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSet.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/InstructionSet.java @@ -1,13 +1,10 @@ package com.falsepattern.jfunge.interpreter.instructions; -import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.LambdaHelper; import lombok.val; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.invoke.LambdaMetafactory; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.function.IntConsumer; @@ -16,25 +13,27 @@ 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) -> { - val lookup = MethodHandles.lookup(); - val methodType = MethodType.methodType(void.class, ExecutionContext.class); - try { - val lambda = (Instruction) LambdaMetafactory.metafactory(lookup, "process", MethodType.methodType(Instruction.class), methodType, lookup.findStatic(clazz, method.getName(), methodType), methodType).getTarget().invokeExact(); - val ann = method.getAnnotation(Instr.class); - instructionSet.accept(lambda, ann.value()); - } catch (Throwable e) { - throw new RuntimeException(e); - } - }); + 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 unload(IntConsumer 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()); - }); + 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()); + }); } @Retention(RetentionPolicy.RUNTIME) From d7fb5207afb28bd14f460f3c21e6b6994d9a163e Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 22 Dec 2022 19:40:06 +0100 Subject: [PATCH 26/35] implement FPDP --- .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/FPDP.java | 158 ++++++++++++++++++ .../com/falsepattern/jfunge/ip/IStack.java | 17 ++ 3 files changed, 177 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FPDP.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 b5f83aa..88779b9 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -2,6 +2,7 @@ import com.falsepattern.jfunge.Globals; import com.falsepattern.jfunge.interpreter.ExecutionContext; +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.MODE; @@ -43,6 +44,7 @@ public class Funge98 implements InstructionSet { static { addFingerprint(_3DSP.INSTANCE); addFingerprint(FPSP.INSTANCE); + addFingerprint(FPDP.INSTANCE); addFingerprint(HRTI.INSTANCE); addFingerprint(MODE.INSTANCE); addFingerprint(MODU.INSTANCE); 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 new file mode 100644 index 0000000..7973a4a --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FPDP.java @@ -0,0 +1,158 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; +import gnu.trove.function.TDoubleFunction; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import lombok.val; +import org.joml.Math; + +import java.nio.charset.StandardCharsets; +import java.text.DecimalFormat; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class FPDP implements Fingerprint { + public static final FPDP INSTANCE = new FPDP(); + @Override + public int code() { + return 0x46504450; + } + + private interface BinOp { + double op(double a, double b); + } + + private static void binop(ExecutionContext ctx, BinOp op) { + val stack = ctx.stack(); + val b = stack.popD(); + val a = stack.popD(); + stack.pushD(op.op(a, b)); + } + + private static void unop(ExecutionContext ctx, TDoubleFunction op) { + val stack = ctx.stack(); + stack.pushD(op.execute(stack.popD())); + } + + @Instr('A') + public static void add(ExecutionContext ctx) { + binop(ctx, Double::sum); + } + + @Instr('B') + public static void sin(ExecutionContext ctx) { + unop(ctx, Math::sin); + } + + @Instr('C') + public static void cos(ExecutionContext ctx) { + unop(ctx, Math::cos); + } + + @Instr('D') + public static void div(ExecutionContext ctx) { + binop(ctx, (a, b) -> a / b); + } + + @Instr('E') + public static void aSin(ExecutionContext ctx) { + unop(ctx, Math::asin); + } + + @Instr('F') + public static void iToF(ExecutionContext ctx) { + val stack = ctx.stack(); + stack.pushD(stack.pop()); + } + + @Instr('G') + public static void atan(ExecutionContext ctx) { + unop(ctx, java.lang.Math::atan); + } + + @Instr('H') + public static void acos(ExecutionContext ctx) { + unop(ctx, Math::acos); + } + + @Instr('I') + public static void fToI(ExecutionContext ctx) { + val stack = ctx.stack(); + stack.push((int)stack.popD()); + } + + @Instr('K') + public static void logE(ExecutionContext ctx) { + unop(ctx, java.lang.Math::log); + } + + @Instr('L') + public static void log10(ExecutionContext ctx) { + unop(ctx, java.lang.Math::log10); + } + + @Instr('M') + public static void mul(ExecutionContext ctx) { + binop(ctx, (a, b) -> a * b); + } + + @Instr('N') + public static void negate(ExecutionContext ctx) { + unop(ctx, (x) -> -x); + } + + private static final DecimalFormat PRINT_FORMAT = new DecimalFormat("0.###### "); + + @SneakyThrows + @Instr('P') + public static void print(ExecutionContext ctx) { + ctx.output() + .write(PRINT_FORMAT.format(ctx.stack().popD()) + .replace("\uFFFD", "NaN ") + .replace("\u221E", "infinity") + .getBytes(StandardCharsets.UTF_8)); + } + + @Instr('Q') + public static void sqrt(ExecutionContext ctx) { + unop(ctx, Math::sqrt); + } + + @Instr('R') + public static void parseDouble(ExecutionContext ctx) { + val stack = ctx.stack(); + val str = stack.popString(); + try { + stack.pushD(Double.parseDouble(str)); + } catch (NumberFormatException e) { + ctx.interpret('r'); + } + } + + @Instr('S') + public static void sub(ExecutionContext ctx) { + binop(ctx, (a, b) -> a - b); + } + + @Instr('T') + public static void tan(ExecutionContext ctx) { + unop(ctx, Math::tan); + } + + @Instr('V') + public static void abs(ExecutionContext ctx) { + unop(ctx, Math::abs); + } + + @Instr('X') + public static void exp(ExecutionContext ctx) { + unop(ctx, Math::exp); + } + + @Instr('Y') + public static void pow(ExecutionContext ctx) { + binop(ctx, java.lang.Math::pow); + } +} diff --git a/src/main/java/com/falsepattern/jfunge/ip/IStack.java b/src/main/java/com/falsepattern/jfunge/ip/IStack.java index cc4b463..9f828de 100644 --- a/src/main/java/com/falsepattern/jfunge/ip/IStack.java +++ b/src/main/java/com/falsepattern/jfunge/ip/IStack.java @@ -122,6 +122,15 @@ default String popString() { return data.toString("UTF-8"); } + default void pushL(long value) { + push((int) (value & 0xFFFFFFFFL)); + push((int) ((value >> 32) & 0xFFFFFFFFL)); + } + + default long popL() { + return ((long) pop() << 32) | (pop() & 0xFFFFFFFFL); + } + default void pushF(float val) { push(Float.floatToRawIntBits(val)); } @@ -130,6 +139,14 @@ default float popF() { return Float.intBitsToFloat(pop()); } + default void pushD(double val) { + pushL(Double.doubleToRawLongBits(val)); + } + + default double popD() { + return Double.longBitsToDouble(popL()); + } + default void pushF2(Vector2fc v) { pushF(v.x()); pushF(v.y()); From bbe3bf2495b656bad3caf690867aa1d466f847ad Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 22 Dec 2022 19:40:39 +0100 Subject: [PATCH 27/35] copy new print code from fpdp to fpsp too --- .../jfunge/interpreter/instructions/fingerprints/FPSP.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 501ecec..f7c346f 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 @@ -108,7 +108,11 @@ public static void negate(ExecutionContext ctx) { @SneakyThrows @Instr('P') public static void print(ExecutionContext ctx) { - ctx.output().write((PRINT_FORMAT.format(ctx.stack().popF())).getBytes(StandardCharsets.UTF_8)); + ctx.output() + .write(PRINT_FORMAT.format(ctx.stack().popF()) + .replace("\uFFFD", "NaN ") + .replace("\u221E", "infinity") + .getBytes(StandardCharsets.UTF_8)); } @Instr('Q') From 0548c311f15c47cd47a150c7585735aae30d0c49 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 22 Dec 2022 19:47:57 +0100 Subject: [PATCH 28/35] implement BASE --- .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/BASE.java | 57 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/BASE.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 88779b9..8b45fc3 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -2,6 +2,7 @@ import com.falsepattern.jfunge.Globals; import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.BASE; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPDP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPSP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.HRTI; @@ -43,6 +44,7 @@ public class Funge98 implements InstructionSet { static { addFingerprint(_3DSP.INSTANCE); + addFingerprint(BASE.INSTANCE); addFingerprint(FPSP.INSTANCE); addFingerprint(FPDP.INSTANCE); addFingerprint(HRTI.INSTANCE); 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 new file mode 100644 index 0000000..5c88be0 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/BASE.java @@ -0,0 +1,57 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class BASE implements Fingerprint { + public static final BASE INSTANCE = new BASE(); + + @Override + public int code() { + return 0x42415345; + } + + @SneakyThrows + @Instr('B') + public static void printBinary(ExecutionContext ctx) { + ctx.output().write(Integer.toBinaryString(ctx.stack().pop()).getBytes()); + } + + @SneakyThrows + @Instr('H') + public static void printHex(ExecutionContext ctx) { + ctx.output().write(Integer.toHexString(ctx.stack().pop()).getBytes()); + } + + @Instr('I') + public static void readIntBase(ExecutionContext ctx) { + int base = ctx.stack().pop(); + //Read input until non-digit character is encountered + StringBuilder sb = new StringBuilder(); + int c; + while (Character.isDigit(c = ctx.input(true))) { + sb.append((char) c); + ctx.input(false); + } + ctx.stack().push(Integer.parseInt(sb.toString(), base)); + } + + @SneakyThrows + @Instr('N') + public static void printInBase(ExecutionContext ctx) { + int base = ctx.stack().pop(); + int value = ctx.stack().pop(); + ctx.output().write(Integer.toString(value, base).getBytes()); + } + + @SneakyThrows + @Instr('O') + public static void printOctal(ExecutionContext ctx) { + ctx.output().write(Integer.toOctalString(ctx.stack().pop()).getBytes()); + } + +} From 4da65e9e89a753159ccd1b9ea624710f9d24cdbf Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 22 Dec 2022 20:01:39 +0100 Subject: [PATCH 29/35] implement CPLI --- .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/CPLI.java | 82 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/CPLI.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 8b45fc3..3d5a1ad 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -3,6 +3,7 @@ import com.falsepattern.jfunge.Globals; import com.falsepattern.jfunge.interpreter.ExecutionContext; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.BASE; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.CPLI; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPDP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPSP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.HRTI; @@ -45,6 +46,7 @@ public class Funge98 implements InstructionSet { static { addFingerprint(_3DSP.INSTANCE); addFingerprint(BASE.INSTANCE); + addFingerprint(CPLI.INSTANCE); addFingerprint(FPSP.INSTANCE); addFingerprint(FPDP.INSTANCE); addFingerprint(HRTI.INSTANCE); diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/CPLI.java b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/CPLI.java new file mode 100644 index 0000000..cb70aec --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/CPLI.java @@ -0,0 +1,82 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import lombok.val; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CPLI implements Fingerprint { +public static final CPLI INSTANCE = new CPLI(); + @Override + public int code() { + return 0x43504C49; + } + + @Instr('A') + public static void add(ExecutionContext ctx) { + val stack = ctx.stack(); + val bi = stack.pop(); + val br = stack.pop(); + val ai = stack.pop(); + val ar = stack.pop(); + stack.push(ar + br); + stack.push(ai + bi); + } + + @Instr('D') + public static void div(ExecutionContext ctx) { + val stack = ctx.stack(); + val bi = stack.pop(); + val br = stack.pop(); + val ai = stack.pop(); + val ar = stack.pop(); + val r = (ar * br + ai * bi) / (br * br + bi * bi); + val i = (ai * br - ar * bi) / (br * br + bi * bi); + stack.push(r); + stack.push(i); + } + + @Instr('M') + public static void mul(ExecutionContext ctx) { + val stack = ctx.stack(); + val bi = stack.pop(); + val br = stack.pop(); + val ai = stack.pop(); + val ar = stack.pop(); + val r = ar * br - ai * bi; + val i = ar * bi + ai * br; + stack.push(r); + stack.push(i); + } + + @SneakyThrows + @Instr('O') + public static void output(ExecutionContext ctx) { + val stack = ctx.stack(); + val i = stack.pop(); + val r = stack.pop(); + ctx.output().write(("(" + r + " + " + i + "i)").getBytes()); + } + + @Instr('S') + public static void sub(ExecutionContext ctx) { + val stack = ctx.stack(); + val bi = stack.pop(); + val br = stack.pop(); + val ai = stack.pop(); + val ar = stack.pop(); + stack.push(ar - br); + stack.push(ai - bi); + } + + @Instr('V') + public static void length(ExecutionContext ctx) { + val stack = ctx.stack(); + val i = stack.pop(); + val r = stack.pop(); + stack.push((int)Math.sqrt(r * r + i * i)); + } +} From 38dba0a41424d02831eb4b48fba110e289b247a3 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 22 Dec 2022 20:02:12 +0100 Subject: [PATCH 30/35] disable TURT for the time being --- .../falsepattern/jfunge/interpreter/instructions/Funge98.java | 3 ++- 1 file changed, 2 insertions(+), 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 3d5a1ad..cd87400 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -58,7 +58,8 @@ public class Funge98 implements InstructionSet { addFingerprint(REFC.INSTANCE); addFingerprint(ROMA.INSTANCE); addFingerprint(TOYS.INSTANCE); - addFingerprint(TURT.INSTANCE); + //TODO Fix TURT, it's broken +// addFingerprint(TURT.INSTANCE); } private static void addFingerprint(Fingerprint print) { From 3f59584bb8ce5b4bb9e1e26f35156a6675b459ca Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 22 Dec 2022 20:42:07 +0100 Subject: [PATCH 31/35] bump lombok ver --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 01e05e1..7699b29 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ 8 - 1.18.22 + 1.18.24 3.0.3 1.10.2 1.5.0 From 47936fb2f2d394ba14e77976d2356f6f105a42f1 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 22 Dec 2022 20:42:19 +0100 Subject: [PATCH 32/35] implement DATE --- .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/DATE.java | 171 ++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DATE.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 cd87400..f9885d5 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -4,6 +4,7 @@ import com.falsepattern.jfunge.interpreter.ExecutionContext; 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.FPDP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPSP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.HRTI; @@ -47,6 +48,7 @@ public class Funge98 implements InstructionSet { addFingerprint(_3DSP.INSTANCE); addFingerprint(BASE.INSTANCE); addFingerprint(CPLI.INSTANCE); + addFingerprint(DATE.INSTANCE); addFingerprint(FPSP.INSTANCE); addFingerprint(FPDP.INSTANCE); addFingerprint(HRTI.INSTANCE); 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 new file mode 100644 index 0000000..1d4df4e --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/DATE.java @@ -0,0 +1,171 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; +import lombok.NoArgsConstructor; +import lombok.val; +import lombok.var; + +import java.time.DateTimeException; +import java.time.Duration; +import java.time.LocalDate; +import java.time.temporal.JulianFields; + +@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) +public class DATE implements Fingerprint { + public static final DATE INSTANCE = new DATE(); + @Override + public int code() { + return 0x44415445; + } + + @Instr('A') + public static void addDaysToDate(ExecutionContext ctx) { + val stack = ctx.stack(); + val days = stack.pop(); + val d = stack.pop(); + val m = stack.pop(); + val y = stack.pop(); + if (y == 0) { + ctx.interpret('r'); + return; + } + val date = dateOrReflect(ctx, () -> LocalDate.of(y, m, d)); + if (date == null) { + return; + } + val newDate = date.plusDays(days); + stack.push(newDate.getYear()); + stack.push(newDate.getMonthValue()); + stack.push(newDate.getDayOfMonth()); + } + + @Instr('C') + public static void julianDayToDate(ExecutionContext ctx) { + val stack = ctx.stack(); + val jd = stack.pop(); + val date = dateOrReflect(ctx, () -> LocalDate.ofEpochDay(jd - 2440588)); + if (date == null) { + return; + } + var y = date.getYear(); + if (y == 0) { + ctx.interpret('r'); + return; + } + if (y < 0) y--; + stack.push(y); + stack.push(date.getMonthValue()); + stack.push(date.getDayOfMonth()); + } + + @Instr('D') + public static void daysBetweenDates(ExecutionContext ctx) { + val stack = ctx.stack(); + val d2 = stack.pop(); + val m2 = stack.pop(); + val y2 = stack.pop(); + val d1 = stack.pop(); + val m1 = stack.pop(); + val y1 = stack.pop(); + if (y1 == 0 || y2 == 0) { + ctx.interpret('r'); + return; + } + val date1 = dateOrReflect(ctx, () -> LocalDate.of(y1, m1, d1)); + if (date1 == null) { + return; + } + val date2 = dateOrReflect(ctx, () -> LocalDate.of(y2, m2, d2)); + if (date2 == null) { + return; + } + stack.push((int) Duration.between(date2.atStartOfDay(), date1.atStartOfDay()).toDays()); + } + + @Instr('J') + public static void dateToJulianDay(ExecutionContext ctx) { + val stack = ctx.stack(); + val d = stack.pop(); + val m = stack.pop(); + var y = stack.pop(); + if (y == 0) { + ctx.interpret('r'); + return; + } + if (y < 0) y++; + int finalY = y; + val date = dateOrReflect(ctx, () -> LocalDate.of(finalY, m, d)); + if (date == null) { + return; + } + val x = (int) JulianFields.JULIAN_DAY.getFrom(date); + System.out.println(x); + stack.push(x); + } + + @Instr('T') + public static void yearPlusDayToDate(ExecutionContext ctx) { + val stack = ctx.stack(); + val day = stack.pop() + 1; + val year = stack.pop(); + if (year == 0) { + ctx.interpret('r'); + return; + } + val date = dateOrReflect(ctx, () -> LocalDate.ofYearDay(year, day)); + if (date == null) { + return; + } + stack.push(date.getYear()); + stack.push(date.getMonthValue()); + stack.push(date.getDayOfMonth()); + } + + @Instr('W') + public static void dayOfWeek(ExecutionContext ctx) { + val stack = ctx.stack(); + val d = stack.pop(); + val m = stack.pop(); + val y = stack.pop(); + if (y == 0) { + ctx.interpret('r'); + return; + } + val date = dateOrReflect(ctx, () -> LocalDate.of(y, m, d)); + if (date == null) { + return; + } + stack.push(date.getDayOfWeek().getValue() - 1); + } + + @Instr('Y') + public static void dayOfYear(ExecutionContext ctx) { + val stack = ctx.stack(); + val d = stack.pop(); + val m = stack.pop(); + val y = stack.pop(); + if (y == 0) { + ctx.interpret('r'); + return; + } + val date = dateOrReflect(ctx, () -> LocalDate.of(y, m, d)); + if (date == null) { + return; + } + stack.push(date.getDayOfYear() - 1); + } + + private interface DateSupplier { + LocalDate supply() throws DateTimeException; + } + + private static LocalDate dateOrReflect(ExecutionContext ctx, DateSupplier supplier) { + try { + return supplier.supply(); + } catch (DateTimeException e) { + ctx.interpret('r'); + return null; + } + } +} From 4f0088a88c8cddf1770fe01a3126176a9251e170 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 22 Dec 2022 20:53:52 +0100 Subject: [PATCH 33/35] fixed some sysinfo bugs, and added environment variable privacy --- src/main/java/com/falsepattern/jfunge/Main.java | 2 ++ .../jfunge/interpreter/FeatureSet.java | 1 + .../jfunge/interpreter/Interpreter.java | 16 ++++++++++------ .../jfunge/interpreter/instructions/Funge98.java | 5 +++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/falsepattern/jfunge/Main.java b/src/main/java/com/falsepattern/jfunge/Main.java index 0a7452a..7d5683e 100644 --- a/src/main/java/com/falsepattern/jfunge/Main.java +++ b/src/main/java/com/falsepattern/jfunge/Main.java @@ -43,6 +43,7 @@ public static void main(String[] args) throws IOException, ParseException { options.addOptionGroup(masterGroup); options.addOption(null, "trefunge", false, "Enable 3D (Trefunge) mode. By default, the interpreter emulates 2D Befunge for compatibility."); options.addOption("t", "concurrent", false, "Enables the Concurrent Funge extension (t instruction). Buggy programs can potentially forkbomb the interpreter."); + options.addOption(null, "env", false, "Allows the interpreter to access the environment variables of the host system."); options.addOption(null, "syscall", false, "Enables the syscall feature (= instruction). This is a very dangerous permission to grant, it can call any arbitrary program on your system."); options.addOption(Option.builder("i") .longOpt("readperm") @@ -105,6 +106,7 @@ public static void main(String[] args) throws IOException, ParseException { val featureSet = FeatureSet.builder(); featureSet.trefunge(cmd.hasOption("trefunge")); featureSet.concurrent(cmd.hasOption("t")); + featureSet.environment(cmd.hasOption("env")); featureSet.allowedInputFiles(cmd.getOptionValues("i")); featureSet.allowedOutputFiles(cmd.getOptionValues("o")); featureSet.perl(cmd.hasOption("perl")); diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java b/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java index f54d2b2..1da9ae1 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/FeatureSet.java @@ -11,6 +11,7 @@ public class FeatureSet { public final String[] allowedInputFiles; public final String[] allowedOutputFiles; public final boolean concurrent; + public final boolean environment; public final long maxIter; public final boolean perl; diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java index 9728058..e080c71 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java @@ -25,7 +25,6 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -75,6 +74,9 @@ public boolean writeFile(String file, byte[] data) throws IOException { private final boolean unrestrictedOutput; private final Path[] allowedOutputPaths; + @Getter + private final Map env; + private final TIntObjectMap> globals = new TIntObjectHashMap<>(); private Integer exitCode = null; @@ -132,6 +134,13 @@ public Interpreter(String[] args, InputStream input, OutputStream output, FileIO } envFlags = env; + if (featureSet.environment) { + this.env = new HashMap<>(System.getenv()); + } else { + this.env = new HashMap<>(); + } + this.env.put("JFUNGE_ENV", featureSet.environment ? "PASS" : "BLOCK"); + if (!featureSet.perl) { fingerprintBlackList.add(PERL.INSTANCE.code()); } @@ -298,11 +307,6 @@ public void step(IP ip) { } while (p == ' '); } - @Override - public Map env() { - return Collections.unmodifiableMap(System.getenv()); - } - @Override public int input(boolean stagger) { var value = -1; 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 f9885d5..ab05c9e 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -470,17 +470,18 @@ public static void sysInfo(ExecutionContext ctx) { val n = s.pop(); val sizes = ctx.IP().stackStack().stackSizes(); //20 envs + s.push(0); for (Map.Entry entry : ctx.env().entrySet()) { String key = entry.getKey(); String value = entry.getValue(); s.pushString(key + "=" + value); } //19 args + s.push(0); + s.push(0); for (String s1 : ctx.args()) { s.pushString(s1); } - s.push(0); - s.push(0); //18 sizes of stack stack for (int size : sizes) { s.push(size); From 08f920f9ba724b79bc0444ceeab732c96e5da349 Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 22 Dec 2022 21:04:51 +0100 Subject: [PATCH 34/35] implemented EVAR --- .../jfunge/interpreter/ExecutionContext.java | 2 + .../jfunge/interpreter/Interpreter.java | 6 ++ .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/EVAR.java | 63 +++++++++++++++++++ 4 files changed, 73 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/EVAR.java diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java b/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java index 1aa38d7..11658f4 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/ExecutionContext.java @@ -41,6 +41,8 @@ public interface ExecutionContext { Map env(); + List envKeys(); + int input(boolean stagger); OutputStream output(); diff --git a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java index e080c71..e34b325 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/Interpreter.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TreeMap; @Accessors(fluent = true) public class Interpreter implements ExecutionContext { @@ -77,6 +78,9 @@ public boolean writeFile(String file, byte[] data) throws IOException { @Getter private final Map env; + @Getter + private final List envKeys; + private final TIntObjectMap> globals = new TIntObjectHashMap<>(); private Integer exitCode = null; @@ -140,6 +144,8 @@ public Interpreter(String[] args, InputStream input, OutputStream output, FileIO this.env = new HashMap<>(); } this.env.put("JFUNGE_ENV", featureSet.environment ? "PASS" : "BLOCK"); + envKeys = new ArrayList<>(); + envKeys.addAll(this.env.keySet()); if (!featureSet.perl) { fingerprintBlackList.add(PERL.INSTANCE.code()); 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 ab05c9e..22fb909 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -5,6 +5,7 @@ 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.EVAR; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPDP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPSP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.HRTI; @@ -49,6 +50,7 @@ public class Funge98 implements InstructionSet { addFingerprint(BASE.INSTANCE); addFingerprint(CPLI.INSTANCE); addFingerprint(DATE.INSTANCE); + addFingerprint(EVAR.INSTANCE); addFingerprint(FPSP.INSTANCE); addFingerprint(FPDP.INSTANCE); addFingerprint(HRTI.INSTANCE); 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 new file mode 100644 index 0000000..2f23ec8 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/EVAR.java @@ -0,0 +1,63 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; +import lombok.NoArgsConstructor; +import lombok.val; + +@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) +public class EVAR implements Fingerprint { + public static final EVAR INSTANCE = new EVAR(); + @Override + public int code() { + return 0x45564152; + } + + @Instr('G') + public static void getEnvironmentVariable(ExecutionContext ctx) { + val stack = ctx.stack(); + val key = stack.popString(); + val value = ctx.env().get(key); + if (value == null) { + ctx.interpret('r'); + return; + } else { + stack.pushString(value); + } + } + + @Instr('N') + public static void getEnvironmentVariableCount(ExecutionContext ctx) { + ctx.stack().push(ctx.envKeys().size()); + } + + @Instr('P') + public static void setEnvironmentVariable(ExecutionContext ctx) { + val stack = ctx.stack(); + val keyValuePar = stack.popString(); + val equalsIndex = keyValuePar.indexOf('='); + if (equalsIndex == -1) { + ctx.interpret('r'); + return; + } + val key = keyValuePar.substring(0, equalsIndex); + val value = keyValuePar.substring(equalsIndex + 1); + if (!ctx.env().containsKey(key)) { + ctx.envKeys().add(key); + } + ctx.env().put(key, value); + } + + @Instr('V') + public static void getEnvironmentVariableAtIndex(ExecutionContext ctx) { + val stack = ctx.stack(); + val index = stack.pop(); + val keys = ctx.envKeys(); + if (index < 0 || index >= keys.size()) { + ctx.interpret('r'); + return; + } + val key = keys.get(index); + stack.pushString(key + "=" + ctx.env().get(key)); + } +} From 9886d2e302ecbf3f6c413148d71e555e2298057c Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Thu, 22 Dec 2022 21:17:21 +0100 Subject: [PATCH 35/35] implemented FIXP --- .../interpreter/instructions/Funge98.java | 2 + .../instructions/fingerprints/FIXP.java | 148 ++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FIXP.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 22fb909..78c5038 100644 --- a/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/Funge98.java @@ -6,6 +6,7 @@ import com.falsepattern.jfunge.interpreter.instructions.fingerprints.CPLI; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.DATE; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.EVAR; +import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FIXP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPDP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.FPSP; import com.falsepattern.jfunge.interpreter.instructions.fingerprints.HRTI; @@ -53,6 +54,7 @@ public class Funge98 implements InstructionSet { addFingerprint(EVAR.INSTANCE); addFingerprint(FPSP.INSTANCE); addFingerprint(FPDP.INSTANCE); + addFingerprint(FIXP.INSTANCE); addFingerprint(HRTI.INSTANCE); addFingerprint(MODE.INSTANCE); addFingerprint(MODU.INSTANCE); 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 new file mode 100644 index 0000000..d891ea5 --- /dev/null +++ b/src/main/java/com/falsepattern/jfunge/interpreter/instructions/fingerprints/FIXP.java @@ -0,0 +1,148 @@ +package com.falsepattern.jfunge.interpreter.instructions.fingerprints; + +import com.falsepattern.jfunge.interpreter.ExecutionContext; +import com.falsepattern.jfunge.interpreter.instructions.Fingerprint; +import lombok.NoArgsConstructor; +import lombok.val; + +import java.util.Random; + +@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) +public class FIXP implements Fingerprint { + public static final FIXP INSTANCE = new FIXP(); + @Override + public int code() { + return 0x46495850; + } + + @Instr('A') + public static void and(ExecutionContext ctx) { + val stack = ctx.stack(); + val b = stack.pop(); + val a = stack.pop(); + stack.push(a & b); + } + + @Instr('B') + public static void acos(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + val result = Math.toDegrees(Math.acos(a / 10000D)); + if (Double.isNaN(result)) { + ctx.interpret('r'); + } else { + stack.push((int) (result * 10000)); + } + } + + @Instr('C') + public static void cos(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + stack.push((int)(Math.cos(Math.toRadians(a / 10000D)) * 10000)); + } + + @Instr('D') + public static void random(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + stack.push(new Random().nextInt(3)); + } + + @Instr('I') + public static void sin(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + stack.push((int)(Math.sin(Math.toRadians(a / 10000D)) * 10000)); + } + + @Instr('J') + public static void asin(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + val result = Math.toDegrees(Math.asin(a / 10000D)); + if (Double.isNaN(result)) { + ctx.interpret('r'); + } else { + stack.push((int) (result * 10000)); + } + } + + @Instr('N') + public static void negate(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + stack.push(-a); + } + + @Instr('O') + public static void or(ExecutionContext ctx) { + val stack = ctx.stack(); + val b = stack.pop(); + val a = stack.pop(); + stack.push(a | b); + } + + @Instr('P') + public static void mulPi(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + stack.push((int)(a * Math.PI)); + } + + @Instr('Q') + public static void sqrt(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + stack.push((int)Math.sqrt(a)); + } + + @Instr('R') + public static void pow(ExecutionContext ctx) { + val stack = ctx.stack(); + val b = stack.pop(); + val a = stack.pop(); + val result = Math.pow(a, b); + if (Double.isNaN(result) || Double.isInfinite(result)) { + ctx.interpret('r'); + } else { + stack.push((int) result); + } + } + + @Instr('S') + public static void sign(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + stack.push(Integer.compare(a, 0)); + } + + @Instr('T') + public static void tan(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + stack.push((int)(Math.tan(Math.toRadians(a / 10000D)) * 10000)); + } + + @Instr('U') + public static void atan(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + stack.push((int)(Math.toDegrees(Math.atan(a / 10000D)) * 10000)); + } + + @Instr('V') + public static void abs(ExecutionContext ctx) { + val stack = ctx.stack(); + val a = stack.pop(); + stack.push(Math.abs(a)); + } + + @Instr('X') + public static void xor(ExecutionContext ctx) { + val stack = ctx.stack(); + val b = stack.pop(); + val a = stack.pop(); + stack.push(a ^ b); + } +}