diff --git a/src/ast.rs b/src/ast.rs index fca0947f..87d24f17 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -74,8 +74,10 @@ pub enum Item { TypeAlias, /// A function. Function(Function), - /// A module, which is ignored. + Use, Module, + /// A placeholder used for error recovery during parsing. + Ignored, } /// Definition of a function. @@ -984,7 +986,11 @@ impl AbstractSyntaxTree for Item { Error::UseKeywordIsNotSupported, *use_decl.span(), )), - parse::Item::Module => Ok(Self::Module), + parse::Item::Module(module) => Err(RichError::new( + Error::ModuleKeywordIsNotSupported, + *module.span(), + )), + parse::Item::Ignored => Ok(Self::Ignored), }; scope.file_id = previous_file_id; @@ -1663,48 +1669,6 @@ impl AbstractSyntaxTree for Match { } } -impl AbstractSyntaxTree for Module { - type From = parse::Module; - - fn analyze(from: &Self::From, ty: &ResolvedType, scope: &mut Scope) -> Result { - assert!(ty.is_unit(), "Modules cannot return anything"); - assert!(scope.is_topmost(), "Modules live in the topmost scope only"); - let assignments = from - .assignments() - .iter() - .map(|s| ModuleAssignment::analyze(s, ty, scope)) - .collect::, RichError>>()?; - debug_assert!(scope.is_topmost()); - - Ok(Self { - name: from.name().shallow_clone(), - span: *from.as_ref(), - assignments, - }) - } -} - -impl AbstractSyntaxTree for ModuleAssignment { - type From = parse::ModuleAssignment; - - fn analyze(from: &Self::From, ty: &ResolvedType, scope: &mut Scope) -> Result { - assert!(ty.is_unit(), "Assignments cannot return anything"); - let ty_expr = scope.resolve(from.ty()).with_span(from)?; - let expression = Expression::analyze(from.expression(), &ty_expr, scope)?; - let value = Value::from_const_expr(&expression) - .ok_or(Error::ExpressionUnexpectedType { - ty: ty_expr.clone(), - }) - .with_span(from.expression())?; - - Ok(Self { - name: from.name().clone(), - value, - span: *from.as_ref(), - }) - } -} - impl AsRef for Assignment { fn as_ref(&self) -> &Span { &self.span diff --git a/src/driver/resolve_order.rs b/src/driver/resolve_order.rs index ad5463d5..efc951bd 100644 --- a/src/driver/resolve_order.rs +++ b/src/driver/resolve_order.rs @@ -68,7 +68,8 @@ impl Program { } // Safe to skip: `Use` items are handled earlier in the loop, and `Module` currently has no functionality. - parse::Item::Module | parse::Item::Use(_) => continue, + // It will handle properly in the following commits. + parse::Item::Module(_) | parse::Item::Use(_) | parse::Item::Ignored => continue, } items.push(new_elem); } @@ -238,7 +239,8 @@ impl DependencyGraph { } // Safe to skip: `Use` items are handled earlier in the loop, and `Module` currently has no functionality. - parse::Item::Module | parse::Item::Use(_) => continue, + // It will handle properly in the following commits. + parse::Item::Module(_) | parse::Item::Use(_) | parse::Item::Ignored => continue, } items.push(new_elem); } diff --git a/src/error.rs b/src/error.rs index 83420973..6998525d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -633,7 +633,9 @@ pub enum Error { declared: ResolvedType, assigned: ResolvedType, }, + // TODO: Remove these once `use` and `mod` are supported by the AST UseKeywordIsNotSupported, + ModuleKeywordIsNotSupported, } #[rustfmt::skip] @@ -860,6 +862,10 @@ impl fmt::Display for Error { f, "The `use` keyword is not supported yet" ), + Error::ModuleKeywordIsNotSupported => write!( + f, + "The `mod` keyword is not supported yet" + ), } } } diff --git a/src/parse.rs b/src/parse.rs index 42b5dac2..d7f50f4b 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -58,8 +58,10 @@ pub enum Item { /// An import declaration (e.g., `use math::add`) that brings another /// [`Item`] into the current scope. Use(UseDecl), - /// A module, which is ignored. - Module, + /// A module containing a collection of nested [`Item`]. + Module(Module), + /// A placeholder used for error recovery during parsing. + Ignored, } #[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] @@ -621,20 +623,26 @@ impl MatchPattern { #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct Module { + file_id: usize, + visibility: Visibility, name: ModuleName, - assignments: Arc<[ModuleAssignment]>, + items: Arc<[Item]>, span: Span, } impl Module { + pub fn visibility(&self) -> &Visibility { + &self.visibility + } + /// Access the name of the module. pub fn name(&self) -> &ModuleName { &self.name } /// Access the assignments of the module. - pub fn assignments(&self) -> &[ModuleAssignment] { - &self.assignments + pub fn items(&self) -> &[Item] { + &self.items } /// Access the span of the module. @@ -643,31 +651,6 @@ impl Module { } } -#[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub struct ModuleAssignment { - name: WitnessName, - ty: AliasedType, - expression: Expression, - span: Span, -} - -impl ModuleAssignment { - /// Access the assigned witness name. - pub fn name(&self) -> &WitnessName { - &self.name - } - - /// Access the assigned witness type. - pub fn ty(&self) -> &AliasedType { - &self.ty - } - - /// Access the assigned witness expression. - pub fn expression(&self) -> &Expression { - &self.expression - } -} - impl fmt::Display for Program { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for item in self.items() { @@ -683,10 +666,8 @@ impl fmt::Display for Item { 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`. - Self::Module => write!(f, "mod witness {{}}"), + Self::Module(module) => write!(f, "{module}"), + Self::Ignored => Ok(()), } } } @@ -729,6 +710,12 @@ impl fmt::Display for Function { } } +impl fmt::Display for FunctionParam { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}: {}", self.identifier(), self.ty()) + } +} + impl fmt::Display for UseDecl { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let _ = write!(f, "{}use ", self.visibility()); @@ -778,9 +765,19 @@ impl fmt::Display for UseItems { } } -impl fmt::Display for FunctionParam { +impl fmt::Display for Module { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}: {}", self.identifier(), self.ty()) + writeln!(f, "{}mod {} {{", self.visibility(), self.name())?; + + for item in self.items() { + let item_str = item.to_string(); + + for line in item_str.lines() { + writeln!(f, " {line}")?; + } + } + + write!(f, "}}") } } @@ -847,6 +844,7 @@ impl TreeLike for ExprTree<'_> { } } +// TODO: Fix the formatter impl fmt::Display for ExprTree<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use SingleExpressionInner as S; @@ -1344,8 +1342,7 @@ impl ChumskyParse for Program { }) .repeated(), ) - // map to empty module - .map_with(|_, _| Item::Module); + .map_with(|_, _| Item::Ignored); Item::parser() .recover_with(via_parser(skip_until_next_item)) @@ -1363,12 +1360,16 @@ impl ChumskyParse for Item { where I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, { - 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); + recursive(|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); + + // Lazy item here + let mod_parser = Module::parser_with_items(item).map(Item::Module); - choice((func_parser, use_parser, type_parser, mod_parser)) + choice((func_parser, use_parser, type_parser, mod_parser)) + }) } } @@ -1442,21 +1443,23 @@ impl ChumskyParse for UseDecl { .or_not() .map(Option::unwrap_or_default); - // Parse the base path prefix (e.g., `dependency_root_path::file::`, `dependency_root_path::dir::file::`, or `crate::dir::file::`). - // We require at least 2 segments here because a valid import needs a minimum - // of 3 items total: the dependency root path (or `crate`), the file, and the specific item/function. let first_segment = select! { Token::Ident(ident) => Identifier::from_str_unchecked(ident), Token::Crate => Identifier::from_str_unchecked(CRATE_STR), }; + // Parse the base path prefix (e.g., `dependency_root_path::file::`, `dependency_root_path::dir::file::`, + // or `crate::dir::file::`). We require at least 2 segments here because a valid import needs a minimum + // of 3 items total: the dependency root path (or `crate`), the file, and the specific item. + // + // With the introduction of `mod` keyword and single-file flattening, 2 total segments are now + // valid: `crate::item`, where `crate` is the program root. let path = first_segment .then_ignore(just(Token::DoubleColon)) .then( Identifier::parser() .then_ignore(just(Token::DoubleColon)) .repeated() - .at_least(1) .collect::>(), ) .map(|(first, mut rest)| { @@ -2082,14 +2085,22 @@ impl Match { } } -impl ChumskyParse for Module { - fn parser<'tokens, 'src: 'tokens, I>() -> impl Parser<'tokens, I, Self, ParseError<'src>> + Clone +impl Module { + pub fn parser_with_items<'tokens, 'src: 'tokens, I>( + item_parser: impl Parser<'tokens, I, Item, ParseError<'src>> + Clone + 'tokens, + ) -> impl Parser<'tokens, I, Self, ParseError<'src>> + Clone where I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, { + let file_id = MAIN_MODULE; + let visibility = just(Token::Pub) + .to(Visibility::Public) + .or_not() + .map(Option::unwrap_or_default); + let name = ModuleName::parser().map_with(|name, e| (name, e.span())); - let assignments = ModuleAssignment::parser() + let items = item_parser .repeated() .collect::>() .delimited_by(just(Token::LBrace), just(Token::RBrace)) @@ -2104,35 +2115,13 @@ impl ChumskyParse for Module { ))) .map(Arc::from); - just(Token::Mod) - .ignore_then(name) - .then(assignments) - .map_with(|(name, assignments), e| Self { + visibility + .then(just(Token::Mod).ignore_then(name).then(items)) + .map_with(move |(visibility, (name, items)), e| Self { + file_id, + visibility, name: name.0, - assignments, - span: e.span(), - }) - } -} - -impl ChumskyParse for ModuleAssignment { - fn parser<'tokens, 'src: 'tokens, I>() -> impl Parser<'tokens, I, Self, ParseError<'src>> + Clone - where - I: ValueInput<'tokens, Token = Token<'src>, Span = Span>, - { - let name = WitnessName::parser(); - - just(Token::Const) - .ignore_then(name) - .then_ignore(just(Token::Colon)) - .then(AliasedType::parser()) - .then_ignore(just(Token::Eq)) - .then(Expression::parser()) - .then_ignore(just(Token::Semi)) - .map_with(|((name, ty), expression), e| Self { - name, - ty, - expression, + items, span: e.span(), }) } @@ -2192,26 +2181,37 @@ impl AsRef for Match { } } -impl AsRef for Module { +impl AsRef for UseDecl { fn as_ref(&self) -> &Span { &self.span } } -impl AsRef for ModuleAssignment { +impl AsRef for Module { fn as_ref(&self) -> &Span { &self.span } } +#[cfg(feature = "arbitrary")] +pub(crate) fn generate_arbitrary_items<'a>( + u: &mut arbitrary::Unstructured<'a>, +) -> arbitrary::Result> { + let mut items_vec = Vec::new(); + + let len = u.int_in_range(0..=2)?; + for _ in 0..len { + items_vec.push(::arbitrary(u)?); + } + + Ok(items_vec) +} + #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for Program { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - let mut items_vec: Vec = Vec::new(); - let len = u.int_in_range(0..=2)?; - for _ in 0..len { - items_vec.push(Item::arbitrary(u)?); - } + let mut items_vec = generate_arbitrary_items(u)?; + // Three equally-likely modes for how `fn main()` is injected: // 0 — no explicit main (arbitrary items only) // 1 — main with arbitrary params and return type @@ -2273,6 +2273,24 @@ impl crate::ArbitraryRec for Function { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for Module { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let file_id = u.int_in_range(0..=3)?; + let visibility = Visibility::arbitrary(u)?; + let name = ModuleName::arbitrary(u)?; + let items_vec = generate_arbitrary_items(u)?; + + Ok(Self { + file_id, + visibility, + name, + items: items_vec.into(), + span: Span::DUMMY, + }) + } +} + #[cfg(feature = "arbitrary")] impl crate::ArbitraryRec for Expression { fn arbitrary_rec(u: &mut arbitrary::Unstructured, budget: usize) -> arbitrary::Result { diff --git a/src/str.rs b/src/str.rs index 19245c83..02a935aa 100644 --- a/src/str.rs +++ b/src/str.rs @@ -305,15 +305,9 @@ impl<'a> arbitrary::Arbitrary<'a> for Hexadecimal { #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct ModuleName(Arc); -impl ModuleName { - /// Return the name of the witness module. - pub fn witness() -> Self { - Self(Arc::from("witness")) - } - - /// Return the name of the parameter module. - pub fn param() -> Self { - Self(Arc::from("param")) +impl Default for ModuleName { + fn default() -> Self { + Self(Arc::from("")) } } @@ -325,6 +319,41 @@ impl From for ModuleName { wrapped_string!(ModuleName, "module name"); +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for ModuleName { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + // TODO: Consider to change it + const RESERVED_NAMES: [&str; 14] = [ + "unwrap_left", + "unwrap_right", + "for_while", + "is_none", + "unwrap", + "assert", + "panic", + "match", + "into", + "fold", + "dbg", + "jet", + "witness", + "param", + ]; + + let len = u.int_in_range(1..=10)?; + let mut string = String::with_capacity(len); + for _ in 0..len { + let offset = u.int_in_range(0..=25)?; + string.push((b'a' + offset) as char) + } + if RESERVED_NAMES.contains(&string.as_str()) || crate::lexer::is_keyword(string.as_str()) { + string.push('_'); + } + + Ok(Self::from_str_unchecked(string.as_str())) + } +} + /// An unresolved identifier parsed from the source code. /// /// During the parsing of `use` statements, the exact kind of the imported