mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 10:42:05 +03:00
Autoscope syntax (#9372)
Add autoscope syntax (`..Ident`). # Important Notes - Also rename previous `Tree.Autoscope` to `SuspendedDefaultArguments`.
This commit is contained in:
parent
d9ca6cf023
commit
a1c0d9ac08
@ -615,7 +615,7 @@ final class TreeToIr {
|
||||
var tree = ast;
|
||||
for (;;) {
|
||||
switch (tree) {
|
||||
case Tree.App app when app.getArg() instanceof Tree.AutoScope -> {
|
||||
case Tree.App app when app.getArg() instanceof Tree.SuspendedDefaultArguments -> {
|
||||
hasDefaultsSuspended = true;
|
||||
tree = app.getFunc();
|
||||
}
|
||||
@ -670,16 +670,6 @@ final class TreeToIr {
|
||||
var loc = getIdentifiedLocation(oprApp.getLhs());
|
||||
args.add(new CallArgument.Specified(Option.empty(), self, loc, meta(), diag()));
|
||||
}
|
||||
} else if (tree instanceof Tree.OprApp oprApp
|
||||
&& isDotDotOperator(oprApp.getOpr().getRight())
|
||||
&& oprApp.getRhs() instanceof Tree.Ident ident) {
|
||||
var methodName = buildName(ident);
|
||||
func = new Name.MethodReference(
|
||||
Option.empty(),
|
||||
methodName,
|
||||
methodName.location(),
|
||||
meta(), diag()
|
||||
);
|
||||
} else if (args.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
@ -1064,12 +1054,21 @@ final class TreeToIr {
|
||||
case Tree.App app -> {
|
||||
var fn = translateExpression(app.getFunc(), isMethod);
|
||||
var loc = getIdentifiedLocation(app);
|
||||
if (app.getArg() instanceof Tree.AutoScope) {
|
||||
if (app.getArg() instanceof Tree.SuspendedDefaultArguments) {
|
||||
yield new Application.Prefix(fn, nil(), true, loc, meta(), diag());
|
||||
} else {
|
||||
yield fn.setLocation(loc);
|
||||
}
|
||||
}
|
||||
case Tree.AutoscopedIdentifier autoscopedIdentifier -> {
|
||||
var methodName = buildName(autoscopedIdentifier.getIdent());
|
||||
yield new Name.MethodReference(
|
||||
Option.empty(),
|
||||
methodName,
|
||||
methodName.location(),
|
||||
meta(), diag()
|
||||
);
|
||||
}
|
||||
case Tree.Invalid __ -> translateSyntaxError(tree, Syntax.UnexpectedExpression$.MODULE$);
|
||||
default -> translateSyntaxError(tree, new Syntax.UnsupportedSyntax("translateExpression"));
|
||||
};
|
||||
@ -1103,7 +1102,7 @@ final class TreeToIr {
|
||||
case Tree.BodyBlock ignored -> null;
|
||||
case Tree.Number ignored -> null;
|
||||
case Tree.Wildcard ignored -> null;
|
||||
case Tree.AutoScope ignored -> null;
|
||||
case Tree.SuspendedDefaultArguments ignored -> null;
|
||||
case Tree.ForeignFunction ignored -> null;
|
||||
case Tree.Import ignored -> null;
|
||||
case Tree.Export ignored -> null;
|
||||
@ -1880,10 +1879,6 @@ final class TreeToIr {
|
||||
return op != null && ".".equals(op.codeRepr());
|
||||
}
|
||||
|
||||
private static boolean isDotDotOperator(Token.Operator op) {
|
||||
return op != null && "..".equals(op.codeRepr());
|
||||
}
|
||||
|
||||
private static Tree maybeManyParensed(Tree t) {
|
||||
for (;;) {
|
||||
switch (t) {
|
||||
|
@ -934,7 +934,7 @@ public class EnsoParserTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoScope() throws Exception {
|
||||
public void testSuspendedDefaultArguments() throws Exception {
|
||||
parseTest("""
|
||||
fn that_meta =
|
||||
c_2 = that_meta.constructor ...
|
||||
@ -942,7 +942,7 @@ public class EnsoParserTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoScope2() throws Exception {
|
||||
public void testSuspendedDefaultArguments2() throws Exception {
|
||||
parseTest("""
|
||||
fn1 = fn ...
|
||||
fn2 = fn 1 ...
|
||||
|
@ -49,7 +49,7 @@ where T: serde::Serialize + Reflect {
|
||||
vec![Digits::reflect(), NumberBase::reflect(), Operator::reflect(), TextSection::reflect()];
|
||||
let stringish_tokens = stringish_tokens.into_iter().map(|t| rust_to_meta[&t.id]);
|
||||
let skip_tokens = vec![
|
||||
AutoScope::reflect(),
|
||||
SuspendedDefaultArguments::reflect(),
|
||||
CloseSymbol::reflect(),
|
||||
Newline::reflect(),
|
||||
OpenSymbol::reflect(),
|
||||
|
@ -865,6 +865,25 @@ fn method_app_in_minus_unary() {
|
||||
(UnaryOprApp "-" (OprApp (Ident Number) (Ok ".") (Ident positive_infinity))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autoscope_operator() {
|
||||
test!("x : ..True", (TypeSignature (Ident x) ":" (AutoscopedIdentifier ".." True)));
|
||||
test!("x = ..True", (Assignment (Ident x) "=" (AutoscopedIdentifier ".." True)));
|
||||
test!("x = f ..True",
|
||||
(Assignment (Ident x) "=" (App (Ident f) (AutoscopedIdentifier ".." True))));
|
||||
expect_invalid_node("x = ..not_a_constructor");
|
||||
expect_invalid_node("x = case a of ..True -> True");
|
||||
expect_invalid_node("x = ..4");
|
||||
expect_invalid_node("x = ..Foo.Bar");
|
||||
expect_invalid_node("x = f .. True");
|
||||
expect_invalid_node("x = f(.. ..)");
|
||||
expect_invalid_node("x = f(.. *)");
|
||||
expect_invalid_node("x = f(.. True)");
|
||||
expect_multiple_operator_error("x = ..");
|
||||
expect_multiple_operator_error("x = .. True");
|
||||
expect_multiple_operator_error("x : .. True");
|
||||
}
|
||||
|
||||
|
||||
// === Import/Export ===
|
||||
|
||||
@ -1005,7 +1024,10 @@ fn type_annotations() {
|
||||
(App (Ident foo)
|
||||
(Group (TypeAnnotated (Ident x) ":" (Ident Int)))))]),
|
||||
("(x : My_Type _)", block![
|
||||
(Group (TypeAnnotated (Ident x) ":" (App (Ident My_Type) (Wildcard -1))))]),
|
||||
(Group
|
||||
(TypeAnnotated (Ident x)
|
||||
":"
|
||||
(App (Ident My_Type) (TemplateFunction 1 (Wildcard 0)))))]),
|
||||
("x : List Int -> Int", block![
|
||||
(TypeSignature (Ident x) ":"
|
||||
(OprApp (App (Ident List) (Ident Int)) (Ok "->") (Ident Int)))]),
|
||||
@ -1219,8 +1241,13 @@ fn case_expression() {
|
||||
(CaseOf (Ident foo) #(
|
||||
((() (TypeAnnotated (Ident v) ":" (Ident My_Type)) "->" (Ident x)))
|
||||
((() (TypeAnnotated (Ident v) ":"
|
||||
(Group (App (App (Ident My_Type) (Wildcard -1)) (Wildcard -1))))
|
||||
"->" (Ident x)))))];
|
||||
(Group (App
|
||||
(App
|
||||
(Ident My_Type)
|
||||
(TemplateFunction 1 (Wildcard 0)))
|
||||
(TemplateFunction 1 (Wildcard 0)))))
|
||||
"->" (Ident x)))))
|
||||
];
|
||||
test(&code.join("\n"), expected);
|
||||
}
|
||||
|
||||
@ -1269,7 +1296,7 @@ fn case_by_type() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_match_auto_scope() {
|
||||
fn pattern_match_suspended_default_arguments() {
|
||||
#[rustfmt::skip]
|
||||
let code = [
|
||||
"case self of",
|
||||
@ -1277,7 +1304,7 @@ fn pattern_match_auto_scope() {
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
let expected = block![
|
||||
(CaseOf (Ident self) #(((() (App (Ident Vector_2d) (AutoScope)) "->" (Ident x)))))];
|
||||
(CaseOf (Ident self) #(((() (App (Ident Vector_2d) (SuspendedDefaultArguments)) "->" (Ident x)))))];
|
||||
test(&code.join("\n"), expected);
|
||||
}
|
||||
|
||||
|
@ -652,7 +652,7 @@ impl<'s> Lexer<'s> {
|
||||
}
|
||||
// Composed of operator characters, but not an operator node.
|
||||
"..." => {
|
||||
let token = token.with_variant(token::Variant::auto_scope());
|
||||
let token = token.with_variant(token::Variant::suspended_default_arguments());
|
||||
self.submit_token(token);
|
||||
}
|
||||
// Decimal vs. method-application must be distinguished before parsing because they
|
||||
@ -714,6 +714,11 @@ fn analyze_operator(token: &str) -> token::OperatorProperties {
|
||||
.with_unary_prefix_mode(token::Precedence::max())
|
||||
.as_compile_time_operation()
|
||||
.as_suspension(),
|
||||
".." =>
|
||||
return operator
|
||||
.with_unary_prefix_mode(token::Precedence::min_valid())
|
||||
.as_compile_time_operation()
|
||||
.as_autoscope(),
|
||||
"@" =>
|
||||
return operator
|
||||
.with_unary_prefix_mode(token::Precedence::max())
|
||||
|
@ -300,26 +300,6 @@ fn is_qualified_name(tree: &syntax::Tree) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn expression_to_type(mut input: syntax::Tree<'_>) -> syntax::Tree<'_> {
|
||||
use syntax::tree::*;
|
||||
if let Variant::Wildcard(wildcard) = &mut *input.variant {
|
||||
wildcard.de_bruijn_index = None;
|
||||
return input;
|
||||
}
|
||||
let mut out = match input.variant {
|
||||
box Variant::TemplateFunction(TemplateFunction { ast, .. }) => expression_to_type(ast),
|
||||
box Variant::Group(Group { open, body: Some(body), close }) =>
|
||||
Tree::group(open, Some(expression_to_type(body)), close),
|
||||
box Variant::OprApp(OprApp { lhs, opr, rhs }) =>
|
||||
Tree::opr_app(lhs.map(expression_to_type), opr, rhs.map(expression_to_type)),
|
||||
box Variant::App(App { func, arg }) =>
|
||||
Tree::app(expression_to_type(func), expression_to_type(arg)),
|
||||
_ => return input,
|
||||
};
|
||||
out.span.left_offset += input.span.left_offset;
|
||||
out
|
||||
}
|
||||
|
||||
fn expression_to_pattern(mut input: syntax::Tree<'_>) -> syntax::Tree<'_> {
|
||||
use syntax::tree::*;
|
||||
if let Variant::Wildcard(wildcard) = &mut *input.variant {
|
||||
@ -334,6 +314,8 @@ fn expression_to_pattern(mut input: syntax::Tree<'_>) -> syntax::Tree<'_> {
|
||||
Tree::app(expression_to_pattern(func), expression_to_pattern(arg)),
|
||||
box Variant::TypeAnnotated(TypeAnnotated { expression, operator, type_ }) =>
|
||||
Tree::type_annotated(expression_to_pattern(expression), operator, type_),
|
||||
box Variant::AutoscopedIdentifier(_) =>
|
||||
return input.with_error("The autoscope operator (..) cannot be used in a pattern."),
|
||||
_ => return input,
|
||||
};
|
||||
out.span.left_offset += input.span.left_offset;
|
||||
|
@ -259,7 +259,7 @@ macro_rules! with_token_definition { ($f:ident ($($args:tt)*)) => { $f! { $($arg
|
||||
Wildcard {
|
||||
pub lift_level: u32
|
||||
},
|
||||
AutoScope,
|
||||
SuspendedDefaultArguments,
|
||||
Ident {
|
||||
pub is_free: bool,
|
||||
pub lift_level: u32,
|
||||
@ -340,6 +340,7 @@ pub struct OperatorProperties {
|
||||
is_arrow: bool,
|
||||
is_sequence: bool,
|
||||
is_suspension: bool,
|
||||
is_autoscope: bool,
|
||||
is_annotation: bool,
|
||||
is_dot: bool,
|
||||
is_special: bool,
|
||||
@ -427,6 +428,11 @@ impl OperatorProperties {
|
||||
Self { is_suspension: true, ..self }
|
||||
}
|
||||
|
||||
/// Return a copy of this operator, modified to be flagged as the autoscope operator.
|
||||
pub fn as_autoscope(self) -> Self {
|
||||
Self { is_autoscope: true, ..self }
|
||||
}
|
||||
|
||||
/// Return a copy of this operator, modified to be flagged as the dot operator.
|
||||
pub fn as_dot(self) -> Self {
|
||||
Self { is_dot: true, ..self }
|
||||
@ -492,6 +498,11 @@ impl OperatorProperties {
|
||||
self.is_suspension
|
||||
}
|
||||
|
||||
/// Return whether this operator is the autoscope operator.
|
||||
pub fn is_autoscope(&self) -> bool {
|
||||
self.is_autoscope
|
||||
}
|
||||
|
||||
/// Return whether this operator is the annotation operator.
|
||||
pub fn is_annotation(&self) -> bool {
|
||||
self.is_annotation
|
||||
|
@ -118,9 +118,9 @@ macro_rules! with_ast_definition { ($f:ident ($($args:tt)*)) => { $f! { $($args)
|
||||
#[reflect(as = "i32")]
|
||||
pub de_bruijn_index: Option<u32>,
|
||||
},
|
||||
/// The auto-scoping marker, `...`.
|
||||
AutoScope {
|
||||
pub token: token::AutoScope<'s>,
|
||||
/// The suspended-default-arguments marker, `...`.
|
||||
SuspendedDefaultArguments {
|
||||
pub token: token::SuspendedDefaultArguments<'s>,
|
||||
},
|
||||
TextLiteral {
|
||||
pub open: Option<token::TextStart<'s>>,
|
||||
@ -162,6 +162,11 @@ macro_rules! with_ast_definition { ($f:ident ($($args:tt)*)) => { $f! { $($args)
|
||||
pub opr: token::Operator<'s>,
|
||||
pub rhs: Option<Tree<'s>>,
|
||||
},
|
||||
/// Application of the autoscope operator to an identifier, e.g. `..True`.
|
||||
AutoscopedIdentifier {
|
||||
pub opr: token::Operator<'s>,
|
||||
pub ident: token::Ident<'s>,
|
||||
},
|
||||
/// Defines the point where operator sections should be expanded to lambdas. Let's consider
|
||||
/// the expression `map (.sum 1)`. It should be desugared to `map (x -> x.sum 1)`, not to
|
||||
/// `map ((x -> x.sum) 1)`. The expression `.sum` will be parsed as operator section
|
||||
@ -897,10 +902,7 @@ pub fn apply_operator<'s>(
|
||||
}
|
||||
if let Ok(opr_) = &opr && opr_.properties.is_type_annotation() {
|
||||
return match (lhs, rhs) {
|
||||
(Some(lhs), Some(rhs)) => {
|
||||
let rhs = crate::expression_to_type(rhs);
|
||||
Tree::type_annotated(lhs, opr.unwrap(), rhs)
|
||||
},
|
||||
(Some(lhs), Some(rhs)) => Tree::type_annotated(lhs, opr.unwrap(), rhs),
|
||||
(lhs, rhs) => {
|
||||
let invalid = Tree::opr_app(lhs, opr, rhs);
|
||||
invalid.with_error("`:` operator must be applied to two operands.")
|
||||
@ -951,6 +953,21 @@ pub fn apply_unary_operator<'s>(opr: token::Operator<'s>, rhs: Option<Tree<'s>>)
|
||||
false => Tree::annotated(opr, token, None, vec![], None),
|
||||
};
|
||||
}
|
||||
if opr.properties.is_autoscope() && let Some(rhs) = rhs {
|
||||
return if let box Variant::Ident(Ident { mut token }) = rhs.variant {
|
||||
let applied_to_type = token.variant.is_type;
|
||||
token.left_offset = rhs.span.left_offset;
|
||||
let autoscope_application = Tree::autoscoped_identifier(opr, token);
|
||||
return if applied_to_type {
|
||||
autoscope_application
|
||||
} else {
|
||||
autoscope_application
|
||||
.with_error("The auto-scope operator may only be applied to a capitalized identifier.")
|
||||
}
|
||||
} else {
|
||||
Tree::unary_opr_app(opr, Some(rhs)).with_error("The auto-scope operator (..) may only be applied to an identifier.")
|
||||
}
|
||||
}
|
||||
if !opr.properties.can_form_section() && rhs.is_none() {
|
||||
let error = format!("Operator `{opr:?}` must be applied to an operand.");
|
||||
let invalid = Tree::unary_opr_app(opr, rhs);
|
||||
@ -990,7 +1007,7 @@ pub fn to_ast(token: Token) -> Tree {
|
||||
Tree::text_literal(default(), default(), vec![newline], default(), default())
|
||||
}
|
||||
token::Variant::Wildcard(wildcard) => Tree::wildcard(token.with_variant(wildcard), default()),
|
||||
token::Variant::AutoScope(t) => Tree::auto_scope(token.with_variant(t)),
|
||||
token::Variant::SuspendedDefaultArguments(t) => Tree::suspended_default_arguments(token.with_variant(t)),
|
||||
token::Variant::OpenSymbol(s) =>
|
||||
Tree::group(Some(token.with_variant(s)), default(), default()).with_error("Unmatched delimiter"),
|
||||
token::Variant::CloseSymbol(s) =>
|
||||
|
Loading…
Reference in New Issue
Block a user