diff --git a/crates/analysis/src/errors.rs b/crates/analysis/src/errors.rs index 27a285f..f0ab9ac 100644 --- a/crates/analysis/src/errors.rs +++ b/crates/analysis/src/errors.rs @@ -1,7 +1,16 @@ use ariadne::{Color, Config, Fmt, IndexType, Label, Report, ReportKind}; use huff_ast::{Definition, IdentifiableNode, Instruction, Macro, Spanned}; -#[derive(Debug, Clone, PartialEq, Eq)] +type InvokeChain<'src, 'ast> = Box<[(&'ast Macro<'src>, &'ast Spanned<&'src str>)]>; + +#[derive(Debug, Clone)] +pub struct Inclusion<'src, 'ast: 'src> { + pub entry_point: Spanned<&'src str>, + pub invoke_stack: InvokeChain<'src, 'ast>, + pub inclusion: Spanned<&'src str>, +} + +#[derive(Debug, Clone)] pub enum AnalysisError<'ast, 'src> { /// When two different definitions have the same. DefinitionNameCollision { @@ -9,11 +18,14 @@ pub enum AnalysisError<'ast, 'src> { duplicate_name: &'src str, }, RecursiveMacroInvocation { - invocation_chain: Box<[(&'ast Macro<'src>, &'ast Spanned<&'src str>)]>, + invocation_chain: InvokeChain<'src, 'ast>, + }, + RecursiveCodeInclusion { + linking_inclusions: Box<[Inclusion<'src, 'ast>]>, }, LabelNotFound { scope: &'ast Macro<'src>, - invocation_chain: Box<[(&'ast Macro<'src>, &'ast Spanned<&'src str>)]>, + invocation_chain: InvokeChain<'src, 'ast>, not_found: &'ast Spanned<&'src str>, }, MacroArgNotFound { @@ -69,7 +81,7 @@ impl AnalysisError<'_, '_> { .with_config(Config::default().with_index_type(IndexType::Byte)) .with_message(format!( "Definitions with duplicate name '{}'", - duplicate_name.escape_debug().fg(Color::Red) + duplicate_name.fg(Color::Red) )); base_report @@ -100,7 +112,7 @@ impl AnalysisError<'_, '_> { .with_config(Config::default().with_index_type(IndexType::Byte)) .with_message(format!( "Cannot expand macro {} with recursive dependency on itself", - first_invoke.0.ident().escape_debug().fg(Color::Red) + first_invoke.0.ident().fg(Color::Red) )); invocation_chain @@ -355,6 +367,66 @@ impl AnalysisError<'_, '_> { ) .finish() } + Self::RecursiveCodeInclusion { linking_inclusions } => { + let recursing_inclusion = linking_inclusions.last().unwrap().inclusion; + let recursing_name = recursing_inclusion.ident(); + + let base_report = Report::build( + ReportKind::Error, + filename.clone(), + recursing_inclusion.1.start, + ) + .with_config(Config::default().with_index_type(IndexType::Byte)) + .with_message(format!( + "Macro {} cannot be included because it recursively includes itself", + recursing_name.fg(Color::Red), + )); + + linking_inclusions + .iter() + .enumerate() + .skip_while(|(_i, inclusion)| inclusion.entry_point.ident() != recursing_name) + .fold(base_report, |report, (i, inclusion)| { + let report = report.with_label( + Label::new((filename.clone(), inclusion.entry_point.1.into_range())) + .with_color(Color::Blue), + ); + + let report = inclusion.invoke_stack.iter().fold( + report, + |report, (scope, invoking)| { + report + .with_label( + Label::new((filename.clone(), scope.name.1.into_range())) + .with_color(Color::Red), + ) + .with_label( + Label::new((filename.clone(), invoking.1.into_range())) + .with_color(Color::Yellow), + ) + }, + ); + + let is_last = i == linking_inclusions.len() - 1; + if !is_last { + report.with_label( + Label::new((filename.clone(), inclusion.inclusion.1.into_range())) + .with_color(Color::Yellow), + ) + } else { + report.with_label( + Label::new((filename.clone(), inclusion.inclusion.1.into_range())) + .with_message("Recursing inclusion") + .with_color(Color::Red), + ) + } + }) + .with_help( + "__codeoffset/__codesize attempts to include the target and all \ + its dependencies, which it cannot do if the dependencies are cyclic.", + ) + .finish() + } } } } diff --git a/crates/analysis/src/lib.rs b/crates/analysis/src/lib.rs index 1abb59f..cff42c7 100644 --- a/crates/analysis/src/lib.rs +++ b/crates/analysis/src/lib.rs @@ -1,7 +1,7 @@ pub mod errors; pub mod label_stack; -use crate::errors::AnalysisError; +use crate::errors::{AnalysisError, Inclusion}; use crate::label_stack::LabelStack; use huff_ast::{Definition, IdentifiableNode, Instruction, Invoke, Macro, MacroStatement, Spanned}; use std::collections::BTreeMap; @@ -26,19 +26,27 @@ pub fn analyze_global_for_dups<'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 's .collect() } +fn get_macro_def<'src, 'ast: 'src>( + global_defs: &BTreeMap<&'src str, Vec<&'ast Definition<'src>>>, + name: &'src str, +) -> Option<&'ast Macro<'src>> { + let possible_defs = global_defs.get(name)?; + possible_defs.into_iter().find_map(|def| match def { + Definition::Macro(entry_point) => Some(entry_point), + _ => None, + }) +} + pub fn analyze_entry_point<'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)>( global_defs: &BTreeMap<&'src str, Vec<&'ast Definition<'src>>>, entry_point_name: &'src str, mut emit_error: E, - macros_to_analyze: &mut Vec<&'src str>, + macros_to_include: &mut Vec>, ) { let mut invoke_stack = Vec::with_capacity(32); let mut label_stack = LabelStack::default(); - let entry_point = if let Some(Definition::Macro(entry_point)) = global_defs - .get(entry_point_name) - .and_then(|defs| defs.first()) - { + let entry_point = if let Some(entry_point) = get_macro_def(global_defs, entry_point_name) { entry_point } else { emit_error(AnalysisError::EntryPointNotFound { @@ -59,31 +67,50 @@ pub fn analyze_entry_point<'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>) &mut label_stack, &mut invoke_stack, &mut emit_error, - macros_to_analyze, + macros_to_include, ); } macro_rules! global_exists { ($global_defs:expr, $ident:expr, $pattern:pat) => { - $global_defs - .get($ident) - .map(|defs| { - defs.iter().any(|def| match def { - $pattern => true, - _ => false, - }) + $global_defs.get($ident).map_or(false, |defs| { + defs.iter().any(|def| match def { + $pattern => true, + _ => false, }) - .unwrap_or(false) + }) }; } +#[derive(Debug, Clone)] +pub struct CodeInclusionFrame<'src, 'ast: 'src> { + pub name: &'src str, + linking_inclusions: Vec>, +} + +impl<'src> CodeInclusionFrame<'src, '_> { + pub fn top(name: &'src str) -> Self { + Self { + name, + linking_inclusions: vec![], + } + } + + fn already_included(&self, name: &'src str) -> bool { + self.linking_inclusions + .iter() + .any(|inclusion| inclusion.entry_point.ident() == name) + } +} + +#[derive(Debug)] struct MacroAnalysis<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> { global_defs: &'a BTreeMap<&'src str, Vec<&'ast Definition<'src>>>, m: &'ast Macro<'src>, label_stack: &'a mut LabelStack<'src, ()>, invoke_stack: &'a mut Vec<(&'ast Macro<'src>, &'ast Spanned<&'src str>)>, emit_error: &'a mut E, - macros_to_analyze: &'a mut Vec<&'src str>, + macros_to_include: &'a mut Vec>, } impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a, 'src, 'ast, E> { @@ -97,7 +124,7 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a label_stack: &'a mut LabelStack<'src, ()>, invoke_stack: &'a mut Vec<(&'ast Macro<'src>, &'ast Spanned<&'src str>)>, emit_error: &'a mut E, - macros_to_analyze: &mut Vec<&'src str>, + macros_to_include: &mut Vec>, ) { MacroAnalysis { global_defs, @@ -105,11 +132,19 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a label_stack, invoke_stack, emit_error, - macros_to_analyze, + macros_to_include, } .analyze(); } + fn entry_point(&self) -> &Macro<'src> { + get_macro_def(self.global_defs, self.current_frame().name).unwrap() + } + + fn current_frame(&self) -> &CodeInclusionFrame<'src, 'ast> { + self.macros_to_include.last().unwrap() + } + fn analyze(&mut self) { let name = self.m.name.0; @@ -205,7 +240,7 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a self.label_stack, self.invoke_stack, self.emit_error, - self.macros_to_analyze, + self.macros_to_include, ); }); self.invoke_stack.pop().unwrap(); @@ -235,23 +270,44 @@ impl<'a, 'src, 'ast: 'src, E: FnMut(AnalysisError<'ast, 'src>)> MacroAnalysis<'a def_type: "macro", not_found: code_ref, }); + return; } if self .global_defs .get(code_ref.ident()) - .map(|defs| { + .map_or(false, |defs| { defs.iter().any( |def| matches!(def, Definition::Macro(m) if m.args.0.len() > 0), ) }) - .unwrap_or(false) { self.emit(AnalysisError::NotYetSupported { intent: "code introspection for macros with arguments".to_owned(), span: ((), code_ref.1), }); + return; + } + let mut linking_inclusions = self.current_frame().linking_inclusions.clone(); + + let entry_point_name = self.entry_point().name; + let inclusion = Inclusion { + invoke_stack: self.invoke_stack.clone().into_boxed_slice(), + entry_point: entry_point_name, + inclusion: *code_ref, + }; + linking_inclusions.push(inclusion); + if self.current_frame().already_included(code_ref.ident()) + && code_ref.ident() != entry_point_name.ident() + { + self.emit(AnalysisError::RecursiveCodeInclusion { + linking_inclusions: linking_inclusions.into_boxed_slice(), + }) + } else { + self.macros_to_include.push(CodeInclusionFrame { + name: code_ref.ident(), + linking_inclusions, + }); } - self.macros_to_analyze.push(code_ref.ident()); } Invoke::BuiltinFuncSig(func_or_error_ref) | Invoke::BuiltinError(func_or_error_ref) => { diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 0f18b3b..bbd0ae6 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -107,17 +107,19 @@ fn main() -> Result<(), Box> { let unique_defs = analyze_global_for_dups(&global_defs, |err| analysis_errors.push(err)); { - let mut to_analyze_stack = vec![cli.entry_point.as_str()]; + let mut to_analyze_stack = vec![CodeInclusionFrame::top(cli.entry_point.as_str())]; let mut analyzed_macros = BTreeSet::new(); - while let Some(next_entrypoint) = to_analyze_stack.pop() { - if analyzed_macros.insert(next_entrypoint) { + while let Some(next_entrypoint) = to_analyze_stack.last() { + let idx_to_remove = to_analyze_stack.len() - 1; + if analyzed_macros.insert(next_entrypoint.name) { analyze_entry_point( &global_defs, - next_entrypoint, + next_entrypoint.name, |err| analysis_errors.push(err), &mut to_analyze_stack, ); } + to_analyze_stack.remove(idx_to_remove); } } diff --git a/crates/compilation/src/lib.rs b/crates/compilation/src/lib.rs index 8cae2fe..dbe2638 100644 --- a/crates/compilation/src/lib.rs +++ b/crates/compilation/src/lib.rs @@ -60,7 +60,6 @@ pub fn generate_for_entrypoint<'src>( &mut included_macros, &mut asm, ); - asm.push(Asm::Mark(end_id)); included_macros.into_iter().skip(1).for_each(|included| { let section_macro = @@ -74,6 +73,8 @@ pub fn generate_for_entrypoint<'src>( asm.push(Asm::Mark(included.end_id)); }); + asm.push(Asm::Mark(end_id)); + globals.assemble(asm.as_slice()) } diff --git a/examples/errors/MutualInclusion.huff b/examples/errors/MutualInclusion.huff new file mode 100644 index 0000000..e482f48 --- /dev/null +++ b/examples/errors/MutualInclusion.huff @@ -0,0 +1,12 @@ +#define macro MAIN() = { + __codeoffset(OTHER) +} + +#define macro OTHER() = { + WOW() +} + +#define macro WOW() = { + __codeoffset(MAIN) + +} diff --git a/examples/features/Quine.huff b/examples/features/Quine.huff new file mode 100644 index 0000000..59addbb --- /dev/null +++ b/examples/features/Quine.huff @@ -0,0 +1,9 @@ +#define macro MAIN() = takes(0) returns(0) { + __codesize(MAIN) + dup1 + __codeoffset(MAIN) + 0x0 + codecopy + 0x0 + return +}