mirror of
https://github.com/PolyhedralDev/Terra.git
synced 2026-06-17 14:21:08 +00:00
Better error handling + other changes
This commit is contained in:
+19
-13
@@ -7,16 +7,17 @@
|
|||||||
|
|
||||||
package com.dfsek.terra.addons.terrascript.lexer;
|
package com.dfsek.terra.addons.terrascript.lexer;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
|
||||||
public class Char {
|
public class Char {
|
||||||
private final char character;
|
private final char character;
|
||||||
private final int index;
|
private final SourcePosition position;
|
||||||
private final int line;
|
|
||||||
|
|
||||||
|
|
||||||
public Char(char character, int index, int line) {
|
public Char(char character, SourcePosition position) {
|
||||||
this.character = character;
|
this.character = character;
|
||||||
this.index = index;
|
this.position = position;
|
||||||
this.line = line;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean is(char... tests) {
|
public boolean is(char... tests) {
|
||||||
@@ -33,18 +34,23 @@ public class Char {
|
|||||||
return Character.toString(character);
|
return Character.toString(character);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if(this == o) return true;
|
||||||
|
if(o == null || getClass() != o.getClass()) return false;
|
||||||
|
Char other = (Char) o;
|
||||||
|
return character == other.character && Objects.equals(position, other.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(character, position);
|
||||||
|
}
|
||||||
|
|
||||||
public char getCharacter() {
|
public char getCharacter() {
|
||||||
return character;
|
return character;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getIndex() {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLine() {
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isWhitespace() {
|
public boolean isWhitespace() {
|
||||||
return Character.isWhitespace(character);
|
return Character.isWhitespace(character);
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-48
@@ -9,7 +9,7 @@ package com.dfsek.terra.addons.terrascript.lexer;
|
|||||||
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
import java.io.StringReader;
|
import java.util.Arrays;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ public class Lexer {
|
|||||||
private Token current;
|
private Token current;
|
||||||
|
|
||||||
public Lexer(String data) {
|
public Lexer(String data) {
|
||||||
reader = new LookaheadStream(new StringReader(data + '\0'));
|
reader = new LookaheadStream(data + '\0');
|
||||||
current = tokenize();
|
current = tokenize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,12 @@ public class Lexer {
|
|||||||
*
|
*
|
||||||
* @throws ParseException If token does not exist
|
* @throws ParseException If token does not exist
|
||||||
*/
|
*/
|
||||||
public Token consume() {
|
public Token consume(String wrongTypeMessage, TokenType expected, TokenType... more) {
|
||||||
|
if(!current.isType(expected) && Arrays.stream(more).noneMatch(t -> t == current.getType())) throw new ParseException(wrongTypeMessage, current.getPosition());
|
||||||
|
return consumeUnchecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Token consumeUnchecked() {
|
||||||
if(current.getType() == TokenType.END_OF_FILE) return current;
|
if(current.getType() == TokenType.END_OF_FILE) return current;
|
||||||
Token temp = current;
|
Token temp = current;
|
||||||
current = tokenize();
|
current = tokenize();
|
||||||
@@ -68,6 +73,7 @@ public class Lexer {
|
|||||||
|
|
||||||
private Token tokenize() throws TokenizerException {
|
private Token tokenize() throws TokenizerException {
|
||||||
consumeWhitespace();
|
consumeWhitespace();
|
||||||
|
SourcePosition position = reader.getPosition();
|
||||||
|
|
||||||
// Skip line if comment
|
// Skip line if comment
|
||||||
while(reader.matchesString("//", true)) skipLine();
|
while(reader.matchesString("//", true)) skipLine();
|
||||||
@@ -77,37 +83,37 @@ public class Lexer {
|
|||||||
|
|
||||||
// Reached end of file
|
// Reached end of file
|
||||||
if(reader.current().isEOF()) {
|
if(reader.current().isEOF()) {
|
||||||
if(!bracketStack.isEmpty()) throw new ParseException("Dangling closing brace", bracketStack.peek().getPosition());
|
if(!bracketStack.isEmpty()) throw new ParseException("Dangling open brace", bracketStack.peek().getPosition());
|
||||||
return new Token(reader.consume().toString(), TokenType.END_OF_FILE, reader.getPosition());
|
return new Token(reader.consume().toString(), TokenType.END_OF_FILE, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if operator token
|
// Check if operator token
|
||||||
if(reader.matchesString("==", true))
|
if(reader.matchesString("==", true))
|
||||||
return new Token("==", TokenType.EQUALS_EQUALS, reader.getPosition());
|
return new Token("==", TokenType.EQUALS_EQUALS, position);
|
||||||
if(reader.matchesString("!=", true))
|
if(reader.matchesString("!=", true))
|
||||||
return new Token("!=", TokenType.BANG_EQUALS, reader.getPosition());
|
return new Token("!=", TokenType.BANG_EQUALS, position);
|
||||||
if(reader.matchesString(">=", true))
|
if(reader.matchesString(">=", true))
|
||||||
return new Token(">=", TokenType.GREATER_EQUAL, reader.getPosition());
|
return new Token(">=", TokenType.GREATER_EQUAL, position);
|
||||||
if(reader.matchesString("<=", true))
|
if(reader.matchesString("<=", true))
|
||||||
return new Token("<=", TokenType.LESS_EQUALS, reader.getPosition());
|
return new Token("<=", TokenType.LESS_EQUALS, position);
|
||||||
if(reader.matchesString(">", true))
|
if(reader.matchesString(">", true))
|
||||||
return new Token(">", TokenType.GREATER, reader.getPosition());
|
return new Token(">", TokenType.GREATER, position);
|
||||||
if(reader.matchesString("<", true))
|
if(reader.matchesString("<", true))
|
||||||
return new Token("<", TokenType.LESS, reader.getPosition());
|
return new Token("<", TokenType.LESS, position);
|
||||||
|
|
||||||
// Check if logical operator
|
// Check if logical operator
|
||||||
if(reader.matchesString("||", true))
|
if(reader.matchesString("||", true))
|
||||||
return new Token("||", TokenType.BOOLEAN_OR, reader.getPosition());
|
return new Token("||", TokenType.BOOLEAN_OR, position);
|
||||||
if(reader.matchesString("&&", true))
|
if(reader.matchesString("&&", true))
|
||||||
return new Token("&&", TokenType.BOOLEAN_AND, reader.getPosition());
|
return new Token("&&", TokenType.BOOLEAN_AND, position);
|
||||||
|
|
||||||
// Check if number
|
// Check if number
|
||||||
if(isNumberStart()) {
|
if(isNumberStart()) {
|
||||||
StringBuilder num = new StringBuilder();
|
StringBuilder num = new StringBuilder();
|
||||||
while(!reader.current().isEOF() && isNumberLike()) {
|
while(!reader.current().isEOF() && isNumberLike()) {
|
||||||
num.append(reader.consume());
|
num.append(reader.consume().getCharacter());
|
||||||
}
|
}
|
||||||
return new Token(num.toString(), TokenType.NUMBER, reader.getPosition());
|
return new Token(num.toString(), TokenType.NUMBER, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if string literal
|
// Check if string literal
|
||||||
@@ -122,95 +128,95 @@ public class Lexer {
|
|||||||
continue;
|
continue;
|
||||||
} else ignoreNext = false;
|
} else ignoreNext = false;
|
||||||
if(reader.current().isEOF())
|
if(reader.current().isEOF())
|
||||||
throw new FormatException("No end of string literal found. ", reader.getPosition());
|
throw new FormatException("No end of string literal found. ", position);
|
||||||
string.append(reader.consume());
|
string.append(reader.consume());
|
||||||
}
|
}
|
||||||
reader.consume(); // Consume last quote
|
reader.consume(); // Consume last quote
|
||||||
|
|
||||||
return new Token(string.toString(), TokenType.STRING, reader.getPosition());
|
return new Token(string.toString(), TokenType.STRING, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(reader.current().is('('))
|
if(reader.current().is('('))
|
||||||
return new Token(reader.consume().toString(), TokenType.OPEN_PAREN, reader.getPosition());
|
return new Token(reader.consume().toString(), TokenType.OPEN_PAREN, position);
|
||||||
if(reader.current().is(')'))
|
if(reader.current().is(')'))
|
||||||
return new Token(reader.consume().toString(), TokenType.CLOSE_PAREN, reader.getPosition());
|
return new Token(reader.consume().toString(), TokenType.CLOSE_PAREN, position);
|
||||||
if(reader.current().is(';'))
|
if(reader.current().is(';'))
|
||||||
return new Token(reader.consume().toString(), TokenType.STATEMENT_END, reader.getPosition());
|
return new Token(reader.consume().toString(), TokenType.STATEMENT_END, position);
|
||||||
if(reader.current().is(','))
|
if(reader.current().is(','))
|
||||||
return new Token(reader.consume().toString(), TokenType.SEPARATOR, reader.getPosition());
|
return new Token(reader.consume().toString(), TokenType.SEPARATOR, position);
|
||||||
|
|
||||||
if(reader.current().is('{')) {
|
if(reader.current().is('{')) {
|
||||||
Token token = new Token(reader.consume().toString(), TokenType.BLOCK_BEGIN, reader.getPosition());
|
Token token = new Token(reader.consume().toString(), TokenType.BLOCK_BEGIN, position);
|
||||||
bracketStack.push(token);
|
bracketStack.push(token);
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
if(reader.current().is('}')) {
|
if(reader.current().is('}')) {
|
||||||
if(bracketStack.isEmpty()) throw new ParseException("Dangling opening brace", new SourcePosition(0, 0));
|
if(bracketStack.isEmpty()) throw new ParseException("Dangling close brace", position);
|
||||||
bracketStack.pop();
|
bracketStack.pop();
|
||||||
return new Token(reader.consume().toString(), TokenType.BLOCK_END, reader.getPosition());
|
return new Token(reader.consume().toString(), TokenType.BLOCK_END, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(reader.current().is('='))
|
if(reader.current().is('='))
|
||||||
return new Token(reader.consume().toString(), TokenType.ASSIGNMENT, reader.getPosition());
|
return new Token(reader.consume().toString(), TokenType.ASSIGNMENT, position);
|
||||||
if(reader.current().is('+'))
|
if(reader.current().is('+'))
|
||||||
return new Token(reader.consume().toString(), TokenType.PLUS, reader.getPosition());
|
return new Token(reader.consume().toString(), TokenType.PLUS, position);
|
||||||
if(reader.current().is('-'))
|
if(reader.current().is('-'))
|
||||||
return new Token(reader.consume().toString(), TokenType.MINUS,
|
return new Token(reader.consume().toString(), TokenType.MINUS,
|
||||||
reader.getPosition());
|
position);
|
||||||
if(reader.current().is('*'))
|
if(reader.current().is('*'))
|
||||||
return new Token(reader.consume().toString(), TokenType.STAR,
|
return new Token(reader.consume().toString(), TokenType.STAR,
|
||||||
reader.getPosition());
|
position);
|
||||||
if(reader.current().is('/'))
|
if(reader.current().is('/'))
|
||||||
return new Token(reader.consume().toString(), TokenType.FORWARD_SLASH, reader.getPosition());
|
return new Token(reader.consume().toString(), TokenType.FORWARD_SLASH, position);
|
||||||
if(reader.current().is('%'))
|
if(reader.current().is('%'))
|
||||||
return new Token(reader.consume().toString(), TokenType.MODULO_OPERATOR, reader.getPosition());
|
return new Token(reader.consume().toString(), TokenType.MODULO_OPERATOR, position);
|
||||||
if(reader.current().is('!'))
|
if(reader.current().is('!'))
|
||||||
return new Token(reader.consume().toString(), TokenType.BANG, reader.getPosition());
|
return new Token(reader.consume().toString(), TokenType.BANG, position);
|
||||||
|
|
||||||
// Read word
|
// Read word
|
||||||
StringBuilder token = new StringBuilder();
|
StringBuilder token = new StringBuilder();
|
||||||
while(!reader.current().isEOF() && !isSyntaxSignificant(reader.current().getCharacter())) {
|
while(!reader.current().isEOF() && !isSyntaxSignificant(reader.current().getCharacter())) {
|
||||||
Char c = reader.consume();
|
Char c = reader.consume();
|
||||||
if(c.isWhitespace()) break;
|
if(c.isWhitespace()) break;
|
||||||
token.append(c);
|
token.append(c.getCharacter());
|
||||||
}
|
}
|
||||||
String tokenString = token.toString();
|
String tokenString = token.toString();
|
||||||
|
|
||||||
// Check if word is a keyword
|
// Check if word is a keyword
|
||||||
if(tokenString.equals("true"))
|
if(tokenString.equals("true"))
|
||||||
return new Token(tokenString, TokenType.BOOLEAN, reader.getPosition());
|
return new Token(tokenString, TokenType.BOOLEAN, position);
|
||||||
if(tokenString.equals("false"))
|
if(tokenString.equals("false"))
|
||||||
return new Token(tokenString, TokenType.BOOLEAN, reader.getPosition());
|
return new Token(tokenString, TokenType.BOOLEAN, position);
|
||||||
|
|
||||||
if(tokenString.equals("num"))
|
if(tokenString.equals("num"))
|
||||||
return new Token(tokenString, TokenType.TYPE_NUMBER, reader.getPosition());
|
return new Token(tokenString, TokenType.TYPE_NUMBER, position);
|
||||||
if(tokenString.equals("str"))
|
if(tokenString.equals("str"))
|
||||||
return new Token(tokenString, TokenType.TYPE_STRING, reader.getPosition());
|
return new Token(tokenString, TokenType.TYPE_STRING, position);
|
||||||
if(tokenString.equals("bool"))
|
if(tokenString.equals("bool"))
|
||||||
return new Token(tokenString, TokenType.TYPE_BOOLEAN, reader.getPosition());
|
return new Token(tokenString, TokenType.TYPE_BOOLEAN, position);
|
||||||
if(tokenString.equals("void"))
|
if(tokenString.equals("void"))
|
||||||
return new Token(tokenString, TokenType.TYPE_VOID, reader.getPosition());
|
return new Token(tokenString, TokenType.TYPE_VOID, position);
|
||||||
|
|
||||||
if(tokenString.equals("if"))
|
if(tokenString.equals("if"))
|
||||||
return new Token(tokenString, TokenType.IF_STATEMENT, reader.getPosition());
|
return new Token(tokenString, TokenType.IF_STATEMENT, position);
|
||||||
if(tokenString.equals("else"))
|
if(tokenString.equals("else"))
|
||||||
return new Token(tokenString, TokenType.ELSE, reader.getPosition());
|
return new Token(tokenString, TokenType.ELSE, position);
|
||||||
if(tokenString.equals("while"))
|
if(tokenString.equals("while"))
|
||||||
return new Token(tokenString, TokenType.WHILE_LOOP, reader.getPosition());
|
return new Token(tokenString, TokenType.WHILE_LOOP, position);
|
||||||
if(tokenString.equals("for"))
|
if(tokenString.equals("for"))
|
||||||
return new Token(tokenString, TokenType.FOR_LOOP, reader.getPosition());
|
return new Token(tokenString, TokenType.FOR_LOOP, position);
|
||||||
|
|
||||||
if(tokenString.equals("return"))
|
if(tokenString.equals("return"))
|
||||||
return new Token(tokenString, TokenType.RETURN, reader.getPosition());
|
return new Token(tokenString, TokenType.RETURN, position);
|
||||||
if(tokenString.equals("continue"))
|
if(tokenString.equals("continue"))
|
||||||
return new Token(tokenString, TokenType.CONTINUE, reader.getPosition());
|
return new Token(tokenString, TokenType.CONTINUE, position);
|
||||||
if(tokenString.equals("break"))
|
if(tokenString.equals("break"))
|
||||||
return new Token(tokenString, TokenType.BREAK, reader.getPosition());
|
return new Token(tokenString, TokenType.BREAK, position);
|
||||||
if(tokenString.equals("fail"))
|
if(tokenString.equals("fail"))
|
||||||
return new Token(tokenString, TokenType.FAIL, reader.getPosition());
|
return new Token(tokenString, TokenType.FAIL, position);
|
||||||
|
|
||||||
// If not keyword, assume it is an identifier
|
// If not keyword, assume it is an identifier
|
||||||
return new Token(tokenString, TokenType.IDENTIFIER, reader.getPosition());
|
return new Token(tokenString, TokenType.IDENTIFIER, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void skipLine() {
|
private void skipLine() {
|
||||||
@@ -241,7 +247,7 @@ public class Lexer {
|
|||||||
|
|
||||||
private boolean isNumberStart() {
|
private boolean isNumberStart() {
|
||||||
return reader.current().isDigit()
|
return reader.current().isDigit()
|
||||||
|| reader.current().is('.') && reader.next(1).isDigit();
|
|| reader.current().is('.') && reader.peek().isDigit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSyntaxSignificant(char c) {
|
public boolean isSyntaxSignificant(char c) {
|
||||||
|
|||||||
+44
-86
@@ -1,31 +1,17 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2020-2021 Polyhedral Development
|
|
||||||
*
|
|
||||||
* The Terra Core Addons are licensed under the terms of the MIT License. For more details,
|
|
||||||
* reference the LICENSE file in this module's root directory.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.dfsek.terra.addons.terrascript.lexer;
|
package com.dfsek.terra.addons.terrascript.lexer;
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stream-like data structure that allows viewing future elements without consuming current.
|
|
||||||
*/
|
|
||||||
public class LookaheadStream {
|
public class LookaheadStream {
|
||||||
private final List<Char> buffer = new ArrayList<>();
|
|
||||||
private final Reader input;
|
|
||||||
private int index = 0;
|
|
||||||
private int line = 0;
|
|
||||||
private boolean end = false;
|
|
||||||
|
|
||||||
public LookaheadStream(Reader r) {
|
private final String source;
|
||||||
this.input = r;
|
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
private SourcePosition position = new SourcePosition(1, 1);
|
||||||
|
|
||||||
|
public LookaheadStream(String source) {
|
||||||
|
this.source = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,10 +20,9 @@ public class LookaheadStream {
|
|||||||
* @return current character
|
* @return current character
|
||||||
*/
|
*/
|
||||||
public Char current() {
|
public Char current() {
|
||||||
return next(0);
|
return new Char(source.charAt(index), position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consume and return one character.
|
* Consume and return one character.
|
||||||
*
|
*
|
||||||
@@ -45,82 +30,55 @@ public class LookaheadStream {
|
|||||||
*/
|
*/
|
||||||
public Char consume() {
|
public Char consume() {
|
||||||
Char consumed = current();
|
Char consumed = current();
|
||||||
consume(1);
|
incrementIndex(1);
|
||||||
return consumed;
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a future character without consuming it.
|
* @return The next character in sequence.
|
||||||
*
|
|
||||||
* @param ahead Distance ahead to peek
|
|
||||||
*
|
|
||||||
* @return Character
|
|
||||||
*/
|
*/
|
||||||
public Char next(int ahead) {
|
public Char peek() {
|
||||||
if(ahead < 0) throw new IllegalArgumentException();
|
int index = this.index + 1;
|
||||||
|
if (index + 1 >= source.length()) return null;
|
||||||
while(buffer.size() <= ahead && !end) {
|
return new Char(source.charAt(index), getPositionAfter(1));
|
||||||
Char item = fetch();
|
|
||||||
if(item != null) {
|
|
||||||
buffer.add(item);
|
|
||||||
} else end = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ahead >= buffer.size()) {
|
|
||||||
return null;
|
|
||||||
} else return buffer.get(ahead);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consume an amount of characters
|
* Determines if the contained sequence of characters matches the string
|
||||||
*
|
*
|
||||||
* @param amount Number of characters to consume
|
* @param check Input string to check against
|
||||||
|
* @param consumeIfMatched Whether to consume the string if there is a match
|
||||||
|
* @return If the string matches
|
||||||
*/
|
*/
|
||||||
public void consume(int amount) {
|
public boolean matchesString(String check, boolean consumeIfMatched) {
|
||||||
if(amount < 0) throw new IllegalArgumentException();
|
boolean matches = check.equals(source.substring(index, Math.min(index + check.length(), source.length())));
|
||||||
while(amount-- > 0) {
|
if (matches && consumeIfMatched) incrementIndex(check.length());
|
||||||
if(!buffer.isEmpty()) buffer.remove(0); // Remove top item from buffer.
|
return matches;
|
||||||
else {
|
|
||||||
if(end) return;
|
|
||||||
Char item = fetch();
|
|
||||||
if(item == null) end = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean matchesString(String check, boolean consume) {
|
|
||||||
if(check == null) return false;
|
|
||||||
|
|
||||||
for(int i = 0; i < check.length(); i++) {
|
|
||||||
if(!next(i).is(check.charAt(i))) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(consume) consume(check.length()); // Consume string
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the next character.
|
* @return Current position within the source file
|
||||||
*
|
|
||||||
* @return Next character
|
|
||||||
*/
|
*/
|
||||||
private Char fetch() {
|
|
||||||
try {
|
|
||||||
int c = input.read();
|
|
||||||
if(c == -1) return null;
|
|
||||||
if(c == '\n') {
|
|
||||||
line++;
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
return new Char((char) c, line, index);
|
|
||||||
} catch(IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SourcePosition getPosition() {
|
public SourcePosition getPosition() {
|
||||||
return new SourcePosition(line, index);
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void incrementIndex(int amount) {
|
||||||
|
position = getPositionAfter(amount);
|
||||||
|
index = Math.min(index + amount, source.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SourcePosition getPositionAfter(int chars) {
|
||||||
|
if (chars < 0) throw new IllegalArgumentException("Negative values are not allowed");
|
||||||
|
int line = position.line();
|
||||||
|
int column = position.column();
|
||||||
|
for (int i = index; i < Math.min(index + chars, source.length() - 1); i++) {
|
||||||
|
if (source.charAt(i) == '\n') {
|
||||||
|
line++;
|
||||||
|
column = 0;
|
||||||
|
}
|
||||||
|
column++;
|
||||||
|
}
|
||||||
|
return new SourcePosition(line, column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-9
@@ -7,17 +7,26 @@
|
|||||||
|
|
||||||
package com.dfsek.terra.addons.terrascript.lexer;
|
package com.dfsek.terra.addons.terrascript.lexer;
|
||||||
|
|
||||||
public class SourcePosition {
|
import java.util.Objects;
|
||||||
private final int line;
|
|
||||||
private final int index;
|
|
||||||
|
public record SourcePosition(int line, int column) {
|
||||||
public SourcePosition(int line, int index) {
|
|
||||||
this.line = line;
|
|
||||||
this.index = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return (line + 1) + ":" + index;
|
return "line " + line + ", column " + column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if(this == o) return true;
|
||||||
|
if(o == null || getClass() != o.getClass()) return false;
|
||||||
|
SourcePosition that = (SourcePosition) o;
|
||||||
|
return line == that.line && column == that.column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(line, column);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+122
-125
@@ -83,23 +83,23 @@ public class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private WhileKeyword parseWhileLoop(ScopeBuilder scopeBuilder) {
|
private WhileKeyword parseWhileLoop(ScopeBuilder scopeBuilder) {
|
||||||
SourcePosition start = lexer.consume().getPosition();
|
SourcePosition start = lexer.consume("Expected 'while' keyword at beginning of while loop", TokenType.WHILE_LOOP).getPosition();
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.OPEN_PAREN);
|
lexer.consume("Expected '(' proceeding 'while' keyword", TokenType.OPEN_PAREN);
|
||||||
scopeBuilder = scopeBuilder.innerLoopScope();
|
scopeBuilder = scopeBuilder.innerLoopScope();
|
||||||
Expression<?> condition = parseExpression(scopeBuilder);
|
Expression<?> condition = parseExpression(scopeBuilder);
|
||||||
ParserUtil.ensureReturnType(condition, Expression.ReturnType.BOOLEAN);
|
ParserUtil.ensureReturnType(condition, Expression.ReturnType.BOOLEAN);
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.CLOSE_PAREN);
|
lexer.consume("Expected ')' proceeding while loop condition", TokenType.CLOSE_PAREN);
|
||||||
return new WhileKeyword(parseStatementBlock(scopeBuilder, ReturnType.VOID), (Expression<Boolean>) condition,
|
return new WhileKeyword(parseStatementBlock(scopeBuilder, ReturnType.VOID), (Expression<Boolean>) condition,
|
||||||
start); // While loop
|
start); // While loop
|
||||||
}
|
}
|
||||||
|
|
||||||
private IfKeyword parseIfStatement(ScopeBuilder scopeBuilder) {
|
private IfKeyword parseIfStatement(ScopeBuilder scopeBuilder) {
|
||||||
SourcePosition start = lexer.consume().getPosition();
|
SourcePosition start = lexer.consume("Expected 'if' keyword at beginning of if statement", TokenType.IF_STATEMENT).getPosition();
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.OPEN_PAREN);
|
lexer.consume("Expected '(' proceeding 'if' keyword", TokenType.OPEN_PAREN);
|
||||||
Expression<?> condition = parseExpression(scopeBuilder);
|
Expression<?> condition = parseExpression(scopeBuilder);
|
||||||
ParserUtil.ensureReturnType(condition, Expression.ReturnType.BOOLEAN);
|
ParserUtil.ensureReturnType(condition, Expression.ReturnType.BOOLEAN);
|
||||||
|
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.CLOSE_PAREN);
|
lexer.consume("Expected ')' proceeding if statement condition", TokenType.CLOSE_PAREN);
|
||||||
|
|
||||||
Block elseBlock = null;
|
Block elseBlock = null;
|
||||||
Block statement = parseStatementBlock(scopeBuilder, ReturnType.VOID);
|
Block statement = parseStatementBlock(scopeBuilder, ReturnType.VOID);
|
||||||
@@ -107,9 +107,9 @@ public class Parser {
|
|||||||
List<Pair<Expression<Boolean>, Block>> elseIf = new ArrayList<>();
|
List<Pair<Expression<Boolean>, Block>> elseIf = new ArrayList<>();
|
||||||
|
|
||||||
while(lexer.hasNext() && lexer.current().isType(TokenType.ELSE)) {
|
while(lexer.hasNext() && lexer.current().isType(TokenType.ELSE)) {
|
||||||
lexer.consume(); // Consume else.
|
lexer.consumeUnchecked(); // Consume else.
|
||||||
if(lexer.current().isType(TokenType.IF_STATEMENT)) {
|
if(lexer.current().isType(TokenType.IF_STATEMENT)) {
|
||||||
lexer.consume(); // Consume if.
|
lexer.consumeUnchecked(); // Consume if.
|
||||||
Expression<?> elseCondition = parseExpression(scopeBuilder);
|
Expression<?> elseCondition = parseExpression(scopeBuilder);
|
||||||
ParserUtil.ensureReturnType(elseCondition, Expression.ReturnType.BOOLEAN);
|
ParserUtil.ensureReturnType(elseCondition, Expression.ReturnType.BOOLEAN);
|
||||||
elseIf.add(Pair.of((Expression<Boolean>) elseCondition, parseStatementBlock(scopeBuilder, ReturnType.VOID)));
|
elseIf.add(Pair.of((Expression<Boolean>) elseCondition, parseStatementBlock(scopeBuilder, ReturnType.VOID)));
|
||||||
@@ -124,42 +124,58 @@ public class Parser {
|
|||||||
|
|
||||||
private Block parseStatementBlock(ScopeBuilder scopeBuilder, ReturnType blockReturnType) {
|
private Block parseStatementBlock(ScopeBuilder scopeBuilder, ReturnType blockReturnType) {
|
||||||
if(lexer.current().isType(TokenType.BLOCK_BEGIN)) {
|
if(lexer.current().isType(TokenType.BLOCK_BEGIN)) {
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.BLOCK_BEGIN);
|
lexer.consumeUnchecked();
|
||||||
Block block = parseBlock(scopeBuilder, blockReturnType);
|
Block block = parseBlock(scopeBuilder, blockReturnType);
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.BLOCK_END);
|
lexer.consume("Expected block end '}' after block statements", TokenType.BLOCK_END);
|
||||||
return block;
|
return block;
|
||||||
} else {
|
} else {
|
||||||
SourcePosition position = lexer.current().getPosition();
|
SourcePosition position = lexer.current().getPosition();
|
||||||
return new Block(Collections.singletonList(parseStatement(lexer, scopeBuilder)), position, blockReturnType);
|
return new Block(Collections.singletonList(parseStatement(scopeBuilder)), position, blockReturnType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ForKeyword parseForLoop(ScopeBuilder scopeBuilder) {
|
private ForKeyword parseForLoop(ScopeBuilder scopeBuilder) {
|
||||||
SourcePosition start = lexer.consume().getPosition();
|
SourcePosition start = lexer.consume("Expected 'for' keyword at beginning of for loop", TokenType.FOR_LOOP).getPosition();
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.OPEN_PAREN);
|
lexer.consume("Expected '(' after 'for' keyword", TokenType.OPEN_PAREN);
|
||||||
scopeBuilder = scopeBuilder.innerLoopScope(); // new scope
|
scopeBuilder = scopeBuilder.innerLoopScope(); // new scope
|
||||||
Token f = lexer.current();
|
|
||||||
ParserUtil.ensureType(f, TokenType.TYPE_NUMBER, TokenType.TYPE_STRING, TokenType.TYPE_BOOLEAN, TokenType.IDENTIFIER);
|
Expression<?> initializer = switch(lexer.current().getType()) {
|
||||||
Expression<?> initializer;
|
case TYPE_NUMBER, TYPE_STRING, TYPE_BOOLEAN -> {
|
||||||
if(f.isVariableDeclaration()) {
|
Token type = lexer.consume("Expected type before declaration", TokenType.TYPE_STRING, TokenType.TYPE_NUMBER, TokenType.TYPE_BOOLEAN, TokenType.TYPE_VOID);
|
||||||
Expression<?> forVar = parseDeclaration(scopeBuilder);
|
Token identifier = lexer.consume("Expected identifier after type", TokenType.IDENTIFIER);
|
||||||
Token name = lexer.current();
|
Expression<?> expr = parseVariableDeclaration(scopeBuilder, type, identifier);
|
||||||
if(scopeBuilder.containsFunction(name.getContent()) || scopeBuilder.containsVariable(name.getContent()))
|
lexer.consume("Expected ';' after initializer within for loop", TokenType.STATEMENT_END);
|
||||||
throw new ParseException(name.getContent() + " is already defined in this scope", name.getPosition());
|
yield expr;
|
||||||
initializer = forVar;
|
}
|
||||||
} else initializer = parseExpression(scopeBuilder);
|
case IDENTIFIER -> {
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.STATEMENT_END);
|
Expression<?> expr = parseAssignment(scopeBuilder);
|
||||||
Expression<?> conditional = parseExpression(scopeBuilder);
|
lexer.consume("Expected ';' after initializer within for loop", TokenType.STATEMENT_END);
|
||||||
|
yield expr;
|
||||||
|
}
|
||||||
|
case STATEMENT_END -> {
|
||||||
|
lexer.consumeUnchecked();
|
||||||
|
yield Expression.NOOP;
|
||||||
|
}
|
||||||
|
default -> throw new ParseException("Unexpected token '" + lexer.current() + "', expected variable declaration or assignment", lexer.current().getPosition());
|
||||||
|
};
|
||||||
|
|
||||||
|
Expression<?> conditional;
|
||||||
|
if (lexer.current().isType(TokenType.STATEMENT_END)) // If no conditional is provided, conditional defaults to true
|
||||||
|
conditional = new BooleanConstant(true, lexer.current().getPosition());
|
||||||
|
else
|
||||||
|
conditional = parseExpression(scopeBuilder);
|
||||||
ParserUtil.ensureReturnType(conditional, Expression.ReturnType.BOOLEAN);
|
ParserUtil.ensureReturnType(conditional, Expression.ReturnType.BOOLEAN);
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.STATEMENT_END);
|
lexer.consume("Expected ';' separator after conditional within for loop", TokenType.STATEMENT_END);
|
||||||
|
|
||||||
Expression<?> incrementer;
|
Expression<?> incrementer;
|
||||||
Token incrementerToken = lexer.consume();
|
if(lexer.current().isType(TokenType.CLOSE_PAREN))
|
||||||
if(scopeBuilder.containsVariable(incrementerToken.getContent())) { // Assume variable assignment
|
// If no incrementer is provided, do nothing
|
||||||
incrementer = parseAssignment(incrementerToken, scopeBuilder);
|
incrementer = Expression.NOOP;
|
||||||
} else incrementer = parseFunctionInvocation(true, incrementerToken, scopeBuilder);
|
else if(scopeBuilder.containsVariable(lexer.current().getContent())) // Assume variable assignment
|
||||||
|
incrementer = parseAssignment(scopeBuilder);
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.CLOSE_PAREN);
|
else
|
||||||
|
incrementer = parseFunctionInvocation(lexer.consume("Expected function call within for loop incrementer", TokenType.IDENTIFIER), scopeBuilder);
|
||||||
|
lexer.consume("Expected ')' after for loop incrementer", TokenType.CLOSE_PAREN);
|
||||||
|
|
||||||
return new ForKeyword(parseStatementBlock(scopeBuilder, ReturnType.VOID), initializer, (Expression<Boolean>) conditional,
|
return new ForKeyword(parseStatementBlock(scopeBuilder, ReturnType.VOID), initializer, (Expression<Boolean>) conditional,
|
||||||
incrementer,
|
incrementer,
|
||||||
@@ -231,7 +247,7 @@ public class Parser {
|
|||||||
|
|
||||||
private Expression<?> parseUnary(ScopeBuilder scopeBuilder) {
|
private Expression<?> parseUnary(ScopeBuilder scopeBuilder) {
|
||||||
if (lexer.current().isType(TokenType.BANG, TokenType.MINUS)) {
|
if (lexer.current().isType(TokenType.BANG, TokenType.MINUS)) {
|
||||||
Token operator = lexer.consume();
|
Token operator = lexer.consumeUnchecked();
|
||||||
Expression<?> right = parseUnary(scopeBuilder);
|
Expression<?> right = parseUnary(scopeBuilder);
|
||||||
return switch(operator.getType()) {
|
return switch(operator.getType()) {
|
||||||
case BANG -> {
|
case BANG -> {
|
||||||
@@ -249,7 +265,7 @@ public class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Expression<?> parsePrimary(ScopeBuilder scopeBuilder) {
|
private Expression<?> parsePrimary(ScopeBuilder scopeBuilder) {
|
||||||
Token token = lexer.consume();
|
Token token = lexer.consumeUnchecked();
|
||||||
return switch(token.getType()) {
|
return switch(token.getType()) {
|
||||||
case NUMBER -> {
|
case NUMBER -> {
|
||||||
String content = token.getContent();
|
String content = token.getContent();
|
||||||
@@ -259,12 +275,12 @@ public class Parser {
|
|||||||
case BOOLEAN -> new BooleanConstant(Boolean.parseBoolean(token.getContent()), token.getPosition());
|
case BOOLEAN -> new BooleanConstant(Boolean.parseBoolean(token.getContent()), token.getPosition());
|
||||||
case OPEN_PAREN -> {
|
case OPEN_PAREN -> {
|
||||||
Expression<?> expr = parseExpression(scopeBuilder);
|
Expression<?> expr = parseExpression(scopeBuilder);
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.CLOSE_PAREN);
|
lexer.consume("Missing ')' at end of expression group", TokenType.CLOSE_PAREN);
|
||||||
yield expr;
|
yield expr;
|
||||||
}
|
}
|
||||||
case IDENTIFIER -> {
|
case IDENTIFIER -> {
|
||||||
if (scopeBuilder.containsFunction(token.getContent()))
|
if (scopeBuilder.containsFunction(token.getContent()))
|
||||||
yield parseFunctionInvocation(false, token, scopeBuilder);
|
yield parseFunctionInvocation(token, scopeBuilder);
|
||||||
else if (scopeBuilder.containsVariable(token.getContent())) {
|
else if (scopeBuilder.containsVariable(token.getContent())) {
|
||||||
ReturnType variableType = scopeBuilder.getVaraibleType(token.getContent());
|
ReturnType variableType = scopeBuilder.getVaraibleType(token.getContent());
|
||||||
yield switch(variableType) {
|
yield switch(variableType) {
|
||||||
@@ -274,9 +290,9 @@ public class Parser {
|
|||||||
default -> throw new ParseException("Illegal type for variable reference: " + variableType, token.getPosition());
|
default -> throw new ParseException("Illegal type for variable reference: " + variableType, token.getPosition());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw new ParseException("Identifier " + token.getContent() + " is not defined in this scope", token.getPosition());
|
throw new ParseException("Identifier '" + token.getContent() + "' is not defined in this scope", token.getPosition());
|
||||||
}
|
}
|
||||||
default -> throw new ParseException("Unexpected token " + token.getType(), token.getPosition());
|
default -> throw new ParseException("Unexpected token '" + token.getContent() + "' when parsing expression", token.getPosition());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +302,7 @@ public class Parser {
|
|||||||
Expression<?> expr = higherPrecedence.apply(scopeBuilder);
|
Expression<?> expr = higherPrecedence.apply(scopeBuilder);
|
||||||
TokenType[] opTypes = operators.keySet().toArray(new TokenType[0]);
|
TokenType[] opTypes = operators.keySet().toArray(new TokenType[0]);
|
||||||
while (lexer.current().isType(opTypes)) {
|
while (lexer.current().isType(opTypes)) {
|
||||||
Token operator = lexer.consume();
|
Token operator = lexer.consumeUnchecked();
|
||||||
Expression<?> right = higherPrecedence.apply(scopeBuilder);
|
Expression<?> right = higherPrecedence.apply(scopeBuilder);
|
||||||
BinaryOperationInfo op = new BinaryOperationInfo(expr, operator, right);
|
BinaryOperationInfo op = new BinaryOperationInfo(expr, operator, right);
|
||||||
init.accept(op);
|
init.accept(op);
|
||||||
@@ -302,24 +318,21 @@ public class Parser {
|
|||||||
private record BinaryOperationInfo(Expression<?> left, Token operator, Expression<?> right) {}
|
private record BinaryOperationInfo(Expression<?> left, Token operator, Expression<?> right) {}
|
||||||
|
|
||||||
private Expression<?> parseDeclaration(ScopeBuilder scopeBuilder) {
|
private Expression<?> parseDeclaration(ScopeBuilder scopeBuilder) {
|
||||||
Token type = lexer.consume();
|
Token type = lexer.consume("Expected type before declaration", TokenType.TYPE_STRING, TokenType.TYPE_NUMBER, TokenType.TYPE_BOOLEAN, TokenType.TYPE_VOID);
|
||||||
|
Token identifier = lexer.consume("Expected identifier after type", TokenType.IDENTIFIER);
|
||||||
|
|
||||||
Token identifier = lexer.consume();
|
return switch(lexer.current().getType()) {
|
||||||
ParserUtil.ensureType(identifier, TokenType.IDENTIFIER);
|
|
||||||
|
|
||||||
Token declarationType = lexer.consume();
|
|
||||||
ParserUtil.ensureType(declarationType, TokenType.ASSIGNMENT, TokenType.OPEN_PAREN);
|
|
||||||
|
|
||||||
return switch(declarationType.getType()) {
|
|
||||||
case ASSIGNMENT -> parseVariableDeclaration(scopeBuilder, type, identifier);
|
case ASSIGNMENT -> parseVariableDeclaration(scopeBuilder, type, identifier);
|
||||||
case OPEN_PAREN -> parseFunctionDeclaration(scopeBuilder, type, identifier);
|
case OPEN_PAREN -> parseFunctionDeclaration(scopeBuilder, type, identifier);
|
||||||
default -> throw new ParseException("Illegal type for declaration: " + type, declarationType.getPosition());
|
default -> throw new ParseException("Expected '=' for variable assignment or '(' for function declaration after identifier '" + identifier.getContent() + "'", lexer.current().getPosition());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Expression<?> parseVariableDeclaration(ScopeBuilder scopeBuilder, Token type, Token identifier) {
|
private Expression<?> parseVariableDeclaration(ScopeBuilder scopeBuilder, Token type, Token identifier) {
|
||||||
ParserUtil.ensureType(type, TokenType.TYPE_STRING, TokenType.TYPE_BOOLEAN, TokenType.TYPE_NUMBER);
|
lexer.consume("Expected '=' after identifier '" + identifier.getContent() + "' for variable declaration", TokenType.ASSIGNMENT);
|
||||||
|
|
||||||
|
if (!type.isVariableDeclaration()) throw new ParseException("Expected type specification at beginning of variable declaration", type.getPosition());
|
||||||
|
|
||||||
if(scopeBuilder.containsVariable(identifier.getContent()))
|
if(scopeBuilder.containsVariable(identifier.getContent()))
|
||||||
throw new ParseException(identifier.getContent() + " is already defined in this scope", identifier.getPosition());
|
throw new ParseException(identifier.getContent() + " is already defined in this scope", identifier.getPosition());
|
||||||
@@ -340,7 +353,10 @@ public class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Expression<?> parseFunctionDeclaration(ScopeBuilder scopeBuilder, Token type, Token identifier) {
|
private Expression<?> parseFunctionDeclaration(ScopeBuilder scopeBuilder, Token type, Token identifier) {
|
||||||
ParserUtil.ensureType(type, TokenType.TYPE_STRING, TokenType.TYPE_BOOLEAN, TokenType.TYPE_NUMBER, TokenType.TYPE_VOID);
|
lexer.consume("Expected '(' after identifier '" + identifier.getContent() + "' for function declaration", TokenType.OPEN_PAREN);
|
||||||
|
|
||||||
|
if(!(type.isType(TokenType.TYPE_STRING, TokenType.TYPE_BOOLEAN, TokenType.TYPE_NUMBER, TokenType.TYPE_VOID)))
|
||||||
|
throw new ParseException("Invalid function declaration return type specification " + type.getType(), type.getPosition());
|
||||||
|
|
||||||
if(scopeBuilder.containsVariable(identifier.getContent()))
|
if(scopeBuilder.containsVariable(identifier.getContent()))
|
||||||
throw new ParseException(identifier.getContent() + " is already defined in this scope", identifier.getPosition());
|
throw new ParseException(identifier.getContent() + " is already defined in this scope", identifier.getPosition());
|
||||||
@@ -349,43 +365,41 @@ public class Parser {
|
|||||||
|
|
||||||
ScopeBuilder functionBodyScope = scopeBuilder.functionScope();
|
ScopeBuilder functionBodyScope = scopeBuilder.functionScope();
|
||||||
|
|
||||||
// Declare argument names into function body scope
|
// Declare parameter names into function body scope
|
||||||
List<Pair<Integer, ReturnType>> argumentInfo = getFunctionArgumentsDeclaration().stream().map(
|
List<Pair<Integer, ReturnType>> parameterInfo = getFunctionParameterDeclaration().stream().map(
|
||||||
arg -> Pair.of(switch(arg.getRight()) {
|
arg -> Pair.of(switch(arg.getRight()) {
|
||||||
case NUMBER -> functionBodyScope.declareNum(arg.getLeft());
|
case NUMBER -> functionBodyScope.declareNum(arg.getLeft());
|
||||||
case BOOLEAN -> functionBodyScope.declareBool(arg.getLeft());
|
case BOOLEAN -> functionBodyScope.declareBool(arg.getLeft());
|
||||||
case STRING -> functionBodyScope.declareStr(arg.getLeft());
|
case STRING -> functionBodyScope.declareStr(arg.getLeft());
|
||||||
default -> throw new IllegalArgumentException("Unsupported argument type: " + arg.getRight());
|
default -> throw new IllegalArgumentException("Unsupported parameter type: " + arg.getRight());
|
||||||
}, arg.getRight())).toList();
|
}, arg.getRight())).toList();
|
||||||
|
|
||||||
Block body = parseStatementBlock(functionBodyScope, returnType);
|
Block body = parseStatementBlock(functionBodyScope, returnType);
|
||||||
|
|
||||||
FunctionBuilder<?> functionBuilder = new UserDefinedFunctionBuilder<>(returnType, argumentInfo, body, functionBodyScope);
|
FunctionBuilder<?> functionBuilder = new UserDefinedFunctionBuilder<>(returnType, parameterInfo, body, functionBodyScope);
|
||||||
|
|
||||||
scopeBuilder.registerFunction(identifier.getContent(), functionBuilder);
|
scopeBuilder.registerFunction(identifier.getContent(), functionBuilder);
|
||||||
return Expression.NOOP;
|
return Expression.NOOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Pair<String, ReturnType>> getFunctionArgumentsDeclaration() {
|
private List<Pair<String, ReturnType>> getFunctionParameterDeclaration() {
|
||||||
List<Pair<String, ReturnType>> arguments = new ArrayList<>();
|
List<Pair<String, ReturnType>> parameters = new ArrayList<>();
|
||||||
while(lexer.current().getType() != TokenType.CLOSE_PAREN) {
|
while(lexer.current().getType() != TokenType.CLOSE_PAREN) {
|
||||||
// Parse argument type
|
// Parse parameter type
|
||||||
Token typeToken = lexer.consume();
|
Token typeToken = lexer.consume("Expected function parameter type declaration", TokenType.TYPE_BOOLEAN, TokenType.TYPE_STRING, TokenType.TYPE_NUMBER);
|
||||||
ParserUtil.ensureType(typeToken, TokenType.TYPE_BOOLEAN, TokenType.TYPE_STRING, TokenType.TYPE_NUMBER);
|
ReturnType type = ParserUtil.getVariableReturnType(typeToken);
|
||||||
ReturnType argType = ParserUtil.getVariableReturnType(typeToken);
|
|
||||||
|
|
||||||
// Parse argument name
|
// Parse parameter name
|
||||||
Token identifierToken = lexer.consume();
|
Token identifierToken = lexer.consume("Expected function parameter identifier", TokenType.IDENTIFIER);
|
||||||
ParserUtil.ensureType(identifierToken, TokenType.IDENTIFIER);
|
String name = identifierToken.getContent();
|
||||||
String argName = identifierToken.getContent();
|
|
||||||
|
|
||||||
arguments.add(Pair.of(argName, argType));
|
parameters.add(Pair.of(name, type));
|
||||||
|
|
||||||
// Consume separator if present, trailing separators are allowed
|
// Consume separator if present, trailing separators are allowed
|
||||||
if(lexer.current().isType(TokenType.SEPARATOR)) lexer.consume();
|
if(lexer.current().isType(TokenType.SEPARATOR)) lexer.consumeUnchecked();
|
||||||
}
|
}
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.CLOSE_PAREN);
|
lexer.consume("Expected ')' after function parameter declaration", TokenType.CLOSE_PAREN);
|
||||||
return arguments;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Block parseBlock(ScopeBuilder scopeBuilder, ReturnType blockReturnType) {
|
private Block parseBlock(ScopeBuilder scopeBuilder, ReturnType blockReturnType) {
|
||||||
@@ -397,7 +411,7 @@ public class Parser {
|
|||||||
|
|
||||||
// Parse each statement
|
// Parse each statement
|
||||||
while(lexer.hasNext() && !lexer.current().isType(TokenType.BLOCK_END)) {
|
while(lexer.hasNext() && !lexer.current().isType(TokenType.BLOCK_END)) {
|
||||||
Expression<?> expression = parseStatement(lexer, scopeBuilder);
|
Expression<?> expression = parseStatement(scopeBuilder);
|
||||||
if(expression != Expression.NOOP) {
|
if(expression != Expression.NOOP) {
|
||||||
expressions.add(expression);
|
expressions.add(expression);
|
||||||
}
|
}
|
||||||
@@ -416,42 +430,36 @@ public class Parser {
|
|||||||
return new Block(expressions, startPosition, blockReturnType);
|
return new Block(expressions, startPosition, blockReturnType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression<?> parseStatement(Lexer lexer, ScopeBuilder scopeBuilder) {
|
private Expression<?> parseStatement(ScopeBuilder scopeBuilder) {
|
||||||
Token token = lexer.current();
|
Token token = lexer.current();
|
||||||
|
|
||||||
// Include BREAK and CONTINUE as valid token types if scope is within a loop
|
|
||||||
if(scopeBuilder.isInLoop()) ParserUtil.ensureType(token, TokenType.IDENTIFIER, TokenType.IF_STATEMENT, TokenType.WHILE_LOOP,
|
|
||||||
TokenType.FOR_LOOP,
|
|
||||||
TokenType.TYPE_NUMBER, TokenType.TYPE_STRING, TokenType.TYPE_BOOLEAN,
|
|
||||||
TokenType.TYPE_VOID,
|
|
||||||
TokenType.RETURN, TokenType.BREAK, TokenType.CONTINUE, TokenType.FAIL);
|
|
||||||
else ParserUtil.ensureType(token, TokenType.IDENTIFIER, TokenType.IF_STATEMENT, TokenType.WHILE_LOOP, TokenType.FOR_LOOP,
|
|
||||||
TokenType.TYPE_NUMBER, TokenType.TYPE_STRING, TokenType.TYPE_BOOLEAN, TokenType.TYPE_VOID,
|
|
||||||
TokenType.RETURN,
|
|
||||||
TokenType.FAIL);
|
|
||||||
|
|
||||||
Expression<?> expression = switch(token.getType()) {
|
Expression<?> expression = switch(token.getType()) {
|
||||||
case FOR_LOOP -> parseForLoop(scopeBuilder);
|
case FOR_LOOP -> parseForLoop(scopeBuilder);
|
||||||
case IF_STATEMENT -> parseIfStatement(scopeBuilder);
|
case IF_STATEMENT -> parseIfStatement(scopeBuilder);
|
||||||
case WHILE_LOOP -> parseWhileLoop(scopeBuilder);
|
case WHILE_LOOP -> parseWhileLoop(scopeBuilder);
|
||||||
case IDENTIFIER -> {
|
case IDENTIFIER -> {
|
||||||
if(scopeBuilder.containsVariable(token.getContent())) yield parseAssignment(lexer.consume(), scopeBuilder); // Assume variable assignment
|
if(scopeBuilder.containsVariable(token.getContent())) yield parseAssignment(scopeBuilder); // Assume variable assignment
|
||||||
else yield parseFunctionInvocation(true, lexer.consume(), scopeBuilder);
|
else yield parseFunctionInvocation(lexer.consumeUnchecked(), scopeBuilder);
|
||||||
}
|
}
|
||||||
case TYPE_NUMBER, TYPE_STRING, TYPE_BOOLEAN, TYPE_VOID -> parseDeclaration(scopeBuilder);
|
case TYPE_NUMBER, TYPE_STRING, TYPE_BOOLEAN, TYPE_VOID -> parseDeclaration(scopeBuilder);
|
||||||
case RETURN -> parseReturn(scopeBuilder);
|
case RETURN -> parseReturn(scopeBuilder);
|
||||||
case BREAK -> new BreakKeyword(lexer.consume().getPosition());
|
case BREAK -> {
|
||||||
case CONTINUE -> new ContinueKeyword(lexer.consume().getPosition());
|
if (!scopeBuilder.isInLoop()) throw new ParseException("Break statements can only be defined inside loops", token.getPosition());
|
||||||
case FAIL -> new FailKeyword(lexer.consume().getPosition());
|
yield new BreakKeyword(lexer.consumeUnchecked().getPosition());
|
||||||
|
}
|
||||||
|
case CONTINUE -> {
|
||||||
|
if (!scopeBuilder.isInLoop()) throw new ParseException("Continue statements can only be defined inside loops", token.getPosition());
|
||||||
|
yield new ContinueKeyword(lexer.consumeUnchecked().getPosition());
|
||||||
|
}
|
||||||
|
case FAIL -> new FailKeyword(lexer.consumeUnchecked().getPosition());
|
||||||
|
case STATEMENT_END -> Expression.NOOP;
|
||||||
default -> throw new UnsupportedOperationException("Unexpected token " + token.getType() + ": " + token.getPosition());
|
default -> throw new UnsupportedOperationException("Unexpected token " + token.getType() + ": " + token.getPosition());
|
||||||
};
|
};
|
||||||
if(!token.isControlStructure() && expression != Expression.NOOP) ParserUtil.ensureType(lexer.consume(), TokenType.STATEMENT_END);
|
if(!token.isControlStructure() && expression != Expression.NOOP) lexer.consume("Expected ';' at end of statement", TokenType.STATEMENT_END);
|
||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReturnKeyword parseReturn(ScopeBuilder scopeBuilder) {
|
private ReturnKeyword parseReturn(ScopeBuilder scopeBuilder) {
|
||||||
Token returnToken = lexer.consume();
|
Token returnToken = lexer.consume("Expected 'return' keyword at beginning of return statement", TokenType.RETURN);
|
||||||
ParserUtil.ensureType(returnToken, TokenType.RETURN);
|
|
||||||
Expression<?> data = null;
|
Expression<?> data = null;
|
||||||
if(!lexer.current().isType(TokenType.STATEMENT_END)) {
|
if(!lexer.current().isType(TokenType.STATEMENT_END)) {
|
||||||
data = parseExpression(scopeBuilder);
|
data = parseExpression(scopeBuilder);
|
||||||
@@ -459,10 +467,10 @@ public class Parser {
|
|||||||
return new ReturnKeyword(data, returnToken.getPosition());
|
return new ReturnKeyword(data, returnToken.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
private VariableAssignmentNode<?> parseAssignment(Token identifier, ScopeBuilder scopeBuilder) {
|
private VariableAssignmentNode<?> parseAssignment(ScopeBuilder scopeBuilder) {
|
||||||
ParserUtil.ensureType(identifier, TokenType.IDENTIFIER);
|
Token identifier = lexer.consume("Expected identifier at beginning of assignment", TokenType.IDENTIFIER);
|
||||||
|
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.ASSIGNMENT);
|
lexer.consume("Expected '=' after identifier for variable assignment", TokenType.ASSIGNMENT);
|
||||||
|
|
||||||
Expression<?> value = parseExpression(scopeBuilder);
|
Expression<?> value = parseExpression(scopeBuilder);
|
||||||
|
|
||||||
@@ -480,48 +488,37 @@ public class Parser {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression<?> parseFunctionInvocation(boolean fullStatement, Token identifier, ScopeBuilder scopeBuilder) {
|
private Expression<?> parseFunctionInvocation(Token identifier, ScopeBuilder scopeBuilder) {
|
||||||
if(!scopeBuilder.containsFunction(identifier.getContent()))
|
if(!scopeBuilder.containsFunction(identifier.getContent()))
|
||||||
throw new ParseException("Function \"" + identifier.getContent() + "\" is not defined in this scope", identifier.getPosition());
|
throw new ParseException("Function '" + identifier.getContent() + "' is not defined in this scope", identifier.getPosition());
|
||||||
|
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.OPEN_PAREN); // Invocation starts with open paren
|
FunctionBuilder<?> builder = scopeBuilder.getFunction(identifier.getContent());
|
||||||
|
|
||||||
List<Expression<?>> args = getFunctionArgs(scopeBuilder); // Extract arguments, consume the rest.
|
lexer.consume("Expected '(' after identifier " + identifier.getContent(), TokenType.OPEN_PAREN); // Invocation starts with open paren
|
||||||
|
|
||||||
ParserUtil.ensureType(lexer.consume(), TokenType.CLOSE_PAREN); // Remove body end
|
List<Expression<?>> args = new ArrayList<>();
|
||||||
|
while(!lexer.current().isType(TokenType.CLOSE_PAREN)) {
|
||||||
if(fullStatement) ParserUtil.ensureType(lexer.current(), TokenType.STATEMENT_END);
|
args.add(parseExpression(scopeBuilder));
|
||||||
|
if (lexer.current().isType(TokenType.CLOSE_PAREN)) break;
|
||||||
|
lexer.consume("Expected ',' between function arguments", TokenType.SEPARATOR);
|
||||||
|
}
|
||||||
|
lexer.consume("Expected ')' after function arguments", TokenType.CLOSE_PAREN);
|
||||||
|
|
||||||
if(ignoredFunctions.contains(identifier.getContent())) {
|
if(ignoredFunctions.contains(identifier.getContent())) {
|
||||||
return Expression.NOOP;
|
return Expression.NOOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(scopeBuilder.containsFunction(identifier.getContent())) {
|
if(builder.argNumber() != -1 && args.size() != builder.argNumber())
|
||||||
FunctionBuilder<?> builder = scopeBuilder.getFunction(identifier.getContent());
|
throw new ParseException("Expected " + builder.argNumber() + " arguments, found " + args.size(), identifier.getPosition());
|
||||||
|
|
||||||
if(builder.argNumber() != -1 && args.size() != builder.argNumber())
|
for(int i = 0; i < args.size(); i++) {
|
||||||
throw new ParseException("Expected " + builder.argNumber() + " arguments, found " + args.size(), identifier.getPosition());
|
Expression<?> argument = args.get(i);
|
||||||
|
if(builder.getArgument(i) == null)
|
||||||
for(int i = 0; i < args.size(); i++) {
|
throw new ParseException("Unexpected argument at position " + i + " in function " + identifier.getContent(),
|
||||||
Expression<?> argument = args.get(i);
|
identifier.getPosition());
|
||||||
if(builder.getArgument(i) == null)
|
ParserUtil.ensureReturnType(argument, builder.getArgument(i));
|
||||||
throw new ParseException("Unexpected argument at position " + i + " in function " + identifier.getContent(),
|
|
||||||
identifier.getPosition());
|
|
||||||
ParserUtil.ensureReturnType(argument, builder.getArgument(i));
|
|
||||||
}
|
|
||||||
return builder.build(args, identifier.getPosition());
|
|
||||||
}
|
}
|
||||||
throw new UnsupportedOperationException("Unsupported function: " + identifier.getContent());
|
return builder.build(args, identifier.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Expression<?>> getFunctionArgs(ScopeBuilder scopeBuilder) {
|
|
||||||
List<Expression<?>> args = new ArrayList<>();
|
|
||||||
|
|
||||||
while(!lexer.current().isType(TokenType.CLOSE_PAREN)) {
|
|
||||||
args.add(parseExpression(scopeBuilder));
|
|
||||||
ParserUtil.ensureType(lexer.current(), TokenType.SEPARATOR, TokenType.CLOSE_PAREN);
|
|
||||||
if(lexer.current().isType(TokenType.SEPARATOR)) lexer.consume();
|
|
||||||
}
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -20,14 +20,14 @@ import com.dfsek.terra.addons.terrascript.parser.lang.Expression;
|
|||||||
|
|
||||||
public class ParserUtil {
|
public class ParserUtil {
|
||||||
|
|
||||||
public static void ensureType(Token token, TokenType... expected) {
|
// public static void ensureType(Token token, TokenType... expected) {
|
||||||
for(TokenType type : expected) if(token.getType().equals(type)) return;
|
// for(TokenType type : expected) if(token.getType().equals(type)) return;
|
||||||
throw new ParseException("Expected " + Arrays.toString(expected) + " but found " + token.getType(), token.getPosition());
|
// throw new ParseException("Expected " + Arrays.toString(expected) + " but found " + token.getType(), token.getPosition());
|
||||||
}
|
// }
|
||||||
|
|
||||||
public static void ensureReturnType(Expression<?> returnable, Expression.ReturnType... types) {
|
public static void ensureReturnType(Expression<?> returnable, Expression.ReturnType... types) {
|
||||||
for(Expression.ReturnType type : types) if(returnable.returnType().equals(type)) return;
|
for(Expression.ReturnType type : types) if(returnable.returnType().equals(type)) return;
|
||||||
throw new ParseException("Expected " + Arrays.toString(types) + " but found " + returnable.returnType(), returnable.getPosition());
|
throw new ParseException("Invalid type " + returnable.returnType() + ", expected " + (types.length == 1 ? types[0].toString() : "one of " + Arrays.toString(types)), returnable.getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Expression.ReturnType getVariableReturnType(Token varToken) {
|
public static Expression.ReturnType getVariableReturnType(Token varToken) {
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ public class ParseException extends RuntimeException {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return super.getMessage() + ": " + position;
|
return "Error at " + position + ": " + super.getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourcePosition getPosition() {
|
public SourcePosition getPosition() {
|
||||||
|
|||||||
+3
@@ -16,6 +16,9 @@ import com.dfsek.terra.addons.terrascript.parser.lang.Expression;
|
|||||||
public interface FunctionBuilder<T extends Function<?>> {
|
public interface FunctionBuilder<T extends Function<?>> {
|
||||||
T build(List<Expression<?>> argumentList, SourcePosition position);
|
T build(List<Expression<?>> argumentList, SourcePosition position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Number of function arguments, -1 if the function uses a vararg at the end
|
||||||
|
*/
|
||||||
int argNumber();
|
int argNumber();
|
||||||
|
|
||||||
Expression.ReturnType getArgument(int position);
|
Expression.ReturnType getArgument(int position);
|
||||||
|
|||||||
+10
-10
@@ -15,16 +15,16 @@ import com.dfsek.terra.api.util.generic.pair.Pair;
|
|||||||
public class UserDefinedFunctionBuilder<T extends Function<?>> implements FunctionBuilder<T> {
|
public class UserDefinedFunctionBuilder<T extends Function<?>> implements FunctionBuilder<T> {
|
||||||
|
|
||||||
private final ReturnType returnType;
|
private final ReturnType returnType;
|
||||||
private final List<Pair<Integer, ReturnType>> argumentInfo;
|
private final List<Pair<Integer, ReturnType>> parameterInfo;
|
||||||
private final ScopeBuilder bodyScopeBuilder;
|
private final ScopeBuilder bodyScopeBuilder;
|
||||||
private final Block body;
|
private final Block body;
|
||||||
|
|
||||||
public UserDefinedFunctionBuilder(ReturnType returnType, List<Pair<Integer, ReturnType>> argumentInfo, Block body,
|
public UserDefinedFunctionBuilder(ReturnType returnType, List<Pair<Integer, ReturnType>> parameterInfo, Block body,
|
||||||
ScopeBuilder functionBodyScope) {
|
ScopeBuilder functionBodyScope) {
|
||||||
this.returnType = returnType;
|
this.returnType = returnType;
|
||||||
this.bodyScopeBuilder = functionBodyScope;
|
this.bodyScopeBuilder = functionBodyScope;
|
||||||
this.body = body;
|
this.body = body;
|
||||||
this.argumentInfo = argumentInfo;
|
this.parameterInfo = parameterInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -44,12 +44,12 @@ public class UserDefinedFunctionBuilder<T extends Function<?>> implements Functi
|
|||||||
Scope bodyScope = threadLocalScope.get();
|
Scope bodyScope = threadLocalScope.get();
|
||||||
// Pass arguments into scope of function body
|
// Pass arguments into scope of function body
|
||||||
for(int i = 0; i < argumentList.size(); i++) {
|
for(int i = 0; i < argumentList.size(); i++) {
|
||||||
Pair<Integer, ReturnType> argInfo = argumentInfo.get(i);
|
Pair<Integer, ReturnType> paramInfo = parameterInfo.get(i);
|
||||||
Expression<?> argExpression = argumentList.get(i);
|
Expression<?> argExpression = argumentList.get(i);
|
||||||
switch(argInfo.getRight()) {
|
switch(paramInfo.getRight()) {
|
||||||
case NUMBER -> bodyScope.setNum(argInfo.getLeft(), argExpression.applyDouble(implementationArguments, scope));
|
case NUMBER -> bodyScope.setNum(paramInfo.getLeft(), argExpression.applyDouble(implementationArguments, scope));
|
||||||
case BOOLEAN -> bodyScope.setBool(argInfo.getLeft(), argExpression.applyBoolean(implementationArguments, scope));
|
case BOOLEAN -> bodyScope.setBool(paramInfo.getLeft(), argExpression.applyBoolean(implementationArguments, scope));
|
||||||
case STRING -> bodyScope.setStr(argInfo.getLeft(), (String) argExpression.evaluate(implementationArguments, scope));
|
case STRING -> bodyScope.setStr(paramInfo.getLeft(), (String) argExpression.evaluate(implementationArguments, scope));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return body.evaluate(implementationArguments, bodyScope).data().evaluate(implementationArguments, scope);
|
return body.evaluate(implementationArguments, bodyScope).data().evaluate(implementationArguments, scope);
|
||||||
@@ -64,11 +64,11 @@ public class UserDefinedFunctionBuilder<T extends Function<?>> implements Functi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int argNumber() {
|
public int argNumber() {
|
||||||
return argumentInfo.size();
|
return parameterInfo.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReturnType getArgument(int position) {
|
public ReturnType getArgument(int position) {
|
||||||
return argumentInfo.get(position).getRight();
|
return parameterInfo.get(position).getRight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-10
@@ -7,23 +7,80 @@
|
|||||||
|
|
||||||
package structure;
|
package structure;
|
||||||
|
|
||||||
|
import com.dfsek.terra.addons.terrascript.lexer.LookaheadStream;
|
||||||
|
import com.dfsek.terra.addons.terrascript.lexer.Char;
|
||||||
|
|
||||||
|
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.io.StringReader;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
import com.dfsek.terra.addons.terrascript.lexer.LookaheadStream;
|
|
||||||
|
|
||||||
|
|
||||||
public class LookaheadStreamTest {
|
public class LookaheadStreamTest {
|
||||||
@Test
|
@Test
|
||||||
public void lookahead() {
|
public void lookahead() {
|
||||||
LookaheadStream lookahead = new LookaheadStream(new StringReader("Test string..."));
|
String testString = "Test string...\nNew line";
|
||||||
|
|
||||||
for(int i = 0; lookahead.next(i) != null; i++) {
|
LookaheadStream lookahead = new LookaheadStream(testString);
|
||||||
System.out.print(lookahead.next(i).getCharacter());
|
|
||||||
}
|
Char first = new Char('T', new SourcePosition(1, 1));
|
||||||
while(lookahead.next(0) != null) {
|
Char second = new Char('e', new SourcePosition(1, 2));
|
||||||
System.out.print(lookahead.consume().getCharacter());
|
Char third = new Char('s', new SourcePosition(1, 3));
|
||||||
}
|
Char space = new Char(' ', new SourcePosition(1, 5));
|
||||||
|
Char newline = new Char('\n', new SourcePosition(1, 15));
|
||||||
|
Char lineTwoColOne = new Char('N', new SourcePosition(2, 1));
|
||||||
|
String lineTwo = "New line";
|
||||||
|
|
||||||
|
assertTrue(lookahead.matchesString("Test", false));
|
||||||
|
assertTrue(lookahead.matchesString(testString, false));
|
||||||
|
assertFalse(lookahead.matchesString(testString + "asdf", false));
|
||||||
|
assertFalse(lookahead.matchesString("Foo", false));
|
||||||
|
|
||||||
|
assertEquals(first, lookahead.current());
|
||||||
|
assertEquals(first, lookahead.current());
|
||||||
|
|
||||||
|
assertEquals(new SourcePosition(1, 1), lookahead.getPosition());
|
||||||
|
assertEquals(new SourcePosition(1, 1), lookahead.getPosition());
|
||||||
|
|
||||||
|
assertEquals(second, lookahead.peek());
|
||||||
|
assertEquals(second, lookahead.peek());
|
||||||
|
|
||||||
|
assertEquals(first, lookahead.consume());
|
||||||
|
|
||||||
|
assertFalse(lookahead.matchesString(testString, false));
|
||||||
|
|
||||||
|
assertEquals(second, lookahead.current());
|
||||||
|
|
||||||
|
assertEquals(second, lookahead.consume());
|
||||||
|
|
||||||
|
assertEquals(third, lookahead.current());
|
||||||
|
|
||||||
|
assertTrue(lookahead.matchesString("st", true));
|
||||||
|
|
||||||
|
assertEquals(space, lookahead.current());
|
||||||
|
|
||||||
|
assertEquals(space, lookahead.consume());
|
||||||
|
|
||||||
|
assertTrue(lookahead.matchesString("string...", false));
|
||||||
|
assertTrue(lookahead.matchesString("string...", true));
|
||||||
|
assertFalse(lookahead.matchesString("string...", false));
|
||||||
|
|
||||||
|
assertEquals(newline, lookahead.current());
|
||||||
|
assertEquals(newline, lookahead.consume());
|
||||||
|
|
||||||
|
assertEquals(lineTwoColOne, lookahead.current());
|
||||||
|
|
||||||
|
assertTrue(lookahead.matchesString(lineTwo, false));
|
||||||
|
assertFalse(lookahead.matchesString(lineTwo + "asdf", false));
|
||||||
|
assertTrue(lookahead.matchesString(lineTwo, true));
|
||||||
|
|
||||||
|
assertEquals(new SourcePosition(2, 8), lookahead.getPosition());
|
||||||
|
|
||||||
|
assertDoesNotThrow(lookahead::consume);
|
||||||
|
assertEquals(new SourcePosition(2, 8), lookahead.getPosition());
|
||||||
|
|
||||||
|
assertDoesNotThrow(lookahead::consume);
|
||||||
|
assertEquals(new SourcePosition(2, 8), lookahead.getPosition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
str myFunction(str functionString, num functionNumber) {
|
||||||
|
test(functionString, functionNumber);
|
||||||
|
void nestedFunction() {
|
||||||
|
test("Hello from nested function", 69);
|
||||||
|
}
|
||||||
|
nestedFunction();
|
||||||
|
return "Return to sender";
|
||||||
|
}
|
||||||
|
|
||||||
|
str functionResult = myFunction("Hello from myFunction", 535);
|
||||||
|
|
||||||
|
test(functionResult, 58);
|
||||||
|
|
||||||
|
void noReturn() test("Single statement function", 42);
|
||||||
|
|
||||||
|
noReturn();
|
||||||
|
|
||||||
bool thing1 = 2 > (2+2) || false;
|
bool thing1 = 2 > (2+2) || false;
|
||||||
|
|
||||||
if(2 > 2 || 3 + 4 <= 2 && 4 + 5 > 2 / 3) {
|
if(2 > 2 || 3 + 4 <= 2 && 4 + 5 > 2 / 3) {
|
||||||
@@ -29,7 +46,9 @@ test("-2 = " + thing, 2);
|
|||||||
thing = -thing;
|
thing = -thing;
|
||||||
test("--2 = " + thing, 2);
|
test("--2 = " + thing, 2);
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
for(num i = 0; i < 5; i = i + 1) {
|
for(num i = 0; i < 5; i = i + 1) {
|
||||||
test("i = " + i, iterator);
|
test("i = " + i, iterator);
|
||||||
|
|||||||
Reference in New Issue
Block a user