Actually just three programming languages in a trenchcoat
1use super::{expression::Precedence, *};
2use crate::{Parser, Spanned};
3use source_span::Span;
4use trilogy_scanner::{Token, TokenType::*};
5
6/// A procedure closure `do` expression.
7///
8/// ```trilogy
9/// do() {}
10/// ```
11#[derive(Clone, Debug)]
12pub struct DoExpression {
13 pub head: DoHead,
14 pub body: DoBody,
15 pub span: Span,
16}
17
18impl Spanned for DoExpression {
19 fn span(&self) -> Span {
20 self.span
21 }
22}
23
24impl DoExpression {
25 pub(crate) fn parse(parser: &mut Parser) -> SyntaxResult<Self> {
26 let head = DoHead::parse(parser)?;
27 Self::parse_with_head(parser, head)
28 }
29
30 pub(crate) fn parse_with_head(parser: &mut Parser, head: DoHead) -> SyntaxResult<Self> {
31 if head.parameter_list.is_none() {
32 parser.error(ErrorKind::DoMissingParameterList.at(head.span()));
33 }
34 let body = DoBody::parse(parser)?;
35 Ok(Self {
36 span: head.span().union(body.span()),
37 head,
38 body,
39 })
40 }
41
42 pub fn r#do(&self) -> &Token {
43 &self.head.r#do
44 }
45}
46
47/// The body of a procedure closure.
48#[derive(Clone, Debug, Spanned)]
49pub enum DoBody {
50 /// A block used as the body of a `do` closure.
51 ///
52 /// Returns unit if no explicit `return` statement is reached.
53 Block(Box<Block>),
54 /// A single expression used as the body and return value of a `do` closure.
55 Expression(Box<Expression>),
56}
57
58impl DoBody {
59 pub(crate) fn parse(parser: &mut Parser) -> SyntaxResult<Self> {
60 if parser.check(OBrace).is_ok() {
61 Ok(Self::Block(Box::new(Block::parse(parser)?)))
62 } else {
63 Ok(Self::Expression(Box::new(Expression::parse_precedence(
64 parser,
65 Precedence::Continuation,
66 )?)))
67 }
68 }
69}
70
71#[cfg(test)]
72mod test {
73 use super::*;
74
75 test_parse!(do_block: "do() {}" => DoExpression::parse => DoExpression { body: DoBody::Block(..), .. });
76 test_parse_error!(do_block_no_params: "do {}" => DoExpression::parse);
77 test_parse!(do_block_params: "do(a, b) {}" => DoExpression::parse => DoExpression { body: DoBody::Block(..), .. });
78 test_parse!(do_block_params_trailing_comma: "do(a, b, ) {}" => DoExpression::parse => DoExpression { body: DoBody::Block(..), .. });
79 test_parse_error!(do_block_params_leading_comma: "do(, a) {}" => DoExpression::parse);
80 test_parse_error!(do_block_params_empty_comma: "do(,) {}" => DoExpression::parse);
81 test_parse_error!(do_block_missing_paren: "do(a {}" => DoExpression::parse => "expected `)` to end parameter list");
82 test_parse_error!(do_block_invalid: "do() { exit }" => DoExpression::parse);
83
84 test_parse!(do_expr_spaced: "do () 3" => DoExpression::parse => DoExpression { body: DoBody::Expression(..), .. });
85 test_parse_error!(do_expr_bang: "do!() 3" => DoExpression::parse => "a `do` closure definition does not use `!`");
86 test_parse_error!(do_no_parens: "do 3" => DoExpression::parse => "a `do` closure requires a parameter list, even if empty");
87
88 test_parse!(do_expr: "do() 3" => DoExpression::parse => DoExpression { body: DoBody::Expression(..), .. });
89 test_parse!(do_expr_params: "do(a, b) a + b" => DoExpression::parse => DoExpression { body: DoBody::Expression(..), .. });
90 test_parse!(do_expr_params_trailing_comma: "do(a, b, ) a + b" => DoExpression::parse => DoExpression { body: DoBody::Expression(..), .. });
91 test_parse_error!(do_expr_params_leading_comma: "do(, a) a" => DoExpression::parse);
92 test_parse_error!(do_expr_params_empty_comma: "do(,) 3" => DoExpression::parse);
93 test_parse_error!(do_expr_invalid: "do() return" => DoExpression::parse);
94}