use std::rc::Rc;
use crate::core::{binding_symbol, definition_metadata, schema_var_reference};
use crate::kernel::{Form, Span};
use crate::lang::data::Metadata;
use crate::lang::data::Symbol;
use crate::lang::protocol::INamespaced;
use crate::vm::error::{CompileError, CompileErrorKind};
use crate::vm::opcode::Instruction;
use super::{Child, Compiler};
impl Compiler {
pub(super) fn excluded_foundation_symbol(&self, name: &str) -> bool {
let Some((namespace, _)) = name.split_once('/') else {
return false;
};
self.excluded_foundation_libraries.iter().any(|library| {
namespace == format!("std.foundation.{library}")
|| crate::kernel::generated::foundation_library_alias(library)
.is_some_and(|alias| alias == namespace)
})
}
pub(super) fn compile_defmacro(
&mut self,
children: &[Child<'_>],
span: &Span,
top: bool,
) -> Result<(), CompileError> {
if !top {
return Err(unsupported(
"defmacro is only supported as a top-level statement",
span.start,
));
}
if children.len() < 4 {
return Err(CompileError::new(
CompileErrorKind::Arity,
"defmacro expects a name, parameters, and a body",
Some(span.start),
));
}
let (name, metadata) = binding_symbol(children[1].form, "defmacro name")
.map_err(|message| unsupported(message, children[1].span.start))?;
self.require_owned_global(&name, children[1].span)?;
let raw = children
.iter()
.map(|child| child.form.clone())
.collect::<Vec<_>>();
let (metadata, rest) = definition_metadata(metadata, &raw[2..], false, true)
.map_err(|message| unsupported(format!("{name}: {message}"), children[1].span.start))?;
let offset = children.len() - rest.len();
let rest_children = &children[offset..];
if rest_children.is_empty() {
return Err(CompileError::new(
CompileErrorKind::Arity,
"defmacro expects a name, parameters, and a body",
Some(span.start),
));
}
let metadata = self.var_metadata(metadata);
self.declare_program_global(&name);
if matches!(
crate::core::form_without_metadata(rest_children[0].form),
Form::Vector(_)
) {
let params = macro_params(rest_children[0].form)
.map_err(|message| unsupported(message, rest_children[0].span.start))?;
let params_child = Child {
form: ¶ms,
span: rest_children[0].span,
children: None,
};
self.compile_function(
Some(&name),
¶ms_child,
&rest_children[1..],
span,
false,
false,
)?;
} else {
let mut count = 0usize;
for clause in rest_children {
let Form::List(parts) = crate::core::form_without_metadata(clause.form) else {
return Err(unsupported(
"defmacro multi-arity clauses must be lists",
clause.span.start,
));
};
if parts.len() < 2 {
return Err(CompileError::new(
CompileErrorKind::Arity,
"defmacro clause expects parameters and a body",
Some(clause.span.start),
));
}
let params = macro_params(&parts[0])
.map_err(|message| unsupported(message, clause.span.start))?;
let params_child = Child {
form: ¶ms,
span: clause.span,
children: None,
};
let body = parts[1..]
.iter()
.map(|form| Child {
form,
span: clause.span,
children: None,
})
.collect::<Vec<_>>();
self.compile_function(None, ¶ms_child, &body, span, false, false)?;
count += 1;
if count > usize::from(u8::MAX) {
return Err(CompileError::new(
CompileErrorKind::Limit,
"defmacro supports at most 255 arity clauses",
Some(span.start),
));
}
}
let name_constant = self.name_constant(&name, children[1].span)?;
self.emit(
Instruction::MakeMultiArity {
name: name_constant,
count: count as u8,
},
Some(span.start),
);
}
let name = self.name_constant(&name, children[1].span)?;
self.emit(Instruction::DefMacro { name, metadata }, Some(span.start));
Ok(())
}
pub(super) fn require_owned_global(&self, name: &str, span: &Span) -> Result<(), CompileError> {
if let Ok(registry) = crate::core::namespace_registry() {
let namespace = registry
.find(&self.namespace)
.unwrap_or_else(|| registry.current());
if let Some(var) = namespace.resolve(&Symbol::parse(name)) {
let owned_by_compilation_namespace =
var.symbol().get_namespace().is_none_or(|owner| {
owner == self.namespace.as_str() || owner == "std.foundation"
});
if !owned_by_compilation_namespace {
return Err(CompileError::new(
CompileErrorKind::UnsupportedForm,
format!("Cannot replace referred Var without ns omission: {name}"),
Some(span.start),
));
}
}
}
Ok(())
}
pub(super) fn name_constant(&mut self, name: &str, span: &Span) -> Result<u32, CompileError> {
self.constant_index_of(crate::core::Value::String(name.to_string()), span)
}
pub(super) fn global_name_constant(
&mut self,
name: &str,
span: &Span,
) -> Result<u32, CompileError> {
let current_alias_local = name.strip_prefix("-/");
let qualified = crate::core::namespace_registry()
.ok()
.and_then(|registry| {
let current = registry
.find(&self.namespace)
.unwrap_or_else(|| registry.current());
if let Some(local) = current_alias_local {
Some(format!("{}/{}", self.namespace, local))
} else if !name.contains('/') && self.globals.iter().any(|global| global == name) {
Some(format!("{}/{}", self.namespace, name))
} else if let Some((alias, local)) = name.split_once('/') {
(if registry.find(alias).is_some() {
registry.resolve(&crate::lang::data::Symbol::parse(name))
} else {
current.resolve(&crate::lang::data::Symbol::parse(name))
})
.map(|var| var.symbol().as_str().to_owned())
.or_else(|| {
current
.lazy_target(alias)
.map(|target| format!("{}/{}", target.as_str(), local))
})
} else {
registry
.resolve(&crate::lang::data::Symbol::parse(name))
.map(|var| var.symbol().as_str().to_owned())
}
})
.unwrap_or_else(|| name.to_owned());
self.constant_index_of(crate::core::Value::String(qualified), span)
}
pub(super) fn declare_program_global(&mut self, name: &str) {
if !self.globals.iter().any(|global| global == name) {
self.globals.push(name.to_string());
}
}
pub(super) fn visible_global(&self, name: &str) -> bool {
if self.excluded_foundation_symbol(name) {
return false;
}
if self.allow_unbound_globals
&& !crate::core::syntax_symbol(name)
&& crate::core::IntrinsicOp::from_symbol(name).is_none()
&& !self.visible_namespace(name)
{
return true;
}
let declared = name
.strip_prefix("-/")
.is_some_and(|local| self.globals.iter().any(|global| global == local))
|| self.globals.iter().any(|global| global == name)
|| name
.strip_prefix(&format!("{}/", self.namespace))
.is_some_and(|local| self.globals.iter().any(|global| global == local));
declared
|| crate::core::namespace_registry()
.map(|registry| {
let current = registry
.find(&self.namespace)
.unwrap_or_else(|| registry.current());
let lazy_visible = name
.split_once('/')
.is_some_and(|(alias, _)| current.lazy_target(alias).is_some());
lazy_visible
|| (name
.split_once('/')
.and_then(|(namespace, _)| registry.find(namespace))
.and_then(|_| registry.resolve(&crate::lang::data::Symbol::parse(name)))
.or_else(|| current.resolve(&crate::lang::data::Symbol::parse(name)))
.or_else(|| registry.resolve(&crate::lang::data::Symbol::parse(name))))
.is_some_and(|var| {
crate::core::IntrinsicOp::from_symbol(name).is_none()
|| var.symbol().get_namespace() != Some("std.foundation")
})
})
.unwrap_or(false)
}
pub(super) fn visible_namespace(&self, name: &str) -> bool {
crate::core::namespace_registry()
.map(|registry| {
let current = registry
.find(&self.namespace)
.unwrap_or_else(|| registry.current());
let visible = current.lazy_target(name).is_some()
|| current
.aliases()
.into_iter()
.any(|(alias, _)| alias.as_str() == name)
|| registry.find(name).is_some();
visible
})
.unwrap_or(false)
}
pub(super) fn visible_bytecode_callable(&self, name: &str) -> bool {
if self.excluded_foundation_symbol(name) {
return false;
}
let Some(canonical) = crate::core::canonical_intrinsic_callable_symbol(name) else {
return false;
};
crate::core::namespace_registry()
.map(|registry| {
let current = registry
.find(&self.namespace)
.unwrap_or_else(|| registry.current());
registry
.resolve(&crate::lang::data::Symbol::parse(&canonical))
.or_else(|| current.resolve(&crate::lang::data::Symbol::parse(&canonical)))
.is_some()
})
.unwrap_or(true)
}
pub(super) fn emit_get_global(&mut self, name: &str, span: &Span) -> Result<(), CompileError> {
let index = self.global_name_constant(name, span)?;
self.emit(Instruction::GetGlobal(index), Some(span.start));
Ok(())
}
pub(super) fn var_metadata(&mut self, metadata: Option<Rc<Metadata>>) -> Option<u16> {
metadata.map(|metadata| {
let index = self.var_metadata.len() as u16;
self.var_metadata.push(metadata);
index
})
}
pub(super) fn compile_declare(
&mut self,
children: &[Child<'_>],
span: &Span,
top: bool,
) -> Result<(), CompileError> {
if !top {
return Err(unsupported(
"declare is only supported as a top-level statement",
span.start,
));
}
let names = &children[1..];
if names.is_empty() {
self.emit(Instruction::Nil, Some(span.start));
return Ok(());
}
for (index, child) in names.iter().enumerate() {
let Form::Symbol(name) = child.form else {
return Err(CompileError::new(
CompileErrorKind::Arity,
"declare expects name symbols",
Some(child.span.start),
));
};
self.declare_program_global(name);
let constant = self.name_constant(name, child.span)?;
self.emit(Instruction::DeclareGlobal(constant), Some(child.span.start));
if index + 1 != names.len() {
self.emit(Instruction::Pop, Some(child.span.start));
}
}
Ok(())
}
pub(super) fn compile_field(
&mut self,
children: &[Child<'_>],
span: &Span,
) -> Result<(), CompileError> {
if children.len() != 3 {
return Err(CompileError::new(
CompileErrorKind::Arity,
"field expects a mutable value and field name",
Some(span.start),
));
}
let field = match children[2].form {
Form::Keyword(field) | Form::Symbol(field) if !field.contains('/') => field,
_ => {
return Err(unsupported(
"field name must be an unqualified keyword or symbol",
children[2].span.start,
))
}
};
let instance = &children[1];
self.compile_form(instance.form, instance.span, instance.children, false)?;
if !self.ctx().fallthrough {
return Ok(());
}
let index = self.name_constant(field, children[2].span)?;
self.emit(Instruction::MutableFieldGet(index), Some(span.start));
Ok(())
}
pub(super) fn compile_instance_of(
&mut self,
children: &[Child<'_>],
span: &Span,
) -> Result<(), CompileError> {
if children.len() != 3 {
return Err(CompileError::new(
CompileErrorKind::Arity,
"instance? expects a struct or mutable type and value",
Some(span.start),
));
}
for argument in &children[1..] {
self.compile_form(argument.form, argument.span, argument.children, false)?;
}
if !self.ctx().fallthrough {
return Ok(());
}
self.emit(Instruction::InstanceOf, Some(span.start));
Ok(())
}
pub(super) fn compile_defn(
&mut self,
children: &[Child<'_>],
span: &Span,
_top: bool,
) -> Result<(), CompileError> {
if children.len() < 4 {
return Err(CompileError::new(
CompileErrorKind::Arity,
"defn expects a name, parameters, and a body",
Some(span.start),
));
}
let (name, metadata) = binding_symbol(children[1].form, "defn name")
.map_err(|message| unsupported(message, children[1].span.start))?;
self.require_owned_global(&name, children[1].span)?;
let raw: Vec<Form> = children.iter().map(|child| child.form.clone()).collect();
let (metadata, rest) = definition_metadata(metadata, &raw[2..], false, false)
.map_err(|message| unsupported(format!("{name}: {message}"), children[1].span.start))?;
if let Some(schema) = schema_var_reference(metadata.as_deref()) {
let schema_name = schema.as_str();
if !self.visible_global(schema_name) {
return Err(CompileError::new(
CompileErrorKind::UnboundSymbol,
format!("schema Var does not exist: {schema_name}"),
Some(children[1].span.start),
));
}
}
let offset = children.len() - rest.len();
let rest_children = &children[offset..];
if rest_children.is_empty() {
return Err(CompileError::new(
CompileErrorKind::Arity,
"defn expects a name, parameters, and a body",
Some(span.start),
));
}
let async_function = metadata.as_ref().is_some_and(|value| value.flag("async"));
if let Some(crate::lang::data::MetadataValue::Symbol(target)) = metadata
.as_ref()
.and_then(|value| value.get_keyword("inline-target"))
{
self.inline_globals
.insert(name.clone(), target.as_str().to_owned());
}
let metadata = self.var_metadata(metadata);
self.declare_program_global(&name);
let single_arity = matches!(
crate::core::form_without_metadata(rest_children[0].form),
Form::Vector(_)
);
if single_arity {
let suspend_allowed = async_function
|| rest_children[1..]
.iter()
.any(|child| self.form_may_suspend(child.form));
self.compile_function(
Some(&name),
&rest_children[0],
&rest_children[1..],
span,
async_function,
suspend_allowed,
)?;
} else {
let mut count = 0usize;
for clause in rest_children {
let clause_forms: &[Form] = match clause.form {
Form::List(forms) => forms,
_ => {
return Err(unsupported(
"defn multi-arity clauses must be lists",
clause.span.start,
))
}
};
let clause_children =
self.list_children(clause_forms, clause.span, clause.children);
if clause_children.len() < 2 {
return Err(CompileError::new(
CompileErrorKind::Arity,
"defn clause expects parameters and a body",
Some(clause.span.start),
));
}
let suspend_allowed = async_function
|| clause_children[1..]
.iter()
.any(|child| self.form_may_suspend(child.form));
self.compile_function(
None,
&clause_children[0],
&clause_children[1..],
span,
async_function,
suspend_allowed,
)?;
count += 1;
if count > u8::MAX as usize {
return Err(CompileError::new(
CompileErrorKind::Limit,
"defn supports at most 255 arity clauses",
Some(span.start),
));
}
}
let name_constant = self.name_constant(&name, children[1].span)?;
self.emit(
Instruction::MakeMultiArity {
name: name_constant,
count: count as u8,
},
Some(span.start),
);
}
if !self.ctx().fallthrough {
return Ok(());
}
let name_index = self.name_constant(&name, children[1].span)?;
self.emit(
Instruction::DefGlobal {
name: name_index,
metadata,
},
Some(span.start),
);
self.emit(Instruction::Pop, Some(span.start));
self.emit(Instruction::VarGlobal(name_index), Some(span.start));
Ok(())
}
}
fn macro_params(form: &Form) -> Result<Form, String> {
let Form::Vector(params) = crate::core::form_without_metadata(form) else {
return Err("macro parameters must be a vector".into());
};
let mut implicit = vec![Form::Symbol("&form".into()), Form::Symbol("&env".into())];
implicit.extend_from_slice(params);
Ok(Form::Vector(implicit))
}
fn unsupported(message: impl Into<String>, position: crate::kernel::Position) -> CompileError {
CompileError::new(
CompileErrorKind::UnsupportedForm,
message.into(),
Some(position),
)
}