Implement annotations (#3780)

- `->` lambda operator isn't bound by nospace groups; see new test case.
- Implemented annotations.
This commit is contained in:
Kaz Wesley 2022-10-10 00:09:01 -07:00 committed by GitHub
parent ea60cd5fab
commit 2fab9ee1e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 103 additions and 7 deletions

View File

@ -629,6 +629,12 @@ 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::max())
.with_binary_infix_precedence(20)
.as_compile_time_operation()
.as_annotation(),
"-" =>
return operator
.with_unary_prefix_mode(token::Precedence::max())
@ -666,7 +672,6 @@ fn analyze_operator(token: &str) -> token::OperatorProperties {
.with_binary_infix_precedence(1)
.as_compile_time_operation()
.as_sequence(),
"@" => return operator.with_binary_infix_precedence(20).as_compile_time_operation(),
"." => return operator.with_binary_infix_precedence(21).with_decimal_interpretation(),
_ => (),
}

View File

@ -25,6 +25,7 @@ pub fn all() -> resolver::SegmentMap<'static> {
macro_map.register(array());
macro_map.register(tuple());
macro_map.register(splice());
macro_map
}

View File

@ -169,7 +169,8 @@ impl<'s> ExpressionBuilder<'s> {
// it's acting as unary.
(true, _, Some(prec)) => self.push_operator(prec, assoc, Arity::Unary(opr)),
// Outside of a nospace group, a unary-only operator is missing an operand.
(false, None, Some(_)) => self.operand(syntax::Tree::unary_opr_app(opr, None).into()),
(false, None, Some(_)) =>
self.operand(syntax::tree::apply_unary_operator(opr, None).into()),
// Binary operator section (no LHS).
(_, Some(prec), _) => self.binary_operator(prec, assoc, opr),
// Failed to compute a role for the operator; this should not be possible.
@ -238,7 +239,7 @@ impl<'s> ExpressionBuilder<'s> {
let rhs_ = rhs.take();
let ast = match opr.opr {
Arity::Unary(opr) =>
Operand::from(rhs_).map(|item| syntax::Tree::unary_opr_app(opr, item)),
Operand::from(rhs_).map(|item| syntax::tree::apply_unary_operator(opr, item)),
Arity::Binary { tokens, lhs_section_termination } => {
let lhs = self.output.pop();
if let Some(lhs_termination) = lhs_section_termination {

View File

@ -337,6 +337,7 @@ pub struct OperatorProperties {
is_arrow: bool,
is_sequence: bool,
is_suspension: bool,
is_annotation: bool,
}
impl OperatorProperties {
@ -392,6 +393,11 @@ impl OperatorProperties {
Self { is_sequence: true, ..self }
}
/// Return a copy of this operator, modified to be flagged as the annotation operator.
pub fn as_annotation(self) -> Self {
Self { is_annotation: true, ..self }
}
/// Return a copy of this operator, modified to be flagged as the execution-suspension operator.
pub fn as_suspension(self) -> Self {
Self { is_suspension: true, ..self }
@ -447,6 +453,11 @@ impl OperatorProperties {
self.is_suspension
}
/// Return whether this operator is the annotation operator.
pub fn is_annotation(&self) -> bool {
self.is_annotation
}
/// Return this operator's associativity.
pub fn associativity(&self) -> Associativity {
match self.is_right_associative {

View File

@ -299,6 +299,13 @@ macro_rules! with_ast_definition { ($f:ident ($($args:tt)*)) => { $f! { $($args)
pub rest: Vec<OperatorDelimitedTree<'s>>,
pub right: token::CloseSymbol<'s>,
},
/// An expression preceded by an annotation, e.g. `@Builtin_Method foo`.
Annotated {
pub token: token::Operator<'s>,
pub annotation: token::Ident<'s>,
pub newlines: Vec<token::Newline<'s>>,
pub expression: Option<Tree<'s>>,
},
}
}};}
@ -735,6 +742,14 @@ pub fn apply<'s>(mut func: Tree<'s>, mut arg: Tree<'s>) -> Tree<'s> {
func_.fractional_digits = mem::take(fractional_digits);
return func;
}
Variant::Annotated(func_ @ Annotated { expression: None, .. }) => {
func_.expression = arg.into();
return func;
}
Variant::Annotated(Annotated { expression: Some(expression), .. }) => {
*expression = apply(mem::take(expression), arg);
return func;
}
_ => (),
}
match &mut *arg.variant {
@ -876,6 +891,18 @@ pub fn apply_operator<'s>(
Tree::opr_app(lhs, opr, rhs)
}
/// Apply a unary operator to an operand.
///
/// For most inputs this will simply construct a `UnaryOprApp`; however, some operators are special.
pub fn apply_unary_operator<'s>(opr: token::Operator<'s>, rhs: Option<Tree<'s>>) -> Tree<'s> {
if opr.properties.is_annotation()
&& let Some(Tree { variant: box Variant::Ident(Ident { token }), .. }) = rhs {
Tree::annotated(opr, token, vec![], None)
} else {
Tree::unary_opr_app(opr, rhs)
}
}
impl<'s> From<Token<'s>> for Tree<'s> {
fn from(token: Token<'s>) -> Self {
match token.variant {
@ -957,6 +984,7 @@ pub fn recurse_left_mut_while<'s>(
| Variant::TypeSignature(_)
| Variant::Lambda(_)
| Variant::Array(_)
| Variant::Annotated(_)
| Variant::Tuple(_) => break,
// Optional LHS.
Variant::ArgumentBlockApplication(ArgumentBlockApplication { lhs, .. })

View File

@ -50,11 +50,25 @@ impl<'s> span::Builder<'s> for Line<'s> {
/// Build a body block from a sequence of lines; this involves reinterpreting the input expressions
/// in statement context (i.e. expressions at the top-level of the block that involve the `=`
/// operator will be reinterpreted as function/variable bindings).
pub fn body_from_lines<'s>(expressions: impl IntoIterator<Item = Line<'s>>) -> Tree<'s> {
pub fn body_from_lines<'s>(lines: impl IntoIterator<Item = Line<'s>>) -> Tree<'s> {
use crate::expression_to_statement;
let expressions = expressions.into_iter();
let statements = expressions.map(|line| line.map_expression(expression_to_statement));
let statements = statements.collect();
let mut lines = lines.into_iter();
let mut statements = Vec::with_capacity(lines.size_hint().0);
while let Some(line) = lines.next() {
let mut statement = line.map_expression(expression_to_statement);
if let Some(Tree {
variant: box Variant::Annotated(Annotated { newlines, expression, .. }),
..
}) = &mut statement.expression
{
while expression.is_none() && let Some(line) = lines.next() {
let statement = line.map_expression(expression_to_statement);
newlines.push(statement.newline);
*expression = statement.expression;
}
}
statements.push(statement);
}
Tree::body_block(statements)
}

View File

@ -1020,6 +1020,42 @@ fn trailing_whitespace() {
}
// === Annotations ===
#[test]
fn annotation_syntax() {
#[rustfmt::skip]
let cases = [
("foo@bar", block![(OprApp (Ident foo) (Ok "@") (Ident bar))]),
("foo @ bar", block![(OprApp (Ident foo) (Ok "@") (Ident bar))]),
("@Bar", block![(Annotated "@" Bar #() ())]),
];
cases.into_iter().for_each(|(code, expected)| test(code, expected));
}
#[test]
fn inline_annotations() {
#[rustfmt::skip]
let cases = [
("@Tail_Call go t", block![(Annotated "@" Tail_Call #() (App (Ident go) (Ident t)))]),
("@Tail_Call go\n a\n b", block![
(Annotated "@" Tail_Call #()
(ArgumentBlockApplication (Ident go) #((Ident a) (Ident b))))]),
];
cases.into_iter().for_each(|(code, expected)| test(code, expected));
}
#[test]
fn multiline_annotations() {
#[rustfmt::skip]
let cases = [
("@Builtin_Type\ntype Date", block![
(Annotated "@" Builtin_Type #(()) (TypeDef type Date #() #() #()))]),
];
cases.into_iter().for_each(|(code, expected)| test(code, expected));
}
// ====================
// === Test Support ===