use super::SymbolKey;
use super::syms::LocalSymtable;
use crate::ast::{ArgSpan, CallSpan, Expr, VarRef};
use crate::bytecode::{self, Register};
use crate::callable::CallableMetadata;
use crate::compiler::codegen::Codegen;
use crate::compiler::exprs::{compile_expr, compile_expr_as_type};
use crate::compiler::syms::{self, SymbolPrototype, TempSymtable};
use crate::compiler::{Error, Result};
use crate::reader::LineCol;
use crate::{ArgSep, ArgSepSyntax, ExprType, RepeatedTypeSyntax, SingularArgSyntax};
use std::rc::Rc;
fn validate_syn_argsep(
md: &Rc<CallableMetadata>,
syn: &ArgSepSyntax,
end_ok: bool,
is_last: bool,
sep: ArgSep,
sep_pos: LineCol,
) -> Result<()> {
debug_assert!(
(!is_last || sep == ArgSep::End) && (is_last || sep != ArgSep::End),
"Parser can only supply an End separator in the last argument"
);
match syn {
ArgSepSyntax::Exactly(exp_sep) => {
debug_assert!(*exp_sep != ArgSep::End, "Use ArgSepSyntax::End");
if sep == *exp_sep || (sep == ArgSep::End && end_ok) {
Ok(())
} else {
Err(Error::CallableSyntax(sep_pos, md.as_ref().clone()))
}
}
ArgSepSyntax::OneOf(exp_seps) => {
if sep == ArgSep::End {
if end_ok {
return Ok(());
}
return Err(Error::CallableSyntax(sep_pos, md.as_ref().clone()));
}
let mut found = false;
for exp_sep in *exp_seps {
debug_assert!(*exp_sep != ArgSep::End, "Use ArgSepSyntax::End");
if sep == *exp_sep {
found = true;
break;
}
}
if !found {
return Err(Error::CallableSyntax(sep_pos, md.as_ref().clone()));
}
Ok(())
}
ArgSepSyntax::End => {
debug_assert!(is_last);
Ok(())
}
}
}
fn define_new_arg(
symtable: &mut LocalSymtable<'_>,
vref: &VarRef,
pos: LineCol,
codegen: &mut Codegen,
) -> Result<()> {
let key = SymbolKey::from(&vref.name);
let vtype = vref.ref_type.unwrap_or(ExprType::Integer);
let reg = symtable
.put_local(key, SymbolPrototype::Scalar(vtype))
.map_err(|e| Error::from_syms(e, pos))?;
codegen.emit_default(reg, vtype, pos);
Ok(())
}
pub(super) fn define_new_args(
span: &CallSpan,
md: &Rc<CallableMetadata>,
symtable: &mut LocalSymtable<'_>,
codegen: &mut Codegen,
) -> Result<()> {
let Some(syntax) = md.find_syntax(span.args.len()) else {
return Err(Error::CallableSyntax(span.vref_pos, md.as_ref().clone()));
};
let mut arg_iter = span.args.iter();
for syn in syntax.singular.iter() {
match syn {
SingularArgSyntax::RequiredValue(_details, _exp_sep) => {
arg_iter.next().expect("Args and their syntax must advance in unison");
}
SingularArgSyntax::RequiredRef(details, _exp_sep) => {
let ArgSpan { expr, .. } =
arg_iter.next().expect("Args and their syntax must advance in unison");
if let Some(Expr::Symbol(span)) = expr
&& let Err(syms::Error::UndefinedSymbol(..)) =
symtable.get_local_or_global(&span.vref)
&& details.define_undefined
{
let key = SymbolKey::from(&span.vref.name);
if symtable.get_callable(&key).is_some() {
continue;
}
define_new_arg(symtable, &span.vref, span.pos, codegen)?;
}
}
SingularArgSyntax::OptionalValue(_details, _exp_sep) => {
arg_iter.next();
}
SingularArgSyntax::AnyValue(_details, _exp_sep) => {
arg_iter.next();
}
};
}
if let Some(syn) = syntax.repeated.as_ref()
&& let RepeatedTypeSyntax::VariableRef = syn.type_syn
{
for arg in arg_iter {
let Some(Expr::Symbol(span)) = &arg.expr else {
continue;
};
let Err(syms::Error::UndefinedSymbol(..)) = symtable.get_local_or_global(&span.vref)
else {
continue;
};
let key = SymbolKey::from(&span.vref.name);
if symtable.get_callable(&key).is_some() {
continue;
}
define_new_arg(symtable, &span.vref, span.pos, codegen)?;
}
}
Ok(())
}
pub(super) fn compile_args(
span: CallSpan,
md: Rc<CallableMetadata>,
symtable: &mut TempSymtable<'_, '_>,
codegen: &mut Codegen,
) -> Result<(Register, Vec<LineCol>)> {
let key_pos = span.vref_pos;
let Some(syntax) = md.find_syntax(span.args.len()) else {
return Err(Error::CallableSyntax(key_pos, md.as_ref().clone()));
};
let mut scope = symtable.temp_scope();
let mut arg_linecols: Vec<LineCol> = Vec::new();
let input_nargs = span.args.len();
let mut arg_iter = span.args.into_iter().peekable();
for (i, syn) in syntax.singular.iter().enumerate() {
let end_ok = i + 1 == syntax.singular.len()
&& input_nargs == syntax.singular.len()
&& syntax.repeated.as_ref().is_some_and(|syn| !syn.require_one);
match syn {
SingularArgSyntax::RequiredValue(details, exp_sep) => {
let ArgSpan { expr, sep, sep_pos } =
arg_iter.next().expect("Args and their syntax must advance in unison");
let arg_pos = expr.as_ref().map(|e| e.start_pos()).unwrap_or(sep_pos);
match expr {
None => return Err(Error::CallableSyntax(key_pos, md.as_ref().clone())),
Some(expr) => {
let temp_value = scope.alloc().map_err(|e| Error::from_syms(e, arg_pos))?;
arg_linecols.push(arg_pos);
compile_expr_as_type(codegen, symtable, temp_value, expr, details.vtype)?;
validate_syn_argsep(
&md,
exp_sep,
end_ok,
arg_iter.peek().is_none(),
sep,
sep_pos,
)?;
}
}
}
SingularArgSyntax::RequiredRef(details, exp_sep) => {
let ArgSpan { expr, sep, sep_pos } =
arg_iter.next().expect("Args and their syntax must advance in unison");
let arg_pos = expr.as_ref().map(|e| e.start_pos()).unwrap_or(sep_pos);
match expr {
None => return Err(Error::CallableSyntax(key_pos, md.as_ref().clone())),
Some(Expr::Symbol(span)) => {
let (reg, vtype) = match symtable.get_local_or_global(&span.vref) {
Ok((reg, SymbolPrototype::Array(info))) if details.require_array => {
(reg, info.subtype)
}
Ok((reg, SymbolPrototype::Scalar(vtype))) if !details.require_array => {
(reg, vtype)
}
Ok((_, _)) => {
return Err(Error::CallableSyntax(span.pos, md.as_ref().clone()));
}
Err(e @ syms::Error::UndefinedSymbol(..)) => {
if !details.define_undefined {
return Err(Error::from_syms(e, span.pos));
}
let key = SymbolKey::from(&span.vref.name);
if symtable.get_callable(&key).is_some() {
return Err(Error::NotAFunction(span.pos, span.vref));
}
unreachable!("Caller must use define_new_args first for commands");
}
Err(e) => return Err(Error::from_syms(e, span.pos)),
};
let temp = scope.alloc().map_err(|e| Error::from_syms(e, arg_pos))?;
arg_linecols.push(arg_pos);
codegen.emit(bytecode::make_load_register_ptr(temp, vtype, reg), arg_pos);
validate_syn_argsep(
&md,
exp_sep,
end_ok,
arg_iter.peek().is_none(),
sep,
sep_pos,
)?;
}
Some(expr) => {
return Err(Error::CallableSyntax(expr.start_pos(), md.as_ref().clone()));
}
}
}
SingularArgSyntax::OptionalValue(details, exp_sep) => {
let (expr, sep, sep_pos) = match arg_iter.next() {
Some(ArgSpan { expr, sep, sep_pos }) => (expr, sep, sep_pos),
None => (None, ArgSep::End, key_pos),
};
let arg_pos = expr.as_ref().map(|e| e.start_pos()).unwrap_or(sep_pos);
let temp_tag = scope.alloc().map_err(|e| Error::from_syms(e, arg_pos))?;
arg_linecols.push(arg_pos);
let tag = match expr {
None => bytecode::VarArgTag::Missing(sep),
Some(expr) => {
let temp_value = scope.alloc().map_err(|e| Error::from_syms(e, arg_pos))?;
arg_linecols.push(arg_pos);
compile_expr_as_type(codegen, symtable, temp_value, expr, details.vtype)?;
bytecode::VarArgTag::Immediate(sep, details.vtype)
}
};
validate_syn_argsep(&md, exp_sep, end_ok, arg_iter.peek().is_none(), sep, sep_pos)?;
codegen.emit(bytecode::make_load_integer(temp_tag, tag.make_u16()), arg_pos);
}
SingularArgSyntax::AnyValue(details, exp_sep) => {
let (expr, sep, sep_pos) = match arg_iter.next() {
Some(ArgSpan { expr, sep, sep_pos }) => (expr, sep, sep_pos),
None => {
if !details.allow_missing {
return Err(Error::CallableSyntax(key_pos, md.as_ref().clone()));
}
(None, ArgSep::End, key_pos)
}
};
let arg_pos = expr.as_ref().map(|e| e.start_pos()).unwrap_or(sep_pos);
let temp_tag = scope.alloc().map_err(|e| Error::from_syms(e, arg_pos))?;
arg_linecols.push(arg_pos);
let tag = match expr {
None => bytecode::VarArgTag::Missing(sep),
Some(expr) => {
let temp_value = scope.alloc().map_err(|e| Error::from_syms(e, arg_pos))?;
arg_linecols.push(arg_pos);
let etype = compile_expr(codegen, symtable, temp_value, expr)?;
bytecode::VarArgTag::Immediate(sep, etype)
}
};
validate_syn_argsep(&md, exp_sep, end_ok, arg_iter.peek().is_none(), sep, sep_pos)?;
codegen.emit(bytecode::make_load_integer(temp_tag, tag.make_u16()), arg_pos);
}
};
}
if let Some(syn) = syntax.repeated.as_ref() {
let mut min_nargs = syntax.singular.len();
if syn.require_one {
min_nargs += 1;
}
if input_nargs < min_nargs {
return Err(Error::CallableSyntax(key_pos, md.as_ref().clone()));
}
let use_plain_values = matches!(syn.type_syn, RepeatedTypeSyntax::TypedValue(_))
&& !syn.allow_missing
&& matches!(syn.sep, ArgSepSyntax::Exactly(_));
if use_plain_values {
while let Some(ArgSpan { expr, sep, sep_pos }) = arg_iter.next() {
let vtype = match syn.type_syn {
RepeatedTypeSyntax::TypedValue(vtype) => vtype,
_ => unreachable!(),
};
let arg_pos = expr.as_ref().map(|e| e.start_pos()).unwrap_or(sep_pos);
validate_syn_argsep(&md, &syn.sep, true, arg_iter.peek().is_none(), sep, sep_pos)?;
match expr {
None => {
return Err(Error::CallableSyntax(arg_pos, md.as_ref().clone()));
}
Some(expr) => {
let temp_value = scope.alloc().map_err(|e| Error::from_syms(e, arg_pos))?;
arg_linecols.push(arg_pos);
compile_expr_as_type(codegen, symtable, temp_value, expr, vtype)?;
}
}
}
} else {
if arg_iter.peek().is_none() {
let temp_tag = scope.alloc().map_err(|e| Error::from_syms(e, key_pos))?;
arg_linecols.push(key_pos);
let tag = bytecode::VarArgTag::Missing(ArgSep::End);
codegen.emit(bytecode::make_load_integer(temp_tag, tag.make_u16()), key_pos);
}
while arg_iter.peek().is_some() {
let ArgSpan { expr, sep, sep_pos } =
arg_iter.next().expect("Args and their syntax must advance in unison");
let arg_pos = expr.as_ref().map(|e| e.start_pos()).unwrap_or(sep_pos);
let temp_tag = scope.alloc().map_err(|e| Error::from_syms(e, arg_pos))?;
arg_linecols.push(arg_pos);
validate_syn_argsep(&md, &syn.sep, true, arg_iter.peek().is_none(), sep, sep_pos)?;
let tag = match expr {
None => {
if !syn.allow_missing {
return Err(Error::CallableSyntax(arg_pos, md.as_ref().clone()));
}
bytecode::VarArgTag::Missing(sep)
}
Some(expr) => match syn.type_syn {
RepeatedTypeSyntax::AnyValue => {
let temp_value =
scope.alloc().map_err(|e| Error::from_syms(e, arg_pos))?;
arg_linecols.push(arg_pos);
let etype = compile_expr(codegen, symtable, temp_value, expr)?;
bytecode::VarArgTag::Immediate(sep, etype)
}
RepeatedTypeSyntax::TypedValue(vtype) => {
let temp_value =
scope.alloc().map_err(|e| Error::from_syms(e, arg_pos))?;
arg_linecols.push(arg_pos);
compile_expr_as_type(codegen, symtable, temp_value, expr, vtype)?;
bytecode::VarArgTag::Immediate(sep, vtype)
}
RepeatedTypeSyntax::VariableRef => {
let Expr::Symbol(span) = expr else {
return Err(Error::CallableSyntax(arg_pos, md.as_ref().clone()));
};
let (reg, vtype) = match symtable.get_local_or_global(&span.vref) {
Ok((reg, SymbolPrototype::Scalar(vtype))) => (reg, vtype),
Ok((_, SymbolPrototype::Array(_))) => {
return Err(Error::CallableSyntax(
arg_pos,
md.as_ref().clone(),
));
}
Err(syms::Error::UndefinedSymbol(..)) => {
let key = SymbolKey::from(&span.vref.name);
if symtable.get_callable(&key).is_some() {
return Err(Error::NotAFunction(span.pos, span.vref));
}
unreachable!(
"Caller must use define_new_args first for commands"
);
}
Err(e) => return Err(Error::from_syms(e, span.pos)),
};
let temp = scope.alloc().map_err(|e| Error::from_syms(e, arg_pos))?;
arg_linecols.push(arg_pos);
codegen
.emit(bytecode::make_load_register_ptr(temp, vtype, reg), arg_pos);
bytecode::VarArgTag::Pointer(sep)
}
},
};
codegen.emit(bytecode::make_load_integer(temp_tag, tag.make_u16()), arg_pos);
}
}
}
if arg_iter.peek().is_some() {
debug_assert!(arg_iter.next().is_some(), "Args and their syntax must advance in unison");
return Err(Error::CallableSyntax(key_pos, md.as_ref().clone()));
}
let first_reg = scope.first().map_err(|e| Error::from_syms(e, key_pos))?;
Ok((first_reg, arg_linecols))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::CallableMetadataBuilder;
use crate::callable::RepeatedSyntax;
use crate::compiler::syms::GlobalSymtable;
use std::borrow::Cow;
use std::collections::HashMap;
#[test]
fn test_compile_args_materializes_missing_repeated_tag() -> Result<()> {
let pos = LineCol { line: 1, col: 1 };
let md = CallableMetadataBuilder::new("OUT")
.with_syntax(&[(
&[],
Some(&RepeatedSyntax {
name: Cow::Borrowed("arg"),
type_syn: RepeatedTypeSyntax::AnyValue,
sep: ArgSepSyntax::Exactly(ArgSep::Short),
require_one: false,
allow_missing: false,
}),
)])
.test_build();
let mut codegen = Codegen::default();
let upcalls = HashMap::default();
let mut global = GlobalSymtable::new(upcalls);
let mut local = global.enter_scope();
let (first_reg, arg_linecols) = {
let mut symtable = local.frozen();
compile_args(
CallSpan { vref: VarRef::new("OUT", None), vref_pos: pos, args: vec![] },
md,
&mut symtable,
&mut codegen,
)?
};
assert_eq!(Register::local(0).unwrap(), first_reg);
assert_eq!(vec![pos], arg_linecols);
let upcall = codegen.get_upcall(SymbolKey::from("OUT"), None, pos)?;
let addr = codegen.emit(bytecode::make_upcall(upcall, first_reg), pos);
codegen.set_arg_linecols(addr, arg_linecols);
codegen.emit(bytecode::make_eof(), LineCol { line: 0, col: 0 });
let image = codegen.build_image(HashMap::default(), HashMap::default(), vec![])?;
assert_eq!(
vec![
"0000: LOADI R64, 0 ; 1:1".to_owned(),
"0001: UPCALL 0, R64 ; 1:1, OUT".to_owned(),
"0002: EOF ; 0:0".to_owned(),
],
image.disasm()
);
Ok(())
}
}