diff --git a/src/main/java/com/annimon/ownlang/parser/Parser.java b/src/main/java/com/annimon/ownlang/parser/Parser.java index a89c921..c6df60c 100644 --- a/src/main/java/com/annimon/ownlang/parser/Parser.java +++ b/src/main/java/com/annimon/ownlang/parser/Parser.java @@ -28,23 +28,23 @@ public final class Parser { private static final Token EOF = new Token(TokenType.EOF, "", -1, -1); - private static final EnumMap assignOperator; + private static final EnumMap ASSIGN_OPERATORS; static { - assignOperator = new EnumMap(TokenType.class); - assignOperator.put(TokenType.EQ, null); - assignOperator.put(TokenType.PLUSEQ, BinaryExpression.Operator.ADD); - assignOperator.put(TokenType.MINUSEQ, BinaryExpression.Operator.SUBTRACT); - assignOperator.put(TokenType.STAREQ, BinaryExpression.Operator.MULTIPLY); - assignOperator.put(TokenType.SLASHEQ, BinaryExpression.Operator.DIVIDE); - assignOperator.put(TokenType.PERCENTEQ, BinaryExpression.Operator.REMAINDER); - assignOperator.put(TokenType.AMPEQ, BinaryExpression.Operator.AND); - assignOperator.put(TokenType.CARETEQ, BinaryExpression.Operator.XOR); - assignOperator.put(TokenType.BAREQ, BinaryExpression.Operator.OR); - assignOperator.put(TokenType.COLONCOLONEQ, BinaryExpression.Operator.PUSH); - assignOperator.put(TokenType.LTLTEQ, BinaryExpression.Operator.LSHIFT); - assignOperator.put(TokenType.GTGTEQ, BinaryExpression.Operator.RSHIFT); - assignOperator.put(TokenType.GTGTGTEQ, BinaryExpression.Operator.URSHIFT); - assignOperator.put(TokenType.ATEQ, BinaryExpression.Operator.AT); + ASSIGN_OPERATORS = new EnumMap(TokenType.class); + ASSIGN_OPERATORS.put(TokenType.EQ, null); + ASSIGN_OPERATORS.put(TokenType.PLUSEQ, BinaryExpression.Operator.ADD); + ASSIGN_OPERATORS.put(TokenType.MINUSEQ, BinaryExpression.Operator.SUBTRACT); + ASSIGN_OPERATORS.put(TokenType.STAREQ, BinaryExpression.Operator.MULTIPLY); + ASSIGN_OPERATORS.put(TokenType.SLASHEQ, BinaryExpression.Operator.DIVIDE); + ASSIGN_OPERATORS.put(TokenType.PERCENTEQ, BinaryExpression.Operator.REMAINDER); + ASSIGN_OPERATORS.put(TokenType.AMPEQ, BinaryExpression.Operator.AND); + ASSIGN_OPERATORS.put(TokenType.CARETEQ, BinaryExpression.Operator.XOR); + ASSIGN_OPERATORS.put(TokenType.BAREQ, BinaryExpression.Operator.OR); + ASSIGN_OPERATORS.put(TokenType.COLONCOLONEQ, BinaryExpression.Operator.PUSH); + ASSIGN_OPERATORS.put(TokenType.LTLTEQ, BinaryExpression.Operator.LSHIFT); + ASSIGN_OPERATORS.put(TokenType.GTGTEQ, BinaryExpression.Operator.RSHIFT); + ASSIGN_OPERATORS.put(TokenType.GTGTGTEQ, BinaryExpression.Operator.URSHIFT); + ASSIGN_OPERATORS.put(TokenType.ATEQ, BinaryExpression.Operator.AT); } private final List tokens; @@ -156,10 +156,10 @@ public final class Parser { return functionDefine(); } if (match(TokenType.MATCH)) { - return new ExprStatement(match()); + return match(); } if (lookMatch(0, TokenType.WORD) && lookMatch(1, TokenType.LPAREN)) { - return new ExprStatement(function(qualifiedName())); + return new ExprStatement(functionChain(qualifiedName())); } return assignmentStatement(); } @@ -185,7 +185,7 @@ public final class Parser { } else { variables.add(null); } - match(TokenType.COMMA); + consume(TokenType.COMMA); } consume(TokenType.EQ); return new DestructuringAssignmentStatement(variables, expression()); @@ -227,36 +227,37 @@ public final class Parser { // for key, value : arr || for (key, value : arr) return foreachMapStatement(); } - - boolean openParen = match(TokenType.LPAREN); // необязательные скобки + + // for (init, condition, increment) body + boolean optParentheses = match(TokenType.LPAREN); final Statement initialization = assignmentStatement(); consume(TokenType.COMMA); final Expression termination = expression(); consume(TokenType.COMMA); final Statement increment = assignmentStatement(); - if (openParen) consume(TokenType.RPAREN); // скобки + if (optParentheses) consume(TokenType.RPAREN); // close opt parentheses final Statement statement = statementOrBlock(); return new ForStatement(initialization, termination, increment, statement); } private ForeachArrayStatement foreachArrayStatement() { - boolean openParen = match(TokenType.LPAREN); // необязательные скобки + boolean optParentheses = match(TokenType.LPAREN); final String variable = consume(TokenType.WORD).getText(); consume(TokenType.COLON); final Expression container = expression(); - if (openParen) consume(TokenType.RPAREN); // скобки + if (optParentheses) consume(TokenType.RPAREN); // close opt parentheses final Statement statement = statementOrBlock(); return new ForeachArrayStatement(variable, container, statement); } private ForeachMapStatement foreachMapStatement() { - boolean openParen = match(TokenType.LPAREN); // необязательные скобки + boolean optParentheses = match(TokenType.LPAREN); final String key = consume(TokenType.WORD).getText(); consume(TokenType.COMMA); final String value = consume(TokenType.WORD).getText(); consume(TokenType.COLON); final Expression container = expression(); - if (openParen) consume(TokenType.RPAREN); // скобки + if (optParentheses) consume(TokenType.RPAREN); // close opt parentheses final Statement statement = statementOrBlock(); return new ForeachMapStatement(key, value, container, statement); } @@ -296,6 +297,26 @@ public final class Parser { return statementOrBlock(); } + private Expression functionChain(Expression qualifiedNameExpr) { + // f1()()() || f1().f2().f3() || f1().key + final Expression expr = function(qualifiedNameExpr); + if (lookMatch(0, TokenType.LPAREN)) { + return functionChain(expr); + } + if (lookMatch(0, TokenType.DOT)) { + final List indices = variableSuffix(); + if (indices == null | indices.isEmpty()) return expr; + + if (lookMatch(0, TokenType.LPAREN)) { + // next function call + return functionChain(new ContainerAccessExpression(expr, indices)); + } + // container access + return new ContainerAccessExpression(expr, indices); + } + return expr; + } + private FunctionalExpression function(Expression qualifiedNameExpr) { // function(arg1, arg2, ...) consume(TokenType.LPAREN); @@ -426,13 +447,13 @@ public final class Parser { } final TokenType currentType = get(0).getType(); - if (!assignOperator.containsKey(currentType)) { + if (!ASSIGN_OPERATORS.containsKey(currentType)) { pos = position; return null; } match(currentType); - final BinaryExpression.Operator op = assignOperator.get(currentType); + final BinaryExpression.Operator op = ASSIGN_OPERATORS.get(currentType); final Expression expression = expression(); return new AssignmentExpression(op, (Accessible) targetExpr, expression); @@ -683,18 +704,18 @@ public final class Parser { } return variable(); } - + private Expression variable() { // function(... if (lookMatch(0, TokenType.WORD) && lookMatch(1, TokenType.LPAREN)) { - return function(new ValueExpression(consume(TokenType.WORD).getText())); + return functionChain(new ValueExpression(consume(TokenType.WORD).getText())); } final Expression qualifiedNameExpr = qualifiedName(); if (qualifiedNameExpr != null) { // variable(args) || arr["key"](args) || obj.key(args) if (lookMatch(0, TokenType.LPAREN)) { - return function(qualifiedNameExpr); + return functionChain(qualifiedNameExpr); } // postfix increment/decrement if (match(TokenType.PLUSPLUS)) { @@ -726,7 +747,7 @@ public final class Parser { } return new ContainerAccessExpression(current.getText(), indices); } - + private List variableSuffix() { // .key1.arr1[expr1][expr2].key2 if (!lookMatch(0, TokenType.DOT) && !lookMatch(0, TokenType.LBRACKET)) { diff --git a/src/main/java/com/annimon/ownlang/parser/ast/ContainerAccessExpression.java b/src/main/java/com/annimon/ownlang/parser/ast/ContainerAccessExpression.java index 3526610..794b842 100644 --- a/src/main/java/com/annimon/ownlang/parser/ast/ContainerAccessExpression.java +++ b/src/main/java/com/annimon/ownlang/parser/ast/ContainerAccessExpression.java @@ -10,14 +10,28 @@ import java.util.List; */ public final class ContainerAccessExpression implements Expression, Accessible { - public final String variable; + public final Expression root; public final List indices; + private boolean rootIsVariable; public ContainerAccessExpression(String variable, List indices) { - this.variable = variable; + this(new VariableExpression(variable), indices); + } + + public ContainerAccessExpression(Expression root, List indices) { + rootIsVariable = root instanceof VariableExpression; + this.root = root; this.indices = indices; } - + + public boolean rootIsVariable() { + return rootIsVariable; + } + + public Expression getRoot() { + return root; + } + @Override public Value eval() { return get(); @@ -60,7 +74,7 @@ public final class ContainerAccessExpression implements Expression, Accessible { } public Value getContainer() { - Value container = Variables.get(variable); + Value container = root.eval(); final int last = indices.size() - 1; for (int i = 0; i < last; i++) { final Value index = index(i); @@ -108,6 +122,6 @@ public final class ContainerAccessExpression implements Expression, Accessible { @Override public String toString() { - return variable + indices; + return root.toString() + indices; } } diff --git a/src/main/java/com/annimon/ownlang/parser/optimization/OptimizationVisitor.java b/src/main/java/com/annimon/ownlang/parser/optimization/OptimizationVisitor.java index 399bd21..46696ad 100644 --- a/src/main/java/com/annimon/ownlang/parser/optimization/OptimizationVisitor.java +++ b/src/main/java/com/annimon/ownlang/parser/optimization/OptimizationVisitor.java @@ -87,8 +87,10 @@ public abstract class OptimizationVisitor implements ResultVisitor { @Override public Node visit(ContainerAccessExpression s, T t) { + final Node root = s.root.accept(this, t); + boolean changed = (root != s.root); + final List indices = new ArrayList<>(s.indices.size()); - boolean changed = false; for (Expression expression : s.indices) { final Node node = expression.accept(this, t); if (node != expression) { @@ -97,7 +99,7 @@ public abstract class OptimizationVisitor implements ResultVisitor { indices.add((Expression) node); } if (changed) { - return new ContainerAccessExpression(s.variable, indices); + return new ContainerAccessExpression((Expression) root, indices); } return s; } diff --git a/src/main/java/com/annimon/ownlang/parser/optimization/VariablesGrabber.java b/src/main/java/com/annimon/ownlang/parser/optimization/VariablesGrabber.java index cd4e2a1..7ea484e 100644 --- a/src/main/java/com/annimon/ownlang/parser/optimization/VariablesGrabber.java +++ b/src/main/java/com/annimon/ownlang/parser/optimization/VariablesGrabber.java @@ -100,8 +100,11 @@ public class VariablesGrabber extends OptimizationVisitor @Override public StringBuilder visit(ContainerAccessExpression s, StringBuilder t) { - visitVariable(s.variable, t); + s.root.accept(this, t); for (Expression index : s.indices) { t.append('['); index.accept(this, t); diff --git a/src/main/java/com/annimon/ownlang/parser/visitors/VariablePrinter.java b/src/main/java/com/annimon/ownlang/parser/visitors/VariablePrinter.java index 4f29f3a..ed2b6e2 100644 --- a/src/main/java/com/annimon/ownlang/parser/visitors/VariablePrinter.java +++ b/src/main/java/com/annimon/ownlang/parser/visitors/VariablePrinter.java @@ -14,12 +14,6 @@ public final class VariablePrinter extends AbstractVisitor { super.visit(s); Console.println(s.target); } - - @Override - public void visit(ContainerAccessExpression s) { - super.visit(s); - Console.println(s.variable); - } @Override public void visit(VariableExpression s) { diff --git a/src/test/resources/modules/std/indexOf.own b/src/test/resources/modules/std/indexOf.own new file mode 100644 index 0000000..176a40b --- /dev/null +++ b/src/test/resources/modules/std/indexOf.own @@ -0,0 +1,13 @@ +use "std" + +def testIndexOf() { + assertEquals(3, indexOf("123/456/789", "/")) +} + +def testIndexOfIndex() { + assertEquals(7, indexOf("123/456/789", "/", 4)) +} + +def testIndexOfNonMatch() { + assertEquals(-1, indexOf("123", "/")) +} \ No newline at end of file diff --git a/src/test/resources/modules/std/lastIndexOf.own b/src/test/resources/modules/std/lastIndexOf.own new file mode 100644 index 0000000..fb19408 --- /dev/null +++ b/src/test/resources/modules/std/lastIndexOf.own @@ -0,0 +1,13 @@ +use "std" + +def testLastIndexOf() { + assertEquals(8, lastIndexOf("/123/456/789", "/")) +} + +def testLastIndexOfIndex() { + assertEquals(4, lastIndexOf("/123/456/789", "/", 6)) +} + +def testLastIndexOfNonMatch() { + assertEquals(-1, lastIndexOf("123", "/")) +} \ No newline at end of file diff --git a/src/test/resources/other/functionChain.own b/src/test/resources/other/functionChain.own new file mode 100644 index 0000000..a270a8f --- /dev/null +++ b/src/test/resources/other/functionChain.own @@ -0,0 +1,23 @@ +def f1() = {"func": ::f2} +def f2() = { + "functions" : { + "add" : def(a, b) = a + b + "mul" : def(a, b) = a * b + "negate" : def(a) = {"result" : -a} + } +} +def f3() = def() = def() = def() = "test" +def f4() = def() = ::f1 + +def testFunctionChain() { + assertEquals(5, f1().func().`functions`.add(2, 3)) + assertEquals(6, f1().func().`functions`.mul(2, 3)) +} + +def testCallChain() { + assertEquals("test", f3()()()()) +} + +def testBoth() { + assertEquals(-123, f4()()().func().`functions`.negate(123).result) +} \ No newline at end of file