use super::context::CompileContext;
use super::statement::parse_statement_block;
use crate::engine::lexer::{Lexer, Token, TokenType};
use crate::engine::types::Val;
use std::sync::atomic::{AtomicU64, Ordering};
static CLOSURE_COUNTER: AtomicU64 = AtomicU64::new(0);
fn is_punct(token: &Token, ch: &str) -> bool {
token.token_type == TokenType::T_STRING && token.value.as_ref().map(|s| s.as_str()) == Some(ch)
}
fn is_type_hint(token: &Token) -> bool {
if token.token_type == TokenType::T_STRING {
if let Some(ref val) = token.value {
return matches!(
val.as_str(),
"int"
| "string"
| "float"
| "bool"
| "array"
| "object"
| "mixed"
| "void"
| "never"
| "self"
| "static"
| "iterable"
);
}
}
token.token_type == TokenType::T_ARRAY || token.token_type == TokenType::T_CALLABLE
}
fn parse_params(
lexer: &mut Lexer,
context: &mut CompileContext,
) -> Result<(Vec<String>, Option<String>), String> {
let mut params = Vec::new();
let mut variadic = None;
let mut current_token = lexer.next_token()?;
if is_punct(¤t_token, ")") {
return Ok((params, variadic));
}
loop {
if is_punct(¤t_token, "?") {
current_token = lexer.next_token()?;
}
if is_type_hint(¤t_token) {
current_token = lexer.next_token()?;
}
let is_variadic = if current_token.token_type == TokenType::T_ELLIPSIS {
current_token = lexer.next_token()?;
true
} else {
false
};
if current_token.token_type != TokenType::T_VARIABLE {
return Err(format!(
"Expected variable parameter in function definition, got {:?}",
current_token.token_type
));
}
let param_name = current_token.value.as_ref().unwrap().as_str();
if is_variadic {
variadic = Some(param_name.to_string());
params.push(param_name.to_string());
current_token = lexer.next_token()?;
if is_punct(¤t_token, ",") {
return Err("Variadic parameter must be the last parameter".to_string());
}
if !is_punct(¤t_token, ")") {
return Err("Expected ')' after variadic parameter".to_string());
}
break;
}
params.push(param_name.to_string());
current_token = lexer.next_token()?;
if current_token.token_type == TokenType::T_EQUAL {
let (_, next_token) = super::expression::parse_expression(lexer, context)?;
current_token = next_token;
}
if is_punct(¤t_token, ")") {
break;
}
if !is_punct(¤t_token, ",") {
return Err("Expected ',' or ')' after parameter".to_string());
}
current_token = lexer.next_token()?;
}
Ok((params, variadic))
}
fn skip_return_type(lexer: &mut Lexer) -> Result<Token, String> {
let token = lexer.next_token()?;
if is_punct(&token, ":") {
let type_token = lexer.next_token()?;
if is_punct(&type_token, "?") {
let _actual_type = lexer.next_token()?;
}
lexer.next_token()
} else {
Ok(token)
}
}
fn parse_use_clause(lexer: &mut Lexer) -> Result<Vec<String>, String> {
let mut captures = Vec::new();
let paren = lexer.next_token()?;
if !is_punct(&paren, "(") {
return Err("Expected '(' after 'use'".to_string());
}
let mut current_token = lexer.next_token()?;
if is_punct(¤t_token, ")") {
return Ok(captures);
}
loop {
if current_token.token_type != TokenType::T_VARIABLE {
return Err("Expected variable in use clause".to_string());
}
captures.push(current_token.value.as_ref().unwrap().as_str().to_string());
current_token = lexer.next_token()?;
if is_punct(¤t_token, ")") {
break;
}
if !is_punct(¤t_token, ",") {
return Err("Expected ',' or ')' in use clause".to_string());
}
current_token = lexer.next_token()?;
}
Ok(captures)
}
fn compile_function_body(
lexer: &mut Lexer,
context: &CompileContext,
function_name: &str,
lineno: u32,
) -> Result<crate::engine::vm::OpArray, String> {
let mut func_context = CompileContext::new();
func_context.set_filename(context.filename.as_deref().unwrap_or(""));
func_context.set_line(lineno);
func_context.op_array.function_name = Some(function_name.to_string());
let brace_token = skip_return_type(lexer)?;
if !is_punct(&brace_token, "{") {
return Err("Expected '{' after function parameters".to_string());
}
parse_statement_block(lexer, &mut func_context)?;
if let Some(slot) = func_context.yield_slot {
func_context.emit_opcode(
crate::engine::vm::Opcode::Return,
crate::engine::vm::temp_var_ref(slot),
crate::engine::facade::null_val(),
crate::engine::facade::null_val(),
);
}
Ok(func_context.finalize())
}
pub fn compile_function(lexer: &mut Lexer, context: &mut CompileContext) -> Result<Token, String> {
let name_token = lexer.next_token()?;
if is_punct(&name_token, "(") {
let (_val, next_token) = compile_closure_inner(lexer, context, name_token.lineno)?;
return Ok(next_token);
}
if name_token.token_type != TokenType::T_STRING {
return Err("Expected function name after 'function'".to_string());
}
let function_name = name_token.value.as_ref().unwrap().as_str();
let paren_token = lexer.next_token()?;
if !is_punct(&paren_token, "(") {
return Err("Expected '(' after function name".to_string());
}
let params = parse_params(lexer, context)?;
let mut func_op_array =
compile_function_body(lexer, context, function_name, name_token.lineno)?;
func_op_array.vars = params
.0
.iter()
.map(|p| crate::engine::vm::var_ref(p))
.collect();
func_op_array.variadic_param = params.1;
context
.function_table
.store_function(function_name, func_op_array);
Ok(lexer.next_token()?)
}
pub fn compile_closure(
lexer: &mut Lexer,
context: &mut CompileContext,
) -> Result<(Val, Token), String> {
let paren_token = lexer.next_token()?;
if !is_punct(&paren_token, "(") {
return Err("Expected '(' after 'function' in closure".to_string());
}
compile_closure_inner(lexer, context, paren_token.lineno)
}
fn compile_closure_inner(
lexer: &mut Lexer,
context: &mut CompileContext,
lineno: u32,
) -> Result<(Val, Token), String> {
let params = parse_params(lexer, context)?;
let mut next = lexer.next_token()?;
if is_punct(&next, ":") {
let type_token = lexer.next_token()?;
if is_punct(&type_token, "?") {
let _actual_type = lexer.next_token()?;
}
next = lexer.next_token()?;
}
let captures = if next.token_type == TokenType::T_USE {
let caps = parse_use_clause(lexer)?;
next = lexer.next_token()?;
caps
} else {
Vec::new()
};
if !is_punct(&next, "{") {
return Err(format!(
"Expected '{{' in closure, got {:?}",
next.token_type
));
}
let closure_name = format!(
"__closure_{}",
CLOSURE_COUNTER.fetch_add(1, Ordering::Relaxed)
);
let mut func_context = CompileContext::new();
func_context.set_filename(context.filename.as_deref().unwrap_or(""));
func_context.set_line(lineno);
func_context.op_array.function_name = Some(closure_name.clone());
func_context.op_array.vars = params
.0
.iter()
.chain(captures.iter())
.map(|p| crate::engine::vm::var_ref(p))
.collect();
func_context.op_array.variadic_param = params.1;
parse_statement_block(lexer, &mut func_context)?;
let func_op_array = func_context.finalize();
context
.function_table
.store_function(&closure_name, func_op_array);
let val = crate::engine::facade::string_val(&closure_name);
let next_token = lexer.next_token()?;
Ok((val, next_token))
}