From 1fe33104b14e687ec046c86e02a029c4680c519a Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Tue, 10 Feb 2026 14:42:35 +0200 Subject: [PATCH 1/5] feat: add pub and use keywords and parse them --- Cargo.toml | 4 +- src/ast.rs | 1 + src/lexer.rs | 13 ++++ src/parse.rs | 194 +++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 198 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 38366446..0ebe9e68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ copy_iterator = "warn" default_trait_access = "warn" doc_link_with_quotes = "warn" doc_markdown = "warn" -empty_enum = "warn" +empty_enums = "warn" enum_glob_use = "allow" expl_impl_clone_on_copy = "warn" explicit_deref_methods = "warn" @@ -152,7 +152,7 @@ struct_field_names = "warn" too_many_lines = "allow" transmute_ptr_to_ptr = "warn" trivially_copy_pass_by_ref = "warn" -unchecked_duration_subtraction = "warn" +unchecked_time_subtraction = "warn" unicode_not_nfc = "warn" unnecessary_box_returns = "warn" unnecessary_join = "warn" diff --git a/src/ast.rs b/src/ast.rs index 3c59f2f7..94ed5c38 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -762,6 +762,7 @@ impl AbstractSyntaxTree for Item { parse::Item::Function(function) => { Function::analyze(function, ty, scope).map(Self::Function) } + parse::Item::Use(_) => todo!(), parse::Item::Module => Ok(Self::Module), } } diff --git a/src/lexer.rs b/src/lexer.rs index fef18927..9ba984ca 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -8,9 +8,18 @@ use crate::str::{Binary, Decimal, Hexadecimal}; pub type Spanned = (T, SimpleSpan); pub type Tokens<'src> = Vec<(Token<'src>, crate::error::Span)>; +/// # Architecture Note: Omitted Keywords +/// The `crate` and `super` keywords were not added to the compiler because they +/// are unnecessary at this stage. Typically, they are used to resolve relative +/// paths during import parsing. However, in our architecture, the prefix before +/// the first `::` in a `use` statement is always an alias. Since all aliases are +/// unique and strictly bound to specific paths, the resolver can always +/// unambiguously resolve the path without needing relative pointers. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Token<'src> { // Keywords + Pub, + Use, Fn, Let, Type, @@ -63,6 +72,8 @@ pub enum Token<'src> { impl<'src> fmt::Display for Token<'src> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Token::Pub => write!(f, "pub"), + Token::Use => write!(f, "use"), Token::Fn => write!(f, "fn"), Token::Let => write!(f, "let"), Token::Type => write!(f, "type"), @@ -134,6 +145,8 @@ pub fn lexer<'src>( choice((just("assert!"), just("panic!"), just("dbg!"), just("list!"))).map(Token::Macro); let keyword = text::ident().map(|s| match s { + "pub" => Token::Pub, + "use" => Token::Use, "fn" => Token::Fn, "let" => Token::Let, "type" => Token::Type, diff --git a/src/parse.rs b/src/parse.rs index c42c6f3d..c96d5ccb 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -52,13 +52,58 @@ pub enum Item { TypeAlias(TypeAlias), /// A function. Function(Function), + /// Use keyword to load other items + Use(UseDecl), /// A module, which is ignored. Module, } +/// Definition of a declaration +#[derive(Clone, Debug)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub struct UseDecl { + visibility: Visibility, + path: Vec, + items: UseItems, + span: Span, +} + +impl UseDecl { + /// Access the visibility of the use declaration. + pub fn visibility(&self) -> &Visibility { + &self.visibility + } + + /// Access the visibility of the function. + pub fn path(&self) -> &Vec { + &self.path + } + + /// Access the visibility of the function. + pub fn items(&self) -> &UseItems { + &self.items + } + + /// Access the span of the use declaration. + pub fn span(&self) -> &Span { + &self.span + } +} + +impl_eq_hash!(UseDecl; visibility, path, items); + +// TODO: We can add support for aliases here. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub enum UseItems { + Single(Identifier), + List(Vec), +} + /// Definition of a function. #[derive(Clone, Debug)] pub struct Function { + visibility: Visibility, name: FunctionName, params: Arc<[FunctionParam]>, ret: Option, @@ -66,7 +111,20 @@ pub struct Function { span: Span, } +#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub enum Visibility { + Public, + #[default] + Private, +} + impl Function { + /// Access the visibility of the function. + pub fn visibility(&self) -> &Visibility { + &self.visibility + } + /// Access the name of the function. pub fn name(&self) -> &FunctionName { &self.name @@ -95,7 +153,7 @@ impl Function { } } -impl_eq_hash!(Function; name, params, ret, body); +impl_eq_hash!(Function; visibility, name, params, ret, body); /// Parameter of a function. #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -222,12 +280,18 @@ pub enum CallName { #[derive(Clone, Debug)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct TypeAlias { + visibility: Visibility, name: AliasName, ty: AliasedType, span: Span, } impl TypeAlias { + /// Access the visibility of the alias. + pub fn visibility(&self) -> &Visibility { + &self.visibility + } + /// Access the name of the alias. pub fn name(&self) -> &AliasName { &self.name @@ -556,6 +620,7 @@ impl fmt::Display for Item { match self { Self::TypeAlias(alias) => write!(f, "{alias}"), Self::Function(function) => write!(f, "{function}"), + Self::Use(use_declaration) => write!(f, "{use_declaration}"), // The parse tree contains no information about the contents of modules. // We print a random empty module `mod witness {}` here // so that `from_string(to_string(x)) = x` holds for all trees `x`. @@ -587,6 +652,47 @@ impl fmt::Display for Function { } } +impl fmt::Display for UseDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Visibility::Public = self.visibility { + write!(f, "pub ")?; + } + + let _ = write!(f, "use "); + + for (i, segment) in self.path.iter().enumerate() { + if i > 0 { + write!(f, "::")?; + } + write!(f, "{}", segment)?; + } + + if !self.path.is_empty() { + write!(f, "::")?; + } + + write!(f, "{};", self.items) + } +} + +impl fmt::Display for UseItems { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UseItems::Single(ident) => write!(f, "{}", ident), + UseItems::List(idents) => { + let _ = write!(f, "{{"); + for (i, ident) in idents.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", ident)?; + } + write!(f, "}}") + } + } + } +} + impl fmt::Display for FunctionParam { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}: {}", self.identifier(), self.ty()) @@ -1138,7 +1244,12 @@ impl ChumskyParse for Program { let skip_until_next_item = any() .then( any() - .filter(|t| !matches!(t, Token::Fn | Token::Type | Token::Mod)) + .filter(|t| { + !matches!( + t, + Token::Pub | Token::Use | Token::Fn | Token::Type | Token::Mod + ) + }) .repeated(), ) // map to empty module @@ -1162,9 +1273,10 @@ impl ChumskyParse for Item { { let func_parser = Function::parser().map(Item::Function); let type_parser = TypeAlias::parser().map(Item::TypeAlias); + let use_parser = UseDecl::parser().map(Item::Use); let mod_parser = Module::parser().map(|_| Item::Module); - choice((func_parser, type_parser, mod_parser)) + choice((func_parser, use_parser, type_parser, mod_parser)) } } @@ -1173,6 +1285,12 @@ impl ChumskyParse for Function { where I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, { + let visibility = just(Token::Pub) + .to(Visibility::Public) + .or_not() + .map(Option::unwrap_or_default) + .labelled("function visibility"); + let params = delimited_with_recovery( FunctionParam::parser() .separated_by(just(Token::Comma)) @@ -1204,12 +1322,14 @@ impl ChumskyParse for Function { ))) .labelled("function body"); - just(Token::Fn) - .ignore_then(FunctionName::parser()) + visibility + .then_ignore(just(Token::Fn)) + .then(FunctionName::parser()) .then(params) .then(ret) .then(body) - .map_with(|(((name, params), ret), body), e| Self { + .map_with(|((((visibility, name), params), ret), body), e| Self { + visibility, name, params, ret, @@ -1219,6 +1339,45 @@ impl ChumskyParse for Function { } } +impl ChumskyParse for UseDecl { + fn parser<'tokens, 'src: 'tokens, I>() -> impl Parser<'tokens, I, Self, ParseError<'src>> + Clone + where + I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, + { + let visibility = just(Token::Pub) + .to(Visibility::Public) + .or_not() + .map(Option::unwrap_or_default); + + let path = Identifier::parser() + .then_ignore(just(Token::Colon).then(just(Token::Colon))) + .repeated() + .at_least(1) + .collect::>(); + + let list = Identifier::parser() + .separated_by(just(Token::Comma)) + .allow_trailing() + .collect() + .delimited_by(just(Token::LBrace), just(Token::RBrace)) + .map(UseItems::List); + let single = Identifier::parser().map(UseItems::Single); + let items = choice((list, single)); + + visibility + .then_ignore(just(Token::Use)) + .then(path) + .then(items) + .then_ignore(just(Token::Semi)) + .map_with(|((visibility, path), items), e| Self { + visibility, + path, + items, + span: e.span(), + }) + } +} + impl ChumskyParse for FunctionParam { fn parser<'tokens, 'src: 'tokens, I>() -> impl Parser<'tokens, I, Self, ParseError<'src>> + Clone where @@ -1461,14 +1620,23 @@ impl ChumskyParse for TypeAlias { where I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, { + let visibility = just(Token::Pub) + .to(Visibility::Public) + .or_not() + .map(Option::unwrap_or_default); + let name = AliasName::parser().map_with(|name, e| (name, e.span())); - just(Token::Type) - .ignore_then(name) - .then_ignore(parse_token_with_recovery(Token::Eq)) - .then(AliasedType::parser()) - .then_ignore(just(Token::Semi)) - .map_with(|(name, ty), e| Self { + visibility + .then( + just(Token::Type) + .ignore_then(name) + .then_ignore(parse_token_with_recovery(Token::Eq)) + .then(AliasedType::parser()) + .then_ignore(just(Token::Semi)), + ) + .map_with(|(visibility, (name, ty)), e| Self { + visibility, name: name.0, ty, span: e.span(), @@ -1953,6 +2121,7 @@ impl crate::ArbitraryRec for Function { fn arbitrary_rec(u: &mut arbitrary::Unstructured, budget: usize) -> arbitrary::Result { use arbitrary::Arbitrary; + let visibility = Visibility::arbitrary(u)?; let name = FunctionName::arbitrary(u)?; let len = u.int_in_range(0..=3)?; let params = (0..len) @@ -1961,6 +2130,7 @@ impl crate::ArbitraryRec for Function { let ret = Option::::arbitrary(u)?; let body = Expression::arbitrary_rec(u, budget).map(Expression::into_block)?; Ok(Self { + visibility, name, params, ret, From 46dbf09cd6d22bb5be1e58a171cd3b1b007db7d7 Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Wed, 11 Feb 2026 14:14:59 +0200 Subject: [PATCH 2/5] feat: add the option CLI argument --lib and driver file --- Cargo.lock | 322 +++++++++++++++++- Cargo.toml | 3 + examples/simple_multilib/crypto/hashes.simf | 5 + examples/simple_multilib/main.simf | 7 + examples/simple_multilib/math/arithmetic.simf | 4 + src/driver/mod.rs | 113 ++++++ src/driver/tests.rs | 198 +++++++++++ src/lib.rs | 50 +++ src/main.rs | 37 +- src/resolution.rs | 38 +++ src/str.rs | 6 + 11 files changed, 764 insertions(+), 19 deletions(-) create mode 100644 examples/simple_multilib/crypto/hashes.simf create mode 100644 examples/simple_multilib/main.simf create mode 100644 examples/simple_multilib/math/arithmetic.simf create mode 100644 src/driver/mod.rs create mode 100644 src/driver/tests.rs create mode 100644 src/resolution.rs diff --git a/Cargo.lock b/Cargo.lock index 8ad3298a..4a7ffedc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,6 +67,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + [[package]] name = "ar_archive_writer" version = "0.2.0" @@ -173,6 +179,12 @@ dependencies = [ "hex-conservative", ] +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + [[package]] name = "bumpalo" version = "3.16.0" @@ -209,7 +221,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acc17a6284abccac6e50db35c1cee87f605474a72939b959a3a67d9371800efd" dependencies = [ - "hashbrown", + "hashbrown 0.15.5", "regex-automata", "serde", "stacker", @@ -291,6 +303,22 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -316,6 +344,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + [[package]] name = "ghost-cell" version = "0.2.6" @@ -333,6 +374,18 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex-conservative" version = "0.2.1" @@ -348,6 +401,24 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -387,6 +458,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.180" @@ -403,6 +480,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "log" version = "0.4.22" @@ -446,11 +529,21 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.114", +] + [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -467,13 +560,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -501,7 +600,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.10", ] [[package]] @@ -533,6 +632,19 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.15" @@ -590,24 +702,40 @@ dependencies = [ "secp256k1-sys", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" -version = "1.0.188" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.114", ] [[package]] @@ -637,7 +765,7 @@ dependencies = [ "bitcoin_hashes", "byteorder", "elements", - "getrandom", + "getrandom 0.2.10", "ghost-cell", "hex-conservative", "miniscript", @@ -664,12 +792,13 @@ dependencies = [ "chumsky", "clap", "either", - "getrandom", + "getrandom 0.2.10", "itertools", "miniscript", "serde", "serde_json", "simplicity-lang", + "tempfile", ] [[package]] @@ -716,15 +845,28 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.31" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +dependencies = [ + "fastrand", + "getrandom 0.4.1", + "once_cell", + "rustix", + "windows-sys", +] + [[package]] name = "unicode-ident" version = "1.0.11" @@ -737,6 +879,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "utf8parse" version = "0.2.2" @@ -749,6 +897,24 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -770,7 +936,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.114", "wasm-bindgen-shared", ] @@ -792,7 +958,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.114", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -803,6 +969,40 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -875,3 +1075,91 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.114", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.114", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/Cargo.toml b/Cargo.toml index 0ebe9e68..33daa88a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,9 @@ arbitrary = { version = "1", optional = true, features = ["derive"] } clap = "4.5.37" chumsky = "0.11.2" +[dev-dependencies] +tempfile = "3" + [target.wasm32-unknown-unknown.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/examples/simple_multilib/crypto/hashes.simf b/examples/simple_multilib/crypto/hashes.simf new file mode 100644 index 00000000..0e463d89 --- /dev/null +++ b/examples/simple_multilib/crypto/hashes.simf @@ -0,0 +1,5 @@ +pub fn sha256(data: u32) -> u256 { + let ctx: Ctx8 = jet::sha_256_ctx_8_init(); + let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, data); + jet::sha_256_ctx_8_finalize(ctx) +} diff --git a/examples/simple_multilib/main.simf b/examples/simple_multilib/main.simf new file mode 100644 index 00000000..81031d4c --- /dev/null +++ b/examples/simple_multilib/main.simf @@ -0,0 +1,7 @@ +use math::arithmetic::add; +use crypto::hashes::sha256; + +fn main() { + let sum: u32 = add(2, 3); + let hash: u256 = sha256(sum); +} \ No newline at end of file diff --git a/examples/simple_multilib/math/arithmetic.simf b/examples/simple_multilib/math/arithmetic.simf new file mode 100644 index 00000000..2f348e0c --- /dev/null +++ b/examples/simple_multilib/math/arithmetic.simf @@ -0,0 +1,4 @@ +pub fn add(a: u32, b: u32) -> u32 { + let (_, res): (bool, u32) = jet::add_32(a, b); + res +} \ No newline at end of file diff --git a/src/driver/mod.rs b/src/driver/mod.rs new file mode 100644 index 00000000..47fefd63 --- /dev/null +++ b/src/driver/mod.rs @@ -0,0 +1,113 @@ +#[cfg(test)] +pub mod tests; + +use std::collections::{HashMap, VecDeque}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use crate::error::ErrorCollector; +use crate::parse::{self, ParseFromStrWithErrors}; +use crate::resolution::LibConfig; + +/// Represents a single, isolated file in the SimplicityHL project. +/// In this architecture, a file and a module are the exact same thing. +#[derive(Debug, Clone)] +pub struct Module { + /// The completely parsed program for this specific file. + /// it contains all the functions, aliases, and imports defined inside the file. + pub parsed_program: parse::Program, +} + +/// The Dependency Graph itself. +pub struct ProjectGraph { + /// Arena Pattern: the data itself lives here. + /// A flat vector guarantees that module data is stored contiguously in memory. + pub modules: Vec, + + /// Fast lookup: File Path -> Module ID. + /// A reverse index mapping absolute file paths to their internal IDs. + /// This solves the duplication problem, ensuring each file is only parsed once. + pub lookup: HashMap, + + /// The Adjacency List: Defines the Directed acyclic Graph (DAG) of imports. + /// + /// The Key (`usize`) is the ID of a "Parent" module (the file doing the importing). + /// The Value (`Vec`) is a list of IDs of the "Child" modules it relies on. + /// + /// Example: If `main.simf` (ID: 0) has `use lib::math;` (ID: 1) and `use lib::io;` (ID: 2), + /// this map will contain: `{ 0: [1, 2] }`. + pub dependencies: HashMap>, +} + +fn parse_and_get_program(prog_file: &Path) -> Result { + let prog_text = std::fs::read_to_string(prog_file).map_err(|e| e.to_string())?; + let file = prog_text.into(); + let mut error_handler = crate::error::ErrorCollector::new(Arc::clone(&file)); + + if let Some(program) = parse::Program::parse_from_str_with_errors(&file, &mut error_handler) { + Ok(program) + } else { + Err(ErrorCollector::to_string(&error_handler))? + } +} + +impl ProjectGraph { + pub fn new(lib_cfg: &LibConfig, root_program: &parse::Program) -> Result { + let mut modules: Vec = vec![Module { + parsed_program: root_program.clone(), + }]; + let mut lookup: HashMap = HashMap::new(); + let mut dependencies: HashMap> = HashMap::new(); + + let root_id = 0; + lookup.insert(lib_cfg.root_path.clone(), root_id); + dependencies.insert(root_id, Vec::new()); + + // Implementation of the standard BFS algorithm with memoization and queue + let mut queue = VecDeque::new(); + queue.push_back(root_id); + + while let Some(curr_id) = queue.pop_front() { + let mut pending_imports: Vec = Vec::new(); + let current_program = &modules[curr_id].parsed_program; + + for elem in current_program.items() { + if let parse::Item::Use(use_decl) = elem { + if let Ok(path) = lib_cfg.get_full_path(use_decl) { + pending_imports.push(path); + } + } + } + + for path in pending_imports { + let full_path = path.with_extension("simf"); + + if !full_path.is_file() { + return Err(format!("File in {:?}, does not exist", full_path)); + } + + if let Some(&existing_id) = lookup.get(&path) { + dependencies.entry(curr_id).or_default().push(existing_id); + continue; + } + + let last_ind = modules.len(); + let program = parse_and_get_program(&full_path)?; + + modules.push(Module { + parsed_program: program, + }); + lookup.insert(path.clone(), last_ind); + dependencies.entry(curr_id).or_default().push(last_ind); + + queue.push_back(last_ind); + } + } + + Ok(Self { + modules, + lookup, + dependencies, + }) + } +} diff --git a/src/driver/tests.rs b/src/driver/tests.rs new file mode 100644 index 00000000..5cfcd829 --- /dev/null +++ b/src/driver/tests.rs @@ -0,0 +1,198 @@ +use super::*; +use std::fs::{self, File}; +use std::io::Write; +use std::path::Path; +use tempfile::TempDir; + +fn create_simf_file(dir: &Path, rel_path: &str, content: &str) -> PathBuf { + let full_path = dir.join(rel_path); + + // Ensure parent directories exist + if let Some(parent) = full_path.parent() { + fs::create_dir_all(parent).unwrap(); + } + + let mut file = File::create(&full_path).expect("Failed to create file"); + file.write_all(content.as_bytes()) + .expect("Failed to write content"); + full_path +} + +// Helper to mock the initial root program parsing +fn parse_root(path: &Path) -> parse::Program { + parse_and_get_program(path).expect("Root parsing failed") +} + +/// Initializes a graph environment for testing. +/// Returns: +/// 1. The constructed `ProjectGraph`. +/// 2. A `HashMap` mapping filenames (e.g., "A.simf") to their `FileID` (usize). +/// 3. The `TempDir` (to keep files alive during the test). +fn setup_graph(files: Vec<(&str, &str)>) -> (ProjectGraph, HashMap, TempDir) { + let temp_dir = TempDir::new().unwrap(); + let mut lib_map = HashMap::new(); + + // Define the standard library path structure + let lib_path = temp_dir.path().join("libs/lib"); + lib_map.insert("lib".to_string(), lib_path); + + let mut root_path = None; + + // Create all requested files + for (name, content) in files { + if name == "main.simf" { + root_path = Some(create_simf_file(temp_dir.path(), name, content)); + } else { + // Names should be passed like "libs/lib/A.simf" + create_simf_file(temp_dir.path(), name, content); + } + } + + let root_p = root_path.expect("main.simf must be defined in file list"); + let root_program = parse_root(&root_p); + + let config = LibConfig::new(lib_map, &root_p); + let graph = ProjectGraph::new(&config, &root_program).unwrap(); + + // Create a lookup map for tests: "A.simf" -> FileID + let mut file_ids = HashMap::new(); + for (path, id) in &graph.lookup { + let file_name = path.file_name().unwrap().to_string_lossy().to_string(); + file_ids.insert(file_name, *id); + } + + (graph, file_ids, temp_dir) +} + +#[cfg(test)] +mod graph_construction_and_dependency_resolution { + use super::*; + + // Creates a file with specific content in the temp directory + #[test] + fn test_simple_import() { + // Setup: + // root.simf -> "use std::math;" + // libs/std/math.simf -> "" + + let (graph, ids, _dir) = setup_graph(vec![ + ("main.simf", "use lib::math::some_func;"), + ("libs/lib/math.simf", ""), + ]); + + assert_eq!(graph.modules.len(), 2, "Should have Root and Math module"); + + let root_id = ids["main"]; + let math_id = ids["math"]; + + assert!( + graph.dependencies[&root_id].contains(&math_id), + "Root (main.simf) should depend on Math (main.simf)" + ); + } + + #[test] + fn test_diamond_dependency_deduplication() { + // Setup: + // root -> imports A, B + // A -> imports Common + // B -> imports Common + // Expected: Common loaded ONLY ONCE. + + let (graph, ids, _dir) = setup_graph(vec![ + ("main.simf", "use lib::A::foo; use lib::B::bar;"), + ("libs/lib/A.simf", "use lib::Common::dummy1;"), + ("libs/lib/B.simf", "use lib::Common::dummy2;"), + ("libs/lib/Common.simf", ""), + ]); + + // Check strict deduplication (Unique modules count) + assert_eq!( + graph.modules.len(), + 4, + "Should resolve exactly 4 unique modules" + ); + + // Verify Graph Topology via IDs + let a_id = ids["A"]; + let b_id = ids["B"]; + let common_id = ids["Common"]; + + // Check A -> Common + assert!( + graph.dependencies[&a_id].contains(&common_id), + "A should depend on Common" + ); + + // Check B -> Common (Crucial: Must be the SAME common_id) + assert!( + graph.dependencies[&b_id].contains(&common_id), + "B should depend on Common" + ); + } + + #[test] + fn test_cyclic_dependency() { + // Setup: A <-> B cycle + // main -> imports A + // A -> imports B + // B -> imports A + + let (graph, ids, _dir) = setup_graph(vec![ + ("main.simf", "use lib::A::entry;"), + ("libs/lib/A.simf", "use lib::B::func;"), + ("libs/lib/B.simf", "use lib::A::func;"), + ]); + + let a_id = ids["A"]; + let b_id = ids["B"]; + + // Check if graph correctly recorded the cycle + assert!( + graph.dependencies[&a_id].contains(&b_id), + "A should depend on B" + ); + assert!( + graph.dependencies[&b_id].contains(&a_id), + "B should depend on A" + ); + } + + #[test] + fn test_ignores_unmapped_imports() { + // Setup: root imports from "unknown", which is not in our lib_map + let (graph, ids, _dir) = setup_graph(vec![("main.simf", "use unknown::library;")]); + + assert_eq!(graph.modules.len(), 1, "Should only contain root"); + assert!(graph.dependencies[&ids["main"]].is_empty()); + } +} + +#[cfg(test)] +mod error_diganostic_and_terminal_formatting { + use super::*; + + #[test] + fn test_missing_file_error() { + // Setup: + // root -> imports missing_lib + + let temp_dir = TempDir::new().unwrap(); + let root_path = create_simf_file(temp_dir.path(), "root.simf", "use std::ghost;"); + // We do NOT create ghost.simf + + let mut lib_map = HashMap::new(); + lib_map.insert("std".to_string(), temp_dir.path().join("libs/std")); + + let root_program = parse_root(&root_path); + let config = LibConfig::new(lib_map, &root_path); + let result = ProjectGraph::new(&config, &root_program); + + assert!(result.is_err(), "Should fail for missing file"); + let err_msg = result.err().unwrap(); + assert!( + err_msg.contains("does not exist"), + "Error message should mention missing file" + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index b4a1032a..e53caba1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod array; pub mod ast; pub mod compile; pub mod debug; +pub mod driver; pub mod dummy_env; pub mod error; pub mod jet; @@ -12,6 +13,7 @@ pub mod named; pub mod num; pub mod parse; pub mod pattern; +pub mod resolution; #[cfg(feature = "serde")] mod serde; pub mod str; @@ -30,8 +32,10 @@ pub extern crate simplicity; pub use simplicity::elements; use crate::debug::DebugSymbols; +use crate::driver::ProjectGraph; use crate::error::{ErrorCollector, WithFile}; use crate::parse::ParseFromStrWithErrors; +use crate::resolution::LibConfig; pub use crate::types::ResolvedType; pub use crate::value::Value; pub use crate::witness::{Arguments, Parameters, WitnessTypes, WitnessValues}; @@ -46,6 +50,31 @@ pub struct TemplateProgram { } impl TemplateProgram { + pub fn new_with_dep>>( + lib_cfg: Option<&LibConfig>, + s: Str, + ) -> Result { + let file = s.into(); + let mut error_handler = ErrorCollector::new(Arc::clone(&file)); + let parse_program = parse::Program::parse_from_str_with_errors(&file, &mut error_handler); + + if let Some(program) = parse_program { + let _ = if let Some(lib_cfg) = lib_cfg { + Some(ProjectGraph::new(lib_cfg, &program)?) + } else { + None + }; + + let ast_program = ast::Program::analyze(&program).with_file(Arc::clone(&file))?; + Ok(Self { + simfony: ast_program, + file, + }) + } else { + Err(ErrorCollector::to_string(&error_handler))? + } + } + /// Parse the template of a SimplicityHL program. /// /// ## Errors @@ -122,6 +151,16 @@ pub struct CompiledProgram { } impl CompiledProgram { + pub fn new_with_dep>>( + lib_cfg: Option<&LibConfig>, + s: Str, + arguments: Arguments, + include_debug_symbols: bool, + ) -> Result { + TemplateProgram::new_with_dep(lib_cfg, s) + .and_then(|template| template.instantiate(arguments, include_debug_symbols)) + } + /// Parse and compile a SimplicityHL program from the given string. /// /// ## See @@ -205,6 +244,17 @@ pub struct SatisfiedProgram { } impl SatisfiedProgram { + pub fn new_with_dep>>( + lib_cfg: Option<&LibConfig>, + s: Str, + arguments: Arguments, + witness_values: WitnessValues, + include_debug_symbols: bool, + ) -> Result { + let compiled = CompiledProgram::new_with_dep(lib_cfg, s, arguments, include_debug_symbols)?; + compiled.satisfy(witness_values) + } + /// Parse, compile and satisfy a SimplicityHL program from the given string. /// /// ## See diff --git a/src/main.rs b/src/main.rs index 5cb7b571..6fc2444a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,9 @@ use base64::display::Base64Display; use base64::engine::general_purpose::STANDARD; use clap::{Arg, ArgAction, Command}; +use simplicityhl::resolution::LibConfig; use simplicityhl::{AbiMeta, CompiledProgram}; -use std::{env, fmt}; +use std::{collections::HashMap, env, fmt, path::PathBuf}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] /// The compilation output. @@ -49,6 +50,14 @@ fn main() -> Result<(), Box> { .action(ArgAction::Set) .help("SimplicityHL program file to build"), ) + .arg( + Arg::new("library") + .long("lib") + .short('L') + .value_name("ALIAS=PATH") + .action(ArgAction::Append) + .help("Link a library with an alias (e.g., --lib math=./libs/math)"), + ) .arg( Arg::new("wit_file") .long("wit") @@ -113,7 +122,31 @@ fn main() -> Result<(), Box> { simplicityhl::Arguments::default() }; - let compiled = match CompiledProgram::new(prog_text, args_opt, include_debug_symbols) { + let lib_args = matches.get_many::("library").unwrap_or_default(); + + let library_map: HashMap = lib_args + .map(|arg| { + let parts: Vec<&str> = arg.splitn(2, '=').collect(); + + if parts.len() != 2 { + eprintln!( + "Error: Library argument must be in format ALIAS=PATH, got '{}'", + arg + ); + std::process::exit(1); + } + + (parts[0].to_string(), std::path::PathBuf::from(parts[1])) + }) + .collect(); + + let config = LibConfig::new(library_map, prog_path); + let compiled = match CompiledProgram::new_with_dep( + Some(&config), + prog_text, + args_opt, + include_debug_symbols, + ) { Ok(program) => program, Err(e) => { eprintln!("{}", e); diff --git a/src/resolution.rs b/src/resolution.rs new file mode 100644 index 00000000..3397776d --- /dev/null +++ b/src/resolution.rs @@ -0,0 +1,38 @@ +use std::collections::HashMap; +use std::path::{Path, PathBuf}; + +use crate::parse::UseDecl; + +#[derive(Debug, Clone)] +pub struct LibConfig { + pub libraries: HashMap, + pub root_path: PathBuf, +} + +impl LibConfig { + pub fn new(libraries: HashMap, raw_root_path: &Path) -> Self { + let root_path = raw_root_path.with_extension(""); + + Self { + libraries, + root_path, + } + } + + pub fn get_full_path(&self, use_decl: &UseDecl) -> Result { + let parts: Vec<&str> = use_decl.path().iter().map(|s| s.as_ref()).collect(); + let first_segment = parts[0]; + + if let Some(lib_root) = self.libraries.get(first_segment) { + let mut full_path = lib_root.clone(); + full_path.extend(&parts[1..]); + + return Ok(full_path); + } + + Err(format!( + "Unknown module or library '{}'. Did you forget to pass --lib {}=...?", + first_segment, first_segment, + )) + } +} diff --git a/src/str.rs b/src/str.rs index c06c33dd..0435fb38 100644 --- a/src/str.rs +++ b/src/str.rs @@ -118,6 +118,12 @@ impl<'a> arbitrary::Arbitrary<'a> for FunctionName { #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct Identifier(Arc); +impl AsRef for Identifier { + fn as_ref(&self) -> &str { + &self.0 + } +} + wrapped_string!(Identifier, "variable identifier"); impl_arbitrary_lowercase_alpha!(Identifier); From e3c42b068f7760ea4e70d5c57e194be23f600b4d Mon Sep 17 00:00:00 2001 From: Sdoba16 Date: Wed, 11 Feb 2026 17:21:29 +0200 Subject: [PATCH 3/5] Added C3 algorithm and fix linearization errors --- src/driver/mod.rs | 104 +++++++++++++++++++++++++++++++++++++++++++- src/driver/tests.rs | 62 ++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 1 deletion(-) diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 47fefd63..1a84066e 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -39,6 +39,12 @@ pub struct ProjectGraph { pub dependencies: HashMap>, } +#[derive(Debug)] +pub enum C3Error { + CycleDetected(Vec), + InconsistentLinearization { module: usize }, +} + fn parse_and_get_program(prog_file: &Path) -> Result { let prog_text = std::fs::read_to_string(prog_file).map_err(|e| e.to_string())?; let file = prog_text.into(); @@ -51,6 +57,39 @@ fn parse_and_get_program(prog_file: &Path) -> Result { } } +// Function used by the C3 algorithm. +fn merge(mut seqs: Vec>) -> Option> { + let mut result = Vec::new(); + + loop { + seqs.retain(|s| !s.is_empty()); + if seqs.is_empty() { + return Some(result); + } + + let mut candidate = None; + + 'outer: for seq in &seqs { + let head = seq[0]; + + if seqs.iter().all(|s| !s[1..].contains(&head)) { + candidate = Some(head); + break 'outer; + } + } + + let head = candidate?; + + result.push(head); + + for seq in &mut seqs { + if seq.first() == Some(&head) { + seq.remove(0); + } + } + } +} + impl ProjectGraph { pub fn new(lib_cfg: &LibConfig, root_program: &parse::Program) -> Result { let mut modules: Vec = vec![Module { @@ -87,7 +126,10 @@ impl ProjectGraph { } if let Some(&existing_id) = lookup.get(&path) { - dependencies.entry(curr_id).or_default().push(existing_id); + let deps = dependencies.entry(curr_id).or_default(); + if !deps.contains(&existing_id) { + deps.push(existing_id); + } continue; } @@ -110,4 +152,64 @@ impl ProjectGraph { dependencies, }) } + + /// Computes the linear resolution order of the project modules. + /// + /// # Errors + /// Returns a `C3Error` if a cyclic dependency is detected or if a valid + /// linearization is mathematically impossible. + pub fn c3_linearize(&self) -> Result, C3Error> { + self.linearize_module(0) + } + + /// Internal helper to initiate the recursive C3 linearization process. + fn linearize_module(&self, root: usize) -> Result, C3Error> { + let mut memo = HashMap::>::new(); + let mut visiting = Vec::::new(); + + self.linearize_rec(root, &mut memo, &mut visiting) + } + + /// Recursively calculates the C3 linearization sequence for a given module. + /// + /// Uses memoization to cache results and tracks the `visiting` path to + /// detect infinite cyclic dependencies (e.g., A imports B, B imports A). + fn linearize_rec( + &self, + module: usize, + memo: &mut HashMap>, + visiting: &mut Vec, + ) -> Result, C3Error> { + if let Some(result) = memo.get(&module) { + return Ok(result.clone()); + } + + if visiting.contains(&module) { + let cycle_start = visiting.iter().position(|m| *m == module).unwrap(); + return Err(C3Error::CycleDetected(visiting[cycle_start..].to_vec())); + } + + visiting.push(module); + + let parents = self.dependencies.get(&module).cloned().unwrap_or_default(); + + let mut seqs: Vec> = Vec::new(); + + for parent in &parents { + let lin = self.linearize_rec(*parent, memo, visiting)?; + seqs.push(lin); + } + + seqs.push(parents.clone()); + + let mut result = vec![module]; + let merged = merge(seqs).ok_or(C3Error::InconsistentLinearization { module })?; + + result.extend(merged); + + visiting.pop(); + memo.insert(module, result.clone()); + + Ok(result) + } } diff --git a/src/driver/tests.rs b/src/driver/tests.rs index 5cfcd829..81575aa8 100644 --- a/src/driver/tests.rs +++ b/src/driver/tests.rs @@ -168,6 +168,68 @@ mod graph_construction_and_dependency_resolution { } } +#[cfg(test)] +mod c3_linearization { + use super::*; + + #[test] + fn test_c3_simple_import() { + // Setup similar to above + let (graph, ids, _dir) = setup_graph(vec![ + ("main.simf", "use lib::math::some_func;"), + ("libs/lib/math.simf", ""), + ]); + + let order = graph.c3_linearize().expect("C3 failed"); + + let root_id = ids["main"]; + let math_id = ids["math"]; + + // Assuming linearization order: Dependent (Root) -> Dependency (Math) + assert_eq!(order, vec![root_id, math_id]); + } + + #[test] + fn test_c3_diamond_dependency_deduplication() { + // Setup: + // root -> imports A, B + // A -> imports Common + // B -> imports Common + // Expected: Common loaded ONLY ONCE. + + let (graph, ids, _dir) = setup_graph(vec![ + ("main.simf", "use lib::A::foo; use lib::B::bar;"), + ("libs/lib/A.simf", "use lib::Common::dummy1;"), + ("libs/lib/B.simf", "use lib::Common::dummy2;"), + ("libs/lib/Common.simf", ""), + ]); + + let order = graph.c3_linearize().expect("C3 failed"); + + // Verify order using IDs from the helper map + let main_id = ids["main"]; + let a_id = ids["A"]; + let b_id = ids["B"]; + let common_id = ids["Common"]; + + // In the `resolve_complication_order` function, the order was reversed. + // Therefore, `common` will be the first, and `main` will be last. + assert_eq!(order, vec![main_id, a_id, b_id, common_id]); + } + + #[test] + fn test_c3_detects_cycle() { + let (graph, _, _dir) = setup_graph(vec![ + ("main.simf", "use lib::A::entry;"), + ("libs/lib/A.simf", "use lib::B::func;"), + ("libs/lib/B.simf", "use lib::A::func;"), + ]); + + let order = graph.c3_linearize(); + matches!(order, Err(C3Error::CycleDetected(_))); + } +} + #[cfg(test)] mod error_diganostic_and_terminal_formatting { use super::*; From ebd02ff2318857155be828b08989f059e130d1f6 Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Mon, 16 Feb 2026 17:31:10 +0200 Subject: [PATCH 4/5] feat: add build_order function, refactor and test it --- src/driver/mod.rs | 149 ++++++++++++++++++++++++++++++++++++++++++-- src/driver/tests.rs | 130 ++++++++++++++++++++++++++++++++++++-- src/lib.rs | 9 ++- src/parse.rs | 4 ++ src/str.rs | 19 ++++++ 5 files changed, 297 insertions(+), 14 deletions(-) diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 1a84066e..7701bc41 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -5,9 +5,10 @@ use std::collections::{HashMap, VecDeque}; use std::path::{Path, PathBuf}; use std::sync::Arc; -use crate::error::ErrorCollector; -use crate::parse::{self, ParseFromStrWithErrors}; +use crate::error::{ErrorCollector, Span}; +use crate::parse::{self, ParseFromStrWithErrors, Visibility}; use crate::resolution::LibConfig; +use crate::str::Identifier; /// Represents a single, isolated file in the SimplicityHL project. /// In this architecture, a file and a module are the exact same thing. @@ -22,13 +23,22 @@ pub struct Module { pub struct ProjectGraph { /// Arena Pattern: the data itself lives here. /// A flat vector guarantees that module data is stored contiguously in memory. - pub modules: Vec, + pub(self) modules: Vec, + + /// The configuration environment. + /// Used to resolve xternal library dependencies and invoke their associated functions. + pub config: Arc, /// Fast lookup: File Path -> Module ID. /// A reverse index mapping absolute file paths to their internal IDs. /// This solves the duplication problem, ensuring each file is only parsed once. pub lookup: HashMap, + /// Fast lookup: Module ID -> File Path. + /// A direct index mapping internal IDs back to their absolute file paths. + /// This serves as the exact inverse of the `lookup` map. + pub paths: Vec, + /// The Adjacency List: Defines the Directed acyclic Graph (DAG) of imports. /// /// The Key (`usize`) is the ID of a "Parent" module (the file doing the importing). @@ -39,6 +49,18 @@ pub struct ProjectGraph { pub dependencies: HashMap>, } +#[derive(Clone, Debug)] +pub struct Resolution { + pub visibility: Visibility, +} + +pub struct Program { + //pub graph: ProjectGraph, + pub items: Arc<[parse::Item]>, + pub scope_items: Vec>, + pub span: Span, +} + #[derive(Debug)] pub enum C3Error { CycleDetected(Vec), @@ -91,15 +113,16 @@ fn merge(mut seqs: Vec>) -> Option> { } impl ProjectGraph { - pub fn new(lib_cfg: &LibConfig, root_program: &parse::Program) -> Result { + pub fn new(config: Arc, root_program: &parse::Program) -> Result { let mut modules: Vec = vec![Module { parsed_program: root_program.clone(), }]; let mut lookup: HashMap = HashMap::new(); + let mut paths: Vec = vec![config.root_path.clone()]; let mut dependencies: HashMap> = HashMap::new(); let root_id = 0; - lookup.insert(lib_cfg.root_path.clone(), root_id); + lookup.insert(config.root_path.clone(), root_id); dependencies.insert(root_id, Vec::new()); // Implementation of the standard BFS algorithm with memoization and queue @@ -112,7 +135,7 @@ impl ProjectGraph { for elem in current_program.items() { if let parse::Item::Use(use_decl) = elem { - if let Ok(path) = lib_cfg.get_full_path(use_decl) { + if let Ok(path) = config.get_full_path(use_decl) { pending_imports.push(path); } } @@ -140,6 +163,7 @@ impl ProjectGraph { parsed_program: program, }); lookup.insert(path.clone(), last_ind); + paths.push(path.clone()); dependencies.entry(curr_id).or_default().push(last_ind); queue.push_back(last_ind); @@ -148,7 +172,9 @@ impl ProjectGraph { Ok(Self { modules, + config, lookup, + paths, dependencies, }) } @@ -212,4 +238,115 @@ impl ProjectGraph { Ok(result) } + + fn process_use_item( + scope_items: &mut [HashMap], + file_id: usize, + ind: usize, + elem: &Identifier, + use_decl_visibility: Visibility, + ) -> Result<(), String> { + if matches!( + scope_items[ind][elem].visibility, + parse::Visibility::Private + ) { + return Err(format!( + "Function {} is private and cannot be used.", + elem.as_inner() + )); + } + + scope_items[file_id].insert( + elem.clone(), + Resolution { + visibility: use_decl_visibility, + }, + ); + + Ok(()) + } + + fn register_def( + items: &mut Vec, + scope: &mut HashMap, + item: &parse::Item, + name: Identifier, + vis: &parse::Visibility, + ) { + items.push(item.clone()); + scope.insert( + name, + Resolution { + visibility: vis.clone(), + }, + ); + } + + // TODO: @LesterEvSe, consider processing more than one error at a time + fn build_program(&self, order: &Vec) -> Result { + let mut items: Vec = Vec::new(); + let mut scope_items: Vec> = + vec![HashMap::new(); order.len()]; + + for &file_id in order { + let program_items = self.modules[file_id].parsed_program.items(); + + for elem in program_items { + match elem { + parse::Item::Use(use_decl) => { + let full_path = self.config.get_full_path(use_decl)?; + let ind = self.lookup[&full_path]; + let visibility = use_decl.visibility(); + + let use_targets = match use_decl.items() { + parse::UseItems::Single(elem) => std::slice::from_ref(elem), + parse::UseItems::List(elems) => elems.as_slice(), + }; + + for target in use_targets { + ProjectGraph::process_use_item( + &mut scope_items, + file_id, + ind, + target, + visibility.clone(), + )?; + } + } + parse::Item::TypeAlias(alias) => { + Self::register_def( + &mut items, + &mut scope_items[file_id], + elem, + alias.name().clone().into(), + alias.visibility(), + ); + } + parse::Item::Function(function) => { + Self::register_def( + &mut items, + &mut scope_items[file_id], + elem, + function.name().clone().into(), + function.visibility(), + ); + } + parse::Item::Module => {} + } + } + } + + Ok(Program { + items: items.into(), + scope_items, + span: *self.modules[0].parsed_program.as_ref(), + }) + } + + pub fn resolve_complication_order(&self) -> Result { + // TODO: @LesterEvSe, resolve errors more appropriately + let mut order = self.c3_linearize().unwrap(); + order.reverse(); + self.build_program(&order) + } } diff --git a/src/driver/tests.rs b/src/driver/tests.rs index 81575aa8..a0b0fc6f 100644 --- a/src/driver/tests.rs +++ b/src/driver/tests.rs @@ -20,7 +20,7 @@ fn create_simf_file(dir: &Path, rel_path: &str, content: &str) -> PathBuf { // Helper to mock the initial root program parsing fn parse_root(path: &Path) -> parse::Program { - parse_and_get_program(path).expect("Root parsing failed") + parse_and_get_program(path).unwrap() } /// Initializes a graph environment for testing. @@ -51,8 +51,8 @@ fn setup_graph(files: Vec<(&str, &str)>) -> (ProjectGraph, HashMap FileID let mut file_ids = HashMap::new(); @@ -87,7 +87,7 @@ mod graph_construction_and_dependency_resolution { assert!( graph.dependencies[&root_id].contains(&math_id), - "Root (main.simf) should depend on Math (main.simf)" + "Root (main.simf) should depend on Math (math.simf)" ); } @@ -230,6 +230,124 @@ mod c3_linearization { } } +#[cfg(test)] +mod visibility_and_access_control { + use super::*; + + #[test] + fn test_local_definitions_visibility() { + // Scenario: + // main.simf defines a private function and a public function. + // Expected: Both should appear in the scope with correct visibility. + + let (graph, ids, _dir) = setup_graph(vec![( + "main.simf", + "fn private_fn() {} pub fn public_fn() {}", + )]); + + let root_id = ids["main"]; + let order = vec![root_id]; // Only one file + + let program = graph + .build_program(&order) + .expect("Failed to build program"); + let scope = &program.scope_items[root_id]; + + // Check private function + let private_res = scope + .get(&Identifier::from("private_fn")) + .expect("private_fn missing"); + assert_eq!(private_res.visibility, Visibility::Private); + + // Check public function + let public_res = scope + .get(&Identifier::from("public_fn")) + .expect("public_fn missing"); + assert_eq!(public_res.visibility, Visibility::Public); + } + + #[test] + fn test_pub_use_propagation() { + // Scenario: Re-exporting. + // 1. A.simf defines `pub fn foo`. + // 2. B.simf imports it and re-exports it via `pub use`. + // 3. main.simf imports it from B. + // Expected: B's scope must contain `foo` marked as Public. + + let (graph, ids, _dir) = setup_graph(vec![ + ("libs/lib/A.simf", "pub fn foo() {}"), + ("libs/lib/B.simf", "pub use lib::A::foo;"), + ("main.simf", "use lib::B::foo;"), + ]); + + let id_a = ids["A"]; + let id_b = ids["B"]; + let id_root = ids["main"]; + + // Manual topological order: A -> B -> Root + let order = vec![id_a, id_b, id_root]; + + let program = graph + .build_program(&order) + .expect("Failed to build program"); + + // Check B's scope + let scope_b = &program.scope_items[id_b]; + let foo_in_b = scope_b + .get(&Identifier::from("foo")) + .expect("foo missing in B"); + + // This is the critical check: Did `pub use` make it Public in B? + assert_eq!( + foo_in_b.visibility, + Visibility::Public, + "B should re-export foo as Public" + ); + + // Check Root's scope + let scope_root = &program.scope_items[id_root]; + let foo_in_root = scope_root + .get(&Identifier::from("foo")) + .expect("foo missing in Root"); + + // Root imported it via `use` (not pub use), so it should be Private in Root + assert_eq!( + foo_in_root.visibility, + Visibility::Private, + "Root should have foo as Private" + ); + } + + #[test] + fn test_private_import_encapsulation_error() { + // Scenario: Access violation. + // 1. A.simf defines `pub fn foo`. + // 2. B.simf imports it via `use` (Private import). + // 3. main.simf tries to import `foo` from B. + // Expected: Error, because B did not re-export foo. + + let (graph, ids, _dir) = setup_graph(vec![ + ("libs/lib/A.simf", "pub fn foo() {}"), + ("libs/lib/B.simf", "use lib::A::foo;"), // <--- Private binding! + ("main.simf", "use lib::B::foo;"), // <--- Should fail + ]); + + let id_a = ids["A"]; + let id_b = ids["B"]; + let id_root = ids["main"]; + + // Order: A -> B -> Root + let order = vec![id_a, id_b, id_root]; + + let result = graph.build_program(&order); + + assert!( + result.is_err(), + "Build should fail when importing a private binding" + ); + } +} + #[cfg(test)] mod error_diganostic_and_terminal_formatting { use super::*; @@ -247,8 +365,8 @@ mod error_diganostic_and_terminal_formatting { lib_map.insert("std".to_string(), temp_dir.path().join("libs/std")); let root_program = parse_root(&root_path); - let config = LibConfig::new(lib_map, &root_path); - let result = ProjectGraph::new(&config, &root_program); + let config = Arc::from(LibConfig::new(lib_map, &root_path)); + let result = ProjectGraph::new(config, &root_program); assert!(result.is_err(), "Should fail for missing file"); let err_msg = result.err().unwrap(); diff --git a/src/lib.rs b/src/lib.rs index e53caba1..501d53d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,8 +59,13 @@ impl TemplateProgram { let parse_program = parse::Program::parse_from_str_with_errors(&file, &mut error_handler); if let Some(program) = parse_program { - let _ = if let Some(lib_cfg) = lib_cfg { - Some(ProjectGraph::new(lib_cfg, &program)?) + // TODO: Consider a proper resolution strategy later. + let _: Option = if let Some(cfg) = lib_cfg { + let config_arc = Arc::new(cfg.clone()); + let graph = ProjectGraph::new(config_arc, &program)?; + + // TODO: Perhaps add an `error_handler` here, too. + Some(graph.resolve_complication_order()?) } else { None }; diff --git a/src/parse.rs b/src/parse.rs index c96d5ccb..32c57788 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -79,6 +79,10 @@ impl UseDecl { &self.path } + pub fn path_buf(&self) -> std::path::PathBuf { + self.path().iter().map(|s| s.as_ref()).collect() + } + /// Access the visibility of the function. pub fn items(&self) -> &UseItems { &self.items diff --git a/src/str.rs b/src/str.rs index 0435fb38..d1249164 100644 --- a/src/str.rs +++ b/src/str.rs @@ -118,6 +118,25 @@ impl<'a> arbitrary::Arbitrary<'a> for FunctionName { #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct Identifier(Arc); +impl From for Identifier { + fn from(alias: AliasName) -> Self { + // We move the inner Arc, so this is cheap + Self(alias.0) + } +} + +impl From for Identifier { + fn from(func: FunctionName) -> Self { + Self(func.0) + } +} + +impl From<&str> for Identifier { + fn from(s: &str) -> Self { + Self(Arc::from(s)) + } +} + impl AsRef for Identifier { fn as_ref(&self) -> &str { &self.0 From d0eaa5bbaa258d733d23dc4498c1287bb1289ef6 Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Fri, 20 Feb 2026 14:26:09 +0200 Subject: [PATCH 5/5] feat: update AST architecture and test module flow - Refactor AST to include tests and modules - Add simple tests for module flow --- Cargo.lock | 526 ++++++------------ examples/multiple_libs/main.simf | 12 + examples/multiple_libs/math/simple_op.simf | 3 + examples/multiple_libs/merkle/build_root.simf | 5 + examples/single_lib/main.simf | 11 + examples/single_lib/temp/funcs.simf | 5 + examples/single_lib/temp/two.simf | 5 + src/ast.rs | 131 ++++- src/driver/mod.rs | 321 +++++++++-- src/driver/tests.rs | 30 +- src/error.rs | 16 + src/lib.rs | 269 ++++++++- src/main.rs | 10 +- src/parse.rs | 43 +- src/resolution.rs | 54 +- src/witness.rs | 7 +- 16 files changed, 958 insertions(+), 490 deletions(-) create mode 100644 examples/multiple_libs/main.simf create mode 100644 examples/multiple_libs/math/simple_op.simf create mode 100644 examples/multiple_libs/merkle/build_root.simf create mode 100644 examples/single_lib/main.simf create mode 100644 examples/single_lib/temp/funcs.simf create mode 100644 examples/single_lib/temp/two.simf diff --git a/Cargo.lock b/Cargo.lock index 4a7ffedc..358e2d97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -19,9 +19,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -34,59 +34,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "once_cell", - "windows-sys", -] - -[[package]] -name = "anyhow" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" - -[[package]] -name = "ar_archive_writer" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" -dependencies = [ - "object", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] name = "arbitrary" -version = "1.1.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a7924531f38b1970ff630f03eb20a2fde69db5c590c93b0f3482e95dcc5fd60" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] @@ -109,9 +94,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.3" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" @@ -121,15 +106,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bech32" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] name = "bitcoin" -version = "0.32.3" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0032b0e8ead7074cda7fc4f034409607e3f03a6f71d66ade8a307f79b4d99e73" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" dependencies = [ "base58ck", "bech32", @@ -150,9 +135,9 @@ checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" [[package]] name = "bitcoin-io" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] name = "bitcoin-private" @@ -171,9 +156,9 @@ dependencies = [ [[package]] name = "bitcoin_hashes" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", "hex-conservative", @@ -181,27 +166,27 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -211,9 +196,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chumsky" @@ -221,8 +206,8 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acc17a6284abccac6e50db35c1cee87f605474a72939b959a3a67d9371800efd" dependencies = [ - "hashbrown 0.15.5", - "regex-automata", + "hashbrown", + "regex-automata 0.3.9", "serde", "stacker", "unicode-ident", @@ -231,18 +216,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.37" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -252,9 +237,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "codegen" @@ -265,32 +250,32 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "derive_arbitrary" -version = "1.1.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a577516173adb681466d517d39bd468293bc2c2a16439375ef0f35bba45f3d" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elements" -version = "0.25.0" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739a0201c8b2d1e35e6509872ddb8250dd37b38d2a462b9cea05988bf9630196" +checksum = "81b2569d3495bfdfce36c504fd4d78752ff4a7699f8a33e6f3ee523bddf9f6ad" dependencies = [ "bech32", "bitcoin", @@ -310,7 +295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -333,9 +318,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -346,15 +331,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", "wasip2", - "wasip3", ] [[package]] @@ -374,23 +358,11 @@ dependencies = [ "foldhash", ] -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hex-conservative" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ "arrayvec", ] @@ -401,29 +373,11 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -436,45 +390,41 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ + "once_cell", "wasm-bindgen", ] -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" -version = "0.2.180" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libfuzzer-sys" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" dependencies = [ "arbitrary", "cc", @@ -482,41 +432,26 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "log" -version = "0.4.22" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "memchr" -version = "2.6.3" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "miniscript" -version = "12.3.1" +version = "12.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82911d2fb527bb9aacd2446d2f517aff3f8e3846ace1b3c24258b61ea3cce2bc" +checksum = "487906208f38448e186e3deb02f2b8ef046a9078b0de00bdb28bf4fb9b76951c" dependencies = [ "bech32", "bitcoin", ] -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -524,19 +459,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] -name = "prettyplease" -version = "0.2.37" +name = "ppv-lite86" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "proc-macro2", - "syn 2.0.114", + "zerocopy", ] [[package]] @@ -550,19 +484,18 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.28" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" dependencies = [ - "ar_archive_writer", "cc", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -600,30 +533,41 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.17", ] [[package]] name = "regex" -version = "1.9.5" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.14", + "regex-syntax 0.8.10", +] + +[[package]] +name = "regex-automata" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-syntax 0.7.5", ] [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.10", ] [[package]] @@ -632,24 +576,30 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] -name = "ryu" -version = "1.0.15" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "santiago" @@ -702,12 +652,6 @@ dependencies = [ "secp256k1-sys", ] -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - [[package]] name = "serde" version = "1.0.228" @@ -735,18 +679,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", - "ryu", + "memchr", "serde", + "serde_core", + "zmij", ] [[package]] @@ -765,7 +711,7 @@ dependencies = [ "bitcoin_hashes", "byteorder", "elements", - "getrandom 0.2.10", + "getrandom 0.2.17", "ghost-cell", "hex-conservative", "miniscript", @@ -775,9 +721,9 @@ dependencies = [ [[package]] name = "simplicity-sys" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875630d128f19818161cefe0a3d910b6aae921d8246711db574a689cb2c11747" +checksum = "e3401ee7331f183a5458c0f5a4b3d5d00bde0fd12e2e03728c537df34efae289" dependencies = [ "bitcoin_hashes", "cc", @@ -788,11 +734,11 @@ name = "simplicityhl" version = "0.5.0-rc.0" dependencies = [ "arbitrary", - "base64 0.21.3", + "base64 0.21.7", "chumsky", "clap", "either", - "getrandom 0.2.10", + "getrandom 0.2.17", "itertools", "miniscript", "serde", @@ -815,15 +761,15 @@ dependencies = [ [[package]] name = "stacker" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" +checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013" dependencies = [ "cc", "cfg-if", "libc", "psm", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -834,20 +780,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -856,22 +791,22 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.25.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" @@ -879,12 +814,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "utf8parse" version = "0.2.2" @@ -893,9 +822,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" @@ -906,45 +835,24 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" -dependencies = [ - "bumpalo", - "log", "once_cell", - "proc-macro2", - "quote", - "syn 2.0.114", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -952,64 +860,48 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", - "wasm-bindgen-backend", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ - "leb128fmt", - "wasmparser", + "unicode-ident", ] [[package]] -name = "wasm-metadata" -version = "0.244.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "wasmparser" -version = "0.244.0" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", + "windows-targets", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] @@ -1081,85 +973,29 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] [[package]] -name = "wit-bindgen-core" -version = "0.51.0" +name = "zerocopy" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ - "anyhow", - "heck", - "wit-parser", + "zerocopy-derive", ] [[package]] -name = "wit-bindgen-rust" -version = "0.51.0" +name = "zerocopy-derive" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn 2.0.114", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", - "wit-bindgen-core", - "wit-bindgen-rust", + "syn", ] [[package]] -name = "wit-component" -version = "0.244.0" +name = "zmij" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" \ No newline at end of file diff --git a/examples/multiple_libs/main.simf b/examples/multiple_libs/main.simf new file mode 100644 index 00000000..8bfee917 --- /dev/null +++ b/examples/multiple_libs/main.simf @@ -0,0 +1,12 @@ +use merkle::build_root::get_root; +use math::simple_op::hash; + +pub fn get_block_value_hash(prev_hash: u32, tx1: u32, tx2: u32) -> u32 { + let root: u32 = get_root(tx1, tx2); + hash(prev_hash, root); +} + +fn main() { + let block_val_hash: u32 = get_block_value(5, 10, 20); + assert!(jet::eq_32(block_val_hash, 27)); +} \ No newline at end of file diff --git a/examples/multiple_libs/math/simple_op.simf b/examples/multiple_libs/math/simple_op.simf new file mode 100644 index 00000000..b152a361 --- /dev/null +++ b/examples/multiple_libs/math/simple_op.simf @@ -0,0 +1,3 @@ +pub fn hash(x: u32, y: u32) -> u32 { + jet::xor_32(x, y) +} \ No newline at end of file diff --git a/examples/multiple_libs/merkle/build_root.simf b/examples/multiple_libs/merkle/build_root.simf new file mode 100644 index 00000000..18242832 --- /dev/null +++ b/examples/multiple_libs/merkle/build_root.simf @@ -0,0 +1,5 @@ +use math::simple_op::hash; + +pub fn get_root(tx1: u32, tx2: u32) -> u32 { + hash(tx1, tx2) +} \ No newline at end of file diff --git a/examples/single_lib/main.simf b/examples/single_lib/main.simf new file mode 100644 index 00000000..19b2b047 --- /dev/null +++ b/examples/single_lib/main.simf @@ -0,0 +1,11 @@ +pub use temp::two::two; +use temp::funcs::{get_five, Smth}; + +fn seven() -> u32 { + 7 +} + +fn main() { + let (_, temp): (bool, u32) = jet::add_32(two(), get_five()); + assert!(jet::eq_32(temp, seven())); +} \ No newline at end of file diff --git a/examples/single_lib/temp/funcs.simf b/examples/single_lib/temp/funcs.simf new file mode 100644 index 00000000..0ff2da55 --- /dev/null +++ b/examples/single_lib/temp/funcs.simf @@ -0,0 +1,5 @@ +pub type Smth = u32; + +pub fn get_five() -> u32 { + 5 +} \ No newline at end of file diff --git a/examples/single_lib/temp/two.simf b/examples/single_lib/temp/two.simf new file mode 100644 index 00000000..aa5cbb53 --- /dev/null +++ b/examples/single_lib/temp/two.simf @@ -0,0 +1,5 @@ +pub use temp::funcs::Smth; + +pub fn two() -> Smth { + 2 +} \ No newline at end of file diff --git a/src/ast.rs b/src/ast.rs index 94ed5c38..0a23b169 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -9,17 +9,19 @@ use miniscript::iter::{Tree, TreeLike}; use simplicity::jet::Elements; use crate::debug::{CallTracker, DebugSymbols, TrackedCallName}; +use crate::driver::FileResolutions; use crate::error::{Error, RichError, Span, WithSpan}; use crate::num::{NonZeroPow2Usize, Pow2Usize}; use crate::parse::MatchPattern; use crate::pattern::Pattern; +use crate::resolution::SourceName; use crate::str::{AliasName, FunctionName, Identifier, ModuleName, WitnessName}; use crate::types::{ AliasedType, ResolvedType, StructuralType, TypeConstructible, TypeDeconstructible, UIntType, }; use crate::value::{UIntValue, Value}; use crate::witness::{Parameters, WitnessTypes, WitnessValues}; -use crate::{impl_eq_hash, parse}; +use crate::{driver, impl_eq_hash, parse}; /// A program consists of the main function. /// @@ -520,8 +522,12 @@ impl TreeLike for ExprTree<'_> { /// 2. Resolving type aliases /// 3. Assigning types to each witness expression /// 4. Resolving calls to custom functions -#[derive(Clone, Debug, Eq, PartialEq, Default)] +#[derive(Clone, Debug, Eq, PartialEq)] struct Scope { + resolutions: Arc<[FileResolutions]>, + paths: Arc<[SourceName]>, + file_id: usize, // ID of the file from which the function is called. + variables: Vec>, aliases: HashMap, parameters: HashMap, @@ -531,7 +537,44 @@ struct Scope { call_tracker: CallTracker, } +impl Default for Scope { + fn default() -> Self { + Self { + resolutions: Arc::from([]), + paths: Arc::from([]), + file_id: 0, + variables: Vec::new(), + aliases: HashMap::new(), + parameters: HashMap::new(), + witnesses: HashMap::new(), + functions: HashMap::new(), + is_main: false, + call_tracker: CallTracker::default(), + } + } +} + impl Scope { + pub fn new(resolutions: Arc<[FileResolutions]>, paths: Arc<[SourceName]>) -> Self { + Self { + resolutions, + paths, + file_id: 0, + variables: Vec::new(), + aliases: HashMap::new(), + parameters: HashMap::new(), + witnesses: HashMap::new(), + functions: HashMap::new(), + is_main: false, + call_tracker: CallTracker::default(), + } + } + + /// Access to current function file id. + pub fn file_id(&self) -> usize { + self.file_id + } + /// Check if the current scope is topmost. pub fn is_topmost(&self) -> bool { self.variables.is_empty() @@ -542,6 +585,11 @@ impl Scope { self.variables.push(HashMap::new()); } + pub fn push_function_scope(&mut self, file_id: usize) { + self.push_scope(); + self.file_id = file_id; + } + /// Push the scope of the main function onto the stack. /// /// ## Panics @@ -564,6 +612,11 @@ impl Scope { self.variables.pop().expect("Stack is empty"); } + pub fn pop_function_scope(&mut self, previous_file_id: usize) { + self.pop_scope(); + self.file_id = previous_file_id; + } + /// Pop the scope of the main function from the stack. /// /// ## Panics @@ -693,9 +746,39 @@ impl Scope { } } - /// Get the definition of a custom function. - pub fn get_function(&self, name: &FunctionName) -> Option<&CustomFunction> { - self.functions.get(name) + /// Get the definition of a custom function with visibility and existence checks. + /// + /// # Errors + /// + /// - `Error::FileNotFound`: The specified `file_id` does not exist in the resolutions. + /// - `Error::FunctionUndefined`: The function is not found in the file's scope OR not defined globally. + /// - `Error::FunctionIsPrivate`: The function exists but is private (and thus not accessible). + pub fn get_function(&self, name: &FunctionName) -> Result<&CustomFunction, Error> { + // The order of the errors is important! + let function = self + .functions + .get(name) + .ok_or_else(|| Error::FunctionUndefined(name.clone()))?; + + let source_name = self.paths[self.file_id].clone(); + + let file_scope = match source_name { + SourceName::Real(path) => self + .resolutions + .get(self.file_id) + .ok_or(Error::FileNotFound(path))?, + SourceName::Virtual(_) => { + return Ok(function); + } + }; + + let identifier: Identifier = name.clone().into(); + + if file_scope.contains_key(&identifier) { + Ok(function) + } else { + Err(Error::FunctionIsPrivate(name.clone())) + } } /// Track a call expression with its span. @@ -718,9 +801,10 @@ trait AbstractSyntaxTree: Sized { } impl Program { - pub fn analyze(from: &parse::Program) -> Result { + // TODO: Add visibility check inside program + pub fn analyze(from: &driver::Program) -> Result { let unit = ResolvedType::unit(); - let mut scope = Scope::default(); + let mut scope = Scope::new(Arc::from(from.resolutions()), Arc::from(from.paths())); let items = from .items() .iter() @@ -762,7 +846,10 @@ impl AbstractSyntaxTree for Item { parse::Item::Function(function) => { Function::analyze(function, ty, scope).map(Self::Function) } - parse::Item::Use(_) => todo!(), + parse::Item::Use(use_decl) => Err(RichError::new( + Error::UnknownLibrary(use_decl.path_buf().to_string_lossy().to_string()), + *use_decl.span(), + )), parse::Item::Module => Ok(Self::Module), } } @@ -774,8 +861,10 @@ impl AbstractSyntaxTree for Function { fn analyze(from: &Self::From, ty: &ResolvedType, scope: &mut Scope) -> Result { assert!(ty.is_unit(), "Function definitions cannot return anything"); assert!(scope.is_topmost(), "Items live in the topmost scope only"); + let previous_file_id = scope.file_id(); if from.name().as_inner() != "main" { + let file_id = from.file_id(); let params = from .params() .iter() @@ -792,12 +881,12 @@ impl AbstractSyntaxTree for Function { .map(|aliased| scope.resolve(aliased).with_span(from)) .transpose()? .unwrap_or_else(ResolvedType::unit); - scope.push_scope(); + scope.push_function_scope(file_id); for param in params.iter() { scope.insert_variable(param.identifier().clone(), param.ty().clone()); } let body = Expression::analyze(from.body(), &ret, scope).map(Arc::new)?; - scope.pop_scope(); + scope.pop_function_scope(previous_file_id); debug_assert!(scope.is_topmost()); let function = CustomFunction { params, body }; scope @@ -1322,14 +1411,9 @@ impl AbstractSyntaxTree for CallName { .get_function(name) .cloned() .map(Self::Custom) - .ok_or(Error::FunctionUndefined(name.clone())) .with_span(from), parse::CallName::ArrayFold(name, size) => { - let function = scope - .get_function(name) - .cloned() - .ok_or(Error::FunctionUndefined(name.clone())) - .with_span(from)?; + let function = scope.get_function(name).cloned().with_span(from)?; // A function that is used in a array fold has the signature: // fn f(element: E, accumulator: A) -> A if function.params().len() != 2 || function.params()[1].ty() != function.body().ty() @@ -1340,11 +1424,7 @@ impl AbstractSyntaxTree for CallName { } } parse::CallName::Fold(name, bound) => { - let function = scope - .get_function(name) - .cloned() - .ok_or(Error::FunctionUndefined(name.clone())) - .with_span(from)?; + let function = scope.get_function(name).cloned().with_span(from)?; // A function that is used in a list fold has the signature: // fn f(element: E, accumulator: A) -> A if function.params().len() != 2 || function.params()[1].ty() != function.body().ty() @@ -1355,11 +1435,7 @@ impl AbstractSyntaxTree for CallName { } } parse::CallName::ForWhile(name) => { - let function = scope - .get_function(name) - .cloned() - .ok_or(Error::FunctionUndefined(name.clone())) - .with_span(from)?; + let function = scope.get_function(name).cloned().with_span(from)?; // A function that is used in a for-while loop has the signature: // fn f(accumulator: A, readonly_context: C, counter: u{N}) -> Either // where @@ -1435,6 +1511,9 @@ fn analyze_named_module( from: &parse::ModuleProgram, ) -> Result, RichError> { let unit = ResolvedType::unit(); + + // IMPORTANT! If modules allow imports, then we need to consider + // passing the resolution conetxt by calling `Scope::new(resolutions)` let mut scope = Scope::default(); let items = from .items() diff --git a/src/driver/mod.rs b/src/driver/mod.rs index 7701bc41..daa2c784 100644 --- a/src/driver/mod.rs +++ b/src/driver/mod.rs @@ -1,13 +1,82 @@ +//! The `driver` module is responsible for module resolution and dependency management. +//! +//! Our compiler operates in a strict pipeline: Lexer -> Parser -> AST. However, +//! the Parser only understands a one file at a time. The `driver` coordinates parsing +//! across multiple files and resolves dependencies to produce a flattened structure +//! ready for AST construction. +//! +//! # Architecture: Dependency Graph & Linearization +//! +//! The driver takes a root file, parses it, and recursively discovers all imported modules +//! to build a Directed Acyclic Graph (DAG) of the project's dependencies. Since the +//! final AST expects a flat, one-dimensional array of items, the driver applies a deterministic +//! linearization strategy. +//! +//! It converts the multi-file project into a single, logically ordered sequence of items, +//! based on dependencies, while respecting visibility rules and avoiding duplicate imports. +//! +//! # Architecture: Project Structure & Entry Point +//! +//! SimplicityHL does not define a project root directory. Instead, the compiler relies +//! on a single entry point: the file passed as the first positional argument. +//! +//! This file must contain the `main` function, which serves as the program's starting point. +//! +//! External libraries are explicitly linked using the `--lib` flag. +//! The driver resolves and parses these external files relative to the entry point +//! during the dependency graph construction. +//! +//! # Package Management & Distribution +//! +//! SimplicityHL is currently **transport-agnostic**, meaning it does not provide its own +//! package registry. Developers can use existing ecosystems to distribute `.simf` modules. +//! +//! ## Distributing via crates.io +//! +//! 1. **Publishing:** Create a Rust library (`cargo new --lib`), and place +//! your `.simf` files inside it. Then publish the crate to `crates.io`. +//! +//! 2. **Downloading:** In the consuming Rust project, add the published crate to +//! your `Cargo.toml`. Running `cargo build` forces Cargo to download the files +//! to your global registry cache. +//! +//! 3. **Locating:** Find the exact path to the downloaded crate package on your system. +//! *(e.g., `~/.cargo/registry/src/index.crates.io-/simplicity-calculator-0.1.0/`)* +//! +//! 4. **Linking:** Pass this path to the compiler using +//! the `--lib` flag, assigning it a custom alias: +//! ```text +//! simc path/to/main.simf --lib calc=~/.cargo/registry/src/.../simplicity-calculator-0.1.0/ +//! ``` +//! +//! 5. **Importing:** Inside your `main.simf`, start with the assigned alias (`calc`). +//! If the `.simf` file is nested inside subdirectories, the path must reflect +//! that folder structure. Follow the alias with the directory names, the target +//! filename, and finally the items to import: +//! ```text +//! // If `calculator.simf` is directly in the library's root: +//! use calc::calculator::{add_32, subtract_32}; +//! +//! // If `calculator.simf` is nested inside a `utils` directory: +//! use calc::utils::calculator::{add_32, subtract_32}; +//! ``` +//! +//! ## Distributing via npm +//! +//! **(This section is not implemented yet.)** + #[cfg(test)] pub mod tests; -use std::collections::{HashMap, VecDeque}; +use std::collections::{BTreeMap, HashMap, VecDeque}; +use std::fmt; use std::path::{Path, PathBuf}; use std::sync::Arc; use crate::error::{ErrorCollector, Span}; +use crate::impl_eq_hash; use crate::parse::{self, ParseFromStrWithErrors, Visibility}; -use crate::resolution::LibConfig; +use crate::resolution::{get_full_path, LibTable, SourceName}; use crate::str::Identifier; /// Represents a single, isolated file in the SimplicityHL project. @@ -27,17 +96,17 @@ pub struct ProjectGraph { /// The configuration environment. /// Used to resolve xternal library dependencies and invoke their associated functions. - pub config: Arc, + pub config: Arc, - /// Fast lookup: File Path -> Module ID. + /// Fast lookup: `SourceName` -> Module ID. /// A reverse index mapping absolute file paths to their internal IDs. /// This solves the duplication problem, ensuring each file is only parsed once. - pub lookup: HashMap, + pub lookup: HashMap, - /// Fast lookup: Module ID -> File Path. + /// Fast lookup: Module ID -> `SourceName`. /// A direct index mapping internal IDs back to their absolute file paths. /// This serves as the exact inverse of the `lookup` map. - pub paths: Vec, + pub paths: Arc<[SourceName]>, /// The Adjacency List: Defines the Directed acyclic Graph (DAG) of imports. /// @@ -49,18 +118,108 @@ pub struct ProjectGraph { pub dependencies: HashMap>, } +pub type FileResolutions = BTreeMap; + +/// The final, flattened representation of a SimplicityHL program. +/// +/// This struct holds the fully resolved sequence of items, paths, and scope +/// resolutions, ready to be passed to the next stage of the compiler. #[derive(Clone, Debug)] -pub struct Resolution { - pub visibility: Visibility, +pub struct Program { + /// The linear sequence of compiled items (`Functions`, `TypeAliases`, etc.). + items: Arc<[parse::Item]>, + + /// The list of source files that make up this program. + paths: Arc<[SourceName]>, + + /// The visibility and scoping resolutions for each file. + // Use BTreeMap instead of HashMap for the impl_eq_hash! macro. + resolutions: Arc<[FileResolutions]>, + + span: Span, } -pub struct Program { - //pub graph: ProjectGraph, - pub items: Arc<[parse::Item]>, - pub scope_items: Vec>, - pub span: Span, +impl Program { + /// Converts a parsed one-file program directly into a driver program. + /// + /// This function takes a raw `parse::Program` and converts it into a `driver::Program`. + /// It is strictly meant for single-file programs. Because it bypasses the full `ProjectGraph` + /// dependency resolution, it will throw an error if the file contains any `use` statements. + /// + /// # Arguments + /// * `parsed` - The parsed single file. + /// * `root_path` - The source path of the file. + /// + /// # Errors + /// Returns an `Err(String)` if the file contains `use` statements, or if registering an item fails. + pub fn from_parse(parsed: &parse::Program, root_path: SourceName) -> Result { + let root_path = root_path.without_extension(); + + let mut items: Vec = Vec::new(); + let mut resolutions: Vec = vec![BTreeMap::new()]; + + let main_file_id = 0usize; + let mut errors: Vec = Vec::new(); + + for item in parsed.items() { + match item { + parse::Item::Use(_) => { + errors.push("Unsuitable Use type".to_string()); + } + parse::Item::TypeAlias(alias) => { + ProjectGraph::register_def( + &mut items, + &mut resolutions, + main_file_id, + item, + alias.name().clone().into(), + &parse::Visibility::Public, + ); + } + parse::Item::Function(function) => { + ProjectGraph::register_def( + &mut items, + &mut resolutions, + main_file_id, + item, + function.name().clone().into(), + &parse::Visibility::Public, + ); + } + parse::Item::Module => {} + } + } + + if !errors.is_empty() { + return Err(errors.join("\n")); + } + + Ok(Program { + items: items.into(), + paths: Arc::from([root_path]), + resolutions: resolutions.into(), + span: *parsed.as_ref(), + }) + } + + /// Access the items of the program. + pub fn items(&self) -> &[parse::Item] { + &self.items + } + + /// Access the paths of the program + pub fn paths(&self) -> &[SourceName] { + &self.paths + } + + /// Access the scope items of the program. + pub fn resolutions(&self) -> &[FileResolutions] { + &self.resolutions + } } +impl_eq_hash!(Program; items, paths, resolutions); + #[derive(Debug)] pub enum C3Error { CycleDetected(Vec), @@ -113,16 +272,36 @@ fn merge(mut seqs: Vec>) -> Option> { } impl ProjectGraph { - pub fn new(config: Arc, root_program: &parse::Program) -> Result { + /// Initializes a new `ProjectGraph` by parsing the root program and discovering all dependencies. + /// + /// Performs a BFS to recursively parse `use` statements, + /// building a DAG of the project's modules. + /// + /// # Arguments + /// * `source_name` - The root file path (extension is stripped internally). + /// * `config` - Library configuration for resolving external imports. + /// * `root_program` - The parsed root file. + /// + /// # Errors + /// + /// This function will return an `Err(String)` in the following scenarios: + /// * A referenced module file (e.g., declared via a `use` statement) does not exist on the file system. + /// * The `parse_and_get_program` function encounters a syntax error while trying to parse one of the discovered dependency files. + pub fn new( + source_name: SourceName, + config: Arc, + root_program: &parse::Program, + ) -> Result { + let source_name = source_name.without_extension(); let mut modules: Vec = vec![Module { parsed_program: root_program.clone(), }]; - let mut lookup: HashMap = HashMap::new(); - let mut paths: Vec = vec![config.root_path.clone()]; + let mut lookup: HashMap = HashMap::new(); + let mut paths: Vec = vec![source_name.clone()]; let mut dependencies: HashMap> = HashMap::new(); let root_id = 0; - lookup.insert(config.root_path.clone(), root_id); + lookup.insert(source_name, root_id); dependencies.insert(root_id, Vec::new()); // Implementation of the standard BFS algorithm with memoization and queue @@ -135,7 +314,7 @@ impl ProjectGraph { for elem in current_program.items() { if let parse::Item::Use(use_decl) = elem { - if let Ok(path) = config.get_full_path(use_decl) { + if let Ok(path) = get_full_path(&config, use_decl) { pending_imports.push(path); } } @@ -143,12 +322,13 @@ impl ProjectGraph { for path in pending_imports { let full_path = path.with_extension("simf"); + let source_path = SourceName::Real(path); if !full_path.is_file() { return Err(format!("File in {:?}, does not exist", full_path)); } - if let Some(&existing_id) = lookup.get(&path) { + if let Some(&existing_id) = lookup.get(&source_path) { let deps = dependencies.entry(curr_id).or_default(); if !deps.contains(&existing_id) { deps.push(existing_id); @@ -162,8 +342,8 @@ impl ProjectGraph { modules.push(Module { parsed_program: program, }); - lookup.insert(path.clone(), last_ind); - paths.push(path.clone()); + lookup.insert(source_path.clone(), last_ind); + paths.push(source_path.clone()); dependencies.entry(curr_id).or_default().push(last_ind); queue.push_back(last_ind); @@ -174,7 +354,7 @@ impl ProjectGraph { modules, config, lookup, - paths, + paths: paths.into(), dependencies, }) } @@ -239,54 +419,51 @@ impl ProjectGraph { Ok(result) } + /// Validates and registers an imported item from a dependency file. + /// + /// Ensures that the imported item is marked as public in the source file. + /// If validation passes, the item is inserted into the current file's resolution table. fn process_use_item( - scope_items: &mut [HashMap], + resolutions: &mut [FileResolutions], file_id: usize, ind: usize, elem: &Identifier, use_decl_visibility: Visibility, ) -> Result<(), String> { - if matches!( - scope_items[ind][elem].visibility, - parse::Visibility::Private - ) { + if matches!(resolutions[ind][elem], parse::Visibility::Private) { return Err(format!( "Function {} is private and cannot be used.", elem.as_inner() )); } - scope_items[file_id].insert( - elem.clone(), - Resolution { - visibility: use_decl_visibility, - }, - ); + resolutions[file_id].insert(elem.clone(), use_decl_visibility); Ok(()) } + /// Registers a newly defined item (like a `Function` or `TypeAlias`) in the resolution table. fn register_def( items: &mut Vec, - scope: &mut HashMap, + resolutions: &mut [FileResolutions], + file_id: usize, item: &parse::Item, name: Identifier, vis: &parse::Visibility, ) { items.push(item.clone()); - scope.insert( - name, - Resolution { - visibility: vis.clone(), - }, - ); + resolutions[file_id].insert(name, vis.clone()); } + /// Constructs the final flattened `Program` structure using the calculated C3 order. + /// + /// Iterates through the modules in the exact order specified by the C3 algorithm. + /// It resolves `use` imports, registers definitions, and enforces visibility rules, + /// combining everything into a single, one-dimensional `Program` structure. // TODO: @LesterEvSe, consider processing more than one error at a time fn build_program(&self, order: &Vec) -> Result { let mut items: Vec = Vec::new(); - let mut scope_items: Vec> = - vec![HashMap::new(); order.len()]; + let mut resolutions: Vec = vec![BTreeMap::new(); order.len()]; for &file_id in order { let program_items = self.modules[file_id].parsed_program.items(); @@ -294,8 +471,9 @@ impl ProjectGraph { for elem in program_items { match elem { parse::Item::Use(use_decl) => { - let full_path = self.config.get_full_path(use_decl)?; - let ind = self.lookup[&full_path]; + let full_path = get_full_path(&self.config, use_decl)?; + let source_full_path = SourceName::Real(full_path); + let ind = self.lookup[&source_full_path]; let visibility = use_decl.visibility(); let use_targets = match use_decl.items() { @@ -305,7 +483,7 @@ impl ProjectGraph { for target in use_targets { ProjectGraph::process_use_item( - &mut scope_items, + &mut resolutions, file_id, ind, target, @@ -316,7 +494,8 @@ impl ProjectGraph { parse::Item::TypeAlias(alias) => { Self::register_def( &mut items, - &mut scope_items[file_id], + &mut resolutions, + file_id, elem, alias.name().clone().into(), alias.visibility(), @@ -325,7 +504,8 @@ impl ProjectGraph { parse::Item::Function(function) => { Self::register_def( &mut items, - &mut scope_items[file_id], + &mut resolutions, + file_id, elem, function.name().clone().into(), function.visibility(), @@ -338,11 +518,13 @@ impl ProjectGraph { Ok(Program { items: items.into(), - scope_items, + paths: self.paths.clone(), + resolutions: resolutions.into(), span: *self.modules[0].parsed_program.as_ref(), }) } + /// Resolves the final compilation order and builds the `Program`. pub fn resolve_complication_order(&self) -> Result { // TODO: @LesterEvSe, resolve errors more appropriately let mut order = self.c3_linearize().unwrap(); @@ -350,3 +532,44 @@ impl ProjectGraph { self.build_program(&order) } } + +impl fmt::Display for Program { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // 1. Print the actual program code first + for item in self.items.iter() { + writeln!(f, "{item}")?; + } + + // 2. Open the Resolution Table block + writeln!(f, "\n/* --- RESOLUTION TABLE ---")?; + + // 3. Logic: Empty vs Populated + if self.resolutions.is_empty() { + writeln!(f, " EMPTY")?; + } else { + for (file_id, scope) in self.resolutions.iter().enumerate() { + if scope.is_empty() { + writeln!(f, " File ID {}: (No resolutions)", file_id)?; + continue; + } + + writeln!(f, " File ID {}:", file_id)?; + + for (ident, resolution) in scope { + writeln!(f, " {}: {:?}", ident, resolution)?; + } + } + } + + // 4. Close the block (This runs for both empty and non-empty cases) + writeln!(f, "*/")?; + + Ok(()) + } +} + +impl AsRef for Program { + fn as_ref(&self) -> &Span { + &self.span + } +} diff --git a/src/driver/tests.rs b/src/driver/tests.rs index a0b0fc6f..c306f28d 100644 --- a/src/driver/tests.rs +++ b/src/driver/tests.rs @@ -4,7 +4,7 @@ use std::io::Write; use std::path::Path; use tempfile::TempDir; -fn create_simf_file(dir: &Path, rel_path: &str, content: &str) -> PathBuf { +pub fn create_simf_file(dir: &Path, rel_path: &str, content: &str) -> PathBuf { let full_path = dir.join(rel_path); // Ensure parent directories exist @@ -51,13 +51,16 @@ fn setup_graph(files: Vec<(&str, &str)>) -> (ProjectGraph, HashMap FileID let mut file_ids = HashMap::new(); for (path, id) in &graph.lookup { - let file_name = path.file_name().unwrap().to_string_lossy().to_string(); + let file_name = match path { + SourceName::Real(path) => path.file_name().unwrap().to_string_lossy().to_string(), + SourceName::Virtual(name) => name.clone(), + }; file_ids.insert(file_name, *id); } @@ -251,19 +254,19 @@ mod visibility_and_access_control { let program = graph .build_program(&order) .expect("Failed to build program"); - let scope = &program.scope_items[root_id]; + let scope = &program.resolutions[root_id]; // Check private function let private_res = scope .get(&Identifier::from("private_fn")) .expect("private_fn missing"); - assert_eq!(private_res.visibility, Visibility::Private); + assert_eq!(*private_res, Visibility::Private); // Check public function let public_res = scope .get(&Identifier::from("public_fn")) .expect("public_fn missing"); - assert_eq!(public_res.visibility, Visibility::Public); + assert_eq!(*public_res, Visibility::Public); } #[test] @@ -292,27 +295,27 @@ mod visibility_and_access_control { .expect("Failed to build program"); // Check B's scope - let scope_b = &program.scope_items[id_b]; + let scope_b = &program.resolutions[id_b]; let foo_in_b = scope_b .get(&Identifier::from("foo")) .expect("foo missing in B"); // This is the critical check: Did `pub use` make it Public in B? assert_eq!( - foo_in_b.visibility, + *foo_in_b, Visibility::Public, "B should re-export foo as Public" ); // Check Root's scope - let scope_root = &program.scope_items[id_root]; + let scope_root = &program.resolutions[id_root]; let foo_in_root = scope_root .get(&Identifier::from("foo")) .expect("foo missing in Root"); // Root imported it via `use` (not pub use), so it should be Private in Root assert_eq!( - foo_in_root.visibility, + *foo_in_root, Visibility::Private, "Root should have foo as Private" ); @@ -365,9 +368,8 @@ mod error_diganostic_and_terminal_formatting { lib_map.insert("std".to_string(), temp_dir.path().join("libs/std")); let root_program = parse_root(&root_path); - let config = Arc::from(LibConfig::new(lib_map, &root_path)); - let result = ProjectGraph::new(config, &root_program); - + let source_name = SourceName::Real(root_path); + let result = ProjectGraph::new(source_name, Arc::from(lib_map), &root_program); assert!(result.is_err(), "Should fail for missing file"); let err_msg = result.err().unwrap(); assert!( diff --git a/src/error.rs b/src/error.rs index 1ded6fdd..b5be77ef 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,6 @@ use std::fmt; use std::ops::Range; +use std::path::PathBuf; use std::sync::Arc; use chumsky::error::Error as ChumskyError; @@ -398,6 +399,7 @@ impl fmt::Display for ErrorCollector { /// Records _what_ happened but not where. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum Error { + UnknownLibrary(String), ArraySizeNonZero(usize), ListBoundPow2(usize), BitStringPow2(usize), @@ -415,11 +417,13 @@ pub enum Error { CannotCompile(String), JetDoesNotExist(JetName), InvalidCast(ResolvedType, ResolvedType), + FileNotFound(PathBuf), MainNoInputs, MainNoOutput, MainRequired, FunctionRedefined(FunctionName), FunctionUndefined(FunctionName), + FunctionIsPrivate(FunctionName), InvalidNumberOfArguments(usize, usize), FunctionNotFoldable(FunctionName), FunctionNotLoopable(FunctionName), @@ -444,6 +448,10 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Error::UnknownLibrary(name) => write!( + f, + "Unknown module or library '{name}'" + ), Error::ArraySizeNonZero(size) => write!( f, "Expected a non-negative integer as array size, found {size}" @@ -495,6 +503,10 @@ impl fmt::Display for Error { f, "Cannot cast values of type `{source}` as values of type `{target}`" ), + Error::FileNotFound(path) => write!( + f, + "File `{}` not found", path.to_string_lossy() + ), Error::MainNoInputs => write!( f, "Main function takes no input parameters" @@ -515,6 +527,10 @@ impl fmt::Display for Error { f, "Function `{name}` was called but not defined" ), + Error::FunctionIsPrivate(name) => write!( + f, + "Function `{name}` is private" + ), Error::InvalidNumberOfArguments(expected, found) => write!( f, "Expected {expected} arguments, found {found} arguments" diff --git a/src/lib.rs b/src/lib.rs index 501d53d8..fe948acb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,7 @@ use crate::debug::DebugSymbols; use crate::driver::ProjectGraph; use crate::error::{ErrorCollector, WithFile}; use crate::parse::ParseFromStrWithErrors; -use crate::resolution::LibConfig; +use crate::resolution::{LibTable, SourceName}; pub use crate::types::ResolvedType; pub use crate::value::Value; pub use crate::witness::{Arguments, Parameters, WitnessTypes, WitnessValues}; @@ -51,7 +51,8 @@ pub struct TemplateProgram { impl TemplateProgram { pub fn new_with_dep>>( - lib_cfg: Option<&LibConfig>, + source_name: SourceName, + libraries: Arc, s: Str, ) -> Result { let file = s.into(); @@ -60,17 +61,17 @@ impl TemplateProgram { if let Some(program) = parse_program { // TODO: Consider a proper resolution strategy later. - let _: Option = if let Some(cfg) = lib_cfg { - let config_arc = Arc::new(cfg.clone()); - let graph = ProjectGraph::new(config_arc, &program)?; + let driver_program: driver::Program = if libraries.is_empty() { + driver::Program::from_parse(&program, source_name)? + } else { + let graph = ProjectGraph::new(source_name, libraries, &program)?; // TODO: Perhaps add an `error_handler` here, too. - Some(graph.resolve_complication_order()?) - } else { - None + graph.resolve_complication_order()? }; - let ast_program = ast::Program::analyze(&program).with_file(Arc::clone(&file))?; + let ast_program = + ast::Program::analyze(&driver_program).with_file(Arc::clone(&file))?; Ok(Self { simfony: ast_program, file, @@ -90,7 +91,12 @@ impl TemplateProgram { let mut error_handler = ErrorCollector::new(Arc::clone(&file)); let parse_program = parse::Program::parse_from_str_with_errors(&file, &mut error_handler); if let Some(program) = parse_program { - let ast_program = ast::Program::analyze(&program).with_file(Arc::clone(&file))?; + let driver_program = driver::Program::from_parse( + &program, + SourceName::Virtual("script.simf".to_string()), + )?; + let ast_program = + ast::Program::analyze(&driver_program).with_file(Arc::clone(&file))?; Ok(Self { simfony: ast_program, file, @@ -157,12 +163,13 @@ pub struct CompiledProgram { impl CompiledProgram { pub fn new_with_dep>>( - lib_cfg: Option<&LibConfig>, + source_name: SourceName, + lib_cfg: Arc, s: Str, arguments: Arguments, include_debug_symbols: bool, ) -> Result { - TemplateProgram::new_with_dep(lib_cfg, s) + TemplateProgram::new_with_dep(source_name, lib_cfg, s) .and_then(|template| template.instantiate(arguments, include_debug_symbols)) } @@ -250,13 +257,20 @@ pub struct SatisfiedProgram { impl SatisfiedProgram { pub fn new_with_dep>>( - lib_cfg: Option<&LibConfig>, + source_name: SourceName, + lib_cfg: Arc, s: Str, arguments: Arguments, witness_values: WitnessValues, include_debug_symbols: bool, ) -> Result { - let compiled = CompiledProgram::new_with_dep(lib_cfg, s, arguments, include_debug_symbols)?; + let compiled = CompiledProgram::new_with_dep( + source_name, + lib_cfg, + s, + arguments, + include_debug_symbols, + )?; compiled.satisfy(witness_values) } @@ -358,6 +372,7 @@ pub(crate) mod tests { use base64::engine::general_purpose::STANDARD; use simplicity::BitMachine; use std::borrow::Cow; + use std::collections::HashMap; use std::path::Path; use crate::*; @@ -375,6 +390,29 @@ pub(crate) mod tests { Self::template_text(Cow::Owned(program_text)) } + pub fn template_lib( + source_name: SourceName, + libraries: Arc, + program_file: &Path, + ) -> Self { + let program_text = std::fs::read_to_string(program_file).unwrap(); + + let program = match TemplateProgram::new_with_dep( + source_name, + libraries, + program_text.as_ref(), + ) { + Ok(x) => x, + Err(error) => panic!("{error}"), + }; + Self { + program, + lock_time: elements::LockTime::ZERO, + sequence: elements::Sequence::MAX, + include_fee_output: false, + } + } + pub fn template_text(program_text: Cow) -> Self { let program = match TemplateProgram::new(program_text.as_ref()) { Ok(x) => x, @@ -416,6 +454,36 @@ pub(crate) mod tests { } impl TestCase { + pub fn temp_env( + main_content: &str, + libs: Vec<(&str, &str, &str)>, + ) -> (Self, tempfile::TempDir) { + let temp_dir = tempfile::TempDir::new().unwrap(); + let main_path = + driver::tests::create_simf_file(temp_dir.path(), "main.simf", main_content); + let mut lib_paths = Vec::new(); + + for (lib_name, rel_path, content) in libs { + driver::tests::create_simf_file(temp_dir.path(), rel_path, content); + + let lib_root = temp_dir + .path() + .join(rel_path) + .parent() + .unwrap() + .to_path_buf(); + lib_paths.push((lib_name.to_string(), lib_root)); + } + + let libs_refs: Vec<(&str, &std::path::Path)> = lib_paths + .iter() + .map(|(k, v)| (k.as_str(), v.as_path())) + .collect(); + + let test_case = Self::program_file_with_libs(&main_path, libs_refs); + (test_case, temp_dir) + } + pub fn program_file>(program_file_path: P) -> Self { TestCase::::template_file(program_file_path) .with_arguments(Arguments::default()) @@ -426,6 +494,28 @@ pub(crate) mod tests { .with_arguments(Arguments::default()) } + pub fn program_file_with_libs(program_file_path: P, libs: I) -> Self + where + P: AsRef, + I: IntoIterator, // Magic trait: accepts anything we can iterate over + K: Into, + V: AsRef, + { + let path_ref = program_file_path.as_ref(); + + let mut libraries = HashMap::new(); + for (k, v) in libs { + libraries.insert(k.into(), v.as_ref().to_path_buf()); + } + + let source_name = + SourceName::Real(path_ref.parent().unwrap_or(Path::new("")).to_path_buf()); + + // 3. Delegate to your existing template_lib method + TestCase::::template_lib(source_name, Arc::from(libraries), path_ref) + .with_arguments(Arguments::default()) + } + #[cfg(feature = "serde")] pub fn with_witness_file>( self, @@ -525,6 +615,157 @@ pub(crate) mod tests { } } + // Real test cases + #[test] + fn module_simple() { + let (test, _dir) = TestCase::temp_env( + "use temp::math::add; fn main() {}", + vec![("temp", "temp/math.simf", "pub fn add() {}")], + ); + + test.with_witness_values(WitnessValues::default()) + .assert_run_success(); + } + + #[test] + fn diamond_dependency_resolution() { + let main_code = r#" + use temp::left::get_left; + use temp::right::get_right; + + fn main() { + let a: BaseType = get_left(); + let b: BaseType = get_right(); + let (_, c): (bool, BaseType) = jet::add_32(a, b); + assert!(jet::eq_32(c, 3)); + } + "#; + + let libs = vec![ + ("temp", "temp/base.simf", "pub type BaseType = u32;"), + ( + "temp", + "temp/left.simf", + "pub use temp::base::BaseType; pub fn get_left() -> BaseType { 1 }", + ), + ( + "temp", + "temp/right.simf", + "pub use temp::base::BaseType; pub fn get_right() -> BaseType { 2 }", + ), + ]; + + let (test, _dir) = TestCase::temp_env(main_code, libs); + test.with_witness_values(WitnessValues::default()) + .assert_run_success(); + } + + #[test] + #[should_panic(expected = "CycleDetected")] + fn cyclic_dependency_error() { + let main_code = "use temp::module_a::TypeA; fn main() {}"; + + let libs = vec![ + ( + "temp", + "temp/module_a.simf", + "pub use temp::module_b::TypeB; pub type TypeA = u32;", + ), + ( + "temp", + "temp/module_b.simf", + "pub use temp::module_a::TypeA; pub type TypeB = u32;", + ), + ]; + + let (test, _dir) = TestCase::temp_env(main_code, libs); + test.with_witness_values(WitnessValues::default()) + .assert_run_success(); + } + + #[test] + fn deep_reexport_chain() { + let main_code = r#" + use temp::level1::{CoreSmth, core_val}; + + fn main() { + let val: CoreSmth = core_val(); + assert!(jet::eq_32(val, 42)); + } + "#; + + let libs = vec![ + ( + "temp", + "temp/level3.simf", + "pub type CoreSmth = u32; pub fn core_val() -> CoreSmth { 42 }", + ), + ( + "temp", + "temp/level2.simf", + "pub use temp::level3::{CoreSmth, core_val};", + ), + ( + "temp", + "temp/level1.simf", + "pub use temp::level2::{CoreSmth, core_val};", + ), + ]; + + let (test, _dir) = TestCase::temp_env(main_code, libs); + test.with_witness_values(WitnessValues::default()) + .assert_run_success(); + } + + #[test] + #[should_panic(expected = "Function SecretType is private and cannot be used")] + fn private_type_visibility_error() { + let main_code = r#" + use temp::hidden::SecretType; + fn main() {} + "#; + + let libs = vec![( + "temp", + "temp/hidden.simf", + "type SecretType = u32; pub fn ok() {}", + )]; + + let (test, _dir) = TestCase::temp_env(main_code, libs); + test.with_witness_values(WitnessValues::default()) + .assert_run_success(); + } + + #[test] + #[should_panic(expected = "was defined multiple times")] + fn name_collision_error() { + let main_code = r#" + use temp::mod_a::Value; + use temp::mod_b::Value; + + fn main() {} + "#; + + let libs = vec![ + ("temp", "temp/mod_a.simf", "pub type Value = u32;"), + ("temp", "temp/mod_b.simf", "pub type Value = u32;"), + ]; + + let (test, _dir) = TestCase::temp_env(main_code, libs); + test.with_witness_values(WitnessValues::default()) + .assert_run_success(); + } + + #[test] + fn single_lib() { + TestCase::program_file_with_libs( + "./examples/single_lib/main.simf", + [("temp", "./examples/single_lib/temp")], + ) + .with_witness_values(WitnessValues::default()) + .assert_run_success(); + } + #[test] fn cat() { TestCase::program_file("./examples/cat.simf") diff --git a/src/main.rs b/src/main.rs index 6fc2444a..c4cd0d27 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,9 @@ use base64::display::Base64Display; use base64::engine::general_purpose::STANDARD; use clap::{Arg, ArgAction, Command}; -use simplicityhl::resolution::LibConfig; +use simplicityhl::resolution::{LibTable, SourceName}; use simplicityhl::{AbiMeta, CompiledProgram}; -use std::{collections::HashMap, env, fmt, path::PathBuf}; +use std::{env, fmt, sync::Arc}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] /// The compilation output. @@ -124,7 +124,7 @@ fn main() -> Result<(), Box> { let lib_args = matches.get_many::("library").unwrap_or_default(); - let library_map: HashMap = lib_args + let libraries: LibTable = lib_args .map(|arg| { let parts: Vec<&str> = arg.splitn(2, '=').collect(); @@ -140,9 +140,9 @@ fn main() -> Result<(), Box> { }) .collect(); - let config = LibConfig::new(library_map, prog_path); let compiled = match CompiledProgram::new_with_dep( - Some(&config), + SourceName::Real(prog_path.to_path_buf()), + Arc::from(libraries), prog_text, args_opt, include_debug_symbols, diff --git a/src/parse.rs b/src/parse.rs index 32c57788..ad808ed4 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -107,6 +107,7 @@ pub enum UseItems { /// Definition of a function. #[derive(Clone, Debug)] pub struct Function { + file_id: usize, // The type required for the driver visibility: Visibility, name: FunctionName, params: Arc<[FunctionParam]>, @@ -124,6 +125,11 @@ pub enum Visibility { } impl Function { + /// Access the file ID of the function. + pub fn file_id(&self) -> usize { + self.file_id + } + /// Access the visibility of the function. pub fn visibility(&self) -> &Visibility { &self.visibility @@ -157,7 +163,7 @@ impl Function { } } -impl_eq_hash!(Function; visibility, name, params, ret, body); +impl_eq_hash!(Function; file_id, visibility, name, params, ret, body); /// Parameter of a function. #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -315,7 +321,7 @@ impl TypeAlias { } } -impl_eq_hash!(TypeAlias; name, ty); +impl_eq_hash!(TypeAlias; visibility, name, ty); /// An expression is something that returns a value. #[derive(Clone, Debug)] @@ -337,7 +343,7 @@ impl Expression { /// Convert the expression into a block expression. #[cfg(feature = "arbitrary")] - fn into_block(self) -> Self { + pub(crate) fn into_block(self) -> Self { match self.inner { ExpressionInner::Single(_) => Expression { span: self.span, @@ -633,15 +639,30 @@ impl fmt::Display for Item { } } +impl fmt::Display for Visibility { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Private => write!(f, ""), + Self::Public => write!(f, "pub "), + } + } +} + impl fmt::Display for TypeAlias { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "type {} = {};", self.name(), self.ty()) + write!( + f, + "{}type {} = {};", + self.visibility(), + self.name(), + self.ty() + ) } } impl fmt::Display for Function { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "fn {}(", self.name())?; + write!(f, "{}fn {}(", self.visibility(), self.name())?; for (i, param) in self.params().iter().enumerate() { if 0 < i { write!(f, ", ")?; @@ -658,11 +679,7 @@ impl fmt::Display for Function { impl fmt::Display for UseDecl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Visibility::Public = self.visibility { - write!(f, "pub ")?; - } - - let _ = write!(f, "use "); + let _ = write!(f, "{}use ", self.visibility()); for (i, segment) in self.path.iter().enumerate() { if i > 0 { @@ -1289,6 +1306,7 @@ impl ChumskyParse for Function { where I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, { + let file_id = 0usize; let visibility = just(Token::Pub) .to(Visibility::Public) .or_not() @@ -1332,7 +1350,8 @@ impl ChumskyParse for Function { .then(params) .then(ret) .then(body) - .map_with(|((((visibility, name), params), ret), body), e| Self { + .map_with(move |((((visibility, name), params), ret), body), e| Self { + file_id, visibility, name, params, @@ -2125,6 +2144,7 @@ impl crate::ArbitraryRec for Function { fn arbitrary_rec(u: &mut arbitrary::Unstructured, budget: usize) -> arbitrary::Result { use arbitrary::Arbitrary; + let file_id: usize = u.int_in_range(0..=3)?; let visibility = Visibility::arbitrary(u)?; let name = FunctionName::arbitrary(u)?; let len = u.int_in_range(0..=3)?; @@ -2134,6 +2154,7 @@ impl crate::ArbitraryRec for Function { let ret = Option::::arbitrary(u)?; let body = Expression::arbitrary_rec(u, budget).map(Expression::into_block)?; Ok(Self { + file_id, visibility, name, params, diff --git a/src/resolution.rs b/src/resolution.rs index 3397776d..10c4c99d 100644 --- a/src/resolution.rs +++ b/src/resolution.rs @@ -1,38 +1,44 @@ use std::collections::HashMap; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use crate::parse::UseDecl; -#[derive(Debug, Clone)] -pub struct LibConfig { - pub libraries: HashMap, - pub root_path: PathBuf, +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub enum SourceName { + Real(PathBuf), + Virtual(String), } -impl LibConfig { - pub fn new(libraries: HashMap, raw_root_path: &Path) -> Self { - let root_path = raw_root_path.with_extension(""); - - Self { - libraries, - root_path, +impl SourceName { + pub fn without_extension(&self) -> SourceName { + match self { + SourceName::Real(path) => SourceName::Real(path.with_extension("")), + SourceName::Virtual(name) => SourceName::Virtual(name.clone()), } } +} - pub fn get_full_path(&self, use_decl: &UseDecl) -> Result { - let parts: Vec<&str> = use_decl.path().iter().map(|s| s.as_ref()).collect(); - let first_segment = parts[0]; +impl Default for SourceName { + fn default() -> Self { + SourceName::Virtual("".to_string()) + } +} - if let Some(lib_root) = self.libraries.get(first_segment) { - let mut full_path = lib_root.clone(); - full_path.extend(&parts[1..]); +pub type LibTable = HashMap; - return Ok(full_path); - } +pub fn get_full_path(libraries: &LibTable, use_decl: &UseDecl) -> Result { + let parts: Vec<&str> = use_decl.path().iter().map(|s| s.as_ref()).collect(); + let first_segment = parts[0]; + + if let Some(lib_root) = libraries.get(first_segment) { + let mut full_path = lib_root.clone(); + full_path.extend(&parts[1..]); - Err(format!( - "Unknown module or library '{}'. Did you forget to pass --lib {}=...?", - first_segment, first_segment, - )) + return Ok(full_path); } + + Err(format!( + "Unknown module or library '{}'. Did you forget to pass --lib {}=...?", + first_segment, first_segment, + )) } diff --git a/src/witness.rs b/src/witness.rs index 6d6ffefc..81e78f38 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -221,8 +221,9 @@ impl crate::ArbitraryOfType for Arguments { mod tests { use super::*; use crate::parse::ParseFromStr; + use crate::resolution::SourceName; use crate::value::ValueConstructible; - use crate::{ast, parse, CompiledProgram, SatisfiedProgram}; + use crate::{ast, driver, parse, CompiledProgram, SatisfiedProgram}; #[test] fn witness_reuse() { @@ -230,7 +231,9 @@ mod tests { assert!(jet::eq_32(witness::A, witness::A)); }"#; let program = parse::Program::parse_from_str(s).expect("parsing works"); - match ast::Program::analyze(&program).map_err(Error::from) { + let driver_program = + driver::Program::from_parse(&program, SourceName::default()).expect("driver works"); + match ast::Program::analyze(&driver_program).map_err(Error::from) { Ok(_) => panic!("Witness reuse was falsely accepted"), Err(Error::WitnessReused(..)) => {} Err(error) => panic!("Unexpected error: {error}"),