use std::collections::BTreeMap;
use std::rc::Rc;
use harn_parser::{SNode, TypeParam, TypedParam};
use crate::chunk::{CompiledFunction, Constant, Op};
use crate::schema;
use crate::value::VmValue;
use super::error::CompileError;
use super::yield_scan::body_contains_yield;
use super::Compiler;
impl Compiler {
pub(super) fn compile_fn_decl(
&mut self,
name: &str,
type_params: &[TypeParam],
params: &[TypedParam],
body: &[SNode],
is_stream: bool,
) -> Result<(), CompileError> {
let mut fn_compiler = Compiler::for_nested_body();
fn_compiler.enum_names = self.enum_names.clone();
fn_compiler.interface_methods = self.interface_methods.clone();
fn_compiler.type_aliases = self.type_aliases.clone();
fn_compiler.struct_layouts = self.struct_layouts.clone();
fn_compiler.declare_param_slots(params);
fn_compiler.record_param_types(params);
fn_compiler.emit_default_preamble(params)?;
fn_compiler.emit_type_checks(params);
let is_gen = is_stream || body_contains_yield(body);
fn_compiler.compile_block(body)?;
for fb in fn_compiler.all_pending_finallys() {
fn_compiler.compile_finally_inline(&fb)?;
}
fn_compiler.chunk.emit(Op::Nil, self.line);
fn_compiler.chunk.emit(Op::Return, self.line);
let func = CompiledFunction {
name: name.to_string(),
type_params: type_params.iter().map(|param| param.name.clone()).collect(),
nominal_type_names: fn_compiler.nominal_type_names(),
params: crate::chunk::ParamSlot::vec_from_typed(params),
default_start: TypedParam::default_start(params),
chunk: Rc::new(fn_compiler.chunk),
is_generator: is_gen,
is_stream,
has_rest_param: params.last().is_some_and(|p| p.rest),
};
let fn_idx = self.chunk.functions.len();
self.chunk.functions.push(Rc::new(func));
self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
self.emit_define_binding(name, false);
Ok(())
}
pub(super) fn compile_tool_decl(
&mut self,
name: &str,
description: &Option<String>,
params: &[TypedParam],
return_type: &Option<harn_parser::TypeExpr>,
body: &[SNode],
) -> Result<(), CompileError> {
let mut fn_compiler = Compiler::for_nested_body();
fn_compiler.enum_names = self.enum_names.clone();
fn_compiler.interface_methods = self.interface_methods.clone();
fn_compiler.type_aliases = self.type_aliases.clone();
fn_compiler.struct_layouts = self.struct_layouts.clone();
fn_compiler.declare_param_slots(params);
fn_compiler.record_param_types(params);
fn_compiler.emit_default_preamble(params)?;
fn_compiler.emit_type_checks(params);
fn_compiler.compile_block(body)?;
for fb in fn_compiler.all_pending_finallys() {
fn_compiler.compile_finally_inline(&fb)?;
}
fn_compiler.chunk.emit(Op::Return, self.line);
let func = CompiledFunction {
name: name.to_string(),
type_params: Vec::new(),
nominal_type_names: fn_compiler.nominal_type_names(),
params: crate::chunk::ParamSlot::vec_from_typed(params),
default_start: TypedParam::default_start(params),
chunk: Rc::new(fn_compiler.chunk),
is_generator: false,
is_stream: false,
has_rest_param: params.last().is_some_and(|p| p.rest),
};
let fn_idx = self.chunk.functions.len();
self.chunk.functions.push(Rc::new(func));
let define_name = self
.chunk
.add_constant(Constant::String("tool_define".into()));
self.chunk.emit_u16(Op::Constant, define_name, self.line);
let reg_name = self
.chunk
.add_constant(Constant::String("tool_registry".into()));
self.chunk.emit_u16(Op::Constant, reg_name, self.line);
self.chunk.emit_u8(Op::Call, 0, self.line);
let tool_name_idx = self.chunk.add_constant(Constant::String(name.to_string()));
self.chunk.emit_u16(Op::Constant, tool_name_idx, self.line);
let desc = description.as_deref().unwrap_or("");
let desc_idx = self.chunk.add_constant(Constant::String(desc.to_string()));
self.chunk.emit_u16(Op::Constant, desc_idx, self.line);
let mut param_count: u16 = 0;
for p in params {
let pn_idx = self.chunk.add_constant(Constant::String(p.name.clone()));
self.chunk.emit_u16(Op::Constant, pn_idx, self.line);
let base_schema = p
.type_expr
.as_ref()
.and_then(Self::type_expr_to_schema_value)
.unwrap_or_else(|| {
VmValue::Dict(Rc::new(BTreeMap::from([(
"type".to_string(),
VmValue::String(Rc::from("any")),
)])))
});
let public_schema =
schema::schema_to_json_schema_value(&base_schema).map_err(|error| {
CompileError {
message: format!(
"failed to lower tool parameter schema for '{}': {}",
p.name, error
),
line: self.line,
}
})?;
let mut param_schema = match public_schema {
VmValue::Dict(map) => (*map).clone(),
_ => BTreeMap::new(),
};
if p.default_value.is_some() {
param_schema.insert("required".to_string(), VmValue::Bool(false));
}
self.emit_vm_value_literal(&VmValue::Dict(Rc::new(param_schema)));
if let Some(default_value) = p.default_value.as_ref() {
let default_key = self.chunk.add_constant(Constant::String("default".into()));
self.chunk.emit_u16(Op::Constant, default_key, self.line);
self.compile_node(default_value)?;
self.chunk.emit_u16(Op::BuildDict, 1, self.line);
self.chunk.emit(Op::Add, self.line);
}
param_count += 1;
}
self.chunk.emit_u16(Op::BuildDict, param_count, self.line);
let params_key = self
.chunk
.add_constant(Constant::String("parameters".into()));
self.chunk.emit_u16(Op::Constant, params_key, self.line);
self.chunk.emit(Op::Swap, self.line);
let handler_key = self.chunk.add_constant(Constant::String("handler".into()));
self.chunk.emit_u16(Op::Constant, handler_key, self.line);
self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
let mut config_entries = 2u16;
if let Some(return_type) = return_type
.as_ref()
.and_then(Self::type_expr_to_schema_value)
{
let return_type =
schema::schema_to_json_schema_value(&return_type).map_err(|error| {
CompileError {
message: format!(
"failed to lower tool return schema for '{}': {}",
name, error
),
line: self.line,
}
})?;
let returns_key = self.chunk.add_constant(Constant::String("returns".into()));
self.chunk.emit_u16(Op::Constant, returns_key, self.line);
self.emit_vm_value_literal(&return_type);
config_entries += 1;
}
self.chunk
.emit_u16(Op::BuildDict, config_entries, self.line);
self.chunk.emit_u8(Op::Call, 4, self.line);
self.emit_define_binding(name, false);
Ok(())
}
pub(super) fn compile_skill_decl(
&mut self,
name: &str,
fields: &[(String, SNode)],
) -> Result<(), CompileError> {
let define_idx = self
.chunk
.add_constant(Constant::String("skill_define".into()));
self.chunk.emit_u16(Op::Constant, define_idx, self.line);
let reg_idx = self
.chunk
.add_constant(Constant::String("skill_registry".into()));
self.chunk.emit_u16(Op::Constant, reg_idx, self.line);
self.chunk.emit_u8(Op::Call, 0, self.line);
let name_const = self.chunk.add_constant(Constant::String(name.to_string()));
self.chunk.emit_u16(Op::Constant, name_const, self.line);
let mut field_count: u16 = 0;
for (key, value) in fields {
let key_idx = self.chunk.add_constant(Constant::String(key.clone()));
self.chunk.emit_u16(Op::Constant, key_idx, self.line);
self.compile_node(value)?;
field_count += 1;
}
self.chunk.emit_u16(Op::BuildDict, field_count, self.line);
self.chunk.emit_u8(Op::Call, 3, self.line);
self.emit_define_binding(name, false);
Ok(())
}
pub(super) fn compile_eval_pack_decl(
&mut self,
binding_name: &str,
pack_id: &str,
fields: &[(String, SNode)],
body: &[SNode],
summarize: &Option<Vec<SNode>>,
run_body: bool,
) -> Result<(), CompileError> {
self.emit_eval_pack_manifest_value(pack_id, fields)?;
self.emit_define_binding(binding_name, false);
if run_body && (!body.is_empty() || summarize.is_some()) {
self.begin_scope();
let mut visible_fields = vec!["id".to_string(), "version".to_string()];
for (field_name, _) in fields {
if !visible_fields.iter().any(|name| name == field_name) {
visible_fields.push(field_name.clone());
}
}
for field_name in visible_fields {
self.emit_get_binding(binding_name);
let field_idx = self
.chunk
.add_constant(Constant::String(field_name.clone()));
self.chunk.emit_u16(Op::GetProperty, field_idx, self.line);
self.emit_define_binding(&field_name, false);
}
for sn in body {
self.compile_node(sn)?;
if Self::produces_value(&sn.node) {
self.chunk.emit(Op::Pop, self.line);
}
}
if let Some(summary_body) = summarize {
for sn in summary_body {
self.compile_node(sn)?;
if Self::produces_value(&sn.node) {
self.chunk.emit(Op::Pop, self.line);
}
}
}
self.end_scope();
}
Ok(())
}
fn emit_eval_pack_manifest_value(
&mut self,
pack_id: &str,
fields: &[(String, SNode)],
) -> Result<(), CompileError> {
let manifest_idx = self
.chunk
.add_constant(Constant::String("eval_pack_manifest".into()));
self.chunk.emit_u16(Op::Constant, manifest_idx, self.line);
let has_id = fields.iter().any(|(key, _)| key == "id");
let has_version = fields.iter().any(|(key, _)| key == "version");
let mut entry_count = fields.len() as u16;
if !has_version {
let key_idx = self.chunk.add_constant(Constant::String("version".into()));
self.chunk.emit_u16(Op::Constant, key_idx, self.line);
let value_idx = self.chunk.add_constant(Constant::Int(1));
self.chunk.emit_u16(Op::Constant, value_idx, self.line);
entry_count += 1;
}
if !has_id {
let key_idx = self.chunk.add_constant(Constant::String("id".into()));
self.chunk.emit_u16(Op::Constant, key_idx, self.line);
let value_idx = self
.chunk
.add_constant(Constant::String(pack_id.to_string()));
self.chunk.emit_u16(Op::Constant, value_idx, self.line);
entry_count += 1;
}
for (key, value) in fields {
let key_idx = self.chunk.add_constant(Constant::String(key.clone()));
self.chunk.emit_u16(Op::Constant, key_idx, self.line);
self.compile_node(value)?;
}
self.chunk.emit_u16(Op::BuildDict, entry_count, self.line);
self.chunk.emit_u8(Op::Call, 1, self.line);
Ok(())
}
pub(super) fn compile_closure(
&mut self,
params: &[TypedParam],
body: &[SNode],
) -> Result<(), CompileError> {
let mut fn_compiler = Compiler::for_nested_body();
fn_compiler.enum_names = self.enum_names.clone();
fn_compiler.interface_methods = self.interface_methods.clone();
fn_compiler.type_aliases = self.type_aliases.clone();
fn_compiler.struct_layouts = self.struct_layouts.clone();
fn_compiler.declare_param_slots(params);
fn_compiler.record_param_types(params);
fn_compiler.emit_default_preamble(params)?;
fn_compiler.emit_type_checks(params);
let is_gen = body_contains_yield(body);
fn_compiler.compile_block(body)?;
for fb in fn_compiler.all_pending_finallys() {
fn_compiler.compile_finally_inline(&fb)?;
}
fn_compiler.chunk.emit(Op::Return, self.line);
let func = CompiledFunction {
name: "<closure>".to_string(),
type_params: Vec::new(),
nominal_type_names: fn_compiler.nominal_type_names(),
params: crate::chunk::ParamSlot::vec_from_typed(params),
default_start: TypedParam::default_start(params),
chunk: Rc::new(fn_compiler.chunk),
is_generator: is_gen,
is_stream: false,
has_rest_param: false,
};
let fn_idx = self.chunk.functions.len();
self.chunk.functions.push(Rc::new(func));
self.chunk.emit_u16(Op::Closure, fn_idx as u16, self.line);
Ok(())
}
}