mirror of
https://github.com/Noratrieb/GRSBPL.git
synced 2026-01-16 12:45:04 +01:00
great commit history
This commit is contained in:
parent
af55c80677
commit
0b96e0dd0d
14 changed files with 1279 additions and 0 deletions
74
src/main/java/com/github/nilstrieb/grsbpl/GrsbplRunner.java
Normal file
74
src/main/java/com/github/nilstrieb/grsbpl/GrsbplRunner.java
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package com.github.nilstrieb.grsbpl;
|
||||||
|
|
||||||
|
import com.github.nilstrieb.grsbpl.language.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class GrsbplRunner {
|
||||||
|
|
||||||
|
private List<String> program;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
if (args.length < 1) {
|
||||||
|
System.err.println("usage: <filename>");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String s = Files.readString(Path.of(args[0]));
|
||||||
|
GrsbplRunner runner = new GrsbplRunner();
|
||||||
|
int exit = runner.run(s);
|
||||||
|
System.exit(exit);
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("File not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int run(String program) {
|
||||||
|
this.program = program.lines().collect(Collectors.toUnmodifiableList());
|
||||||
|
try {
|
||||||
|
List<Token> tokens = new Lexer().lex(program.toCharArray());
|
||||||
|
Interpreter interpreter = new Interpreter();
|
||||||
|
return interpreter.run(tokens);
|
||||||
|
} catch (LexException e) {
|
||||||
|
showError(e.getMessage(), e.getLineNumber(), e.getLineOffset(), e.getLineLength());
|
||||||
|
} catch (RunException e) {
|
||||||
|
showError(e.getMessage(), e.getLineNumber(), e.getLineOffset(), e.getLineLength());
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showError(String message, int line, int offset, int length) {
|
||||||
|
if (length == -1) {
|
||||||
|
length = program.get(line - 1).length() - offset + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.err.println("[GRSBPL Runtime Execution Error]");
|
||||||
|
System.err.println();
|
||||||
|
if (line - 1 > 0) {
|
||||||
|
System.err.println(" " + (line - 1) + " | " + program.get(line - 2));
|
||||||
|
}
|
||||||
|
System.err.println(" " + line + " | " + program.get(line - 1));
|
||||||
|
System.err.println(" " + s(len(line)) + " " + s(offset) + "^".repeat(length - 1));
|
||||||
|
System.err.println(" " + s(len(line)) + " " + s(offset) + message);
|
||||||
|
System.err.println();
|
||||||
|
if (program.size() > line + 1) {
|
||||||
|
System.err.println(" " + (line + 1) + " | " + program.get(line));
|
||||||
|
}
|
||||||
|
if (program.size() > line + 2) {
|
||||||
|
System.err.println(" " + (line + 2) + " | " + program.get(line + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String s(int length) {
|
||||||
|
return " ".repeat(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int len(int i) {
|
||||||
|
return String.valueOf(i).length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package com.github.nilstrieb.grsbpl.language;
|
||||||
|
|
||||||
|
import java.util.OptionalInt;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stack that exclusively holds integers
|
||||||
|
*/
|
||||||
|
public class IntStack {
|
||||||
|
private int[] values;
|
||||||
|
private int pointer;
|
||||||
|
|
||||||
|
private static final int INITIAL_CAPACITY = 64;
|
||||||
|
|
||||||
|
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 boolean isEmpty() {
|
||||||
|
return pointer < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int peek() {
|
||||||
|
return values[pointer];
|
||||||
|
}
|
||||||
|
|
||||||
|
public OptionalInt tryPop() {
|
||||||
|
if (pointer == -1) {
|
||||||
|
return OptionalInt.empty();
|
||||||
|
} else {
|
||||||
|
return OptionalInt.of(pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void apply2(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,352 @@
|
||||||
|
package com.github.nilstrieb.grsbpl.language;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static com.github.nilstrieb.grsbpl.language.TokenType.*;
|
||||||
|
|
||||||
|
public class Interpreter {
|
||||||
|
|
||||||
|
private static final int STACK_LIMIT = 1_000_000;
|
||||||
|
|
||||||
|
private Stack<StackFrame> frames;
|
||||||
|
private Map<String, Integer> labels;
|
||||||
|
private Map<String, FunctionData> functions;
|
||||||
|
private List<Token> program;
|
||||||
|
private int position;
|
||||||
|
|
||||||
|
public int run(List<Token> chars) {
|
||||||
|
program = chars;
|
||||||
|
frames = new Stack<>();
|
||||||
|
frames.push(new StackFrame());
|
||||||
|
functions = new HashMap<>();
|
||||||
|
labels = new HashMap<>();
|
||||||
|
position = 0;
|
||||||
|
|
||||||
|
firstPass();
|
||||||
|
position = 0;
|
||||||
|
|
||||||
|
while (hasNext()) {
|
||||||
|
executeNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
return rest();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntStack stack() {
|
||||||
|
return frames.peek().getStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Integer> variables() {
|
||||||
|
return frames.peek().getVariables();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int rest() {
|
||||||
|
OptionalInt i = stack().tryPop();
|
||||||
|
|
||||||
|
if (i.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return i.getAsInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void firstPass() {
|
||||||
|
while (hasNext()) {
|
||||||
|
TokenType type = advance().getType();
|
||||||
|
if (type == COLUMN) {
|
||||||
|
labels.put(expect(IDENTIFIER).getStringValue(), position);
|
||||||
|
} else if (type == FUNCTION) {
|
||||||
|
FunctionData fn = functionHeader();
|
||||||
|
functions.put(fn.name, fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FunctionData functionHeader() {
|
||||||
|
String name = expect(IDENTIFIER).getStringValue();
|
||||||
|
int paramCount = expect(CHARACTER).getIntValue();
|
||||||
|
return new FunctionData(position, paramCount, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeNext() {
|
||||||
|
switch (peek().getType()) {
|
||||||
|
// values
|
||||||
|
case CHARACTER -> number();
|
||||||
|
case CHAR -> character();
|
||||||
|
case AMPERSAND -> store();
|
||||||
|
case AT -> load();
|
||||||
|
// binary operators
|
||||||
|
case PLUS -> add();
|
||||||
|
case MINUS -> subtract();
|
||||||
|
case STAR -> multiply();
|
||||||
|
case SLASH -> divide();
|
||||||
|
case PERCENT -> modulo();
|
||||||
|
case BNOT -> bnot();
|
||||||
|
case AND -> and();
|
||||||
|
case OR -> or();
|
||||||
|
case XOR -> xor();
|
||||||
|
// other operators
|
||||||
|
case NOT -> not();
|
||||||
|
case DUP -> dup();
|
||||||
|
case SWAP -> swap();
|
||||||
|
case POP -> pop();
|
||||||
|
// io
|
||||||
|
case OUT -> out();
|
||||||
|
case NOUT -> nout();
|
||||||
|
case IN -> in();
|
||||||
|
// control flow
|
||||||
|
case COLUMN -> ignoreLabel();
|
||||||
|
case GOTO -> condGoto();
|
||||||
|
case FUNCTION -> functionHeader();
|
||||||
|
case IDENTIFIER -> callFunction();
|
||||||
|
case RETURN -> returnFn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///// values
|
||||||
|
|
||||||
|
private void number() {
|
||||||
|
Token number = advance();
|
||||||
|
stack().push(number.getIntValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void character() {
|
||||||
|
Token character = advance();
|
||||||
|
stack().push((char) character.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void store() {
|
||||||
|
consume(); // &
|
||||||
|
Token variable = expect(IDENTIFIER);
|
||||||
|
String name = variable.getStringValue();
|
||||||
|
variables().put(name, stack().pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load() {
|
||||||
|
consume(); // @
|
||||||
|
Token variable = expect(IDENTIFIER);
|
||||||
|
String name = variable.getStringValue();
|
||||||
|
stack().push(variables().get(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
///// binary operators
|
||||||
|
|
||||||
|
private void add() {
|
||||||
|
consume();
|
||||||
|
stack().apply2(Integer::sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void subtract() {
|
||||||
|
consume();
|
||||||
|
stack().apply2((i1, i2) -> i1 - i2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void multiply() {
|
||||||
|
consume();
|
||||||
|
stack().apply2((i1, i2) -> i1 * i2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void divide() {
|
||||||
|
consume();
|
||||||
|
stack().apply2((i1, i2) -> i1 / i2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void modulo() {
|
||||||
|
consume();
|
||||||
|
stack().apply2((i1, i2) -> i1 % i2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bnot() {
|
||||||
|
consume();
|
||||||
|
int value = ~stack().pop();
|
||||||
|
stack().push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void and() {
|
||||||
|
consume();
|
||||||
|
stack().apply2((i1, i2) -> i1 & i2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void or() {
|
||||||
|
consume();
|
||||||
|
stack().apply2((i1, i2) -> i1 | i2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void xor() {
|
||||||
|
consume();
|
||||||
|
stack().apply2((i1, i2) -> i1 ^ i2);
|
||||||
|
}
|
||||||
|
|
||||||
|
///// other operators
|
||||||
|
|
||||||
|
private void not() {
|
||||||
|
consume();
|
||||||
|
int value = stack().pop();
|
||||||
|
if (value == 0) {
|
||||||
|
stack().push(1);
|
||||||
|
} else {
|
||||||
|
stack().push(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dup() {
|
||||||
|
consume();
|
||||||
|
int value = stack().peek();
|
||||||
|
stack().push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void swap() {
|
||||||
|
consume();
|
||||||
|
stack().swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pop() {
|
||||||
|
consume();
|
||||||
|
if (stack().isEmpty()) {
|
||||||
|
throw runException("Cannot pop empty stack");
|
||||||
|
}
|
||||||
|
stack().pop(); // checked pop
|
||||||
|
}
|
||||||
|
|
||||||
|
///// IO
|
||||||
|
|
||||||
|
private void out() {
|
||||||
|
consume();
|
||||||
|
System.out.print((char) stack().pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nout() {
|
||||||
|
consume();
|
||||||
|
System.out.print(stack().pop());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void in() {
|
||||||
|
consume();
|
||||||
|
try {
|
||||||
|
stack().push(System.in.read());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw runException("[VM] - Error reading input");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///// control flow
|
||||||
|
|
||||||
|
private void ignoreLabel() {
|
||||||
|
consume();
|
||||||
|
expect(IDENTIFIER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void condGoto() {
|
||||||
|
consume();
|
||||||
|
String label = expect(IDENTIFIER).getStringValue();
|
||||||
|
if (stack().peek() != 0) {
|
||||||
|
Integer index = labels.get(label);
|
||||||
|
if (index == null) {
|
||||||
|
throw runException("Label '" + label + "' not found");
|
||||||
|
}
|
||||||
|
position = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void callFunction() {
|
||||||
|
String name = advance().getStringValue();
|
||||||
|
FunctionData p = functions.get(name);
|
||||||
|
if (p != null) {
|
||||||
|
call(p);
|
||||||
|
} else {
|
||||||
|
throw runException("Function '" + name + "' not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void call(FunctionData fn) {
|
||||||
|
if (frames.size() > STACK_LIMIT) {
|
||||||
|
throw runException("Stackoverflow, limit of " + STACK_LIMIT + " stack frames reached.");
|
||||||
|
}
|
||||||
|
|
||||||
|
frames.peek().setPosition(position);
|
||||||
|
position = fn.index;
|
||||||
|
IntStack temp = new IntStack();
|
||||||
|
for (int i = 0; i < fn.paramCount; i++) {
|
||||||
|
temp.push(stack().pop());
|
||||||
|
}
|
||||||
|
frames.push(new StackFrame());
|
||||||
|
for (int i = 0; i < fn.paramCount; i++) {
|
||||||
|
stack().push(temp.pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void returnFn() {
|
||||||
|
consume();
|
||||||
|
OptionalInt returnValue = frames.pop().getStack().tryPop();
|
||||||
|
if (returnValue.isEmpty()) {
|
||||||
|
throw runException("Function has to return some value, but no value was found on the stack");
|
||||||
|
}
|
||||||
|
if (frames.isEmpty()) {
|
||||||
|
throw runException("Tried to return outside of function, probably forgot to skip a function");
|
||||||
|
}
|
||||||
|
stack().push(returnValue.getAsInt());
|
||||||
|
position = frames.peek().getPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
///// parsing helper methods
|
||||||
|
|
||||||
|
private Token expect(TokenType type) {
|
||||||
|
if (peek().getType() == type) {
|
||||||
|
return advance();
|
||||||
|
} else {
|
||||||
|
throw runException("Excepted token '" + type + "' but found '" + peek().getType() + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token advance() {
|
||||||
|
if (position == program.size()) {
|
||||||
|
return new Token(EOF);
|
||||||
|
}
|
||||||
|
return program.get(position++);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token peek() {
|
||||||
|
if (position == program.size()) {
|
||||||
|
return new Token(EOF);
|
||||||
|
}
|
||||||
|
return program.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void consume() {
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasNext() {
|
||||||
|
return position < program.size() - 1; // last token is EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
private RunException runException(String message) {
|
||||||
|
Token last = program.get(position - 1);
|
||||||
|
int length;
|
||||||
|
if (peek().getLineNumber() == last.getLineNumber()) {
|
||||||
|
length = peek().getLineOffset() - last.getLineOffset();
|
||||||
|
} else {
|
||||||
|
// length cannot be known, make it -1, the error message writer with access to the source code will figure it out
|
||||||
|
length = -1;
|
||||||
|
}
|
||||||
|
return new RunException(message, last.getLineNumber(), last.getLineOffset(), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The values for a function
|
||||||
|
*/
|
||||||
|
static class FunctionData {
|
||||||
|
private final String name;
|
||||||
|
public int index;
|
||||||
|
public int paramCount;
|
||||||
|
|
||||||
|
public FunctionData(int i, int paramCount, String name) {
|
||||||
|
this.index = i;
|
||||||
|
this.paramCount = paramCount;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.github.nilstrieb.grsbpl.language;
|
||||||
|
|
||||||
|
public class LexException extends RuntimeException {
|
||||||
|
private final int lineNumber;
|
||||||
|
private final int lineOffset;
|
||||||
|
private final int lineLength;
|
||||||
|
|
||||||
|
public LexException(String message, int lineNumber, int lineOffset, int lineLength) {
|
||||||
|
super(message);
|
||||||
|
this.lineNumber = lineNumber;
|
||||||
|
this.lineOffset = lineOffset;
|
||||||
|
this.lineLength = lineLength;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLineNumber() {
|
||||||
|
return lineNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLineOffset() {
|
||||||
|
return lineOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLineLength() {
|
||||||
|
return lineLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
193
src/main/java/com/github/nilstrieb/grsbpl/language/Lexer.java
Normal file
193
src/main/java/com/github/nilstrieb/grsbpl/language/Lexer.java
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
package com.github.nilstrieb.grsbpl.language;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.github.nilstrieb.grsbpl.language.TokenType.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Lexer lexes the input and transforms it into tokens that the interpreter can then run
|
||||||
|
* Makes everything a lot easier
|
||||||
|
*/
|
||||||
|
public class Lexer {
|
||||||
|
private static final Map<String, TokenType> KEYWORDS = new HashMap<>();
|
||||||
|
|
||||||
|
private char[] program;
|
||||||
|
private List<Token> tokens;
|
||||||
|
private int position;
|
||||||
|
private int lineNumber;
|
||||||
|
private int lineOffset;
|
||||||
|
private int offsetLock;
|
||||||
|
|
||||||
|
static {
|
||||||
|
KEYWORDS.put("out", OUT);
|
||||||
|
KEYWORDS.put("nout", NOUT);
|
||||||
|
KEYWORDS.put("in", IN);
|
||||||
|
KEYWORDS.put("goto", GOTO);
|
||||||
|
KEYWORDS.put("not", NOT);
|
||||||
|
KEYWORDS.put("swap", SWAP);
|
||||||
|
KEYWORDS.put("bnot", BNOT);
|
||||||
|
KEYWORDS.put("and", AND);
|
||||||
|
KEYWORDS.put("or", OR);
|
||||||
|
KEYWORDS.put("xor", XOR);
|
||||||
|
KEYWORDS.put("dup", DUP);
|
||||||
|
KEYWORDS.put("pop", POP);
|
||||||
|
KEYWORDS.put("function", FUNCTION);
|
||||||
|
KEYWORDS.put("return", RETURN);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<Token> lex(char[] chars) {
|
||||||
|
program = chars;
|
||||||
|
position = 0;
|
||||||
|
tokens = new ArrayList<>();
|
||||||
|
lineOffset = 0;
|
||||||
|
lineNumber = 1;
|
||||||
|
|
||||||
|
while (hasNext()) {
|
||||||
|
try {
|
||||||
|
next();
|
||||||
|
} catch (LexException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw lexException("Unkown Syntax Error. " + e.getClass().getName() + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add(EOF);
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void next() {
|
||||||
|
lockOffset();
|
||||||
|
char next = advance();
|
||||||
|
switch (next) {
|
||||||
|
case '+' -> add(PLUS);
|
||||||
|
case '-' -> add(MINUS);
|
||||||
|
case '*' -> add(STAR);
|
||||||
|
case '/' -> add(SLASH);
|
||||||
|
case '%' -> add(PERCENT);
|
||||||
|
case '&' -> add(AMPERSAND);
|
||||||
|
case '@' -> add(AT);
|
||||||
|
case ':' -> add(COLUMN);
|
||||||
|
case '\'' -> character();
|
||||||
|
case ' ', '\t', '\r', '\n' -> {
|
||||||
|
}
|
||||||
|
case '#' -> comment();
|
||||||
|
default -> {
|
||||||
|
if (Character.isDigit(next)) {
|
||||||
|
number();
|
||||||
|
} else {
|
||||||
|
ident();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void character() {
|
||||||
|
char 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 -> throw new LexException("Invalid escape sequence: \\" + escaped, lineNumber, offsetLock, lineOffset - offsetLock);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
add(CHAR, value);
|
||||||
|
consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void comment() {
|
||||||
|
while (true) {
|
||||||
|
char next = advance();
|
||||||
|
if (next == '\n') {
|
||||||
|
break;
|
||||||
|
} else if (next == '#') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ident() {
|
||||||
|
StringBuilder text = new StringBuilder(String.valueOf(last()));
|
||||||
|
while (isAlphaNumeric(peek())) {
|
||||||
|
text.append(advance());
|
||||||
|
}
|
||||||
|
TokenType type = KEYWORDS.get(text.toString());
|
||||||
|
if (type == null) {
|
||||||
|
add(IDENTIFIER, text.toString());
|
||||||
|
} else {
|
||||||
|
add(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void number() {
|
||||||
|
StringBuilder number = new StringBuilder(String.valueOf(last()));
|
||||||
|
while (Character.isDigit(peek())) {
|
||||||
|
number.append(advance());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int value = Integer.parseInt(number.toString());
|
||||||
|
add(CHARACTER, value);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw lexException("Value not an integer: " + number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAlphaNumeric(char c) {
|
||||||
|
return Character.isAlphabetic(c) || Character.isDigit(c) || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasNext() {
|
||||||
|
return position < program.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void consume() {
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private char last() {
|
||||||
|
return program[position - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
private char peek() {
|
||||||
|
if (hasNext()) {
|
||||||
|
return program[position];
|
||||||
|
} else {
|
||||||
|
return '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private char advance() {
|
||||||
|
lineOffset++;
|
||||||
|
char c = program[position++];
|
||||||
|
if (c == '\n') {
|
||||||
|
lineNumber++;
|
||||||
|
lineOffset = 0;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lockOffset() {
|
||||||
|
offsetLock = lineOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LexException lexException(String message) {
|
||||||
|
throw new LexException(message, lineNumber, offsetLock, lineOffset - offsetLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(TokenType tokenType) {
|
||||||
|
tokens.add(new Token(tokenType, lineNumber, offsetLock));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(TokenType tokenType, Object value) {
|
||||||
|
tokens.add(new Token(tokenType, value, lineNumber, offsetLock));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.github.nilstrieb.grsbpl.language;
|
||||||
|
|
||||||
|
public class RunException extends RuntimeException {
|
||||||
|
private final int lineNumber;
|
||||||
|
private final int lineOffset;
|
||||||
|
private final int lineLength;
|
||||||
|
|
||||||
|
public RunException(String message, int lineNumber, int lineOffset, int lineLength) {
|
||||||
|
super(message);
|
||||||
|
this.lineNumber = lineNumber;
|
||||||
|
this.lineOffset = lineOffset;
|
||||||
|
this.lineLength = lineLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLineNumber() {
|
||||||
|
return lineNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLineOffset() {
|
||||||
|
return lineOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLineLength() {
|
||||||
|
return lineLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.github.nilstrieb.grsbpl.language;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class StackFrame {
|
||||||
|
private final IntStack stack;
|
||||||
|
private final Map<String, Integer> variables;
|
||||||
|
private int position;
|
||||||
|
|
||||||
|
public StackFrame() {
|
||||||
|
stack = new IntStack();
|
||||||
|
variables = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntStack getStack() {
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Integer> getVariables() {
|
||||||
|
return variables;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosition(int position) {
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package com.github.nilstrieb.grsbpl.language;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class Token {
|
||||||
|
|
||||||
|
private final TokenType type;
|
||||||
|
private final Object value;
|
||||||
|
private final int lineNumber;
|
||||||
|
private final int lineOffset;
|
||||||
|
|
||||||
|
public Token(TokenType type, int lineNumber, int lineOffset) {
|
||||||
|
this(type, null, lineNumber, lineOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Token(TokenType type, Object value, int lineNumber, int lineOffset) {
|
||||||
|
this.type = type;
|
||||||
|
this.value = value;
|
||||||
|
this.lineNumber = lineNumber;
|
||||||
|
this.lineOffset = lineOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Token(TokenType type) {
|
||||||
|
this(type, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TokenType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStringValue() {
|
||||||
|
return (String) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIntValue() {
|
||||||
|
return (int) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLineNumber() {
|
||||||
|
return lineNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLineOffset() {
|
||||||
|
return lineOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
Token token = (Token) o;
|
||||||
|
|
||||||
|
if (lineNumber != token.lineNumber) return false;
|
||||||
|
if (lineOffset != token.lineOffset) return false;
|
||||||
|
if (type != token.type) return false;
|
||||||
|
return Objects.equals(value, token.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Token{" +
|
||||||
|
"type=" + type +
|
||||||
|
", value=" + value +
|
||||||
|
", lineNumber=" + lineNumber +
|
||||||
|
", lineOffset=" + lineOffset +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.github.nilstrieb.grsbpl.language;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The different types of tokens
|
||||||
|
*/
|
||||||
|
public enum TokenType {
|
||||||
|
|
||||||
|
// values
|
||||||
|
CHARACTER, CHAR, AMPERSAND("&"), AT("@"),
|
||||||
|
|
||||||
|
// binary operators
|
||||||
|
PLUS("+"), MINUS("-"), STAR("*"), SLASH("/"), PERCENT("%"),
|
||||||
|
BNOT, AND, OR, XOR,
|
||||||
|
|
||||||
|
// other operators
|
||||||
|
NOT, DUP, SWAP, POP,
|
||||||
|
|
||||||
|
// io
|
||||||
|
OUT, NOUT, IN,
|
||||||
|
|
||||||
|
// control flow
|
||||||
|
COLUMN(":"), GOTO, FUNCTION, IDENTIFIER, RETURN,
|
||||||
|
|
||||||
|
// end
|
||||||
|
EOF;
|
||||||
|
|
||||||
|
private final String display;
|
||||||
|
|
||||||
|
TokenType() {
|
||||||
|
this.display = super.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenType(String display) {
|
||||||
|
this.display = display;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return display;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/test/java/com/github/nilstrieb/grsbpl/IntStackTest.java
Normal file
45
src/test/java/com/github/nilstrieb/grsbpl/IntStackTest.java
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package com.github.nilstrieb.grsbpl;
|
||||||
|
|
||||||
|
import com.github.nilstrieb.grsbpl.language.IntStack;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
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.apply2((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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
195
src/test/java/com/github/nilstrieb/grsbpl/InterpreterTest.java
Normal file
195
src/test/java/com/github/nilstrieb/grsbpl/InterpreterTest.java
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
package com.github.nilstrieb.grsbpl;
|
||||||
|
|
||||||
|
import com.github.nilstrieb.grsbpl.language.Interpreter;
|
||||||
|
import com.github.nilstrieb.grsbpl.language.Lexer;
|
||||||
|
import com.github.nilstrieb.grsbpl.language.Token;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
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 java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class InterpreterTest {
|
||||||
|
|
||||||
|
static Interpreter interpreter;
|
||||||
|
|
||||||
|
static OutStream out;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
interpreter = new Interpreter();
|
||||||
|
out = new OutStream();
|
||||||
|
System.setOut(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void arithmeticOperations() {
|
||||||
|
String program = "1 1 * 2 +";
|
||||||
|
assertEquals(3, run(program));
|
||||||
|
String program2 = "10 5 /";
|
||||||
|
assertEquals(2, run(program2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bigNumbers() {
|
||||||
|
String program = "1000 1234 +";
|
||||||
|
assertEquals(2234, run(program));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void comment() {
|
||||||
|
String program = "1 # sdkfjsaf se9 83 252h43ui\n 2 # test 5 # +";
|
||||||
|
assertEquals(3, run(program));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void variables() {
|
||||||
|
String program = "1 &one 2 &two 3 &three 8 @two +";
|
||||||
|
assertEquals(10, run(program));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void labels() {
|
||||||
|
String program = "1 :first 2 0";
|
||||||
|
assertEquals(0, run(program));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void gotoBack() {
|
||||||
|
String program = "100000000 &i \n" +
|
||||||
|
":start \n" +
|
||||||
|
"@i nout '\n' out \n" +
|
||||||
|
"@i 1 - &i \n" +
|
||||||
|
"@i goto start \n" +
|
||||||
|
" 0";
|
||||||
|
int result = 0;
|
||||||
|
assertEquals(result, run(program));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void gotoSkip() {
|
||||||
|
String program = "1 :first 0 goto first 1 goto skip 3754 78349758 :skip";
|
||||||
|
int result = 1;
|
||||||
|
assertEquals(result, run(program));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fizzBuzz() throws IOException, URISyntaxException {
|
||||||
|
String program = Files.readString(Path.of(getClass().getClassLoader().getResource("fizzbuzz.grsbpl").toURI()));
|
||||||
|
int result = 0;
|
||||||
|
StringBuilder resultString = new StringBuilder();
|
||||||
|
for (int i = 1; i < 100; i++) {
|
||||||
|
if (i % 15 == 0) resultString.append("FizzBuzz\n");
|
||||||
|
else if (i % 5 == 0) resultString.append("Buzz\n");
|
||||||
|
else if (i % 3 == 0) resultString.append("Fizz\n");
|
||||||
|
else resultString.append(i).append("\n");
|
||||||
|
}
|
||||||
|
assertEquals(result, run(program));
|
||||||
|
assertEquals(resultString.toString(), out.getOut());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stackManipulationTest() {
|
||||||
|
String program = "1 2 swap";
|
||||||
|
assertEquals(1, run(program));
|
||||||
|
|
||||||
|
String program2 = "0 not";
|
||||||
|
assertEquals(1, run(program2));
|
||||||
|
|
||||||
|
String program3 = "1 not";
|
||||||
|
assertEquals(0, run(program3));
|
||||||
|
|
||||||
|
String program4 = "5 dup pop";
|
||||||
|
assertEquals(5, run(program4));
|
||||||
|
|
||||||
|
String program5 = "1 2 pop";
|
||||||
|
assertEquals(1, run(program5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bitwise() {
|
||||||
|
String p1 = "10 10 xor";
|
||||||
|
assertEquals(0, run(p1));
|
||||||
|
|
||||||
|
String p2 = "1 bnot";
|
||||||
|
assertEquals(~1, run(p2));
|
||||||
|
|
||||||
|
String p3 = 0xFF + " 1 and";
|
||||||
|
assertEquals(1, run(p3));
|
||||||
|
|
||||||
|
String p4 = 0b001 + " " + 0b101 + " or";
|
||||||
|
assertEquals(0b101, run(p4));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void functionTest() {
|
||||||
|
String program = "" +
|
||||||
|
"1 printNumber " +
|
||||||
|
"2 printNumber " +
|
||||||
|
"3 printNumber " +
|
||||||
|
"1 goto end" +
|
||||||
|
" " +
|
||||||
|
"function printNumber 1 nout 0 return" +
|
||||||
|
":end 0";
|
||||||
|
int result = 0;
|
||||||
|
assertEquals(result, run(program));
|
||||||
|
assertEquals("123", out.getOut());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void factorial() throws URISyntaxException, IOException {
|
||||||
|
String program0 = 0 + Files.readString(Path.of(getClass().getClassLoader().getResource("factorial.grsbpl").toURI()));
|
||||||
|
String program1 = 1 + Files.readString(Path.of(getClass().getClassLoader().getResource("factorial.grsbpl").toURI()));
|
||||||
|
String program10 = 10 + Files.readString(Path.of(getClass().getClassLoader().getResource("factorial.grsbpl").toURI()));
|
||||||
|
|
||||||
|
assertEquals(1, run(program0));
|
||||||
|
assertEquals(1, run(program1));
|
||||||
|
assertEquals(3628800, run(program10));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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";
|
||||||
|
|
||||||
|
assertEquals(0, run(program));
|
||||||
|
assertEquals("hello world!\n", out.getOut());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class OutStream extends PrintStream {
|
||||||
|
private final StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
public OutStream() {
|
||||||
|
super(new OutputStream() {
|
||||||
|
@Override
|
||||||
|
public void write(int b) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void print(char c) {
|
||||||
|
builder.append(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void print(int i) {
|
||||||
|
builder.append(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOut() {
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int run(String program) {
|
||||||
|
List<Token> tokens = new Lexer().lex(program.toCharArray());
|
||||||
|
return interpreter.run(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
122
src/test/java/com/github/nilstrieb/grsbpl/LexerTest.java
Normal file
122
src/test/java/com/github/nilstrieb/grsbpl/LexerTest.java
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
package com.github.nilstrieb.grsbpl;
|
||||||
|
|
||||||
|
import com.github.nilstrieb.grsbpl.language.Lexer;
|
||||||
|
import com.github.nilstrieb.grsbpl.language.Token;
|
||||||
|
import com.github.nilstrieb.grsbpl.language.TokenType;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.github.nilstrieb.grsbpl.language.TokenType.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class LexerTest {
|
||||||
|
|
||||||
|
Lexer lexer;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
lexer = new Lexer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void keywords() {
|
||||||
|
String program = "out in nout xor or and not bnot pop dup swap goto function return not";
|
||||||
|
List<TokenType> expected = List.of(OUT, IN, NOUT, XOR, OR, AND, NOT, BNOT, POP, DUP, SWAP, GOTO, FUNCTION, RETURN, NOT, EOF);
|
||||||
|
List<TokenType> actual = getTypes(lex(program));
|
||||||
|
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void symbols() {
|
||||||
|
String program = "+ & @ - % / * : +";
|
||||||
|
List<TokenType> expected = List.of(PLUS, AMPERSAND, AT, MINUS, PERCENT, SLASH, STAR, COLUMN, PLUS, EOF);
|
||||||
|
List<TokenType> actual = getTypes(lex(program));
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void identifiers() {
|
||||||
|
String program = "out test xor hallo + stack";
|
||||||
|
List<TokenType> expected = List.of(OUT, IDENTIFIER, XOR, IDENTIFIER, PLUS, IDENTIFIER, EOF);
|
||||||
|
List<Token> actual = lex(program);
|
||||||
|
assertEquals(expected, getTypes(actual));
|
||||||
|
|
||||||
|
Token test = new Token(IDENTIFIER, "test", 1, 4);
|
||||||
|
assertEquals(test, actual.get(1));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void numbers() {
|
||||||
|
String program = "out 347 test 64006 in";
|
||||||
|
List<TokenType> expected = List.of(OUT, CHARACTER, IDENTIFIER, CHARACTER, IN, EOF);
|
||||||
|
List<Token> actual = lex(program);
|
||||||
|
assertEquals(expected, getTypes(actual));
|
||||||
|
|
||||||
|
Token test = new Token(CHARACTER, 347, 1, 4);
|
||||||
|
assertEquals(test, actual.get(1));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void chars() {
|
||||||
|
String program = "'h' '\\n' '\\r' '\\f' '\\\\' '\\b' '\\'' '\\0'";
|
||||||
|
List<Character> expected = List.of('h', '\n', '\r', '\f', '\\', '\b', '\'', '\0');
|
||||||
|
List<Token> actual = lex(program);
|
||||||
|
assertEquals(expected, actual.stream()
|
||||||
|
.map(Token::getValue)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.limit(8)
|
||||||
|
.collect(Collectors.toUnmodifiableList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void comments() {
|
||||||
|
String program = "goto # hallo # goto #test\n goto";
|
||||||
|
List<TokenType> expected = List.of(GOTO, GOTO, GOTO, EOF);
|
||||||
|
List<TokenType> actual = getTypes(lex(program));
|
||||||
|
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void lineNumber() {
|
||||||
|
String program = "goto \n \n goto \ngoto";
|
||||||
|
List<TokenType> expected = List.of(GOTO, GOTO, GOTO, EOF);
|
||||||
|
List<Token> actual = lex(program);
|
||||||
|
assertEquals(expected, getTypes(actual));
|
||||||
|
|
||||||
|
Token test = new Token(GOTO, null, 4, 0);
|
||||||
|
assertEquals(test, actual.get(2));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void identifierName() {
|
||||||
|
String program = "test ABC g9tgq fe_53f";
|
||||||
|
List<String> expected = List.of("test", "ABC", "g9tgq", "fe_53f");
|
||||||
|
assertEquals(expected, getValues(lex(program)));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Token> lex(String program) {
|
||||||
|
return lexer.lex(program.toCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Object> getValues(List<Token> tokens) {
|
||||||
|
return tokens.stream()
|
||||||
|
.map(Token::getValue)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toUnmodifiableList());
|
||||||
|
}
|
||||||
|
List<TokenType> getTypes(List<Token> tokens) {
|
||||||
|
return tokens.stream()
|
||||||
|
.map(Token::getType)
|
||||||
|
.collect(Collectors.toUnmodifiableList());
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/test/resources/factorial.grsbpl
Normal file
9
src/test/resources/factorial.grsbpl
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
factorial 1 goto exit
|
||||||
|
|
||||||
|
function factorial 1
|
||||||
|
dup not goto isZero
|
||||||
|
&del dup 1 - factorial * return
|
||||||
|
:isZero
|
||||||
|
1 return
|
||||||
|
|
||||||
|
:exit pop
|
||||||
19
src/test/resources/fizzbuzz.grsbpl
Normal file
19
src/test/resources/fizzbuzz.grsbpl
Normal 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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue