From 8a4ccb8b2bfda5d446239100c1dbf1ebc53ffc5f Mon Sep 17 00:00:00 2001
From: nils <48135649+Nilstrieb@users.noreply.github.com>
Date: Thu, 27 May 2021 16:59:47 +0200
Subject: [PATCH] Add files via upload
---
StackBasedLanguage/README.md | 92 ++++++
StackBasedLanguage/pom.xml | 30 ++
.../src/main/java/IntStack.java | 63 ++++
.../src/main/java/Interpreter.java | 295 ++++++++++++++++++
.../src/test/java/IntStackTest.java | 41 +++
.../src/test/java/InterpreterTest.java | 178 +++++++++++
.../src/test/resources/fizzbuzz.grsbpl | 19 ++
7 files changed, 718 insertions(+)
create mode 100644 StackBasedLanguage/README.md
create mode 100644 StackBasedLanguage/pom.xml
create mode 100644 StackBasedLanguage/src/main/java/IntStack.java
create mode 100644 StackBasedLanguage/src/main/java/Interpreter.java
create mode 100644 StackBasedLanguage/src/test/java/IntStackTest.java
create mode 100644 StackBasedLanguage/src/test/java/InterpreterTest.java
create mode 100644 StackBasedLanguage/src/test/resources/fizzbuzz.grsbpl
diff --git a/StackBasedLanguage/README.md b/StackBasedLanguage/README.md
new file mode 100644
index 0000000..aeaa21e
--- /dev/null
+++ b/StackBasedLanguage/README.md
@@ -0,0 +1,92 @@
+# GRSBPL - Generic Random Stack Based Programming Language
+
+uses some form of reverse polish notation
+
+```
+1 5 * 5 +
+> 10
+```
+
+There is a stack and variables. Operations are done on the stack, and you can store results in variables (a bit like in
+the JVM). The stack contains integer values. Floating point numbers are not supported.
+
+When the program finishes (run to the end of the program), the last value on the stack is returned. If the stack is
+clear, 0 is always returned. If there is an error during execution, -1 is returned along with an error message to
+stderr.
+
+## Operators and keywords:
+
+* any number `n` -> push the numeric value of n
+* any character `'c'` -> push c as its escaped ascii value
+* `+` -> add two values on the stack, pops both and pushes the result
+* `-` -> subtract two values on the stack, pops both and pushes the result
+* `*` -> multiply two values on the stack, pops both and pushes the result
+* `/` -> divide, pops both and pushes the result
+* `%` -> mod, pops both and pushes the result
+* `not` -> invert stack value (!=0 -> 0, 0 -> 1)
+* `swap` -> swaps the 2 top stack values
+* `out` -> pop and output it to the console as ascii
+* `nount` -> pop and output as a number to the console
+* `in` -> push input char as ascii to the stack
+* `# comment #` text between # is ignored
+* `# comment\n` text after # is ignored
+* `&word` -> pop and store it in a variable
+* `@word` -> load variable and push it, does not consume the variable
+* `:indent` -> define a label
+* `goto ident` -> goto a label if the value on the stack is !=0, peek
+
+Identifier: \w
+
+Character escape sequences:
+\n, \r, \\, \0, \', \b, \f
+
+## Examples:
+
+FizzBuzz
+
+```grsbpl
+1 &i # init loop counter
+:start # set start label
+@i 100 - not goto exit # if i is 100, exit
+@i 15 % not goto print_fizz_buzz # fizzbuzz
+@i 5 % not goto print_buzz # buzz
+@i 3 % not goto print_fizz # fizz
+@i nout '\n' out # normal number
+:end # go back here after printing
+@i 1 + &i # increment i
+1 goto start # go back to the start
+
+:print_fizz_buzz
+ 'F' out 'i' out 'z' out 'z' out 'B' out 'u' out 'z' out 'z' out '\n' out
+ goto end
+:print_fizz
+ 'F' out 'i' out 'z' out 'z' out '\n' out
+ goto end
+:print_buzz
+ 'B' out 'u' out 'z' out 'z' out '\n' out
+ goto end
+
+:exit 0
+```
+
+## Some Tips
+
+* Increment a variable:
+ `@i 1 + &i`
+* Pop a value from the stack and discard it:
+ `&dev_null` (just use any unused variable)
+* Goto if equal
+ `@i 100 - not goto finished`
+* Goto not equal
+ `@i 100 - goto finished`
+* Exit the program
+ `... goto exit ... :exit 0`
+* Exit with exit code depending on the branch
+ ```grsbpl
+ ...
+ 69 swap goto exit # push 69 to the 2nd stack position
+ ...
+ 5 swap goto exit # push 5 to the 2nd stack position
+ ...
+ :exit &del # pop the top stack value to expose the pushed value
+ ```
\ No newline at end of file
diff --git a/StackBasedLanguage/pom.xml b/StackBasedLanguage/pom.xml
new file mode 100644
index 0000000..5fb4e18
--- /dev/null
+++ b/StackBasedLanguage/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+
+ com.github.nilstrieb
+ GRSBPL
+ 1.0-SNAPSHOT
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.6.1
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.6.1
+ test
+
+
+
+
+ 14
+ 14
+
+
+
\ No newline at end of file
diff --git a/StackBasedLanguage/src/main/java/IntStack.java b/StackBasedLanguage/src/main/java/IntStack.java
new file mode 100644
index 0000000..c6a232c
--- /dev/null
+++ b/StackBasedLanguage/src/main/java/IntStack.java
@@ -0,0 +1,63 @@
+import java.util.OptionalInt;
+import java.util.function.BiFunction;
+
+public class IntStack {
+ private int[] values;
+ private int pointer;
+
+ private static final int INITIAL_CAPACITY = 100;
+
+ public IntStack() {
+ this(INITIAL_CAPACITY);
+ }
+
+ public IntStack(int initialCapacity) {
+ values = new int[initialCapacity];
+ pointer = -1;
+ }
+
+ public void push(int value) {
+ checkResize();
+ values[++pointer] = value;
+ }
+
+ public int pop() {
+ if (pointer == -1) {
+ throw new IndexOutOfBoundsException("Cannot pop below zero");
+ }
+ return values[pointer--];
+ }
+
+ public int peek() {
+ return values[pointer];
+ }
+
+ public OptionalInt tryPop() {
+ if (pointer == -1) {
+ return OptionalInt.empty();
+ } else {
+ return OptionalInt.of(pop());
+ }
+ }
+
+ public void performOn2(BiFunction function) {
+ int val2 = pop();
+ int val1 = pop();
+ push(function.apply(val1, val2));
+ }
+
+ private void checkResize() {
+ if (pointer == values.length - 1) {
+ int[] newValues = new int[values.length * 2];
+ System.arraycopy(values, 0, newValues, 0, values.length);
+ this.values = newValues;
+ }
+ }
+
+ public void swap() {
+ int val1 = pop();
+ int val2 = pop();
+ push(val1);
+ push(val2);
+ }
+}
diff --git a/StackBasedLanguage/src/main/java/Interpreter.java b/StackBasedLanguage/src/main/java/Interpreter.java
new file mode 100644
index 0000000..54bd7c9
--- /dev/null
+++ b/StackBasedLanguage/src/main/java/Interpreter.java
@@ -0,0 +1,295 @@
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+
+public class Interpreter {
+
+ private final Map KEYWORDS = Map.of(
+ "out", this::out,
+ "nout", this::nout,
+ "in", this::in,
+ "goto", this::condGoto,
+ "not", this::not,
+ "swap", this::swap
+ );
+
+ private IntStack stack;
+ private HashMap variables;
+ private HashMap labels;
+ private char[] program;
+ private int i;
+ private int lineNumber;
+
+ public static void main(String[] args) {
+ if (args.length < 2) {
+ System.err.println("usage: ");
+ System.exit(1);
+ }
+ try {
+ String s = Files.readString(Path.of(args[1]));
+ Interpreter interpreter = new Interpreter();
+ int exit = interpreter.run(s.toCharArray());
+ System.exit(exit);
+ } catch (IOException e) {
+ System.err.println("File not found");
+ }
+ }
+
+ public int run(char[] chars) {
+ program = chars;
+ stack = new IntStack();
+ variables = new HashMap<>();
+ labels = new HashMap<>();
+ i = 0;
+ lineNumber = 1;
+
+ firstPass();
+ i = 0;
+ lineNumber = 1;
+
+ while (hasNext()) {
+ try {
+ runStatement();
+ } catch (Exception e) {
+ System.err.println("Exception occurred on line: " + lineNumber);
+ e.printStackTrace();
+ return -1;
+ }
+ }
+
+ return rest();
+ }
+
+ private int rest() {
+ OptionalInt i = stack.tryPop();
+
+ if (i.isEmpty()) {
+ return 0;
+ } else {
+ return i.getAsInt();
+ }
+ }
+
+ private void firstPass() {
+ while (hasNext()) {
+ if (advance() == ':') {
+ label();
+ }
+ }
+ }
+
+ private void runStatement() {
+ switch (advance()) {
+ case '+' -> add();
+ case '-' -> subtract();
+ case '*' -> multiply();
+ case '/' -> divide();
+ case '%' -> modulo();
+ case '\'' -> character();
+ case '\n' -> lineNumber++;
+ case ' ', '\t', '\r' -> {
+ }
+ case '#' -> comment();
+ case '&' -> store();
+ case '@' -> load();
+ case ':' -> ignoreLabel();
+ default -> {
+ if (Character.isDigit(current())) {
+ number();
+ } else {
+ keyword();
+ }
+ }
+ }
+ }
+
+ private void add() {
+ stack.performOn2(Integer::sum);
+ }
+
+ private void subtract() {
+ stack.performOn2((i1, i2) -> i1 - i2);
+ }
+
+ private void multiply() {
+ stack.performOn2((i1, i2) -> i1 * i2);
+ }
+
+ private void divide() {
+ stack.performOn2((i1, i2) -> i1 / i2);
+ }
+
+ private void modulo() {
+ stack.performOn2((i1, i2) -> i1 % i2);
+ }
+
+
+ private void character() {
+ int value = advance();
+ if (value == '\\') {
+ char escaped = advance();
+ value = switch (escaped) {
+ case 'n' -> '\n';
+ case 'r' -> '\r';
+ case '\\' -> '\\';
+ case '0' -> '\0';
+ case '\'' -> '\'';
+ case 'b' -> '\b';
+ case 'f' -> '\f';
+ default -> {
+ System.err.println("Invalid escape sequence: \\" + escaped + " on line " + lineNumber);
+ System.exit(1);
+ throw new IllegalStateException("system exit failed");
+ }
+ };
+ }
+ stack.push(value);
+ consume();
+ }
+
+ private void comment() {
+ while (true) {
+ char next = advance();
+ if (next == '\n') {
+ lineNumber++;
+ break;
+ } else if (next == '#') {
+ break;
+ }
+ }
+ }
+
+ private void store() {
+ whitespace();
+ String name = ident();
+ variables.put(name, stack.pop());
+ }
+
+ private void load() {
+ whitespace();
+ String name = ident();
+ stack.push(variables.get(name));
+ }
+
+ private void label() {
+ whitespace();
+ String name = ident();
+ labels.put(name, i);
+ }
+
+ // consume but don't use
+ private void ignoreLabel() {
+ whitespace();
+ ident();
+ }
+
+ private void number() {
+ String number = String.valueOf(current());
+ while (Character.isDigit(peek())) {
+ number += advance();
+ }
+ stack.push(Integer.parseInt(number));
+ }
+
+ private String ident() {
+ String word = String.valueOf(current());
+
+ while (Character.isAlphabetic(peek()) || Character.isDigit(peek()) || peek() == '_') {
+ word += advance();
+ }
+
+ return word;
+ }
+
+ private void keyword() {
+ String word = ident();
+
+ Runnable r = KEYWORDS.get(word);
+
+ if (r == null) {
+ throw new RuntimeException("Invalid keyword: " + word);
+ }
+ r.run();
+ }
+
+ private void whitespace() {
+ while (Character.isWhitespace(advance())) {
+ if (current() == '\n') {
+ lineNumber++;
+ }
+ }
+ }
+
+ // keywords
+ private void out() {
+ System.out.print((char) stack.pop());
+ }
+
+ private void nout() {
+ System.out.print(stack.pop());
+ }
+
+ private void in() {
+ try {
+ stack.push(System.in.read());
+ } catch (IOException e) {
+ System.err.println("Error reading input on line number " + lineNumber);
+ System.exit(1);
+ }
+ }
+
+ private void condGoto() {
+ whitespace();
+ String label = ident();
+ consume();
+ if (stack.peek() != 0) {
+ Integer index = labels.get(label);
+ if (index == null) {
+ System.err.println("Label :" + label + " not found on line number: " + lineNumber);
+ System.exit(1);
+ }
+ i = index;
+ }
+ }
+
+ private void not() {
+ int value = stack.pop();
+ if (value == 0) {
+ stack.push(1);
+ } else {
+ stack.push(0);
+ }
+ }
+
+ private void swap() {
+ stack.swap();
+ }
+
+ // parsing
+ private char current() {
+ return program[i - 1];
+ }
+
+ private char advance() {
+ if (i == program.length) {
+ return '\0';
+ }
+ return program[i++];
+ }
+
+ private char peek() {
+ if (i == program.length) {
+ return '\0';
+ }
+ return program[i];
+ }
+
+ private void consume() {
+ i++;
+ }
+
+ private boolean hasNext() {
+ return i < program.length;
+ }
+}
\ No newline at end of file
diff --git a/StackBasedLanguage/src/test/java/IntStackTest.java b/StackBasedLanguage/src/test/java/IntStackTest.java
new file mode 100644
index 0000000..099a754
--- /dev/null
+++ b/StackBasedLanguage/src/test/java/IntStackTest.java
@@ -0,0 +1,41 @@
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class IntStackTest {
+
+ @Test
+ void pushPop() {
+ IntStack s = new IntStack();
+
+ s.push(100);
+ s.push(50);
+
+ assertEquals(50, s.pop());
+ assertEquals(100, s.pop());
+
+ assertThrows(IndexOutOfBoundsException.class, s::pop);
+ }
+
+ @Test
+ void applyFunction() {
+ IntStack s = new IntStack();
+ s.push(10);
+ s.push(2);
+ s.performOn2((i1, i2) -> i1 / i2);
+ assertEquals(5, s.pop());
+ assertThrows(IndexOutOfBoundsException.class, s::pop);
+ }
+
+ @Test
+ void resize() {
+ IntStack s = new IntStack();
+ for (int i = 0; i < 1000; i++) {
+ s.push(i);
+ }
+
+ for (int i = 999; i >= 0; i--) {
+ assertEquals(i, s.pop());
+ }
+ }
+}
\ No newline at end of file
diff --git a/StackBasedLanguage/src/test/java/InterpreterTest.java b/StackBasedLanguage/src/test/java/InterpreterTest.java
new file mode 100644
index 0000000..25c2b92
--- /dev/null
+++ b/StackBasedLanguage/src/test/java/InterpreterTest.java
@@ -0,0 +1,178 @@
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class InterpreterTest {
+
+ static Interpreter interpreter;
+
+ @BeforeEach
+ void setup() {
+ interpreter = new Interpreter();
+ }
+
+ @Test
+ void arithmeticOperations() {
+ String program = "1 1 * 2 +";
+ int result = 3;
+ assertEquals(result, interpreter.run(program.toCharArray()));
+ }
+
+ @Test
+ void bigNumbers() {
+ String program = "1000 1234 +";
+ int result = 2234;
+
+ assertEquals(result, interpreter.run(program.toCharArray()));
+ }
+
+ @Test
+ void comment() {
+ String program = "1 # sdkfjsaf se9 83 252h43ui\n 2 # test 5 # +";
+ int result = 3;
+
+ assertEquals(result, interpreter.run(program.toCharArray()));
+ }
+
+ @Test
+ void variables() {
+ String program = "1 &one 2 &two 3 &three 8 @two +";
+ int result = 10;
+ assertEquals(result, interpreter.run(program.toCharArray()));
+ }
+
+ @Test
+ void labels() {
+ String program = "1 :first 2 0";
+ int result = 0;
+ assertEquals(result, interpreter.run(program.toCharArray()));
+ }
+
+ @Test
+ void gotoBack() {
+ String program = "10 &i \n" +
+ ":start \n" +
+ "@i nout '\n' out \n" +
+ "@i 1 - &i \n" +
+ "@i goto start \n" +
+ " 0";
+ int result = 0;
+ assertEquals(result, interpreter.run(program.toCharArray()));
+ }
+
+ @Test
+ void gotoSkip() {
+ String program = "1 :first 0 goto first 1 goto skip 3754 78349758 :skip";
+ int result = 1;
+ assertEquals(result, interpreter.run(program.toCharArray()));
+ }
+
+ @Test
+ void fizzBuzz() throws IOException, URISyntaxException {
+ String program = Files.readString(Path.of(getClass().getResource("fizzbuzz.grsbpl").toURI()));
+ int result = 0;
+ assertEquals(result, interpreter.run(program.toCharArray()));
+ }
+
+ @Test
+ void stackManipulationTest() {
+ String program = "1 2 swap";
+ int result = 1;
+ assertEquals(result, interpreter.run(program.toCharArray()));
+
+ String program2 = "0 not";
+ int result2 = 1;
+ assertEquals(result2, interpreter.run(program2.toCharArray()));
+
+ String program3 = "1 not";
+ int result3 = 0;
+ assertEquals(result3, interpreter.run(program3.toCharArray()));
+ }
+
+ @Test
+ void outTest() {
+ String program = "'\n' '!' 'd' 'l' 'r' 'o' 'w' ' ' 'o' 'l' 'l' 'e' 'h' out out out out out out out out out out out out out 0";
+ int result = 0;
+
+ OutStream o = null;
+ try {
+ o = new OutStream();
+ System.setOut(o);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ assertEquals(result, interpreter.run(program.toCharArray()));
+ assertEquals("hello world!\n", o.getOut());
+ }
+
+ static class OutStream extends PrintStream {
+ private final StringBuilder builder = new StringBuilder();
+
+ public OutStream() throws FileNotFoundException {
+ super(new OutputStream() {
+ @Override
+ public void write(int b) {
+ }
+ });
+ }
+
+ @Override
+ public void print(boolean b) {
+ builder.append(b);
+ }
+
+ @Override
+ public void print(char c) {
+ builder.append(c);
+ }
+
+ @Override
+ public void print(int i) {
+ builder.append(i);
+ }
+
+ @Override
+ public void print(long l) {
+ builder.append(l);
+ }
+
+ @Override
+ public void print(float f) {
+ builder.append(f);
+ }
+
+ @Override
+ public void print(double d) {
+ builder.append(d);
+ }
+
+ @Override
+ public void print(char[] s) {
+ builder.append(s);
+ }
+
+ @Override
+ public void print(String s) {
+ builder.append(s);
+ }
+
+ @Override
+ public void print(Object obj) {
+ super.print(obj);
+ }
+
+ public String getOut() {
+ return builder.toString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/StackBasedLanguage/src/test/resources/fizzbuzz.grsbpl b/StackBasedLanguage/src/test/resources/fizzbuzz.grsbpl
new file mode 100644
index 0000000..d33c1a5
--- /dev/null
+++ b/StackBasedLanguage/src/test/resources/fizzbuzz.grsbpl
@@ -0,0 +1,19 @@
+1 &i # init loop counter
+:start # set start label
+@i 100 - not goto finished # if i is 100, finish
+@i 15 % not goto print_fizz_buzz # fizzbuzz
+@i 5 % not goto print_buzz # buzz
+@i 3 % not goto print_fizz # fizz
+@i nout '\n' out # normal number
+:end # go back here after printing
+@i 1 + &i # increment i
+1 goto start # go back to the start
+
+:print_fizz_buzz
+ 'F' out 'i' out 'z' out 'z' out 'B' out 'u' out 'z' out 'z' out '\n' out goto end
+:print_fizz
+ 'F' out 'i' out 'z' out 'z' out '\n' out goto end
+:print_buzz
+ 'B' out 'u' out 'z' out 'z' out '\n' out goto end
+
+:finished 0
\ No newline at end of file