diff --git a/analysis/src/vm/batcher.rs b/analysis/src/vm/batcher.rs index 6f2f016c2..b5d182f8e 100644 --- a/analysis/src/vm/batcher.rs +++ b/analysis/src/vm/batcher.rs @@ -162,9 +162,7 @@ mod tests { env!("CARGO_MANIFEST_DIR") )); let file_name = base_path.join(path); - let expected = fs::read_to_string(file_name) - .unwrap() - .replace("machine Main", "machine ::Main"); + let expected = fs::read_to_string(file_name).unwrap(); // remove the batch comments from the expected output before compiling let input = expected diff --git a/ast/Cargo.toml b/ast/Cargo.toml index 0934f018d..271791075 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -13,4 +13,7 @@ diff = "0.1" log = "0.4.18" derive_more = "0.99.17" +[dev-dependencies] +pretty_assertions = "1.3.0" + diff --git a/ast/src/asm_analysis/display.rs b/ast/src/asm_analysis/display.rs index a02650ceb..e7a0edd5e 100644 --- a/ast/src/asm_analysis/display.rs +++ b/ast/src/asm_analysis/display.rs @@ -5,7 +5,11 @@ use std::{ use itertools::Itertools; -use crate::{indent, write_items_indented}; +use crate::{ + indent, + parsed::asm::{AbsoluteSymbolPath, Part}, + write_indented_by, write_items_indented, +}; use super::{ AnalysisASMFile, AssignmentStatement, CallableSymbol, CallableSymbolDefinitionRef, @@ -17,9 +21,36 @@ use super::{ impl Display for AnalysisASMFile { fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let mut current_path = AbsoluteSymbolPath::default(); + for (name, machine) in &self.machines { - write!(f, "machine {name}{machine}")?; + let [diff @ .., Part::Named(machine_name)] = &name.relative_to(¤t_path).parts[..] + else { + unreachable!() + }; + for part in diff { + match part { + Part::Super => { + current_path.pop(); + write_indented_by(f, "}\n", current_path.parts.len())?; + } + Part::Named(m) => { + write_indented_by(f, format!("mod {m} {{\n"), current_path.parts.len())?; + current_path.push(m.clone()); + } + } + } + + write_indented_by( + f, + format!("machine {machine_name}{machine}"), + current_path.parts.len(), + )?; } + for (i, _) in current_path.parts.iter().enumerate().rev() { + write_indented_by(f, "}\n", i)?; + } + Ok(()) } } @@ -247,3 +278,59 @@ impl Display for IncompatibleSet { write!(f, "{}", self.0.iter().format(", ")) } } + +#[cfg(test)] +mod test { + use super::*; + use crate::parsed::asm::parse_absolute_path; + use number::GoldilocksField; + use pretty_assertions::assert_eq; + + #[test] + fn display_asm_analysis_file() { + let file = AnalysisASMFile:: { + machines: [ + "::x::Y", + "::x::r::T", + "::x::f::Y", + "::M", + "::t::x::y::R", + "::t::F", + "::X", + ] + .into_iter() + .map(|s| (parse_absolute_path(s), Machine::default())) + .collect(), + }; + assert_eq!( + file.to_string(), + r#"machine M { +} +machine X { +} +mod t { + machine F { + } + mod x { + mod y { + machine R { + } + } + } +} +mod x { + machine Y { + } + mod f { + machine Y { + } + } + mod r { + machine T { + } + } +} +"# + ); + } +} diff --git a/ast/src/lib.rs b/ast/src/lib.rs index 7e237a246..36524e9d4 100644 --- a/ast/src/lib.rs +++ b/ast/src/lib.rs @@ -77,7 +77,7 @@ pub fn evaluate_unary_operation(op: UnaryOperator, v: T) -> T { } /// quick and dirty String to String indentation -fn indent(s: S, indentation: usize) -> String { +pub fn indent(s: S, indentation: usize) -> String { s.to_string() .split('\n') .map(|line| match line { @@ -87,6 +87,14 @@ fn indent(s: S, indentation: usize) -> String { .join("\n") } +pub fn write_indented_by(f: &mut W, s: S, indentation: usize) -> Result +where + S: Display, + W: Write, +{ + write!(f, "{}", indent(s, indentation)) +} + fn write_items(f: &mut W, items: I) -> Result where S: Display, @@ -112,7 +120,8 @@ where W: Write, { for item in items.into_iter() { - writeln!(f, "{}", indent(item, by))?; + write_indented_by(f, item, by)?; + writeln!(f)?; } Ok(()) } diff --git a/ast/src/parsed/asm.rs b/ast/src/parsed/asm.rs index c39343d64..cf06dee4a 100644 --- a/ast/src/parsed/asm.rs +++ b/ast/src/parsed/asm.rs @@ -1,4 +1,7 @@ -use std::{fmt::Display, iter::once}; +use std::{ + fmt::Display, + iter::{once, repeat}, +}; use number::AbstractNumberType; @@ -121,6 +124,7 @@ pub struct AbsoluteSymbolPath { /// Panics if the path does not start with '::'. pub fn parse_absolute_path(s: &str) -> AbsoluteSymbolPath { match s.strip_prefix("::") { + Some("") => AbsoluteSymbolPath::default(), Some(s) => s .split("::") .fold(AbsoluteSymbolPath::default(), |path, part| { @@ -131,12 +135,48 @@ pub fn parse_absolute_path(s: &str) -> AbsoluteSymbolPath { } impl AbsoluteSymbolPath { + /// Removes and returns the last path component (unless empty). pub fn pop(&mut self) -> Option { self.parts.pop() } -} -impl AbsoluteSymbolPath { + /// Appends a part to the end of the path. + pub fn push(&mut self, part: String) { + self.parts.push(part); + } + + /// Returns the relative path from base to self. + /// In other words, base.join(self.relative_to(base)) == self. + pub fn relative_to(&self, base: &AbsoluteSymbolPath) -> SymbolPath { + let common_prefix_len = self.common_prefix(base).parts.len(); + // Start with max(0, base.parts.len() - common_root.parts.len()) + // repetitions of "super". + let parts = repeat(Part::Super) + .take(base.parts.len().saturating_sub(common_prefix_len)) + // append the parts of self after the common root. + .chain( + self.parts + .iter() + .skip(common_prefix_len) + .cloned() + .map(Part::Named), + ) + .collect(); + SymbolPath { parts } + } + + /// Returns the common prefix of two paths. + pub fn common_prefix(&self, other: &AbsoluteSymbolPath) -> AbsoluteSymbolPath { + let parts = self + .parts + .iter() + .zip(other.parts.iter()) + .map_while(|(a, b)| if a == b { Some(a.clone()) } else { None }) + .collect(); + + AbsoluteSymbolPath { parts } + } + /// Resolves a relative path in the context of this absolute path. pub fn join + Display>(mut self, other: P) -> Self { for part in other.into().parts { @@ -339,3 +379,80 @@ pub struct Param { pub index: Option, pub ty: Option, } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn common_prefix() { + assert_eq!( + parse_absolute_path("::a::b").common_prefix(&parse_absolute_path("::a::c")), + parse_absolute_path("::a") + ); + assert_eq!( + parse_absolute_path("::a::b").common_prefix(&parse_absolute_path("::a")), + parse_absolute_path("::a") + ); + assert_eq!( + parse_absolute_path("::a").common_prefix(&parse_absolute_path("::a::c")), + parse_absolute_path("::a") + ); + assert_eq!( + parse_absolute_path("::x").common_prefix(&parse_absolute_path("::y::t")), + parse_absolute_path("::") + ); + assert_eq!( + parse_absolute_path("::x::r::v").common_prefix(&parse_absolute_path("::x::r::t")), + parse_absolute_path("::x::r") + ); + } + + #[test] + fn relative_to() { + assert_eq!( + parse_absolute_path("::a::b") + .relative_to(&parse_absolute_path("::a::c")) + .to_string(), + "super::b".to_string() + ); + assert_eq!( + parse_absolute_path("::a::b") + .relative_to(&parse_absolute_path("::a")) + .to_string(), + "b".to_string() + ); + assert_eq!( + parse_absolute_path("::x") + .relative_to(&parse_absolute_path("::y::t")) + .to_string(), + "super::super::x".to_string() + ); + assert_eq!( + parse_absolute_path("::x::r::v") + .relative_to(&parse_absolute_path("::x::r")) + .to_string(), + "v".to_string() + ); + assert_eq!( + parse_absolute_path("::x") + .relative_to(&parse_absolute_path("::x::t::k")) + .to_string(), + "super::super".to_string() + ); + assert_eq!( + parse_absolute_path("::x") + .relative_to(&parse_absolute_path("::x")) + .to_string(), + "".to_string() + ); + } + + #[test] + fn relative_to_join() { + let v = parse_absolute_path("::x::r::v"); + let base = parse_absolute_path("::x::t"); + let rel = v.relative_to(&base); + assert_eq!(base.join(rel), v); + } +} diff --git a/importer/src/path_canonicalizer.rs b/importer/src/path_canonicalizer.rs index afc42ae09..699843447 100644 --- a/importer/src/path_canonicalizer.rs +++ b/importer/src/path_canonicalizer.rs @@ -14,6 +14,8 @@ use ast::parsed::{ folder::Folder, }; +/// Changes all symbol references (symbol paths) from relative paths +/// to absolute paths, and removes all import statements. pub fn canonicalize_paths( program: ASMProgram, ) -> Result, String> {