From 72581d5213fcaba8f61bbeb16b71b4035ed85b3f Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Tue, 10 Feb 2026 14:42:35 +0200 Subject: [PATCH 1/2] feat: add pub and use keywords and parse them --- examples/modules.simf | 12 ++ examples/modules/main.simf | 9 ++ examples/modules/temp/get_five.simf | 3 + src/ast.rs | 68 +++++++++- src/lexer.rs | 9 ++ src/parse.rs | 187 ++++++++++++++++++++++++++-- 6 files changed, 276 insertions(+), 12 deletions(-) create mode 100644 examples/modules.simf create mode 100644 examples/modules/main.simf create mode 100644 examples/modules/temp/get_five.simf diff --git a/examples/modules.simf b/examples/modules.simf new file mode 100644 index 00000000..bf3bead8 --- /dev/null +++ b/examples/modules.simf @@ -0,0 +1,12 @@ +use example::of::my::file; +pub use another::file::*; +pub use some::list::{of, items}; + +pub fn get_five() -> u32 { + 5 +} + +fn main() { + let five: u32 = dbg!(get_five()); + assert!(jet::eq_32(five, 5)); +} \ No newline at end of file diff --git a/examples/modules/main.simf b/examples/modules/main.simf new file mode 100644 index 00000000..857e2fc7 --- /dev/null +++ b/examples/modules/main.simf @@ -0,0 +1,9 @@ +//pub use temp::get_five; + +fn get_five() -> u32 { + 7 +} + +fn main() { + let five: u32 = get_five(); +} \ No newline at end of file diff --git a/examples/modules/temp/get_five.simf b/examples/modules/temp/get_five.simf new file mode 100644 index 00000000..e5def94a --- /dev/null +++ b/examples/modules/temp/get_five.simf @@ -0,0 +1,3 @@ +pub fn get_five() -> u32 { + 5 +} \ No newline at end of file diff --git a/src/ast.rs b/src/ast.rs index 6cda4851..6eda96d5 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -11,7 +11,7 @@ use simplicity::jet::Elements; use crate::debug::{CallTracker, DebugSymbols, TrackedCallName}; use crate::error::{Error, RichError, Span, WithSpan}; use crate::num::{NonZeroPow2Usize, Pow2Usize}; -use crate::parse::MatchPattern; +use crate::parse::{MatchPattern, UseDecl}; use crate::pattern::Pattern; use crate::str::{AliasName, FunctionName, Identifier, ModuleName, WitnessName}; use crate::types::{ @@ -73,6 +73,8 @@ pub enum Item { TypeAlias, /// A function. Function(Function), + /// A use declaration + Use(UseDecl), /// A module, which is ignored. Module, } @@ -759,6 +761,12 @@ impl AbstractSyntaxTree for Item { parse::Item::Function(function) => { Function::analyze(function, ty, scope).map(Self::Function) } + parse::Item::Use(_) => { + println!("WARN: Skipping use declaration (not implemented yet)"); + Ok(Self::Module) + //todo!() + //Use::analyze(use_declaration).map(Self::Use) + } parse::Item::Module => Ok(Self::Module), } } @@ -820,6 +828,64 @@ impl AbstractSyntaxTree for Function { } } +/* +impl AbstractSyntaxTree for UseDecl { + type From = parse::UseDecl; + + 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"); + + if from.name().as_inner() != "main" { + let params = from + .params() + .iter() + .map(|param| { + let identifier = param.identifier().clone(); + let ty = scope.resolve(param.ty())?; + Ok(FunctionParam { identifier, ty }) + }) + .collect::, Error>>() + .with_span(from)?; + let ret = from + .ret() + .as_ref() + .map(|aliased| scope.resolve(aliased).with_span(from)) + .transpose()? + .unwrap_or_else(ResolvedType::unit); + scope.push_scope(); + 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(); + debug_assert!(scope.is_topmost()); + let function = CustomFunction { params, body }; + scope + .insert_function(from.name().clone(), function) + .with_span(from)?; + + return Ok(Self::Custom); + } + + if !from.params().is_empty() { + return Err(Error::MainNoInputs).with_span(from); + } + if let Some(aliased) = from.ret() { + let resolved = scope.resolve(aliased).with_span(from)?; + if !resolved.is_unit() { + return Err(Error::MainNoOutput).with_span(from); + } + } + + scope.push_main_scope(); + let body = Expression::analyze(from.body(), ty, scope)?; + scope.pop_main_scope(); + Ok(Self::Main(body)) + } +} +*/ + impl AbstractSyntaxTree for Statement { type From = parse::Statement; diff --git a/src/lexer.rs b/src/lexer.rs index 71c004b6..d2006922 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -11,6 +11,9 @@ pub type Tokens<'src> = Vec<(Token<'src>, crate::error::Span)>; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Token<'src> { // Keywords + Pub, + Use, + As, Fn, Let, Type, @@ -63,6 +66,9 @@ 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::As => write!(f, "as"), Token::Fn => write!(f, "fn"), Token::Let => write!(f, "let"), Token::Type => write!(f, "type"), @@ -134,6 +140,9 @@ 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, + "as" => Token::As, "fn" => Token::Fn, "let" => Token::Let, "type" => Token::Type, diff --git a/src/parse.rs b/src/parse.rs index f47dda5e..b2f0b7bc 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, // TODO: Maybe change to Arc<[Identifier]> for consisten + 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: Add aliases +#[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,19 @@ pub struct Function { span: Span, } +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +pub enum Visibility { + Public, + 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 +152,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 +279,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 +619,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 +651,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 +1243,7 @@ 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 +1267,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 +1279,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(|v| v.unwrap_or(Visibility::Private)) + .labelled("function visibility"); + let params = delimited_with_recovery( FunctionParam::parser() .separated_by(just(Token::Comma)) @@ -1204,12 +1316,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 +1333,48 @@ 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>, + { + // TODO: Check does it possible to parse `use a: : smth`, because we need parse only `use a::smth` + let double_colon = just(Token::Colon).then(just(Token::Colon)).labelled("::"); + + let visibility = + just(Token::Pub).to(Visibility::Public) + .or_not() + .map(|v| v.unwrap_or(Visibility::Private)); + + let path = Identifier::parser() + .then_ignore(double_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 @@ -1347,7 +1503,7 @@ impl ChumskyParse for CallName { where I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, { - let double_colon = just(Token::Colon).then(just(Token::Colon)).labelled("::"); + let double_colon: chumsky::label::Labelled, I, extra::Full>, chumsky::primitive::Just, I, extra::Full>, Token<'_>, Token<'_>, extra::Full>, &str> = just(Token::Colon).then(just(Token::Colon)).labelled("::"); let turbofish_start = double_colon.clone().then(just(Token::LAngle)).ignored(); @@ -1461,14 +1617,21 @@ impl ChumskyParse for TypeAlias { where I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, { + let visibility = + just(Token::Pub).to(Visibility::Public) + .or_not() + .map(|v| v.unwrap_or(Visibility::Private)); + let name = AliasName::parser().map_with(|name, e| (name, e.span())); - - just(Token::Type) + + 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(|(name, ty), e| Self { + .then_ignore(just(Token::Semi))) + .map_with(|(visibility, (name, ty)), e| Self { + visibility, name: name.0, ty, span: e.span(), @@ -1953,6 +2116,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 +2125,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 800315df56d21c139fb71c019527fb1b9b3233af Mon Sep 17 00:00:00 2001 From: LesterEvSe Date: Wed, 11 Feb 2026 14:14:59 +0200 Subject: [PATCH 2/2] feat: add the option CLI argument --lib and driver file --- Cargo.lock | 322 +++++++++++++++++++++++++++++++++++-- Cargo.toml | 5 +- examples/modules/main.simf | 7 +- src/driver.rs | 317 ++++++++++++++++++++++++++++++++++++ src/lib.rs | 38 ++++- src/main.rs | 47 +++++- src/parse.rs | 39 +++-- src/str.rs | 6 + src/tracker.rs | 4 +- src/witness.rs | 4 +- 10 files changed, 736 insertions(+), 53 deletions(-) create mode 100644 src/driver.rs diff --git a/Cargo.lock b/Cargo.lock index dbc6e55a..8e622e6d 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 = "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 = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +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 c3a8cc30..4634a8bf 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"] } @@ -152,7 +155,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/examples/modules/main.simf b/examples/modules/main.simf index 857e2fc7..ae05f4e4 100644 --- a/examples/modules/main.simf +++ b/examples/modules/main.simf @@ -1,9 +1,10 @@ -//pub use temp::get_five; +pub use temp::get_five::get_five; -fn get_five() -> u32 { +fn seven() -> u32 { 7 } fn main() { - let five: u32 = get_five(); + let five: u32 = dbg!(get_five()); + let seven: u32 = dbg!(seven()); } \ No newline at end of file diff --git a/src/driver.rs b/src/driver.rs new file mode 100644 index 00000000..58dc6db6 --- /dev/null +++ b/src/driver.rs @@ -0,0 +1,317 @@ +use std::collections::{HashMap, VecDeque}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use crate::error::ErrorCollector; +use crate::parse::{self, Item, ParseFromStrWithErrors, Program, UseDecl}; +use crate::LibConfig; + +/// Graph Node: One file = One module +#[derive(Debug, Clone)] +pub struct Module { + /// Parsed AST (your `parse::Program`) + /// Using Option to first create the node, then add the AST + pub parsed_program: Program, +} + +/// The Dependency Graph itself +pub struct ProjectGraph { + /// Arena Pattern: the data itself lies here. Vector guarantees data lives in one place. + pub modules: Vec, + + /// Fast lookup: Path -> ID + /// Solves the duplicate problem (so as not to parse a.simf twice) + pub lookup: HashMap, + + /// Adjacency list: Who depends on whom + pub dependencies: HashMap>, +} + +fn get_full_path( + library_map: &HashMap, + 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) = library_map.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, + )) +} + +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: &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 Item::Use(use_decl) = elem { + if let Ok(path) = get_full_path(&lib_cfg.libraries, 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, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::{self, File}; + use std::io::Write; + use std::path::Path; + use tempfile::TempDir; + + // --- Helper to setup environment --- + + // Creates a file with specific content in the temp directory + 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 + // (Assuming your parser works via a helper function) + fn parse_root(path: &Path) -> Program { + parse_and_get_program(path).expect("Root parsing failed") + } + + #[test] + fn test_simple_import() { + // Setup: + // root.simf -> "use std::math;" + // libs/std/math.simf -> "" + + let temp_dir = TempDir::new().unwrap(); + let root_path = create_simf_file(temp_dir.path(), "root.simf", "use std::math::some_func;"); + create_simf_file(temp_dir.path(), "libs/std/math.simf", ""); + + // Setup Library Map + let mut lib_map = HashMap::new(); + lib_map.insert("std".to_string(), temp_dir.path().join("libs/std")); + + // Parse Root + let root_program = parse_root(&root_path); + let config = LibConfig::new(lib_map, &root_path); + + // Run Logic + let graph = ProjectGraph::new(&config, &root_program).expect("Graph build failed"); + + // Assertions + assert_eq!(graph.modules.len(), 2, "Should have Root and Math module"); + assert!( + graph.dependencies[&0].contains(&1), + "Root should depend on Math" + ); + } + + #[test] + fn test_diamond_dependency_deduplication() { + // Setup: + // root -> imports A, B + // A -> imports Common + // B -> imports Common + // Expected: Common loaded ONLY ONCE. + + let temp_dir = TempDir::new().unwrap(); + let root_path = create_simf_file( + temp_dir.path(), + "root.simf", + "use lib::A::foo; use lib::B::bar;", + ); + create_simf_file( + temp_dir.path(), + "libs/lib/A.simf", + "use lib::Common::dummy1;", + ); + create_simf_file( + temp_dir.path(), + "libs/lib/B.simf", + "use lib::Common::dummy2;", + ); + create_simf_file(temp_dir.path(), "libs/lib/Common.simf", ""); // Empty leaf + + let mut lib_map = HashMap::new(); + lib_map.insert("lib".to_string(), temp_dir.path().join("libs/lib")); + + let root_program = parse_root(&root_path); + let config = LibConfig::new(lib_map, &root_path); + let graph = ProjectGraph::new(&config, &root_program).expect("Graph build failed"); + + // Assertions + // Structure: Root(0), A(1), B(2), Common(3) + assert_eq!( + graph.modules.len(), + 4, + "Should resolve exactly 4 unique modules" + ); + + // Check A -> Common + let a_id = 1; + let common_id = 3; + assert!(graph.dependencies[&a_id].contains(&common_id)); + + // Check B -> Common (Should point to SAME ID) + let b_id = 2; + assert!(graph.dependencies[&b_id].contains(&common_id)); + } + + #[test] + fn test_cyclic_dependency() { + // Setup: + // A -> imports B + // B -> imports A + // Expected: Should finish without infinite loop + + let temp_dir = TempDir::new().unwrap(); + let a_path = create_simf_file( + temp_dir.path(), + "libs/test/A.simf", + "use test::B::some_test;", + ); + create_simf_file( + temp_dir.path(), + "libs/test/B.simf", + "use test::A::another_test;", + ); + + let mut lib_map = HashMap::new(); + lib_map.insert("test".to_string(), temp_dir.path().join("libs/test")); + + let root_program = parse_root(&a_path); + let config = LibConfig::new(lib_map, &a_path); + let graph = ProjectGraph::new(&config, &root_program).expect("Graph build failed"); + + println!("Graph dependencies: {:?}", graph.dependencies); + println!("lookup: {:?}", graph.lookup); + assert_eq!(graph.modules.len(), 2, "Should only have A and B"); + + // A depends on B + assert!(graph.dependencies[&0].contains(&1)); + // B depends on A (Circular) + assert!(graph.dependencies[&1].contains(&0)); + } + + #[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" + ); + } + + #[test] + fn test_ignores_unmapped_imports() { + // Setup: + // root -> "use unknown::library;" + // "unknown" is NOT in library_map. + // Expected: It should simply skip this import (based on `if let Ok(path)` logic) + + let temp_dir = TempDir::new().unwrap(); + let root_path = create_simf_file(temp_dir.path(), "root.simf", "use unknown::library;"); + + let lib_map = HashMap::new(); // Empty map + + let root_program = parse_root(&root_path); + let config = LibConfig::new(lib_map, &root_path); + let graph = + ProjectGraph::new(&config, &root_program).expect("Should succeed but ignore import"); + + assert_eq!(graph.modules.len(), 1, "Should only contain root"); + assert!( + graph.dependencies[&0].is_empty(), + "Root should have no resolved dependencies" + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index a9e5bc0e..459ad2ea 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; @@ -20,6 +21,8 @@ pub mod types; pub mod value; mod witness; +use std::collections::HashMap; +use std::path::{Path, PathBuf}; use std::sync::Arc; use simplicity::jet::elements::ElementsEnv; @@ -30,12 +33,29 @@ 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; pub use crate::types::ResolvedType; pub use crate::value::Value; pub use crate::witness::{Arguments, Parameters, WitnessTypes, WitnessValues}; +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, + } + } +} + /// The template of a SimplicityHL program. /// /// A template has parameterized values that need to be supplied with arguments. @@ -51,11 +71,18 @@ impl TemplateProgram { /// ## Errors /// /// The string is not a valid SimplicityHL program. - pub fn new>>(s: Str) -> Result { + pub fn new>>(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, @@ -115,11 +142,12 @@ impl CompiledProgram { /// - [`TemplateProgram::new`] /// - [`TemplateProgram::instantiate`] pub fn new>>( + lib_cfg: Option<&LibConfig>, s: Str, arguments: Arguments, include_debug_symbols: bool, ) -> Result { - TemplateProgram::new(s) + TemplateProgram::new(lib_cfg, s) .and_then(|template| template.instantiate(arguments, include_debug_symbols)) } @@ -186,12 +214,13 @@ impl SatisfiedProgram { /// - [`TemplateProgram::instantiate`] /// - [`CompiledProgram::satisfy`] pub fn new>>( + lib_cfg: Option<&LibConfig>, s: Str, arguments: Arguments, witness_values: WitnessValues, include_debug_symbols: bool, ) -> Result { - let compiled = CompiledProgram::new(s, arguments, include_debug_symbols)?; + let compiled = CompiledProgram::new(lib_cfg, s, arguments, include_debug_symbols)?; compiled.satisfy(witness_values) } @@ -294,7 +323,7 @@ pub(crate) mod tests { } pub fn template_text(program_text: Cow) -> Self { - let program = match TemplateProgram::new(program_text.as_ref()) { + let program = match TemplateProgram::new(None, program_text.as_ref()) { Ok(x) => x, Err(error) => panic!("{error}"), }; @@ -631,6 +660,7 @@ fn main() { } "#; match SatisfiedProgram::new( + None, prog_text, Arguments::default(), WitnessValues::default(), diff --git a/src/main.rs b/src/main.rs index fdd090cc..f6690f2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,8 @@ use base64::display::Base64Display; use base64::engine::general_purpose::STANDARD; use clap::{Arg, ArgAction, Command}; -use simplicityhl::{Arguments, CompiledProgram}; -use std::{env, fmt}; +use simplicityhl::{Arguments, CompiledProgram, LibConfig}; +use std::{collections::HashMap, env, fmt, path::PathBuf}; #[cfg_attr(feature = "serde", derive(serde::Serialize))] /// The compilation output. @@ -41,6 +41,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") .value_name("WITNESS_FILE") @@ -69,14 +77,37 @@ fn main() -> Result<(), Box> { let include_debug_symbols = matches.get_flag("debug"); let output_json = matches.get_flag("json"); - let compiled = - match CompiledProgram::new(prog_text, Arguments::default(), include_debug_symbols) { - Ok(program) => program, - Err(e) => { - eprintln!("{}", e); + 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( + Some(&config), + prog_text, + Arguments::default(), + include_debug_symbols, + ) { + Ok(program) => program, + Err(e) => { + eprintln!("{}", e); + std::process::exit(1); + } + }; #[cfg(feature = "serde")] let witness_opt = matches diff --git a/src/parse.rs b/src/parse.rs index b2f0b7bc..919f751c 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -63,7 +63,7 @@ pub enum Item { #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct UseDecl { visibility: Visibility, - path: Vec, // TODO: Maybe change to Arc<[Identifier]> for consisten + path: Vec, // TODO: Maybe change to Arc<[Identifier]> for consisten items: UseItems, span: Span, } @@ -1243,7 +1243,12 @@ impl ChumskyParse for Program { let skip_until_next_item = any() .then( any() - .filter(|t| !matches!(t, Token::Pub | Token::Use | 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 @@ -1279,8 +1284,8 @@ impl ChumskyParse for Function { where I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, { - let visibility = - just(Token::Pub).to(Visibility::Public) + let visibility = just(Token::Pub) + .to(Visibility::Public) .or_not() .map(|v| v.unwrap_or(Visibility::Private)) .labelled("function visibility"); @@ -1341,8 +1346,8 @@ impl ChumskyParse for UseDecl { // TODO: Check does it possible to parse `use a: : smth`, because we need parse only `use a::smth` let double_colon = just(Token::Colon).then(just(Token::Colon)).labelled("::"); - let visibility = - just(Token::Pub).to(Visibility::Public) + let visibility = just(Token::Pub) + .to(Visibility::Public) .or_not() .map(|v| v.unwrap_or(Visibility::Private)); @@ -1503,7 +1508,7 @@ impl ChumskyParse for CallName { where I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, { - let double_colon: chumsky::label::Labelled, I, extra::Full>, chumsky::primitive::Just, I, extra::Full>, Token<'_>, Token<'_>, extra::Full>, &str> = just(Token::Colon).then(just(Token::Colon)).labelled("::"); + let double_colon = just(Token::Colon).then(just(Token::Colon)).labelled("::"); let turbofish_start = double_colon.clone().then(just(Token::LAngle)).ignored(); @@ -1617,19 +1622,21 @@ impl ChumskyParse for TypeAlias { where I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, { - let visibility = - just(Token::Pub).to(Visibility::Public) + let visibility = just(Token::Pub) + .to(Visibility::Public) .or_not() .map(|v| v.unwrap_or(Visibility::Private)); - + let name = AliasName::parser().map_with(|name, e| (name, e.span())); - + visibility - .then(just(Token::Type) - .ignore_then(name) - .then_ignore(parse_token_with_recovery(Token::Eq)) - .then(AliasedType::parser()) - .then_ignore(just(Token::Semi))) + .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, diff --git a/src/str.rs b/src/str.rs index 71190b69..7869cfb8 100644 --- a/src/str.rs +++ b/src/str.rs @@ -115,6 +115,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); diff --git a/src/tracker.rs b/src/tracker.rs index 4a6f693a..82f15a49 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -472,7 +472,7 @@ mod tests { #[test] fn test_debug_and_jet_tracing() { - let program = TemplateProgram::new(TEST_PROGRAM).unwrap(); + let program = TemplateProgram::new(None, TEST_PROGRAM).unwrap(); let program = program.instantiate(Arguments::default(), true).unwrap(); let satisfied = program.satisfy(WitnessValues::default()).unwrap(); @@ -541,7 +541,7 @@ mod tests { fn test_arith_jet_trace_regression() { let env = create_test_env(); - let program = TemplateProgram::new(TEST_ARITHMETIC_JETS).unwrap(); + let program = TemplateProgram::new(None, TEST_ARITHMETIC_JETS).unwrap(); let program = program.instantiate(Arguments::default(), true).unwrap(); let satisfied = program.satisfy(WitnessValues::default()).unwrap(); diff --git a/src/witness.rs b/src/witness.rs index 031b3a71..ca91d85c 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -235,7 +235,7 @@ mod tests { WitnessName::from_str_unchecked("A"), Value::u16(42), )])); - match SatisfiedProgram::new(s, Arguments::default(), witness, false) { + match SatisfiedProgram::new(None, s, Arguments::default(), witness, false) { Ok(_) => panic!("Ill-typed witness assignment was falsely accepted"), Err(error) => assert_eq!( "Witness `A` was declared with type `u32` but its assigned value is of type `u16`", @@ -254,7 +254,7 @@ fn main() { assert!(jet::is_zero_32(f())); }"#; - match CompiledProgram::new(s, Arguments::default(), false) { + match CompiledProgram::new(None, s, Arguments::default(), false) { Ok(_) => panic!("Witness outside main was falsely accepted"), Err(error) => { assert!(error