use crate::engine::compile::context::CompileContext;
use crate::engine::compile::expression::parse_expression;
use crate::engine::compile::expression::helpers::token_is_punct;
use crate::engine::facade::null_val;
use crate::engine::lexer::{Token, Lexer, TokenType};
use crate::engine::types::{ClassEntry, ClassMethod, Visibility};
use super::{parse_statement, skip_attribute_block};
fn parse_class_body(
lexer: &mut Lexer,
context: &mut CompileContext,
ce: &mut ClassEntry,
) -> Result<(), String> {
let mut next = lexer.next_token()?;
loop {
if token_is_punct(&next, "}") { break; }
if next.token_type == TokenType::T_EOF {
return Err("Unexpected EOF in class/trait body".to_string());
}
if next.token_type == TokenType::T_ATTRIBUTE {
next = skip_attribute_block(lexer)?;
continue;
}
if next.token_type == TokenType::T_USE {
next = compile_use_trait(lexer, context, ce)?;
continue;
}
let visibility = match next.token_type {
TokenType::T_PUBLIC => { next = lexer.next_token()?; Visibility::Public }
TokenType::T_PROTECTED => { next = lexer.next_token()?; Visibility::Protected }
TokenType::T_PRIVATE => { next = lexer.next_token()?; Visibility::Private }
_ => Visibility::Public,
};
let is_static = if next.token_type == TokenType::T_STATIC {
next = lexer.next_token()?;
true
} else {
false
};
if next.token_type == TokenType::T_FUNCTION {
next = compile_class_method(lexer, context, ce, visibility, is_static)?;
} else if next.token_type == TokenType::T_VARIABLE {
next = compile_class_property(lexer, context, ce, &next)?;
} else {
next = lexer.next_token()?;
}
}
Ok(())
}
fn compile_use_trait(
lexer: &mut Lexer,
context: &CompileContext,
ce: &mut ClassEntry,
) -> Result<Token, String> {
let trait_name_token = lexer.next_token()?;
let trait_name = trait_name_token.value.as_ref()
.ok_or("Expected trait name after 'use'")?
.as_str()
.to_string();
let resolved_trait = context.resolve_class_name(&trait_name);
let trait_key = format!("__trait_{}", resolved_trait);
if let Some(trait_ce) = context.class_table.get(&trait_key) {
for (method_name, method) in &trait_ce.methods {
if !ce.methods.contains_key(method_name) {
let mut new_ops = Vec::new();
for op in &method.op_array.ops {
new_ops.push(crate::engine::vm::Op::new(
op.opcode,
crate::engine::vm::execute_data::clone_val(&op.op1),
crate::engine::vm::execute_data::clone_val(&op.op2),
crate::engine::vm::execute_data::clone_val(&op.result),
op.extended_value,
));
}
let method_file = method
.op_array
.filename
.clone()
.filter(|f| !f.is_empty())
.unwrap_or_else(|| format!("{}::{}", ce.name, method_name));
let mut new_op_array =
crate::engine::vm::OpArray::with_capacity(new_ops.len(), method_file);
new_op_array.ops = new_ops;
ce.methods.insert(method_name.clone(), ClassMethod {
name: method.name.clone(),
visibility: method.visibility,
is_static: method.is_static,
params: method.params.clone(),
op_array: new_op_array,
});
}
}
for (prop_name, prop_val) in &trait_ce.default_properties {
if !ce.default_properties.contains_key(prop_name) {
ce.default_properties.insert(prop_name.clone(), crate::engine::vm::execute_data::clone_val(prop_val));
}
}
}
let next = lexer.next_token()?;
if token_is_punct(&next, ";") {
Ok(lexer.next_token()?)
} else {
Ok(next)
}
}
pub(crate) fn compile_class(
lexer: &mut Lexer,
context: &mut CompileContext,
) -> Result<Token, String> {
let name_token = lexer.next_token()?;
let class_name = name_token.value.as_ref()
.ok_or("Expected class name")?
.as_str()
.to_string();
let resolved_name = context.resolve_class_name(&class_name);
let mut ce = ClassEntry::new(&resolved_name);
let mut next = lexer.next_token()?;
if next.token_type == TokenType::T_EXTENDS {
let parent_token = lexer.next_token()?;
let parent_name = parent_token.value.as_ref()
.ok_or("Expected parent class name")?
.as_str();
ce.parent_name = Some(context.resolve_class_name(parent_name));
next = lexer.next_token()?;
}
if next.token_type == TokenType::T_IMPLEMENTS {
next = lexer.next_token()?;
while next.token_type == TokenType::T_STRING {
next = lexer.next_token()?;
if token_is_punct(&next, ",") {
next = lexer.next_token()?;
}
}
}
if !token_is_punct(&next, "{") {
return Err("Expected '{' after class declaration".to_string());
}
parse_class_body(lexer, context, &mut ce)?;
context.register_class(ce);
Ok(lexer.next_token()?)
}
pub(crate) fn compile_trait(
lexer: &mut Lexer,
context: &mut CompileContext,
) -> Result<Token, String> {
let name_token = lexer.next_token()?;
let trait_name = name_token.value.as_ref()
.ok_or("Expected trait name")?
.as_str()
.to_string();
let resolved_name = context.resolve_class_name(&trait_name);
let mut ce = ClassEntry::new(&resolved_name);
let next = lexer.next_token()?;
if !token_is_punct(&next, "{") {
return Err("Expected '{' after trait name".to_string());
}
parse_class_body(lexer, context, &mut ce)?;
let trait_key = format!("__trait_{}", resolved_name);
ce.name = trait_key.clone();
context.register_class(ce);
Ok(lexer.next_token()?)
}
fn compile_class_method(
lexer: &mut Lexer,
context: &CompileContext,
ce: &mut ClassEntry,
visibility: Visibility,
is_static: bool,
) -> Result<Token, String> {
let method_name_token = lexer.next_token()?;
let method_name = method_name_token.value.as_ref()
.ok_or("Expected method name")?
.as_str()
.to_string();
let open_paren = lexer.next_token()?;
if !token_is_punct(&open_paren, "(") {
return Err("Expected '(' after method name".to_string());
}
let mut params = Vec::new();
let mut pt = lexer.next_token()?;
while !token_is_punct(&pt, ")") {
if pt.token_type == TokenType::T_VARIABLE {
let pname = pt.value.as_ref().unwrap().as_str();
let pname = if pname.starts_with('$') { &pname[1..] } else { pname };
params.push(pname.to_string());
}
pt = lexer.next_token()?;
if token_is_punct(&pt, ",") {
pt = lexer.next_token()?;
}
}
let open_brace = lexer.next_token()?;
if !token_is_punct(&open_brace, "{") {
return Err("Expected '{' after method parameters".to_string());
}
let mut method_context = CompileContext::new();
if let Some(ref f) = context.filename {
method_context.set_filename(f);
}
let mut body_token = lexer.next_token()?;
let mut brace_depth = 1;
while brace_depth > 0 {
if token_is_punct(&body_token, "{") { brace_depth += 1; }
if token_is_punct(&body_token, "}") {
brace_depth -= 1;
if brace_depth == 0 { break; }
}
if body_token.token_type == TokenType::T_EOF {
return Err("Unexpected EOF in method body".to_string());
}
body_token = parse_statement(lexer, &mut method_context, body_token)?;
}
let method = ClassMethod {
name: method_name.clone(),
visibility,
is_static,
params,
op_array: method_context.take_op_array(),
};
ce.methods.insert(method_name, method);
Ok(lexer.next_token()?)
}
fn compile_class_property(
lexer: &mut Lexer,
context: &mut CompileContext,
ce: &mut ClassEntry,
token: &Token,
) -> Result<Token, String> {
let prop_name = token.value.as_ref().unwrap().as_str();
let prop_name = if prop_name.starts_with('$') { &prop_name[1..] } else { prop_name };
let prop_name = prop_name.to_string();
let peek = lexer.next_token()?;
let mut next = if peek.token_type == TokenType::T_EQUAL {
let (default_val, after) = parse_expression(lexer, context)?;
ce.default_properties.insert(prop_name, default_val);
after
} else {
ce.default_properties.insert(prop_name, null_val());
peek
};
if token_is_punct(&next, ";") {
next = lexer.next_token()?;
}
Ok(next)
}