diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/Function.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/Function.java deleted file mode 100644 index d60f60cc8..000000000 --- a/common/src/main/java/com/dfsek/terra/api/structures/parser/Function.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.dfsek.terra.api.structures.parser; - -import com.dfsek.terra.api.math.vector.Location; -import com.dfsek.terra.api.platform.world.Chunk; - -public interface Function { - T apply(Location location); - - T apply(Location location, Chunk chunk); - - String name(); -} diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/FunctionBuilder.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/FunctionBuilder.java index 8b24fbcf0..b340b4bf9 100644 --- a/common/src/main/java/com/dfsek/terra/api/structures/parser/FunctionBuilder.java +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/FunctionBuilder.java @@ -1,6 +1,8 @@ package com.dfsek.terra.api.structures.parser; import com.dfsek.terra.api.structures.parser.exceptions.ParseException; +import com.dfsek.terra.api.structures.parser.lang.Argument; +import com.dfsek.terra.api.structures.parser.lang.Function; import java.util.List; diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/Parser.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/Parser.java index 7a991d226..ba9fe389c 100644 --- a/common/src/main/java/com/dfsek/terra/api/structures/parser/Parser.java +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/Parser.java @@ -1,6 +1,13 @@ package com.dfsek.terra.api.structures.parser; import com.dfsek.terra.api.structures.parser.exceptions.ParseException; +import com.dfsek.terra.api.structures.parser.lang.Block; +import com.dfsek.terra.api.structures.parser.lang.Function; +import com.dfsek.terra.api.structures.parser.lang.Item; +import com.dfsek.terra.api.structures.parser.lang.Keyword; +import com.dfsek.terra.api.structures.parser.lang.Statement; +import com.dfsek.terra.api.structures.parser.lang.keywords.IfKeyword; +import com.dfsek.terra.api.structures.parser.lang.statements.EqualsStatement; import com.dfsek.terra.api.structures.tokenizer.Token; import com.dfsek.terra.api.structures.tokenizer.Tokenizer; import com.dfsek.terra.api.structures.tokenizer.exceptions.TokenizerException; @@ -16,6 +23,8 @@ import java.util.stream.Collectors; public class Parser { private final String data; private final Map>> functions = new HashMap<>(); + private final Set keywords = Sets.newHashSet("if"); + Set allowedArguments = Sets.newHashSet(Token.Type.STRING, Token.Type.NUMBER, Token.Type.IDENTIFIER); public Parser(String data) { @@ -27,49 +36,94 @@ public class Parser { return this; } - public List> parse() throws ParseException { + public Block parse() throws ParseException { Tokenizer tokenizer = new Tokenizer(data); - List> builtFunctions = new GlueList<>(); - List functionBuilder = new GlueList<>(); - Token token = null; - while(tokenizer.hasNext()) { - try { - token = tokenizer.fetch(); - functionBuilder.add(token); - if(token.getType().equals(Token.Type.STATEMENT_END)) { - Token identifier = functionBuilder.remove(0); - checkType(identifier, Token.Type.IDENTIFIER); // First token must be identifier + List tokens = new GlueList<>(); + try { + while(tokenizer.hasNext()) tokens.add(tokenizer.fetch()); + } catch(TokenizerException e) { + throw new ParseException("Failed to tokenize input", e); + } - if(!functions.containsKey(identifier.getContent())) - throw new ParseException("No such function " + identifier.getContent() + ": " + identifier.getStart()); - - checkType(functionBuilder.remove(0), Token.Type.BODY_BEGIN); // Second is body begin + return parseBlock(tokens); + } - List args = getArgs(functionBuilder); // Extract arguments, consume the rest. + private Keyword parseKeyword(List tokens, List functionAndArguments) throws ParseException { - functionBuilder.remove(0); // Remove body end + Token identifier = functionAndArguments.remove(0); + System.out.println("Parsing keyword at " + identifier.getStart()); + checkType(identifier, Token.Type.IDENTIFIER); + if(!keywords.contains(identifier.getContent())) + throw new ParseException("No such keyword " + identifier.getContent() + ": " + identifier.getStart()); + Keyword k = null; + if(identifier.getContent().equals("if")) { - checkType(functionBuilder.remove(0), Token.Type.STATEMENT_END); + checkType(functionAndArguments.remove(0), Token.Type.BODY_BEGIN); - List arg = args.stream().map(Token::getContent).collect(Collectors.toList()); + Function left = parseFunction(functionAndArguments, false); - FunctionBuilder builder = functions.get(identifier.getContent()); - if(arg.size() != builder.getArguments().size()) - throw new ParseException("Expected " + builder.getArguments().size() + " arguments, found " + arg.size() + ": " + identifier.getStart()); + Statement statement = null; + Token comparator = functionAndArguments.remove(0); + checkType(comparator, Token.Type.BOOLEAN_OPERATOR); - builtFunctions.add(functions.get(identifier.getContent()).build(arg)); + Function right = parseFunction(functionAndArguments, false); - functionBuilder.clear(); - } - } catch(TokenizerException e) { - throw new ParseException("Failed to tokenize input", e); + checkType(functionAndArguments.remove(0), Token.Type.BODY_END); + if(comparator.getContent().equals("==")) { + statement = new EqualsStatement(left, right); } + k = new IfKeyword(parseBlock(tokens), statement); + } - if(token != null) checkType(token, Token.Type.STATEMENT_END); - return builtFunctions; + return k; + } + + private Block parseBlock(List tokens) throws ParseException { + List> parsedItems = new GlueList<>(); + List functionArgs = new GlueList<>(); + + while(tokens.size() > 0) { + Token token = tokens.remove(0); + System.out.println(token); + if(token.getType().equals(Token.Type.BLOCK_END)) break; + functionArgs.add(token); + if(token.getType().equals(Token.Type.STATEMENT_END)) { + parsedItems.add(parseFunction(functionArgs, true)); + functionArgs.clear(); + } else if(token.getType().equals(Token.Type.BLOCK_BEGIN)) { + parsedItems.add(parseKeyword(tokens, functionArgs)); + functionArgs.clear(); + } + } + return new Block(parsedItems); + } + + private Function parseFunction(List functionAndArguments, boolean fullStatement) throws ParseException { + Token identifier = functionAndArguments.remove(0); + System.out.println("Parsing function at " + identifier.getStart()); + checkType(identifier, Token.Type.IDENTIFIER); // First token must be identifier + + if(!functions.containsKey(identifier.getContent())) + throw new ParseException("No such function " + identifier.getContent() + ": " + identifier.getStart()); + + checkType(functionAndArguments.remove(0), Token.Type.BODY_BEGIN); // Second is body begin + + + List args = getArgs(functionAndArguments); // Extract arguments, consume the rest. + + functionAndArguments.remove(0); // Remove body end + + if(fullStatement) checkType(functionAndArguments.remove(0), Token.Type.STATEMENT_END); + + List arg = args.stream().map(Token::getContent).collect(Collectors.toList()); + + FunctionBuilder builder = functions.get(identifier.getContent()); + if(arg.size() != builder.getArguments().size()) + throw new ParseException("Expected " + builder.getArguments().size() + " arguments, found " + arg.size() + ": " + identifier.getStart()); + return functions.get(identifier.getContent()).build(arg); } private List getArgs(List functionBuilder) throws ParseException { diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/Argument.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Argument.java similarity index 53% rename from common/src/main/java/com/dfsek/terra/api/structures/parser/Argument.java rename to common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Argument.java index ffe9eb84d..98a7c246f 100644 --- a/common/src/main/java/com/dfsek/terra/api/structures/parser/Argument.java +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Argument.java @@ -1,4 +1,4 @@ -package com.dfsek.terra.api.structures.parser; +package com.dfsek.terra.api.structures.parser.lang; public interface Argument { T parse(String input); diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Block.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Block.java index 385264f2b..95ea2bf01 100644 --- a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Block.java +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Block.java @@ -1,4 +1,30 @@ package com.dfsek.terra.api.structures.parser.lang; -public class Block { +import com.dfsek.terra.api.math.vector.Location; +import com.dfsek.terra.api.platform.world.Chunk; + +import java.util.List; + +public class Block implements Item { + private final List> items; + + public Block(List> items) { + this.items = items; + } + + public List> getItems() { + return items; + } + + @Override + public Void apply(Location location) { + items.forEach(item -> item.apply(location)); + return null; + } + + @Override + public Void apply(Location location, Chunk chunk) { + items.forEach(item -> item.apply(location, chunk)); + return null; + } } diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Expression.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Expression.java index b2c539e3e..8a25fd846 100644 --- a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Expression.java +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Expression.java @@ -1,4 +1,4 @@ package com.dfsek.terra.api.structures.parser.lang; -public class Expression { +public interface Expression extends Item { } diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Function.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Function.java new file mode 100644 index 000000000..884516e4c --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Function.java @@ -0,0 +1,6 @@ +package com.dfsek.terra.api.structures.parser.lang; + +public interface Function extends Expression { + + String name(); +} diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Item.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Item.java index 0940ba797..449523dc0 100644 --- a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Item.java +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Item.java @@ -1,4 +1,10 @@ package com.dfsek.terra.api.structures.parser.lang; -public interface Item { +import com.dfsek.terra.api.math.vector.Location; +import com.dfsek.terra.api.platform.world.Chunk; + +public interface Item { + T apply(Location location); + + T apply(Location location, Chunk chunk); } diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Keyword.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Keyword.java new file mode 100644 index 000000000..5644784de --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Keyword.java @@ -0,0 +1,4 @@ +package com.dfsek.terra.api.structures.parser.lang; + +public interface Keyword extends Expression { +} diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Statement.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Statement.java new file mode 100644 index 000000000..f268f4592 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/Statement.java @@ -0,0 +1,4 @@ +package com.dfsek.terra.api.structures.parser.lang; + +public interface Statement extends Item { +} diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/IfKeyword.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/IfKeyword.java new file mode 100644 index 000000000..22a060f65 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/IfKeyword.java @@ -0,0 +1,29 @@ +package com.dfsek.terra.api.structures.parser.lang.keywords; + +import com.dfsek.terra.api.math.vector.Location; +import com.dfsek.terra.api.platform.world.Chunk; +import com.dfsek.terra.api.structures.parser.lang.Block; +import com.dfsek.terra.api.structures.parser.lang.Keyword; +import com.dfsek.terra.api.structures.parser.lang.Statement; + +public class IfKeyword implements Keyword { + private final Block conditional; + private final Statement statement; + + public IfKeyword(Block conditional, Statement statement) { + this.conditional = conditional; + this.statement = statement; + } + + @Override + public Void apply(Location location) { + if(statement.apply(location)) conditional.apply(location); + return null; + } + + @Override + public Void apply(Location location, Chunk chunk) { + if(statement.apply(location, chunk)) conditional.apply(location, chunk); + return null; + } +} diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/ReturnKeyword.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/ReturnKeyword.java new file mode 100644 index 000000000..402ebeccc --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/ReturnKeyword.java @@ -0,0 +1,17 @@ +package com.dfsek.terra.api.structures.parser.lang.keywords; + +import com.dfsek.terra.api.math.vector.Location; +import com.dfsek.terra.api.platform.world.Chunk; +import com.dfsek.terra.api.structures.parser.lang.Keyword; + +public class ReturnKeyword implements Keyword { + @Override + public Void apply(Location location) { + return null; + } + + @Override + public Void apply(Location location, Chunk chunk) { + return null; + } +} diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/statements/EqualsStatement.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/statements/EqualsStatement.java new file mode 100644 index 000000000..b55178685 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/statements/EqualsStatement.java @@ -0,0 +1,26 @@ +package com.dfsek.terra.api.structures.parser.lang.statements; + +import com.dfsek.terra.api.math.vector.Location; +import com.dfsek.terra.api.platform.world.Chunk; +import com.dfsek.terra.api.structures.parser.lang.Item; +import com.dfsek.terra.api.structures.parser.lang.Statement; + +public class EqualsStatement implements Statement { + private final Item left; + private final Item right; + + public EqualsStatement(Item left, Item right) { + this.left = left; + this.right = right; + } + + @Override + public Boolean apply(Location location) { + return left.apply(location).equals(right.apply(location)); + } + + @Override + public Boolean apply(Location location, Chunk chunk) { + return left.apply(location, chunk).equals(right.apply(location, chunk)); + } +} diff --git a/common/src/main/java/com/dfsek/terra/api/structures/script/StructureScript.java b/common/src/main/java/com/dfsek/terra/api/structures/script/StructureScript.java new file mode 100644 index 000000000..d4a3f2508 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/structures/script/StructureScript.java @@ -0,0 +1,4 @@ +package com.dfsek.terra.api.structures.script; + +public class StructureScript { +} diff --git a/common/src/main/java/com/dfsek/terra/api/structures/tokenizer/Position.java b/common/src/main/java/com/dfsek/terra/api/structures/tokenizer/Position.java index 2c0651409..17d319edb 100644 --- a/common/src/main/java/com/dfsek/terra/api/structures/tokenizer/Position.java +++ b/common/src/main/java/com/dfsek/terra/api/structures/tokenizer/Position.java @@ -11,6 +11,6 @@ public class Position { @Override public String toString() { - return (line + 1) + ":" + (index + 1); + return (line + 1) + ":" + index; } } diff --git a/common/src/main/java/com/dfsek/terra/api/structures/tokenizer/Token.java b/common/src/main/java/com/dfsek/terra/api/structures/tokenizer/Token.java index dd6d88373..34f5f9eb1 100644 --- a/common/src/main/java/com/dfsek/terra/api/structures/tokenizer/Token.java +++ b/common/src/main/java/com/dfsek/terra/api/structures/tokenizer/Token.java @@ -29,6 +29,53 @@ public class Token { } public enum Type { - IDENTIFIER, NUMBER, STRING, BOOLEAN, BODY_BEGIN, BODY_END, STATEMENT_END, SEPARATOR, BLOCK_BEGIN, BLOCK_END + /** + * Function identifier or language keyword + */ + IDENTIFIER, + /** + * Numeric literal + */ + NUMBER, + /** + * String literal + */ + STRING, + /** + * Boolean literal + */ + BOOLEAN, + /** + * Beginning of function body + */ + BODY_BEGIN, + /** + * Ending of function body + */ + BODY_END, + /** + * End of statement + */ + STATEMENT_END, + /** + * Argument separator + */ + SEPARATOR, + /** + * Beginning of code block + */ + BLOCK_BEGIN, + /** + * End of code block + */ + BLOCK_END, + /** + * assignment operator + */ + ASSIGNMENT, + /** + * Boolean operator + */ + BOOLEAN_OPERATOR } } diff --git a/common/src/main/java/com/dfsek/terra/api/structures/tokenizer/Tokenizer.java b/common/src/main/java/com/dfsek/terra/api/structures/tokenizer/Tokenizer.java index 9eb73ffc3..d4a77b5ff 100644 --- a/common/src/main/java/com/dfsek/terra/api/structures/tokenizer/Tokenizer.java +++ b/common/src/main/java/com/dfsek/terra/api/structures/tokenizer/Tokenizer.java @@ -11,7 +11,7 @@ import java.util.Set; public class Tokenizer { private final Lookahead reader; - private final Set syntaxSignificant = Sets.newHashSet(';', '(', ')', '"', ',', '\\', // Currently used chars + private final Set syntaxSignificant = Sets.newHashSet(';', '(', ')', '"', ',', '\\', '=', // Currently used chars '{', '}'); // Reserved chars @@ -32,6 +32,13 @@ public class Tokenizer { if(reader.matches("/*", true)) skipTo("*/"); // Skip multi line comment + if(reader.matches("true", true)) + return new Token("true", Token.Type.BOOLEAN, new Position(reader.getLine(), reader.getIndex())); + if(reader.matches("false", true)) + return new Token("false", Token.Type.BOOLEAN, new Position(reader.getLine(), reader.getIndex())); + if(reader.matches("==", true)) + return new Token("==", Token.Type.BOOLEAN_OPERATOR, new Position(reader.getLine(), reader.getIndex())); + if(isNumberStart()) { StringBuilder num = new StringBuilder(); while(!reader.current().isEOF() && isNumberLike()) { @@ -55,6 +62,7 @@ public class Tokenizer { string.append(reader.consume()); } reader.consume(); // Consume last quote + return new Token(string.toString(), Token.Type.STRING, new Position(reader.getLine(), reader.getIndex())); } @@ -70,6 +78,8 @@ public class Tokenizer { return new Token(reader.consume().toString(), Token.Type.BLOCK_BEGIN, new Position(reader.getLine(), reader.getIndex())); if(reader.current().is('}')) return new Token(reader.consume().toString(), Token.Type.BLOCK_END, new Position(reader.getLine(), reader.getIndex())); + if(reader.current().is('=')) + return new Token(reader.consume().toString(), Token.Type.ASSIGNMENT, new Position(reader.getLine(), reader.getIndex())); StringBuilder token = new StringBuilder(); while(!reader.current().isEOF() && !isSyntaxSignificant(reader.current().getCharacter())) { @@ -77,7 +87,10 @@ public class Tokenizer { if(!c.isWhitespace()) token.append(c); } - return new Token(token.toString(), Token.Type.IDENTIFIER, new Position(reader.getLine(), reader.getIndex())); + String tokenString = token.toString(); + + + return new Token(tokenString, Token.Type.IDENTIFIER, new Position(reader.getLine(), reader.getIndex())); } private boolean isNumberLike() { diff --git a/common/src/test/java/structure/ParserTest.java b/common/src/test/java/structure/ParserTest.java index 24d57a8b5..3f4cd18d3 100644 --- a/common/src/test/java/structure/ParserTest.java +++ b/common/src/test/java/structure/ParserTest.java @@ -2,11 +2,12 @@ package structure; import com.dfsek.terra.api.math.vector.Location; import com.dfsek.terra.api.platform.world.Chunk; -import com.dfsek.terra.api.structures.parser.Argument; -import com.dfsek.terra.api.structures.parser.Function; import com.dfsek.terra.api.structures.parser.FunctionBuilder; import com.dfsek.terra.api.structures.parser.Parser; import com.dfsek.terra.api.structures.parser.exceptions.ParseException; +import com.dfsek.terra.api.structures.parser.lang.Argument; +import com.dfsek.terra.api.structures.parser.lang.Function; +import com.dfsek.terra.api.structures.parser.lang.Item; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; @@ -32,11 +33,11 @@ public class ParserTest { }); long l = System.nanoTime(); - List> functions = parser.parse(); + List> functions = parser.parse().getItems(); long t = System.nanoTime() - l; System.out.println("Took " + (double) t / 1000000); - for(Function f : functions) System.out.println(f); + for(Item f : functions) System.out.println(f); } private static class Test1 implements Function { diff --git a/common/src/test/resources/test.tesf b/common/src/test/resources/test.tesf index 7197182b4..916c3537c 100644 --- a/common/src/test/resources/test.tesf +++ b/common/src/test/resources/test.tesf @@ -1,2 +1,8 @@ test("hello", 1); test("ghgj{}()\"\\hgjhgj", 3.4); + +if(test("hello", 1) == test("hello", 1)) { + test("hello", 1); +} + +test("ghgj{}()\"\\hgjhgj", 3.4); \ No newline at end of file