From dcb6ab8e8cf4008b9804a64e20032d117144bc64 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 29 Nov 2023 06:43:33 +0100 Subject: [PATCH 1/3] Multi line chained operator syntax --- .../java/org/enso/compiler/core/TreeToIr.java | 36 ++++++++++++++----- .../enso/compiler/core/EnsoParserTest.java | 12 +++++++ .../org/enso/compiler/ExecCompilerTest.java | 17 +++++++++ 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java index 38f0398ee346..5409c9a49be0 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java @@ -552,7 +552,7 @@ Name.MethodReference translateMethodReference(Tree sig, boolean alwaysLocation) method = buildName(id); loc = getIdentifiedLocation(sig); } - case Tree.OprApp app when ".".equals(app.getOpr().getRight().codeRepr()) -> { + case Tree.OprApp app when isDotOperator(app.getOpr().getRight()) -> { type = Option.apply(buildQualifiedName(app.getLhs())); method = buildName(app.getRhs()); if (alwaysLocation) { @@ -568,7 +568,7 @@ loc, meta(), diag() ); } - private Expression translateCall(Tree ast) { + private Expression translateCall(Tree ast, boolean isMethod) { var args = new java.util.ArrayList(); var hasDefaultsSuspended = false; var tree = ast; @@ -597,25 +597,41 @@ private Expression translateCall(Tree ast) { args.add(new CallArgument.Specified(Option.empty(), expr, loc, meta(), diag())); tree = app.getFunc(); } + case Tree.OperatorBlockApplication app -> { + var at = args.size(); + var self = translateExpression(app.getLhs(), false); + for (var l : app.getExpressions()) { + var expr = isDotOperator(l.getExpression().getOperator().getRight()) ? + translateExpression(l.getExpression().getExpression(), true) : + translateSyntaxError(l.getExpression().getExpression(), Syntax.UnexpectedExpression$.MODULE$); + if (expr instanceof Application.Prefix pref) { + var arg = new CallArgument.Specified(Option.empty(), self, self.location(), meta(), diag()); + expr = new Application.Prefix(pref.function(), join(arg, pref.arguments()), false, expr.location(), meta(), diag()); + } + var loc = getIdentifiedLocation(l.getExpression().getExpression()); + args.add(at, new CallArgument.Specified(Option.empty(), expr, loc, meta(), diag())); + self = expr; + } + return self; + } default -> { Expression func; if (tree instanceof Tree.OprApp oprApp - && oprApp.getOpr().getRight() != null - && ".".equals(oprApp.getOpr().getRight().codeRepr()) + && isDotOperator(oprApp.getOpr().getRight()) && oprApp.getRhs() instanceof Tree.Ident) { func = translateExpression(oprApp.getRhs(), true); if (oprApp.getLhs() == null && args.isEmpty()) { return func; } if (oprApp.getLhs() != null) { - var self = translateExpression(oprApp.getLhs(), false); + var self = translateExpression(oprApp.getLhs(), isMethod); var loc = getIdentifiedLocation(oprApp.getLhs()); args.add(new CallArgument.Specified(Option.empty(), self, loc, meta(), diag())); } } else if (args.isEmpty()) { return null; } else { - func = translateExpression(tree, false); + func = translateExpression(tree, isMethod); } java.util.Collections.reverse(args); var argsList = CollectionConverters.asScala(args.iterator()).toList(); @@ -682,7 +698,7 @@ private Expression translateExpressionImpl(Tree tree, boolean isMethod) throws S if (tree == null) { return null; } - var callExpression = translateCall(tree); + var callExpression = translateCall(tree, isMethod); if (callExpression != null) { return callExpression; } @@ -1360,7 +1376,7 @@ Pattern translatePattern(Tree block) throws SyntaxException { ); } case Tree.Ident id -> new Pattern.Name(buildName(id), getIdentifiedLocation(id), meta(), diag()); - case Tree.OprApp app when ".".equals(app.getOpr().getRight().codeRepr()) -> { + case Tree.OprApp app when isDotOperator(app.getOpr().getRight()) -> { var qualifiedName = buildQualifiedName(app); yield new Pattern.Constructor( qualifiedName, fields, getIdentifiedLocation(app), meta(), diag() @@ -1795,6 +1811,10 @@ private static final scala.collection.immutable.List join(T head, scala.c return scala.collection.immutable.$colon$colon$.MODULE$.apply(head, tail); } + private static boolean isDotOperator(Token.Operator op) { + return op != null && ".".equals(op.codeRepr()); + } + private static Tree maybeManyParensed(Tree t) { for (;;) { switch (t) { diff --git a/engine/runtime-parser/src/test/java/org/enso/compiler/core/EnsoParserTest.java b/engine/runtime-parser/src/test/java/org/enso/compiler/core/EnsoParserTest.java index ce362a0a7890..3a3e1bf4f79c 100644 --- a/engine/runtime-parser/src/test/java/org/enso/compiler/core/EnsoParserTest.java +++ b/engine/runtime-parser/src/test/java/org/enso/compiler/core/EnsoParserTest.java @@ -1246,6 +1246,18 @@ public void testSkip() throws Exception { equivalenceTest("a = x", "a = SKIP FREEZE x.f y"); } + @Test + public void testBlockSyntax() throws Exception { + equivalenceTest(""" + nums v fm ff n = v . map fm . filter ff . take n + """, """ + nums v fm ff n = v + . map fm + . filter ff + . take n + """); + } + @Test public void testPrivateModules() throws Exception { List moduleCodes = List.of( diff --git a/engine/runtime/src/test/java/org/enso/compiler/ExecCompilerTest.java b/engine/runtime/src/test/java/org/enso/compiler/ExecCompilerTest.java index 27353b5519b3..ed8177e7d8e2 100644 --- a/engine/runtime/src/test/java/org/enso/compiler/ExecCompilerTest.java +++ b/engine/runtime/src/test/java/org/enso/compiler/ExecCompilerTest.java @@ -126,6 +126,23 @@ public void dotUnderscore() throws Exception { } } + @Test + public void chainedSyntax() throws Exception { + var module = ctx.eval("enso", """ + from Standard.Base import all + + nums n = [1, 2, 3, 4, 5] + . map (x-> x*2) + . filter (x-> x % 3 == 0) + . take n + """); + var run = module.invokeMember("eval_expression", "nums"); + var six = run.execute(1); + assertTrue(six.hasArrayElements()); + assertEquals(1, six.getArraySize()); + assertEquals(6, six.getArrayElement(0).asInt()); + } + @Test public void testInvalidEnsoProjectRef() throws Exception { var module = From 472b9a7ccaf3cc2bd2799d5b7d396443e2e1aa3b Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 29 Nov 2023 08:58:34 +0100 Subject: [PATCH 2/3] Support for all chained operators --- .../java/org/enso/compiler/core/TreeToIr.java | 52 +++++++++++++------ .../enso/compiler/core/EnsoParserTest.java | 22 ++++++++ .../org/enso/compiler/ExecCompilerTest.java | 12 +++++ 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java index 5409c9a49be0..f2d4f40b45ac 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java @@ -601,9 +601,11 @@ private Expression translateCall(Tree ast, boolean isMethod) { var at = args.size(); var self = translateExpression(app.getLhs(), false); for (var l : app.getExpressions()) { - var expr = isDotOperator(l.getExpression().getOperator().getRight()) ? - translateExpression(l.getExpression().getExpression(), true) : - translateSyntaxError(l.getExpression().getExpression(), Syntax.UnexpectedExpression$.MODULE$); + var invoke = isDotOperator(l.getExpression().getOperator().getRight()); + if (self == null || !invoke) { + return null; + } + var expr = translateExpression(l.getExpression().getExpression(), true); if (expr instanceof Application.Prefix pref) { var arg = new CallArgument.Specified(Option.empty(), self, self.location(), meta(), diag()); expr = new Application.Prefix(pref.function(), join(arg, pref.arguments()), false, expr.location(), meta(), diag()); @@ -766,22 +768,27 @@ yield switch (op.codeRepr()) { default -> { var lhs = unnamedCallArgument(app.getLhs()); var rhs = unnamedCallArgument(app.getRhs()); - var name = new Name.Literal( - op.codeRepr(), true, getIdentifiedLocation(op), Option.empty(), meta(), diag() - ); var loc = getIdentifiedLocation(app); - if (lhs == null && rhs == null) { - yield new Section.Sides(name, loc, meta(), diag()); - } else if (lhs == null) { - yield new Section.Right(name, rhs, loc, meta(), diag()); - } else if (rhs == null) { - yield new Section.Left(lhs, name, loc, meta(), diag()); - } else { - yield new Operator.Binary(lhs, name, rhs, loc,meta(), diag()); - } + yield applyOperator(op, lhs, rhs, loc); } }; } + case Tree.OperatorBlockApplication app -> { + Expression expr = null; + var lhs = unnamedCallArgument(app.getLhs()); + for (var l : app.getExpressions()) { + var op = l.getExpression().getOperator().getRight(); + if (op == null || isDotOperator(op)) { + yield translateSyntaxError(l.getExpression().getExpression(), Syntax.UnexpectedExpression$.MODULE$); + } + var rhs = unnamedCallArgument(l.getExpression().getExpression()); + var loc = getIdentifiedLocation(app); + var both = applyOperator(op, lhs, rhs, loc); + expr = both; + lhs = new CallArgument.Specified(Option.empty(), expr, loc, meta(), diag()); + } + yield expr; + } case Tree.Array arr -> { List items = nil(); if (arr.getFirst() != null) { @@ -1014,6 +1021,21 @@ loc, meta(), diag() }; } + private Operator applyOperator(Token.Operator op, CallArgument lhs, CallArgument rhs, Option loc) { + var name = new Name.Literal( + op.codeRepr(), true, getIdentifiedLocation(op), Option.empty(), meta(), diag() + ); + if (lhs == null && rhs == null) { + return new Section.Sides(name, loc, meta(), diag()); + } else if (lhs == null) { + return new Section.Right(name, rhs, loc, meta(), diag()); + } else if (rhs == null) { + return new Section.Left(lhs, name, loc, meta(), diag()); + } else { + return new Operator.Binary(lhs, name, rhs, loc,meta(), diag()); + } + } + Tree applySkip(Tree tree) { // Termination: // Every iteration either breaks, or reduces [`tree`] to a substructure of [`tree`]. diff --git a/engine/runtime-parser/src/test/java/org/enso/compiler/core/EnsoParserTest.java b/engine/runtime-parser/src/test/java/org/enso/compiler/core/EnsoParserTest.java index 3a3e1bf4f79c..e90ea7cb46cb 100644 --- a/engine/runtime-parser/src/test/java/org/enso/compiler/core/EnsoParserTest.java +++ b/engine/runtime-parser/src/test/java/org/enso/compiler/core/EnsoParserTest.java @@ -1258,6 +1258,28 @@ public void testBlockSyntax() throws Exception { """); } + @Test + public void testBlockSyntaxOperators() throws Exception { + equivalenceTest(""" + value = nums * each random + constant + """, """ + value = nums + * each random + + constant + """); + } + + @Test + public void testBlockSyntaxOperators2() throws Exception { + equivalenceTest(""" + value = (nums + each random) * constant + """, """ + value = nums + + each random + * constant + """); + } + @Test public void testPrivateModules() throws Exception { List moduleCodes = List.of( diff --git a/engine/runtime/src/test/java/org/enso/compiler/ExecCompilerTest.java b/engine/runtime/src/test/java/org/enso/compiler/ExecCompilerTest.java index ed8177e7d8e2..73e3efc3e7cf 100644 --- a/engine/runtime/src/test/java/org/enso/compiler/ExecCompilerTest.java +++ b/engine/runtime/src/test/java/org/enso/compiler/ExecCompilerTest.java @@ -143,6 +143,18 @@ public void chainedSyntax() throws Exception { assertEquals(6, six.getArrayElement(0).asInt()); } + @Test + public void chainedSyntaxOperator() throws Exception { + var module = ctx.eval("enso", """ + nums n = n + * 2 + % 3 + """); + var run = module.invokeMember("eval_expression", "nums"); + var result = run.execute(5); + assertEquals("10 % 3 is one", 1, result.asInt()); + } + @Test public void testInvalidEnsoProjectRef() throws Exception { var module = From 248075e86cbe298000a46d39841abadbc777a118 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 1 Dec 2023 06:02:13 +0100 Subject: [PATCH 3/3] More testa for chained operator syntax --- .../java/org/enso/compiler/core/TreeToIr.java | 15 ++++++---- .../enso/compiler/core/EnsoParserTest.java | 28 ++++++++++++++++--- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java b/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java index f2d4f40b45ac..74eb9f34bc8c 100644 --- a/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java +++ b/engine/runtime-parser/src/main/java/org/enso/compiler/core/TreeToIr.java @@ -605,11 +605,16 @@ private Expression translateCall(Tree ast, boolean isMethod) { if (self == null || !invoke) { return null; } - var expr = translateExpression(l.getExpression().getExpression(), true); - if (expr instanceof Application.Prefix pref) { - var arg = new CallArgument.Specified(Option.empty(), self, self.location(), meta(), diag()); - expr = new Application.Prefix(pref.function(), join(arg, pref.arguments()), false, expr.location(), meta(), diag()); - } + var expr = switch (translateExpression(l.getExpression().getExpression(), true)) { + case Application.Prefix pref -> { + var arg = new CallArgument.Specified(Option.empty(), self, self.location(), meta(), diag()); + yield new Application.Prefix(pref.function(), join(arg, pref.arguments()), false, pref.location(), meta(), diag()); + } + case Expression any -> { + var arg = new CallArgument.Specified(Option.empty(), self, self.location(), meta(), diag()); + yield new Application.Prefix(any, join(arg, nil()), false, any.location(), meta(), diag()); + } + }; var loc = getIdentifiedLocation(l.getExpression().getExpression()); args.add(at, new CallArgument.Specified(Option.empty(), expr, loc, meta(), diag())); self = expr; diff --git a/engine/runtime-parser/src/test/java/org/enso/compiler/core/EnsoParserTest.java b/engine/runtime-parser/src/test/java/org/enso/compiler/core/EnsoParserTest.java index e90ea7cb46cb..254577a45410 100644 --- a/engine/runtime-parser/src/test/java/org/enso/compiler/core/EnsoParserTest.java +++ b/engine/runtime-parser/src/test/java/org/enso/compiler/core/EnsoParserTest.java @@ -1,18 +1,17 @@ package org.enso.compiler.core; -import com.oracle.truffle.api.source.Source; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.util.List; import java.util.function.Function; + import org.enso.compiler.core.ir.Module; import org.junit.AfterClass; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; - import org.junit.BeforeClass; import org.junit.Test; @@ -1280,6 +1279,28 @@ public void testBlockSyntaxOperators2() throws Exception { """); } + @Test + public void testBlockSyntaxOperators3() throws Exception { + equivalenceTest(""" + v = (rect1 . width) . center + """, """ + v = rect1 + . width + . center + """); + } + + @Test + public void testBlockSyntaxOperators4() throws Exception { + equivalenceTest(""" + v = (rect1 . width 4) . center 3 2 + """, """ + v = rect1 + . width 4 + . center 3 2 + """); + } + @Test public void testPrivateModules() throws Exception { List moduleCodes = List.of( @@ -1338,8 +1359,7 @@ private static Module compile(String code) { } public static Module compile(EnsoParser c, String code) { - var src = Source.newBuilder("enso", code, "test-" + Integer.toHexString(code.hashCode()) + ".enso").build(); - var ir = c.compile(src.getCharacters()); + var ir = c.compile(code); assertNotNull("IR was generated", ir); return ir; }