From abc59901c10a7def51efa3c09e522875bad0991c Mon Sep 17 00:00:00 2001 From: dfsek Date: Fri, 25 Dec 2020 00:12:28 -0700 Subject: [PATCH] Add for loops --- .../terra/api/structures/parser/Parser.java | 57 ++++++++++++++----- .../api/structures/parser/ParserUtil.java | 13 +++++ .../api/structures/parser/lang/Block.java | 12 +++- .../lang/keywords/looplike/ForKeyword.java | 45 +++++++++++++++ .../keywords/{ => looplike}/IfKeyword.java | 2 +- .../keywords/{ => looplike}/WhileKeyword.java | 4 +- .../terra/api/structures/tokenizer/Token.java | 9 ++- .../api/structures/tokenizer/Tokenizer.java | 2 + common/src/test/resources/test.tesf | 5 ++ 9 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/looplike/ForKeyword.java rename common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/{ => looplike}/IfKeyword.java (94%) rename common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/{ => looplike}/WhileKeyword.java (89%) 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 8f5fc8214..21af51372 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 @@ -17,9 +17,10 @@ import com.dfsek.terra.api.structures.parser.lang.functions.builtin.SqrtFunction import com.dfsek.terra.api.structures.parser.lang.keywords.BreakKeyword; import com.dfsek.terra.api.structures.parser.lang.keywords.ContinueKeyword; import com.dfsek.terra.api.structures.parser.lang.keywords.FailKeyword; -import com.dfsek.terra.api.structures.parser.lang.keywords.IfKeyword; import com.dfsek.terra.api.structures.parser.lang.keywords.ReturnKeyword; -import com.dfsek.terra.api.structures.parser.lang.keywords.WhileKeyword; +import com.dfsek.terra.api.structures.parser.lang.keywords.looplike.ForKeyword; +import com.dfsek.terra.api.structures.parser.lang.keywords.looplike.IfKeyword; +import com.dfsek.terra.api.structures.parser.lang.keywords.looplike.WhileKeyword; import com.dfsek.terra.api.structures.parser.lang.operations.BinaryOperation; import com.dfsek.terra.api.structures.parser.lang.operations.BooleanAndOperation; import com.dfsek.terra.api.structures.parser.lang.operations.BooleanNotOperation; @@ -113,21 +114,52 @@ public class Parser { private Keyword parseLoopLike(List tokens, Map> variableMap) throws ParseException { Token identifier = tokens.remove(0); - ParserUtil.checkType(identifier, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP); + ParserUtil.checkType(identifier, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP, Token.Type.FOR_LOOP); ParserUtil.checkType(tokens.remove(0), Token.Type.GROUP_BEGIN); - Returnable comparator = parseExpression(tokens, true, variableMap); - ParserUtil.checkReturnType(comparator, Returnable.ReturnType.BOOLEAN); + + if(identifier.getType().equals(Token.Type.FOR_LOOP)) { + Token f = tokens.get(0); + ParserUtil.checkType(f, Token.Type.NUMBER_VARIABLE, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.IDENTIFIER); + Item initializer; + if(f.isVariableDeclaration()) { + Variable forVar = parseVariableDeclaration(tokens, ParserUtil.getVariableReturnType(f)); + Token name = tokens.get(1); + ParserUtil.checkType(tokens.remove(0), Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.NUMBER_VARIABLE); + initializer = parseAssignment(forVar, tokens, variableMap); + variableMap.put(name.getContent(), forVar); + } else initializer = parseExpression(tokens, true, variableMap); + ParserUtil.checkType(tokens.remove(0), Token.Type.STATEMENT_END); + Returnable conditional = parseExpression(tokens, true, variableMap); + ParserUtil.checkReturnType(conditional, Returnable.ReturnType.BOOLEAN); + ParserUtil.checkType(tokens.remove(0), Token.Type.STATEMENT_END); + + Item incrementer; + Token token = tokens.get(0); + if(variableMap.containsKey(token.getContent())) { // Assume variable assignment + Variable variable = variableMap.get(token.getContent()); + incrementer = parseAssignment(variable, tokens, variableMap); + } else incrementer = parseFunction(tokens, true, variableMap); + + ParserUtil.checkType(tokens.remove(0), Token.Type.GROUP_END); + + ParserUtil.checkType(tokens.remove(0), Token.Type.BLOCK_BEGIN); + + return new ForKeyword(parseBlock(tokens, variableMap), initializer, (Returnable) conditional, incrementer, identifier.getPosition()); + } + + Returnable first = parseExpression(tokens, true, variableMap); + ParserUtil.checkReturnType(first, Returnable.ReturnType.BOOLEAN); ParserUtil.checkType(tokens.remove(0), Token.Type.GROUP_END); ParserUtil.checkType(tokens.remove(0), Token.Type.BLOCK_BEGIN); if(identifier.getType().equals(Token.Type.IF_STATEMENT)) - return new IfKeyword(parseBlock(tokens, variableMap), (Returnable) comparator, identifier.getPosition()); // If statement + return new IfKeyword(parseBlock(tokens, variableMap), (Returnable) first, identifier.getPosition()); // If statement else if(identifier.getType().equals(Token.Type.WHILE_LOOP)) - return new WhileKeyword(parseBlock(tokens, variableMap), (Returnable) comparator, identifier.getPosition()); // While loop + return new WhileKeyword(parseBlock(tokens, variableMap), (Returnable) first, identifier.getPosition()); // While loop else throw new UnsupportedOperationException("Unknown keyword " + identifier.getContent() + ": " + identifier.getPosition()); } @@ -269,7 +301,8 @@ public class Parser { Token token = tokens.get(0); if(token.getType().equals(Token.Type.BLOCK_END)) break; // Stop parsing at block end. - ParserUtil.checkType(token, Token.Type.IDENTIFIER, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP, Token.Type.NUMBER_VARIABLE, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.RETURN, Token.Type.BREAK, Token.Type.CONTINUE, Token.Type.FAIL); + ParserUtil.checkType(token, Token.Type.IDENTIFIER, Token.Type.IF_STATEMENT, Token.Type.WHILE_LOOP, Token.Type.FOR_LOOP, + Token.Type.NUMBER_VARIABLE, Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.RETURN, Token.Type.BREAK, Token.Type.CONTINUE, Token.Type.FAIL); if(token.isLoopLike()) { // Parse loop-like tokens (if, while, etc) parsedItems.add(parseLoopLike(tokens, parsedVariables)); @@ -279,14 +312,8 @@ public class Parser { parsedItems.add(parseAssignment(variable, tokens, parsedVariables)); } else parsedItems.add(parseFunction(tokens, true, parsedVariables)); } else if(token.isVariableDeclaration()) { - Variable temp; - if(token.getType().equals(Token.Type.NUMBER_VARIABLE)) - temp = parseVariableDeclaration(tokens, Returnable.ReturnType.NUMBER); - else if(token.getType().equals(Token.Type.STRING_VARIABLE)) - temp = parseVariableDeclaration(tokens, Returnable.ReturnType.STRING); - else temp = parseVariableDeclaration(tokens, Returnable.ReturnType.BOOLEAN); + Variable temp = parseVariableDeclaration(tokens, ParserUtil.getVariableReturnType(token)); Token name = tokens.get(1); - ParserUtil.checkType(name, Token.Type.IDENTIFIER); // Name must be an identifier. if(functions.containsKey(name.getContent()) || parsedVariables.containsKey(name.getContent()) || builtinFunctions.contains(name.getContent())) diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/ParserUtil.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/ParserUtil.java index bb22de681..a44b7a634 100644 --- a/common/src/main/java/com/dfsek/terra/api/structures/parser/ParserUtil.java +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/ParserUtil.java @@ -46,4 +46,17 @@ public class ParserUtil { if(!token.isBinaryOperator()) throw new ParseException("Expected binary operator, found " + token.getType() + ": " + token.getPosition()); } + + public static Returnable.ReturnType getVariableReturnType(Token varToken) throws ParseException { + switch(varToken.getType()) { + case NUMBER_VARIABLE: + return Returnable.ReturnType.NUMBER; + case STRING_VARIABLE: + return Returnable.ReturnType.STRING; + case BOOLEAN_VARIABLE: + return Returnable.ReturnType.BOOLEAN; + default: + throw new ParseException("Unexpected token " + varToken.getType() + "; expected variable declaration: " + varToken.getPosition()); + } + } } 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 e02d0053c..757ad40c7 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 @@ -37,6 +37,16 @@ public class Block implements Item { } public enum ReturnLevel { - NONE, BREAK, CONTINUE, RETURN, FAIL + NONE(false), BREAK(false), CONTINUE(false), RETURN(true), FAIL(true); + + private final boolean returnFast; + + ReturnLevel(boolean returnFast) { + this.returnFast = returnFast; + } + + public boolean isReturnFast() { + return returnFast; + } } } diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/looplike/ForKeyword.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/looplike/ForKeyword.java new file mode 100644 index 000000000..39f96a36a --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/looplike/ForKeyword.java @@ -0,0 +1,45 @@ +package com.dfsek.terra.api.structures.parser.lang.keywords.looplike; + +import com.dfsek.terra.api.structures.parser.lang.Block; +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.Returnable; +import com.dfsek.terra.api.structures.structure.Rotation; +import com.dfsek.terra.api.structures.structure.buffer.Buffer; +import com.dfsek.terra.api.structures.tokenizer.Position; + +public class ForKeyword implements Keyword { + private final Block conditional; + private final Item initializer; + private final Returnable statement; + private final Item incrementer; + private final Position position; + + public ForKeyword(Block conditional, Item initializer, Returnable statement, Item incrementer, Position position) { + this.conditional = conditional; + this.initializer = initializer; + this.statement = statement; + this.incrementer = incrementer; + this.position = position; + } + + @Override + public Block.ReturnLevel apply(Buffer buffer, Rotation rotation, int recursions) { + for(initializer.apply(buffer, rotation, recursions); statement.apply(buffer, rotation, recursions); incrementer.apply(buffer, rotation, recursions)) { + Block.ReturnLevel level = conditional.apply(buffer, rotation, recursions); + if(level.equals(Block.ReturnLevel.BREAK)) break; + if(level.isReturnFast()) return level; + } + return Block.ReturnLevel.NONE; + } + + @Override + public Position getPosition() { + return position; + } + + @Override + public ReturnType returnType() { + return ReturnType.VOID; + } +} 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/looplike/IfKeyword.java similarity index 94% rename from common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/IfKeyword.java rename to common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/looplike/IfKeyword.java index 678add710..62e4892b1 100644 --- 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/looplike/IfKeyword.java @@ -1,4 +1,4 @@ -package com.dfsek.terra.api.structures.parser.lang.keywords; +package com.dfsek.terra.api.structures.parser.lang.keywords.looplike; import com.dfsek.terra.api.structures.parser.lang.Block; import com.dfsek.terra.api.structures.parser.lang.Keyword; diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/WhileKeyword.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/looplike/WhileKeyword.java similarity index 89% rename from common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/WhileKeyword.java rename to common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/looplike/WhileKeyword.java index bdc1f17f3..ab6629812 100644 --- a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/WhileKeyword.java +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/looplike/WhileKeyword.java @@ -1,4 +1,4 @@ -package com.dfsek.terra.api.structures.parser.lang.keywords; +package com.dfsek.terra.api.structures.parser.lang.keywords.looplike; import com.dfsek.terra.api.structures.parser.lang.Block; import com.dfsek.terra.api.structures.parser.lang.Keyword; @@ -23,7 +23,7 @@ public class WhileKeyword implements Keyword { while(statement.apply(buffer, rotation, recursions)) { Block.ReturnLevel level = conditional.apply(buffer, rotation, recursions); if(level.equals(Block.ReturnLevel.BREAK)) break; - if(level.equals(Block.ReturnLevel.RETURN)) return Block.ReturnLevel.RETURN; + if(level.isReturnFast()) return level; } return Block.ReturnLevel.NONE; } 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 5916f9288..2acba38d1 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 @@ -70,7 +70,8 @@ public class Token { public boolean isLoopLike() { return type.equals(Type.IF_STATEMENT) - || type.equals(Type.WHILE_LOOP); + || type.equals(Type.WHILE_LOOP) + || type.equals(Type.FOR_LOOP); } public boolean isIdentifier() { @@ -214,6 +215,10 @@ public class Token { /** * ID declaration */ - ID + ID, + /** + * For loop initializer token + */ + FOR_LOOP } } 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 a9f52e4f1..f209bf0b2 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 @@ -133,6 +133,8 @@ public class Tokenizer { return new Token(tokenString, Token.Type.IF_STATEMENT, new Position(reader.getLine(), reader.getIndex())); if(tokenString.equals("while")) return new Token(tokenString, Token.Type.WHILE_LOOP, new Position(reader.getLine(), reader.getIndex())); + if(tokenString.equals("for")) + return new Token(tokenString, Token.Type.FOR_LOOP, new Position(reader.getLine(), reader.getIndex())); if(tokenString.equals("return")) return new Token(tokenString, Token.Type.RETURN, new Position(reader.getLine(), reader.getIndex())); diff --git a/common/src/test/resources/test.tesf b/common/src/test/resources/test.tesf index 0ab62eebc..c511a3ba3 100644 --- a/common/src/test/resources/test.tesf +++ b/common/src/test/resources/test.tesf @@ -13,6 +13,11 @@ bool truetest = false; num iterator = 0; +for(num i = 0; i < 5; i = i + 1) { + test("i = " + i, iterator); +} + + while(iterator < 5) { test("always, even after " + 2, iterator); iterator = iterator + 1;