datex_core/ast/
chain.rs

1use crate::ast::error::pattern::Pattern;
2use crate::ast::lexer::Token;
3use crate::ast::utils::whitespace;
4use crate::ast::{DatexExpression, DatexParserTrait};
5use chumsky::prelude::*;
6
7#[derive(Clone, Debug, PartialEq)]
8pub enum ApplyOperation {
9    /// Apply a function to an argument
10    FunctionCall(DatexExpression),
11
12    // TODO #356: Implement MultiFunctionCall(Vec<DatexExpression>),
13    /// Apply a property access to an argument
14    PropertyAccess(DatexExpression),
15
16    /// Generic property access, e.g. `a<b>`
17    GenericAccess(DatexExpression),
18}
19
20pub fn chain_without_whitespace_apply<'a>(
21    unary: impl DatexParserTrait<'a>,
22    key: impl DatexParserTrait<'a>,
23    any: impl DatexParserTrait<'a>,
24) -> impl DatexParserTrait<'a> {
25    unary
26        .clone()
27        .then(
28            choice((
29                // generic access: a<b>
30                just(Token::LeftAngle)
31                    .ignore_then(any.clone())
32                    .then_ignore(just(Token::RightAngle))
33                    .map(ApplyOperation::GenericAccess),
34                // property access
35                just(Token::Dot)
36                    .padded_by(whitespace())
37                    .ignore_then(key)
38                    .map(ApplyOperation::PropertyAccess),
39            ))
40            .repeated()
41            .collect::<Vec<_>>(),
42        )
43        .labelled(Pattern::Custom("chain_no_whitespace_atom"))
44        .map(|(val, args)| {
45            if args.is_empty() {
46                val
47            } else {
48                DatexExpression::ApplyChain(Box::new(val), args)
49            }
50        })
51}
52
53pub fn keyed_parameters<'a>(
54    key: impl DatexParserTrait<'a>,
55    expression: impl DatexParserTrait<'a>,
56) -> impl DatexParserTrait<'a> {
57    key.then_ignore(just(Token::Colon).padded_by(whitespace()))
58        .then(expression.clone())
59        .separated_by(just(Token::Comma).padded_by(whitespace()))
60        .at_least(0)
61        .allow_trailing()
62        .collect()
63        .padded_by(whitespace())
64        .delimited_by(just(Token::LeftParen), just(Token::RightParen))
65        .padded_by(whitespace())
66        .map(DatexExpression::Map)
67}
68
69pub fn indexed_parameters<'a>(
70    expression: impl DatexParserTrait<'a>,
71) -> impl DatexParserTrait<'a> {
72    expression
73        .clone()
74        .separated_by(just(Token::Comma).padded_by(whitespace()))
75        .at_least(0)
76        .allow_trailing()
77        .collect()
78        .padded_by(whitespace())
79        .delimited_by(just(Token::LeftParen), just(Token::RightParen))
80        .padded_by(whitespace())
81        .map(DatexExpression::List)
82}
83
84pub fn chain<'a>(
85    unary: impl DatexParserTrait<'a>,
86    key: impl DatexParserTrait<'a>,
87    atom: impl DatexParserTrait<'a>,
88    expression: impl DatexParserTrait<'a>,
89) -> impl DatexParserTrait<'a> {
90    unary
91        .clone()
92        .then(
93            choice((
94                // generic access: a<b>
95                just(Token::LeftAngle)
96                    .ignore_then(expression.clone())
97                    .then_ignore(just(Token::RightAngle))
98                    .map(ApplyOperation::GenericAccess),
99                // apply #1: function call with multiple arguments
100                // x(a: 4, b: 5)
101                choice((
102                    keyed_parameters(key.clone(), expression.clone()),
103                    indexed_parameters(expression.clone()),
104                ))
105                .map(ApplyOperation::FunctionCall),
106                // apply #2: an atomic value (e.g. "text", [1,2,3]) - whitespace or newline required before
107                // print "sdf"
108                just(Token::Whitespace)
109                    .repeated()
110                    .at_least(1)
111                    .ignore_then(atom.padded_by(whitespace()))
112                    .map(ApplyOperation::FunctionCall),
113                // property access
114                // TODO #357: allow integer index access and ranges in dot access notation
115                /*
116                whatever.0x10.test -> not allowed
117                whatever.10.test -> allowed
118                whatever.10u8.test -> disallowed
119                whatever.1e10.test -> disallowed
120                whatever.-1.test -> ?
121                whatever.2..5.test -> later, but allowed
122                */
123                just(Token::Dot)
124                    .padded_by(whitespace())
125                    .ignore_then(key)
126                    .map(ApplyOperation::PropertyAccess),
127            ))
128            .repeated()
129            .collect::<Vec<_>>(),
130        )
131        .labelled(Pattern::Custom("chain"))
132        .map(|(val, args)| {
133            // if only single value, return it directly
134            if args.is_empty() {
135                val
136            } else {
137                DatexExpression::ApplyChain(Box::new(val), args)
138            }
139        })
140}