Add files via upload

This commit is contained in:
nora 2021-05-27 16:59:47 +02:00 committed by GitHub
parent 99b5e92e1c
commit 8a4ccb8b2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 718 additions and 0 deletions

View file

@ -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
```

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.nilstrieb</groupId>
<artifactId>GRSBPL</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>14</maven.compiler.source>
<maven.compiler.target>14</maven.compiler.target>
</properties>
</project>

View file

@ -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<Integer, Integer, Integer> 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);
}
}

View file

@ -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<String, Runnable> 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<String, Integer> variables;
private HashMap<String, Integer> labels;
private char[] program;
private int i;
private int lineNumber;
public static void main(String[] args) {
if (args.length < 2) {
System.err.println("usage: <filename>");
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;
}
}

View file

@ -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());
}
}
}

View file

@ -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();
}
}
}

View file

@ -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