use crate::{
Result,
ast::{
ast::{AstCall, AstCallFunction},
identifier::MaybeQualifiedFunctionIdentifier,
parse::Parser,
},
token::{keyword::Keyword, operator::Operator, 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(TokenKind::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 = if self.current()?.is_identifier() {
self.consume(TokenKind::Identifier)?
} else {
self.consume_name()?
};
}
let function = if namespace_fragments.is_empty() {
MaybeQualifiedFunctionIdentifier::new(current.fragment)
} else {
MaybeQualifiedFunctionIdentifier::new(current.fragment).with_namespaces(namespace_fragments)
};
let arguments = self.parse_tuple()?;
Ok(AstCall {
token,
function,
arguments,
})
}
pub(crate) fn parse_function_call(&mut self) -> Result<AstCallFunction<'bump>> {
let first_ident_token = self.consume(TokenKind::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 = if self.current()?.is_identifier() {
self.consume(TokenKind::Identifier)?
} else {
self.consume_name()?
};
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 crate::{
ast::{ast::Ast, parse::parse},
bump::Bump,
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");
}
}