use crate::{
Result,
ast::{
ast::{Ast, AstCall, AstCallFunction, AstList, AstLiteral, AstLiteralText, AstTuple},
identifier::MaybeQualifiedFunctionIdentifier,
parse::{Parser, Precedence},
},
bump::BumpFragment,
token::{
keyword::Keyword,
operator::Operator,
separator::Separator::Comma,
token::{Literal, Token, TokenKind},
},
};
impl<'bump> Parser<'bump> {
pub(crate) fn parse_call(&mut self) -> Result<AstCall<'bump>> {
let token = self.consume_keyword(Keyword::Call)?;
let first_ident = self.consume_identifier()?;
let mut namespace_fragments = Vec::new();
let mut current = first_ident;
while self.current()?.is_operator(Operator::DoubleColon) {
namespace_fragments.push(current.fragment);
self.advance()?;
current = self.consume_identifier()?;
}
let function = if namespace_fragments.is_empty() {
MaybeQualifiedFunctionIdentifier::new(current.fragment)
} else {
MaybeQualifiedFunctionIdentifier::new(current.fragment).with_namespaces(namespace_fragments)
};
let arguments = self.parse_call_arguments()?;
Ok(AstCall {
token,
function,
arguments,
})
}
fn parse_call_arguments(&mut self) -> Result<AstTuple<'bump>> {
let token = self.consume_operator(Operator::OpenParen)?;
let mut nodes = Vec::with_capacity(4);
loop {
self.skip_new_line()?;
if self.current()?.is_operator(Operator::CloseParen) {
break;
}
nodes.push(self.parse_call_argument()?);
if self.consume_if(TokenKind::Separator(Comma))?.is_none() {
break;
};
}
self.consume_operator(Operator::CloseParen)?;
Ok(AstTuple {
token,
nodes,
})
}
fn parse_call_argument(&mut self) -> Result<Ast<'bump>> {
if self.current()?.is_operator(Operator::OpenBracket) {
let token = self.consume_operator(Operator::OpenBracket)?;
let mut nodes = Vec::with_capacity(4);
loop {
self.skip_new_line()?;
if self.is_eof() || self.current()?.is_operator(Operator::CloseBracket) {
break;
}
nodes.push(self.parse_call_argument()?);
if self.consume_if(TokenKind::Separator(Comma))?.is_none() {
break;
};
}
self.consume_operator(Operator::CloseBracket)?;
return Ok(Ast::List(AstList {
token,
nodes,
}));
}
if self.current()?.is_identifier() && !self.is_function_call_pattern() {
return self.parse_qualified_name_argument();
}
self.parse_node(Precedence::None)
}
fn parse_qualified_name_argument(&mut self) -> Result<Ast<'bump>> {
let start = self.current()?.fragment;
let line = start.line();
let column = start.column();
let segments = self.parse_double_colon_separated_identifiers()?;
let mut joined = String::new();
for segment in &segments {
if !joined.is_empty() {
joined.push_str("::");
}
joined.push_str(segment.text());
}
let text = self.bump().alloc_str(&joined);
let fragment = BumpFragment::Statement {
text,
offset: 0,
source_end: 0,
line,
column,
};
Ok(Ast::Literal(AstLiteral::Text(AstLiteralText(Token {
kind: TokenKind::Literal(Literal::Text),
fragment,
}))))
}
pub(crate) fn parse_function_call(&mut self) -> Result<AstCallFunction<'bump>> {
let first_ident_token = self.consume_identifier()?;
let start_token = first_ident_token;
if self.current()?.is_operator(Operator::OpenParen) {
let open_paren_token = self.advance()?;
let arguments = self.parse_tuple_call(open_paren_token)?;
let function = MaybeQualifiedFunctionIdentifier::new(first_ident_token.fragment);
return Ok(AstCallFunction {
token: start_token,
function,
arguments,
});
}
let mut current_ident_token = first_ident_token;
let mut namespace_fragments = Vec::new();
while self.current()?.is_operator(Operator::DoubleColon) {
namespace_fragments.push(current_ident_token.fragment);
self.advance()?;
let next_ident_token = self.consume_identifier()?;
if self.current()?.is_operator(Operator::OpenParen) {
let open_paren_token = self.advance()?;
let arguments = self.parse_tuple_call(open_paren_token)?;
let function = MaybeQualifiedFunctionIdentifier::new(next_ident_token.fragment)
.with_namespaces(namespace_fragments);
return Ok(AstCallFunction {
token: start_token,
function,
arguments,
});
} else {
current_ident_token = next_ident_token;
}
}
unreachable!("parse_function_call called on non-function call pattern")
}
pub(crate) fn is_function_call_pattern(&self) -> bool {
let tokens_len = self.tokens.len();
if self.position >= tokens_len {
return false;
}
if !unsafe { self.tokens.get_unchecked(self.position) }.is_identifier() {
return false;
}
if self.position + 1 < tokens_len
&& unsafe { self.tokens.get_unchecked(self.position + 1) }.is_operator(Operator::OpenParen)
{
return true;
}
let mut pos = self.position + 1;
while pos + 2 < tokens_len {
if !unsafe { self.tokens.get_unchecked(pos) }.is_operator(Operator::DoubleColon) {
return false;
}
pos += 1;
let token = unsafe { self.tokens.get_unchecked(pos) };
if !token.is_identifier() && !token.is_keyword_as_ident() {
return false;
}
pos += 1;
if unsafe { self.tokens.get_unchecked(pos) }.is_operator(Operator::OpenParen) {
return true;
}
}
false
}
}
#[cfg(test)]
pub mod tests {
use bumpalo::Bump;
use crate::{
ast::{ast::Ast, parse::parse},
token::tokenize,
};
#[test]
fn test_namespaced_call() {
let bump = Bump::new();
let source = "CALL ns::greet()";
let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
let result = parse(&bump, source, tokens).unwrap();
assert_eq!(result.len(), 1);
let Ast::Call(call) = result[0].first_unchecked() else {
panic!("expected Call")
};
assert_eq!(call.function.name.text(), "greet");
assert_eq!(call.function.namespaces.len(), 1);
assert_eq!(call.function.namespaces[0].text(), "ns");
}
#[test]
fn test_simple_call() {
let bump = Bump::new();
let source = "CALL greet()";
let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
let result = parse(&bump, source, tokens).unwrap();
assert_eq!(result.len(), 1);
let Ast::Call(call) = result[0].first_unchecked() else {
panic!("expected Call")
};
assert_eq!(call.function.name.text(), "greet");
assert!(call.function.namespaces.is_empty());
}
#[test]
fn test_simple_function_call() {
let bump = Bump::new();
let source = "func()";
let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
let result = parse(&bump, source, tokens).unwrap();
assert_eq!(result.len(), 1);
let call = result[0].first_unchecked().as_call_function();
assert_eq!(call.function.name.text(), "func");
assert!(call.function.namespaces.is_empty());
assert_eq!(call.arguments.len(), 0);
}
#[test]
fn test_function_call_with_args() {
let bump = Bump::new();
let source = "func('arg')";
let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
let result = parse(&bump, source, tokens).unwrap();
assert_eq!(result.len(), 1);
let call = result[0].first_unchecked().as_call_function();
assert_eq!(call.function.name.text(), "func");
assert!(call.function.namespaces.is_empty());
assert_eq!(call.arguments.len(), 1);
}
#[test]
fn test_namespaced_function_call() {
let bump = Bump::new();
let source = "blob::hex('deadbeef')";
let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
let result = parse(&bump, source, tokens).unwrap();
assert_eq!(result.len(), 1);
let call = result[0].first_unchecked().as_call_function();
assert_eq!(call.function.name.text(), "hex");
assert_eq!(call.function.namespaces.len(), 1);
assert_eq!(call.function.namespaces[0].text(), "blob");
assert_eq!(call.arguments.len(), 1);
}
#[test]
fn test_deeply_nested_function_call() {
let bump = Bump::new();
let source = "ext::crypto::hash::sha256('data')";
let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
let result = parse(&bump, source, tokens).unwrap();
assert_eq!(result.len(), 1);
let call = result[0].first_unchecked().as_call_function();
assert_eq!(call.function.name.text(), "sha256");
assert_eq!(call.function.namespaces.len(), 3);
assert_eq!(call.function.namespaces[0].text(), "ext");
assert_eq!(call.function.namespaces[1].text(), "crypto");
assert_eq!(call.function.namespaces[2].text(), "hash");
assert_eq!(call.arguments.len(), 1);
}
#[test]
fn test_identifier_without_parens_not_function_call() {
let bump = Bump::new();
let source = "identifier";
let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
let result = parse(&bump, source, tokens).unwrap();
assert_eq!(result.len(), 1);
assert!(result[0].first_unchecked().as_identifier().text() == "identifier");
}
#[test]
fn test_namespaced_function_call_with_keyword_name() {
let bump = Bump::new();
let source = "clock::set(1000)";
let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
let result = parse(&bump, source, tokens).unwrap();
assert_eq!(result.len(), 1);
let call = result[0].first_unchecked().as_call_function();
assert_eq!(call.function.name.text(), "set");
assert_eq!(call.function.namespaces.len(), 1);
assert_eq!(call.function.namespaces[0].text(), "clock");
}
#[test]
fn test_namespace_access_without_parens_not_function_call() {
let bump = Bump::new();
let source = "namespace::identifier";
let tokens = tokenize(&bump, source).unwrap().into_iter().collect();
let result = parse(&bump, source, tokens).unwrap();
assert_eq!(result.len(), 1);
let infix = result[0].first_unchecked().as_infix();
assert_eq!(infix.left.as_identifier().text(), "namespace");
assert_eq!(infix.right.as_identifier().text(), "identifier");
}
}