From 283495832fd732f980b37d5f623555a8a0c290ee Mon Sep 17 00:00:00 2001 From: dfsek Date: Sun, 27 Dec 2020 20:42:42 -0700 Subject: [PATCH] Add else if and else --- .../terra/api/structures/parser/Parser.java | 134 ++++++++++++------ .../lang/keywords/looplike/IfKeyword.java | 35 ++++- .../terra/api/structures/tokenizer/Token.java | 6 +- .../api/structures/tokenizer/Tokenizer.java | 2 + common/src/test/resources/test.tesf | 20 +++ 5 files changed, 149 insertions(+), 48 deletions(-) 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 df01dd94f..a8208c7fb 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 @@ -54,6 +54,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +@SuppressWarnings("unchecked") public class Parser { private final String data; private final Map>> functions = new HashMap<>(); @@ -111,7 +112,6 @@ public class Parser { } - @SuppressWarnings("unchecked") private Keyword parseLoopLike(TokenHolder tokens, Map> variableMap) throws ParseException { Token identifier = tokens.consume(); @@ -119,56 +119,100 @@ public class Parser { ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_BEGIN); - - if(identifier.getType().equals(Token.Type.FOR_LOOP)) { - Token f = tokens.get(); - 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)); - ParserUtil.checkType(tokens.consume(), Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.NUMBER_VARIABLE); - Token name = tokens.get(); - - if(functions.containsKey(name.getContent()) || variableMap.containsKey(name.getContent()) || builtinFunctions.contains(name.getContent())) - throw new ParseException(name.getContent() + " is already defined in this scope", name.getPosition()); - - initializer = parseAssignment(forVar, tokens, variableMap); - variableMap.put(name.getContent(), forVar); - } else initializer = parseExpression(tokens, true, variableMap); - ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END); - Returnable conditional = parseExpression(tokens, true, variableMap); - ParserUtil.checkReturnType(conditional, Returnable.ReturnType.BOOLEAN); - ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END); - - Item incrementer; - Token token = tokens.get(); - 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.consume(), Token.Type.GROUP_END); - - ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_BEGIN); - - return new ForKeyword(parseBlock(tokens, variableMap), initializer, (Returnable) conditional, incrementer, identifier.getPosition()); + switch(identifier.getType()) { + case FOR_LOOP: + return parseForLoop(tokens, variableMap, identifier.getPosition()); + case IF_STATEMENT: + return parseIfStatement(tokens, variableMap, identifier.getPosition()); + case WHILE_LOOP: + return parseWhileLoop(tokens, variableMap, identifier.getPosition()); + default: + throw new UnsupportedOperationException("Unknown keyword " + identifier.getContent() + ": " + identifier.getPosition()); } + } + private WhileKeyword parseWhileLoop(TokenHolder tokens, Map> variableMap, Position start) throws ParseException { Returnable first = parseExpression(tokens, true, variableMap); ParserUtil.checkReturnType(first, Returnable.ReturnType.BOOLEAN); ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END); - ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_BEGIN); - - if(identifier.getType().equals(Token.Type.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) first, identifier.getPosition()); // While loop - else throw new UnsupportedOperationException("Unknown keyword " + identifier.getContent() + ": " + identifier.getPosition()); + return new WhileKeyword(parseStatementBlock(tokens, variableMap), (Returnable) first, start); // While loop + } + + private IfKeyword parseIfStatement(TokenHolder tokens, Map> variableMap, Position start) throws ParseException { + Returnable condition = parseExpression(tokens, true, variableMap); + ParserUtil.checkReturnType(condition, Returnable.ReturnType.BOOLEAN); + + ParserUtil.checkType(tokens.consume(), Token.Type.GROUP_END); + + Block elseBlock = null; + Block statement = parseStatementBlock(tokens, variableMap); + + List, Block>> elseIf = new GlueList<>(); + + System.out.println(tokens.get()); + + while(tokens.get().getType().equals(Token.Type.ELSE)) { + tokens.consume(); // Consume else. + System.out.println("int: " + tokens.get()); + if(tokens.get().getType().equals(Token.Type.IF_STATEMENT)) { + System.out.println("else if"); + tokens.consume(); // Consume if. + Returnable elseCondition = parseExpression(tokens, true, variableMap); + ParserUtil.checkReturnType(elseCondition, Returnable.ReturnType.BOOLEAN); + elseIf.add(new IfKeyword.Pair<>((Returnable) elseCondition, parseStatementBlock(tokens, variableMap))); + } else { + elseBlock = parseStatementBlock(tokens, variableMap); + break; // Else must be last. + } + } + + return new IfKeyword(statement, (Returnable) condition, elseIf, elseBlock, start); // If statement + } + + private Block parseStatementBlock(TokenHolder tokens, Map> variableMap) throws ParseException { + ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_BEGIN); + Block block = parseBlock(tokens, variableMap); + ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_END); + return block; + } + + private ForKeyword parseForLoop(TokenHolder tokens, Map> variableMap, Position start) throws ParseException { + Token f = tokens.get(); + 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)); + ParserUtil.checkType(tokens.consume(), Token.Type.STRING_VARIABLE, Token.Type.BOOLEAN_VARIABLE, Token.Type.NUMBER_VARIABLE); + Token name = tokens.get(); + + if(functions.containsKey(name.getContent()) || variableMap.containsKey(name.getContent()) || builtinFunctions.contains(name.getContent())) + throw new ParseException(name.getContent() + " is already defined in this scope", name.getPosition()); + + initializer = parseAssignment(forVar, tokens, variableMap); + variableMap.put(name.getContent(), forVar); + } else initializer = parseExpression(tokens, true, variableMap); + ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END); + Returnable conditional = parseExpression(tokens, true, variableMap); + ParserUtil.checkReturnType(conditional, Returnable.ReturnType.BOOLEAN); + ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END); + + Item incrementer; + Token token = tokens.get(); + 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.consume(), Token.Type.GROUP_END); + + ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_BEGIN); + Block block = parseBlock(tokens, variableMap); + ParserUtil.checkType(tokens.consume(), Token.Type.BLOCK_END); + return new ForKeyword(block, initializer, (Returnable) conditional, incrementer, start); } - @SuppressWarnings("unchecked") private Returnable parseExpression(TokenHolder tokens, boolean full, Map> variableMap) throws ParseException { boolean booleanInverted = false; // Check for boolean not operator if(tokens.get().getType().equals(Token.Type.BOOLEAN_NOT)) { @@ -244,7 +288,6 @@ public class Parser { return assemble(left, right, binaryOperator); } - @SuppressWarnings("unchecked") private BinaryOperation assemble(Returnable left, Returnable right, Token binaryOperator) throws ParseException { if(binaryOperator.isStrictNumericOperator()) ParserUtil.checkArithmeticOperation(left, right, binaryOperator); // Numeric type checking @@ -311,6 +354,7 @@ public class Parser { if(token.isLoopLike()) { // Parse loop-like tokens (if, while, etc) parsedItems.add(parseLoopLike(tokens, parsedVariables)); + continue; // No statement end } else if(token.isIdentifier()) { // Parse identifiers if(parsedVariables.containsKey(token.getContent())) { // Assume variable assignment Variable variable = parsedVariables.get(token.getContent()); @@ -336,12 +380,11 @@ public class Parser { else if(token.getType().equals(Token.Type.FAIL)) parsedItems.add(new FailKeyword(tokens.consume().getPosition())); else throw new UnsupportedOperationException("Unexpected token " + token.getType() + ": " + token.getPosition()); - if(tokens.hasNext()) ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END, Token.Type.BLOCK_END); + if(tokens.hasNext()) ParserUtil.checkType(tokens.consume(), Token.Type.STATEMENT_END); } return new Block(parsedItems, first.getPosition()); } - @SuppressWarnings("unchecked") private Assignment parseAssignment(Variable variable, TokenHolder tokens, Map> variableMap) throws ParseException { Token name = tokens.get(); @@ -356,7 +399,6 @@ public class Parser { return new Assignment<>((Variable) variable, (Returnable) expression, name.getPosition()); } - @SuppressWarnings("unchecked") private Function parseFunction(TokenHolder tokens, boolean fullStatement, Map> variableMap) throws ParseException { Token identifier = tokens.consume(); ParserUtil.checkType(identifier, Token.Type.IDENTIFIER); // First token must be identifier diff --git a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/looplike/IfKeyword.java b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/looplike/IfKeyword.java index 7d287b9e8..7a5f4ee88 100644 --- a/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/looplike/IfKeyword.java +++ b/common/src/main/java/com/dfsek/terra/api/structures/parser/lang/keywords/looplike/IfKeyword.java @@ -6,26 +6,41 @@ 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; +import org.jetbrains.annotations.Nullable; +import java.util.List; import java.util.Random; public class IfKeyword implements Keyword { private final Block conditional; private final Returnable statement; private final Position position; + private final List, Block>> elseIf; + private final Block elseBlock; - public IfKeyword(Block conditional, Returnable statement, Position position) { + public IfKeyword(Block conditional, Returnable statement, List, Block>> elseIf, @Nullable Block elseBlock, Position position) { this.conditional = conditional; this.statement = statement; this.position = position; + this.elseIf = elseIf; + this.elseBlock = elseBlock; } @Override public Block.ReturnLevel apply(Buffer buffer, Rotation rotation, Random random, int recursions) { if(statement.apply(buffer, rotation, random, recursions)) return conditional.apply(buffer, rotation, random, recursions); + else { + for(Pair, Block> pair : elseIf) { + if(pair.getLeft().apply(buffer, rotation, random, recursions)) { + return pair.getRight().apply(buffer, rotation, random, recursions); + } + } + if(elseBlock != null) return elseBlock.apply(buffer, rotation, random, recursions); + } return Block.ReturnLevel.NONE; } + @Override public Position getPosition() { return position; @@ -35,4 +50,22 @@ public class IfKeyword implements Keyword { public ReturnType returnType() { return ReturnType.VOID; } + + public static class Pair { + private final L left; + private final R right; + + public Pair(L left, R right) { + this.left = left; + this.right = right; + } + + public L getLeft() { + return left; + } + + public R getRight() { + return right; + } + } } 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 2acba38d1..31af0c837 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 @@ -219,6 +219,10 @@ public class Token { /** * For loop initializer token */ - FOR_LOOP + FOR_LOOP, + /** + * Else keyword + */ + ELSE } } 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 f209bf0b2..b5335f1c9 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 @@ -131,6 +131,8 @@ public class Tokenizer { if(tokenString.equals("if")) return new Token(tokenString, Token.Type.IF_STATEMENT, new Position(reader.getLine(), reader.getIndex())); + if(tokenString.equals("else")) + return new Token(tokenString, Token.Type.ELSE, 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")) diff --git a/common/src/test/resources/test.tesf b/common/src/test/resources/test.tesf index c511a3ba3..543f55399 100644 --- a/common/src/test/resources/test.tesf +++ b/common/src/test/resources/test.tesf @@ -27,6 +27,26 @@ while(iterator < 5) { test("not after " + 2, iterator); } +if(true) { + test("true!" + 2, iterator); +} else { + test("false!" + 2, iterator); + } + +if(false) { + test("true!" + 2, iterator); +} else { + test("false!" + 2, iterator); +} + +if(false) { + test("true again!" + 2, iterator); +} else if(true == true) { + test("false again!" + 2, iterator); +} else { + test("not logged!" + 2, iterator); +} + if(true && !(boolean && false) && true) { num scopedVar = 2;