diff --git a/book/src/list-functions-lists.md b/book/src/list-functions-lists.md index ad9a7399..10723fa0 100644 --- a/book/src/list-functions-lists.md +++ b/book/src/list-functions-lists.md @@ -34,7 +34,7 @@ fn cons(x: A, xs: List) -> List Append an element to the end of a list. ```nbt -fn cons_end(xs: List, x: A) -> List +fn cons_end(x: A, xs: List) -> List ``` ### `is_empty` diff --git a/examples/tests/lists.nbt b/examples/tests/lists.nbt index 22ba39c6..f4043c02 100644 --- a/examples/tests/lists.nbt +++ b/examples/tests/lists.nbt @@ -10,8 +10,8 @@ assert_eq(tail(xs), [2, 3]) assert_eq(cons(0, xs), [0, 1, 2, 3]) -assert_eq(cons_end(xs, 4), [1, 2, 3, 4]) -assert_eq(cons_end([], 0), [0]) +assert_eq(cons_end(4, xs), [1, 2, 3, 4]) +assert_eq(cons_end(0, []), [0]) assert(is_empty([])) assert(!is_empty(xs)) diff --git a/numbat/modules/core/lists.nbt b/numbat/modules/core/lists.nbt index 3cba05e8..24e2217e 100644 --- a/numbat/modules/core/lists.nbt +++ b/numbat/modules/core/lists.nbt @@ -15,7 +15,7 @@ fn tail(xs: List) -> List fn cons(x: A, xs: List) -> List @description("Append an element to the end of a list") -fn cons_end(xs: List, x: A) -> List +fn cons_end(x: A, xs: List) -> List @description("Check if a list is empty") fn is_empty(xs: List) -> Bool = xs == [] @@ -55,7 +55,7 @@ fn range(start: Scalar, end: Scalar) -> List = fn reverse(xs: List) -> List = if is_empty(xs) then [] - else cons_end(reverse(tail(xs)), head(xs)) + else cons_end(head(xs), reverse(tail(xs))) @description("Generate a new list by applying a function to each element of the input list") fn map(f: Fn[(A) -> B], xs: List) -> List = diff --git a/numbat/src/ffi/lists.rs b/numbat/src/ffi/lists.rs index ae4e0292..609465c9 100644 --- a/numbat/src/ffi/lists.rs +++ b/numbat/src/ffi/lists.rs @@ -36,8 +36,8 @@ pub fn cons(mut args: Args) -> Result { } pub fn cons_end(mut args: Args) -> Result { - let mut list = list_arg!(args); let element = arg!(args); + let mut list = list_arg!(args); list.push_back(element); return_list!(list) diff --git a/numbat/src/parser.rs b/numbat/src/parser.rs index 04bad6d3..c4917301 100644 --- a/numbat/src/parser.rs +++ b/numbat/src/parser.rs @@ -108,6 +108,9 @@ pub enum ParseErrorKind { #[error("Expected identifier")] ExpectedIdentifier, + #[error("Expected identifier or function call after postfix apply (`//`)")] + ExpectedIdentifierOrCallAfterPostfixApply, + #[error("Expected dimension identifier, '1', or opening parenthesis")] ExpectedDimensionPrimary, @@ -880,16 +883,30 @@ impl<'a> Parser<'a> { let mut expr = self.condition()?; let mut full_span = expr.full_span(); while self.match_exact(TokenKind::PostfixApply).is_some() { - let identifier = self.identifier()?; - let identifier_span = self.last().unwrap().span; - full_span = full_span.extend(&identifier_span); - - expr = Expression::FunctionCall( - identifier_span, - full_span, - Box::new(Expression::Identifier(identifier_span, identifier)), - vec![expr], - ); + match self.call()? { + Expression::Identifier(span, ident) => { + full_span = full_span.extend(&span); + + expr = Expression::FunctionCall( + span, + full_span, + Box::new(Expression::Identifier(span, ident)), + vec![expr], + ); + } + Expression::FunctionCall(call_span, fn_full_span, call, mut params) => { + full_span = full_span.extend(&fn_full_span); + + params.push(expr); + expr = Expression::FunctionCall(call_span, full_span, call, params); + } + _other => { + return Err(ParseError::new( + ParseErrorKind::ExpectedIdentifierOrCallAfterPostfixApply, + full_span, + )) + } + } } Ok(expr) } @@ -1857,7 +1874,7 @@ mod tests { for input in inputs { match parse(input, 0) { Err((_, errors)) => { - assert_eq!(errors[0].kind, error_kind); + assert_eq!(errors[0].kind, error_kind, "Failed on {}", input); } _ => { panic!(); @@ -2734,6 +2751,24 @@ mod tests { vec![binop!(scalar!(1.0), Add, scalar!(1.0))], ), ); + parse_as_expression( + &["1 + 1 // kefir(2)"], + Expression::FunctionCall( + Span::dummy(), + Span::dummy(), + Box::new(identifier!("kefir")), + vec![scalar!(2.0), binop!(scalar!(1.0), Add, scalar!(1.0))], + ), + ); + + should_fail_with(&["1 // print()"], ParseErrorKind::InlineProcedureUsage); + + should_fail_with(&["1 // +"], ParseErrorKind::ExpectedPrimary); + + should_fail_with( + &["1 // 2", "1 // 1 +"], + ParseErrorKind::ExpectedIdentifierOrCallAfterPostfixApply, + ); } #[test]