Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi line chained operator syntax #8415

Merged
merged 5 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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<CallArgument>();
var hasDefaultsSuspended = false;
var tree = ast;
Expand Down Expand Up @@ -597,25 +597,48 @@ 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 invoke = isDotOperator(l.getExpression().getOperator().getRight());
if (self == null || !invoke) {
return null;
}
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;
}
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();
Expand Down Expand Up @@ -682,7 +705,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;
}
Expand Down Expand Up @@ -750,22 +773,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<Expression> items = nil();
if (arr.getFirst() != null) {
Expand Down Expand Up @@ -998,6 +1026,21 @@ loc, meta(), diag()
};
}

private Operator applyOperator(Token.Operator op, CallArgument lhs, CallArgument rhs, Option<IdentifiedLocation> 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`].
Expand Down Expand Up @@ -1360,7 +1403,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()
Expand Down Expand Up @@ -1795,6 +1838,10 @@ private static final <T> scala.collection.immutable.List<T> 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -1246,6 +1245,62 @@ 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 testBlockSyntaxOperators() throws Exception {
equivalenceTest("""
JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
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
""");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to @somebody1234's comment this is the desired behavior.

}

@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<String> moduleCodes = List.of(
Expand Down Expand Up @@ -1304,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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,35 @@ 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 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 =
Expand Down
Loading