diff --git a/Cargo.lock b/Cargo.lock index ac69607c4d2..87314665107 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -642,6 +642,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bytecount" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" + [[package]] name = "bytemuck" version = "1.13.1" @@ -2322,6 +2328,7 @@ dependencies = [ "iai", "iter-extended", "nargo", + "nargo_fmt", "nargo_toml", "noir_lsp", "noirc_abi", @@ -2346,6 +2353,17 @@ dependencies = [ "tower", ] +[[package]] +name = "nargo_fmt" +version = "0.16.0" +dependencies = [ + "bytecount", + "noirc_frontend", + "serde", + "thiserror", + "toml", +] + [[package]] name = "nargo_toml" version = "0.16.0" diff --git a/Cargo.toml b/Cargo.toml index 80848fbcf1e..e57c53afcc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "tooling/bb_abstraction_leaks", "tooling/lsp", "tooling/nargo", + "tooling/nargo_fmt", "tooling/nargo_cli", "tooling/nargo_toml", "tooling/noirc_abi", @@ -49,6 +50,7 @@ arena = { path = "compiler/utils/arena" } fm = { path = "compiler/fm" } iter-extended = { path = "compiler/utils/iter-extended" } nargo = { path = "tooling/nargo" } +nargo_fmt = { path = "tooling/nargo_fmt" } nargo_cli = { path = "tooling/nargo_cli" } nargo_toml = { path = "tooling/nargo_toml" } noir_lsp = { path = "tooling/lsp" } diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index 06bbddb9744..213bdd3fefb 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -3,8 +3,8 @@ use std::fmt::Display; use crate::token::{Attributes, Token}; use crate::{ - Distinctness, Ident, Path, Pattern, Recoverable, Statement, UnresolvedTraitConstraint, - UnresolvedType, UnresolvedTypeData, Visibility, + Distinctness, Ident, Path, Pattern, Recoverable, Statement, StatementKind, + UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, Visibility, }; use acvm::FieldElement; use iter_extended::vecmap; @@ -170,8 +170,14 @@ impl Expression { // as a sequence of { if, tuple } rather than a function call. This behavior matches rust. let kind = if matches!(&lhs.kind, ExpressionKind::If(..)) { ExpressionKind::Block(BlockExpression(vec![ - Statement::Expression(lhs), - Statement::Expression(Expression::new(ExpressionKind::Tuple(arguments), span)), + Statement { kind: StatementKind::Expression(lhs), span }, + Statement { + kind: StatementKind::Expression(Expression::new( + ExpressionKind::Tuple(arguments), + span, + )), + span, + }, ])) } else { ExpressionKind::Call(Box::new(CallExpression { func: Box::new(lhs), arguments })) @@ -429,8 +435,8 @@ pub struct IndexExpression { pub struct BlockExpression(pub Vec); impl BlockExpression { - pub fn pop(&mut self) -> Option { - self.0.pop() + pub fn pop(&mut self) -> Option { + self.0.pop().map(|stmt| stmt.kind) } pub fn len(&self) -> usize { @@ -497,7 +503,7 @@ impl Display for BlockExpression { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "{{")?; for statement in &self.0 { - let statement = statement.to_string(); + let statement = statement.kind.to_string(); for line in statement.lines() { writeln!(f, " {line}")?; } diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index a1834a8b18a..639d4d8f763 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -14,11 +14,17 @@ use noirc_errors::{Span, Spanned}; /// for an identifier that already failed to parse. pub const ERROR_IDENT: &str = "$error"; +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Statement { + pub kind: StatementKind, + pub span: Span, +} + /// Ast node for statements in noir. Statements are always within a block { } /// of some kind and are terminated via a Semicolon, except if the statement /// ends in a block, such as a Statement::Expression containing an if expression. #[derive(Debug, PartialEq, Eq, Clone)] -pub enum Statement { +pub enum StatementKind { Let(LetStatement), Constrain(ConstrainStatement), Expression(Expression), @@ -32,62 +38,67 @@ pub enum Statement { Error, } -impl Recoverable for Statement { - fn error(_: Span) -> Self { - Statement::Error - } -} - impl Statement { - pub fn new_let( - ((pattern, r#type), expression): ((Pattern, UnresolvedType), Expression), - ) -> Statement { - Statement::Let(LetStatement { pattern, r#type, expression }) - } - pub fn add_semicolon( self, semi: Option, span: Span, last_statement_in_block: bool, emit_error: &mut dyn FnMut(ParserError), - ) -> Statement { + ) -> Self { let missing_semicolon = ParserError::with_reason(ParserErrorReason::MissingSeparatingSemi, span); - match self { - Statement::Let(_) - | Statement::Constrain(_) - | Statement::Assign(_) - | Statement::Semi(_) - | Statement::Error => { + + let kind = match self.kind { + StatementKind::Let(_) + | StatementKind::Constrain(_) + | StatementKind::Assign(_) + | StatementKind::Semi(_) + | StatementKind::Error => { // To match rust, statements always require a semicolon, even at the end of a block if semi.is_none() { emit_error(missing_semicolon); } - self + self.kind } // A semicolon on a for loop is optional and does nothing - Statement::For(_) => self, + StatementKind::For(_) => self.kind, - Statement::Expression(expr) => { + StatementKind::Expression(expr) => { match (&expr.kind, semi, last_statement_in_block) { // Semicolons are optional for these expressions (ExpressionKind::Block(_), semi, _) | (ExpressionKind::If(_), semi, _) => { if semi.is_some() { - Statement::Semi(expr) + StatementKind::Semi(expr) } else { - Statement::Expression(expr) + StatementKind::Expression(expr) } } (_, None, false) => { emit_error(missing_semicolon); - Statement::Expression(expr) + StatementKind::Expression(expr) } - (_, Some(_), _) => Statement::Semi(expr), - (_, None, true) => Statement::Expression(expr), + (_, Some(_), _) => StatementKind::Semi(expr), + (_, None, true) => StatementKind::Expression(expr), } } - } + }; + + Statement { kind, span: self.span } + } +} + +impl Recoverable for StatementKind { + fn error(_: Span) -> Self { + StatementKind::Error + } +} + +impl StatementKind { + pub fn new_let( + ((pattern, r#type), expression): ((Pattern, UnresolvedType), Expression), + ) -> StatementKind { + StatementKind::Let(LetStatement { pattern, r#type, expression }) } /// Create a Statement::Assign value, desugaring any combined operators like += if needed. @@ -96,7 +107,7 @@ impl Statement { operator: Token, mut expression: Expression, span: Span, - ) -> Statement { + ) -> StatementKind { // Desugar `a = b` to `a = a b`. This relies on the evaluation of `a` having no side effects, // which is currently enforced by the restricted syntax of LValues. if operator != Token::Assign { @@ -111,7 +122,7 @@ impl Statement { expression = Expression::new(ExpressionKind::Infix(Box::new(infix)), span); } - Statement::Assign(AssignStatement { lvalue, expression }) + StatementKind::Assign(AssignStatement { lvalue, expression }) } } @@ -468,16 +479,16 @@ pub struct ForLoopStatement { pub block: Expression, } -impl Display for Statement { +impl Display for StatementKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Statement::Let(let_statement) => let_statement.fmt(f), - Statement::Constrain(constrain) => constrain.fmt(f), - Statement::Expression(expression) => expression.fmt(f), - Statement::Assign(assign) => assign.fmt(f), - Statement::For(for_loop) => for_loop.fmt(f), - Statement::Semi(semi) => write!(f, "{semi};"), - Statement::Error => write!(f, "Error"), + StatementKind::Let(let_statement) => let_statement.fmt(f), + StatementKind::Constrain(constrain) => constrain.fmt(f), + StatementKind::Expression(expression) => expression.fmt(f), + StatementKind::Assign(assign) => assign.fmt(f), + StatementKind::For(for_loop) => for_loop.fmt(f), + StatementKind::Semi(semi) => write!(f, "{semi};"), + StatementKind::Error => write!(f, "Error"), } } } diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index eef3bbb3700..6beb4a3e0a6 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -17,12 +17,11 @@ use crate::node_interner::{ FuncId, NodeInterner, StmtId, StructId, TraitId, TraitImplKey, TypeAliasId, }; -use crate::parser::ParserError; - +use crate::parser::{ParserError, SortedModule}; use crate::{ ExpressionKind, Generics, Ident, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, - NoirTypeAlias, ParsedModule, Path, Shared, StructType, TraitItem, Type, TypeBinding, - TypeVariableKind, UnresolvedGenerics, UnresolvedType, + NoirTypeAlias, Path, Shared, StructType, TraitItem, Type, TypeBinding, TypeVariableKind, + UnresolvedGenerics, UnresolvedType, }; use fm::FileId; use iter_extended::vecmap; @@ -195,7 +194,7 @@ impl DefCollector { pub fn collect( mut def_map: CrateDefMap, context: &mut Context, - ast: ParsedModule, + ast: SortedModule, root_file_id: FileId, ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index 748c1dd26cd..92c2e0fcebc 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -7,9 +7,9 @@ use crate::{ graph::CrateId, hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait}, node_interner::TraitId, - parser::SubModule, + parser::{SortedModule, SubModule}, FunctionDefinition, Ident, LetStatement, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, - NoirTypeAlias, ParsedModule, TraitImplItem, TraitItem, TypeImpl, + NoirTypeAlias, TraitImplItem, TraitItem, TypeImpl, }; use super::{ @@ -35,7 +35,7 @@ struct ModCollector<'a> { /// This performs the entirety of the definition collection phase of the name resolution pass. pub fn collect_defs( def_collector: &mut DefCollector, - ast: ParsedModule, + ast: SortedModule, file_id: FileId, module_id: LocalModuleId, crate_id: CrateId, @@ -410,7 +410,7 @@ impl<'a> ModCollector<'a> { Ok(child) => { errors.extend(collect_defs( self.def_collector, - submodule.contents, + submodule.contents.into_sorted(), file_id, child, crate_id, @@ -467,8 +467,8 @@ impl<'a> ModCollector<'a> { context.visited_files.insert(child_file_id, location); // Parse the AST for the module we just found and then recursively look for it's defs - //let ast = parse_file(&context.file_manager, child_file_id, errors); let (ast, parsing_errors) = parse_file(&context.file_manager, child_file_id); + let ast = ast.into_sorted(); errors.extend( parsing_errors.iter().map(|e| (e.clone().into(), child_file_id)).collect::>(), diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index 3ea75c57865..311afc05d28 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -86,6 +86,7 @@ impl CrateDefMap { // First parse the root file. let root_file_id = context.crate_graph[crate_id].root_file_id; let (ast, parsing_errors) = parse_file(&context.file_manager, root_file_id); + let ast = ast.into_sorted(); #[cfg(feature = "aztec")] let ast = match aztec_library::transform(ast, &crate_id, context) { diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index cd7bb97cda2..8d92984a565 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -33,7 +33,7 @@ use crate::node_interner::{ use crate::{ hir::{def_map::CrateDefMap, resolution::path_resolver::PathResolver}, BlockExpression, Expression, ExpressionKind, FunctionKind, Ident, Literal, NoirFunction, - Statement, + StatementKind, }; use crate::{ ArrayLiteral, ContractFunctionType, Distinctness, Generics, LValue, NoirStruct, NoirTypeAlias, @@ -933,9 +933,9 @@ impl<'a> Resolver<'a> { }) } - pub fn resolve_stmt(&mut self, stmt: Statement) -> HirStatement { + pub fn resolve_stmt(&mut self, stmt: StatementKind) -> HirStatement { match stmt { - Statement::Let(let_stmt) => { + StatementKind::Let(let_stmt) => { let expression = self.resolve_expression(let_stmt.expression); let definition = DefinitionKind::Local(Some(expression)); HirStatement::Let(HirLetStatement { @@ -944,20 +944,22 @@ impl<'a> Resolver<'a> { expression, }) } - Statement::Constrain(constrain_stmt) => { + StatementKind::Constrain(constrain_stmt) => { let expr_id = self.resolve_expression(constrain_stmt.0); let assert_message = constrain_stmt.1; HirStatement::Constrain(HirConstrainStatement(expr_id, self.file, assert_message)) } - Statement::Expression(expr) => HirStatement::Expression(self.resolve_expression(expr)), - Statement::Semi(expr) => HirStatement::Semi(self.resolve_expression(expr)), - Statement::Assign(assign_stmt) => { + StatementKind::Expression(expr) => { + HirStatement::Expression(self.resolve_expression(expr)) + } + StatementKind::Semi(expr) => HirStatement::Semi(self.resolve_expression(expr)), + StatementKind::Assign(assign_stmt) => { let identifier = self.resolve_lvalue(assign_stmt.lvalue); let expression = self.resolve_expression(assign_stmt.expression); let stmt = HirAssignStatement { lvalue: identifier, expression }; HirStatement::Assign(stmt) } - Statement::For(for_loop) => { + StatementKind::For(for_loop) => { let start_range = self.resolve_expression(for_loop.start_range); let end_range = self.resolve_expression(for_loop.end_range); let (identifier, block) = (for_loop.identifier, for_loop.block); @@ -976,11 +978,11 @@ impl<'a> Resolver<'a> { HirStatement::For(HirForStatement { start_range, end_range, block, identifier }) } - Statement::Error => HirStatement::Error, + StatementKind::Error => HirStatement::Error, } } - pub fn intern_stmt(&mut self, stmt: Statement) -> StmtId { + pub fn intern_stmt(&mut self, stmt: StatementKind) -> StmtId { let hir_stmt = self.resolve_stmt(stmt); self.interner.push_stmt(hir_stmt) } @@ -1519,7 +1521,7 @@ impl<'a> Resolver<'a> { fn resolve_block(&mut self, block_expr: BlockExpression) -> HirExpression { let statements = - self.in_new_scope(|this| vecmap(block_expr.0, |stmt| this.intern_stmt(stmt))); + self.in_new_scope(|this| vecmap(block_expr.0, |stmt| this.intern_stmt(stmt.kind))); HirExpression::Block(HirBlockExpression(statements)) } @@ -1708,7 +1710,7 @@ mod test { } let mut errors = Vec::new(); - for func in program.functions { + for func in program.into_sorted().functions { let id = interner.push_test_function_definition(func.name().to_string()); let resolver = Resolver::new(&mut interner, &path_resolver, &def_maps, file); @@ -1724,7 +1726,7 @@ mod test { init_src_code_resolution(src); let mut all_captures: Vec> = Vec::new(); - for func in program.functions { + for func in program.into_sorted().functions { let name = func.name().to_string(); let id = interner.push_test_function_definition(name); path_resolver.insert_func(func.name().to_owned(), id); diff --git a/compiler/noirc_frontend/src/hir/type_check/mod.rs b/compiler/noirc_frontend/src/hir/type_check/mod.rs index 4c3ecce3ede..a3c5a5eb98d 100644 --- a/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -446,7 +446,7 @@ mod test { }, ); - let func_meta = vecmap(program.functions, |nf| { + let func_meta = vecmap(program.into_sorted().functions, |nf| { let resolver = Resolver::new(&mut interner, &path_resolver, &def_maps, file); let (hir_func, func_meta, resolver_errors) = resolver.resolve_function(nf, main_id); assert_eq!(resolver_errors, vec![]); diff --git a/compiler/noirc_frontend/src/parser/mod.rs b/compiler/noirc_frontend/src/parser/mod.rs index 8fc882068eb..10ed2a7373a 100644 --- a/compiler/noirc_frontend/src/parser/mod.rs +++ b/compiler/noirc_frontend/src/parser/mod.rs @@ -18,7 +18,7 @@ use crate::{ast::ImportStatement, Expression, NoirStruct}; use crate::{ BlockExpression, ExpressionKind, ForLoopStatement, Ident, IndexExpression, LetStatement, MethodCallExpression, NoirFunction, NoirTrait, NoirTraitImpl, NoirTypeAlias, Path, PathKind, - Pattern, Recoverable, Statement, TypeImpl, UnresolvedType, UseTree, + Pattern, Recoverable, Statement, StatementKind, TypeImpl, UnresolvedType, UseTree, }; use acvm::FieldElement; @@ -190,7 +190,7 @@ where /// Recovery strategy for statements: If a statement fails to parse skip until the next ';' or fail /// if we find a '}' first. -fn statement_recovery() -> impl NoirParser { +fn statement_recovery() -> impl NoirParser { use Token::*; try_skip_until([Semicolon, RightBrace], RightBrace) } @@ -217,9 +217,8 @@ fn force<'a, T: 'a>(parser: impl NoirParser + 'a) -> impl NoirParser, pub functions: Vec, pub types: Vec, @@ -236,6 +235,55 @@ pub struct ParsedModule { pub submodules: Vec, } +/// A ParsedModule contains an entire Ast for one file. +#[derive(Clone, Debug, Default)] +pub struct ParsedModule { + pub items: Vec, +} + +impl ParsedModule { + pub fn into_sorted(self) -> SortedModule { + let mut module = SortedModule::default(); + + for item in self.items { + match item.kind { + ItemKind::Import(import) => module.push_import(import), + ItemKind::Function(func) => module.push_function(func), + ItemKind::Struct(typ) => module.push_type(typ), + ItemKind::Trait(noir_trait) => module.push_trait(noir_trait), + ItemKind::TraitImpl(trait_impl) => module.push_trait_impl(trait_impl), + ItemKind::Impl(r#impl) => module.push_impl(r#impl), + ItemKind::TypeAlias(type_alias) => module.push_type_alias(type_alias), + ItemKind::Global(global) => module.push_global(global), + ItemKind::ModuleDecl(mod_name) => module.push_module_decl(mod_name), + ItemKind::Submodules(submodule) => module.push_submodule(submodule), + } + } + + module + } +} + +#[derive(Clone, Debug)] +pub struct Item { + pub kind: ItemKind, + pub span: Span, +} + +#[derive(Clone, Debug)] +pub enum ItemKind { + Import(UseTree), + Function(NoirFunction), + Struct(NoirStruct), + Trait(NoirTrait), + TraitImpl(NoirTraitImpl), + Impl(TypeImpl), + TypeAlias(NoirTypeAlias), + Global(LetStatement), + ModuleDecl(Ident), + Submodules(SubModule), +} + /// A submodule defined via `mod name { contents }` in some larger file. /// These submodules always share the same file as some larger ParsedModule #[derive(Clone, Debug)] @@ -245,7 +293,7 @@ pub struct SubModule { pub is_contract: bool, } -impl ParsedModule { +impl SortedModule { fn push_function(&mut self, func: NoirFunction) { self.functions.push(func); } @@ -380,10 +428,10 @@ impl ForRange { /// ... /// } /// } - fn into_for(self, identifier: Ident, block: Expression, for_loop_span: Span) -> Statement { + fn into_for(self, identifier: Ident, block: Expression, for_loop_span: Span) -> StatementKind { match self { ForRange::Range(start_range, end_range) => { - Statement::For(ForLoopStatement { identifier, start_range, end_range, block }) + StatementKind::For(ForLoopStatement { identifier, start_range, end_range, block }) } ForRange::Array(array) => { let array_span = array.span; @@ -396,11 +444,14 @@ impl ForRange { let array_ident = Ident::new(array_name, array_span); // let fresh1 = array; - let let_array = Statement::Let(LetStatement { - pattern: Pattern::Identifier(array_ident.clone()), - r#type: UnresolvedType::unspecified(), - expression: array, - }); + let let_array = Statement { + kind: StatementKind::Let(LetStatement { + pattern: Pattern::Identifier(array_ident.clone()), + r#type: UnresolvedType::unspecified(), + expression: array, + }), + span: array_span, + }; // array.len() let segments = vec![array_ident]; @@ -429,24 +480,33 @@ impl ForRange { })); // let elem = array[i]; - let let_elem = Statement::Let(LetStatement { - pattern: Pattern::Identifier(identifier), - r#type: UnresolvedType::unspecified(), - expression: Expression::new(loop_element, array_span), - }); + let let_elem = Statement { + kind: StatementKind::Let(LetStatement { + pattern: Pattern::Identifier(identifier), + r#type: UnresolvedType::unspecified(), + expression: Expression::new(loop_element, array_span), + }), + span: array_span, + }; let block_span = block.span; - let new_block = BlockExpression(vec![let_elem, Statement::Expression(block)]); + let new_block = BlockExpression(vec![ + let_elem, + Statement { kind: StatementKind::Expression(block), span: block_span }, + ]); let new_block = Expression::new(ExpressionKind::Block(new_block), block_span); - let for_loop = Statement::For(ForLoopStatement { - identifier: fresh_identifier, - start_range, - end_range, - block: new_block, - }); + let for_loop = Statement { + kind: StatementKind::For(ForLoopStatement { + identifier: fresh_identifier, + start_range, + end_range, + block: new_block, + }), + span: for_loop_span, + }; let block = ExpressionKind::Block(BlockExpression(vec![let_array, for_loop])); - Statement::Expression(Expression::new(block, for_loop_span)) + StatementKind::Expression(Expression::new(block, for_loop_span)) } } } @@ -472,35 +532,37 @@ impl std::fmt::Display for TopLevelStatement { impl std::fmt::Display for ParsedModule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for decl in &self.module_decls { + let module = self.clone().into_sorted(); + + for decl in &module.module_decls { writeln!(f, "mod {decl};")?; } - for import in &self.imports { + for import in &module.imports { write!(f, "{import}")?; } - for global_const in &self.globals { + for global_const in &module.globals { write!(f, "{global_const}")?; } - for type_ in &self.types { + for type_ in &module.types { write!(f, "{type_}")?; } - for function in &self.functions { + for function in &module.functions { write!(f, "{function}")?; } - for impl_ in &self.impls { + for impl_ in &module.impls { write!(f, "{impl_}")?; } - for type_alias in &self.type_aliases { + for type_alias in &module.type_aliases { write!(f, "{type_alias}")?; } - for submodule in &self.submodules { + for submodule in &module.submodules { write!(f, "{submodule}")?; } diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 7723c9f3690..961a15f7737 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -23,15 +23,15 @@ //! prevent other parsers from being tried afterward since there is no longer an error. Thus, they should //! be limited to cases like the above `fn` example where it is clear we shouldn't back out of the //! current parser to try alternative parsers in a `choice` expression. -use super::spanned; use super::{ foldl_with_span, labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, parenthesized, then_commit, then_commit_ignore, top_level_statement_recovery, ExprParser, ForRange, NoirParser, ParsedModule, ParserError, ParserErrorReason, Precedence, SubModule, TopLevelStatement, }; +use super::{spanned, Item, ItemKind}; use crate::ast::{ - Expression, ExpressionKind, LetStatement, Statement, UnresolvedType, UnresolvedTypeData, + Expression, ExpressionKind, LetStatement, StatementKind, UnresolvedType, UnresolvedTypeData, }; use crate::lexer::Lexer; use crate::parser::{force, ignore_then_commit, statement_recovery}; @@ -40,7 +40,7 @@ use crate::{ BinaryOp, BinaryOpKind, BlockExpression, ConstrainStatement, Distinctness, FunctionDefinition, FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Lambda, Literal, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Path, PathKind, Pattern, - Recoverable, TraitBound, TraitImplItem, TraitItem, TypeImpl, UnaryOp, + Recoverable, Statement, TraitBound, TraitImplItem, TraitItem, TypeImpl, UnaryOp, UnresolvedTraitConstraint, UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, }; @@ -71,19 +71,21 @@ fn module() -> impl NoirParser { recursive(|module_parser| { empty() .map(|_| ParsedModule::default()) - .then(top_level_statement(module_parser).repeated()) - .foldl(|mut program, statement| { + .then(spanned(top_level_statement(module_parser)).repeated()) + .foldl(|mut program, (statement, span)| { + let mut push_item = |kind| program.items.push(Item { kind, span }); + match statement { - TopLevelStatement::Function(f) => program.push_function(f), - TopLevelStatement::Module(m) => program.push_module_decl(m), - TopLevelStatement::Import(i) => program.push_import(i), - TopLevelStatement::Struct(s) => program.push_type(s), - TopLevelStatement::Trait(t) => program.push_trait(t), - TopLevelStatement::TraitImpl(t) => program.push_trait_impl(t), - TopLevelStatement::Impl(i) => program.push_impl(i), - TopLevelStatement::TypeAlias(t) => program.push_type_alias(t), - TopLevelStatement::SubModule(s) => program.push_submodule(s), - TopLevelStatement::Global(c) => program.push_global(c), + TopLevelStatement::Function(f) => push_item(ItemKind::Function(f)), + TopLevelStatement::Module(m) => push_item(ItemKind::ModuleDecl(m)), + TopLevelStatement::Import(i) => push_item(ItemKind::Import(i)), + TopLevelStatement::Struct(s) => push_item(ItemKind::Struct(s)), + TopLevelStatement::Trait(t) => push_item(ItemKind::Trait(t)), + TopLevelStatement::TraitImpl(t) => push_item(ItemKind::TraitImpl(t)), + TopLevelStatement::Impl(i) => push_item(ItemKind::Impl(i)), + TopLevelStatement::TypeAlias(t) => push_item(ItemKind::TypeAlias(t)), + TopLevelStatement::SubModule(s) => push_item(ItemKind::Submodules(s)), + TopLevelStatement::Global(c) => push_item(ItemKind::Global(c)), TopLevelStatement::Error => (), } program @@ -638,15 +640,20 @@ fn trait_bound() -> impl NoirParser { }) } -fn block_expr<'a>(statement: impl NoirParser + 'a) -> impl NoirParser + 'a { +fn block_expr<'a>( + statement: impl NoirParser + 'a, +) -> impl NoirParser + 'a { block(statement).map(ExpressionKind::Block).map_with_span(Expression::new) } -fn block<'a>(statement: impl NoirParser + 'a) -> impl NoirParser + 'a { +fn block<'a>( + statement: impl NoirParser + 'a, +) -> impl NoirParser + 'a { use Token::*; statement .recover_via(statement_recovery()) .then(just(Semicolon).or_not().map_with_span(|s, span| (s, span))) + .map_with_span(|(kind, rest), span| (Statement { kind, span }, rest)) .repeated() .validate(check_statements_require_semicolon) .delimited_by(just(LeftBrace), just(RightBrace)) @@ -654,7 +661,7 @@ fn block<'a>(statement: impl NoirParser + 'a) -> impl NoirParser impl NoirParser { token_kind(TokenKind::Ident).map_with_span(Ident::from_token) } -fn statement<'a, P, P2>(expr_parser: P, expr_no_constructors: P2) -> impl NoirParser + 'a +fn statement<'a, P, P2>( + expr_parser: P, + expr_no_constructors: P2, +) -> impl NoirParser + 'a where P: ExprParser + 'a, P2: ExprParser + 'a, @@ -769,16 +779,16 @@ where assignment(expr_parser.clone()), for_loop(expr_no_constructors, statement), return_statement(expr_parser.clone()), - expr_parser.map(Statement::Expression), + expr_parser.map(StatementKind::Expression), )) }) } -fn fresh_statement() -> impl NoirParser { +fn fresh_statement() -> impl NoirParser { statement(expression(), expression_no_constructors()) } -fn constrain<'a, P>(expr_parser: P) -> impl NoirParser + 'a +fn constrain<'a, P>(expr_parser: P) -> impl NoirParser + 'a where P: ExprParser + 'a, { @@ -786,14 +796,14 @@ where keyword(Keyword::Constrain).labelled(ParsingRuleLabel::Statement), expr_parser, ) - .map(|expr| Statement::Constrain(ConstrainStatement(expr, None))) + .map(|expr| StatementKind::Constrain(ConstrainStatement(expr, None))) .validate(|expr, span, emit| { emit(ParserError::with_reason(ParserErrorReason::ConstrainDeprecated, span)); expr }) } -fn assertion<'a, P>(expr_parser: P) -> impl NoirParser + 'a +fn assertion<'a, P>(expr_parser: P) -> impl NoirParser + 'a where P: ExprParser + 'a, { @@ -814,11 +824,11 @@ where } } - Statement::Constrain(ConstrainStatement(condition, message_str)) + StatementKind::Constrain(ConstrainStatement(condition, message_str)) }) } -fn assertion_eq<'a, P>(expr_parser: P) -> impl NoirParser + 'a +fn assertion_eq<'a, P>(expr_parser: P) -> impl NoirParser + 'a where P: ExprParser + 'a, { @@ -845,11 +855,11 @@ where emit(ParserError::with_reason(ParserErrorReason::AssertMessageNotString, span)); } } - Statement::Constrain(ConstrainStatement(predicate, message_str)) + StatementKind::Constrain(ConstrainStatement(predicate, message_str)) }) } -fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a +fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a where P: ExprParser + 'a, { @@ -858,7 +868,7 @@ where let p = p.then(optional_type_annotation()); let p = then_commit_ignore(p, just(Token::Assign)); let p = then_commit(p, expr_parser); - p.map(Statement::new_let) + p.map(StatementKind::new_let) } fn pattern() -> impl NoirParser { @@ -900,7 +910,7 @@ fn pattern() -> impl NoirParser { .labelled(ParsingRuleLabel::Pattern) } -fn assignment<'a, P>(expr_parser: P) -> impl NoirParser + 'a +fn assignment<'a, P>(expr_parser: P) -> impl NoirParser + 'a where P: ExprParser + 'a, { @@ -909,7 +919,7 @@ where then_commit(fallible, expr_parser).map_with_span( |((identifier, operator), expression), span| { - Statement::assign(identifier, operator, expression, span) + StatementKind::assign(identifier, operator, expression, span) }, ) } @@ -1186,14 +1196,14 @@ fn expression_no_constructors() -> impl ExprParser { .labelled(ParsingRuleLabel::Expression) } -fn return_statement<'a, P>(expr_parser: P) -> impl NoirParser + 'a +fn return_statement<'a, P>(expr_parser: P) -> impl NoirParser + 'a where P: ExprParser + 'a, { ignore_then_commit(keyword(Keyword::Return), expr_parser.or_not()) .validate(|_, span, emit| { emit(ParserError::with_reason(ParserErrorReason::EarlyReturn, span)); - Statement::Error + StatementKind::Error }) .labelled(ParsingRuleLabel::Statement) } @@ -1216,7 +1226,7 @@ fn expression_with_precedence<'a, P, P2, S>( where P: ExprParser + 'a, P2: ExprParser + 'a, - S: NoirParser + 'a, + S: NoirParser + 'a, { if precedence == Precedence::Highest { if is_type_expression { @@ -1283,7 +1293,7 @@ fn term<'a, P, P2, S>( where P: ExprParser + 'a, P2: ExprParser + 'a, - S: NoirParser + 'a, + S: NoirParser + 'a, { recursive(move |term_parser| { choice(( @@ -1325,7 +1335,7 @@ fn atom_or_right_unary<'a, P, P2, S>( where P: ExprParser + 'a, P2: ExprParser + 'a, - S: NoirParser + 'a, + S: NoirParser + 'a, { enum UnaryRhs { Call(Vec), @@ -1375,7 +1385,7 @@ where fn if_expr<'a, P, S>(expr_no_constructors: P, statement: S) -> impl NoirParser + 'a where P: ExprParser + 'a, - S: NoirParser + 'a, + S: NoirParser + 'a, { recursive(|if_parser| { let if_block = block_expr(statement.clone()); @@ -1384,7 +1394,10 @@ where // Wrap the inner `if` expression in a block expression. // i.e. rewrite the sugared form `if cond1 {} else if cond2 {}` as `if cond1 {} else { if cond2 {} }`. let if_expression = Expression::new(kind, span); - let desugared_else = BlockExpression(vec![Statement::Expression(if_expression)]); + let desugared_else = BlockExpression(vec![Statement { + kind: StatementKind::Expression(if_expression), + span, + }]); Expression::new(ExpressionKind::Block(desugared_else), span) })); @@ -1410,10 +1423,10 @@ fn lambda<'a>( }) } -fn for_loop<'a, P, S>(expr_no_constructors: P, statement: S) -> impl NoirParser + 'a +fn for_loop<'a, P, S>(expr_no_constructors: P, statement: S) -> impl NoirParser + 'a where P: ExprParser + 'a, - S: NoirParser + 'a, + S: NoirParser + 'a, { keyword(Keyword::For) .ignore_then(ident()) @@ -1519,7 +1532,7 @@ fn atom<'a, P, P2, S>( where P: ExprParser + 'a, P2: ExprParser + 'a, - S: NoirParser + 'a, + S: NoirParser + 'a, { choice(( if_expr(expr_no_constructors, statement.clone()), @@ -1834,13 +1847,13 @@ mod test { // Regression for #1310: this should be parsed as a block and not a function call let res = parse_with(block(fresh_statement()), "{ if true { 1 } else { 2 } (3, 4) }").unwrap(); - match unwrap_expr(res.0.last().unwrap()) { + match unwrap_expr(&res.0.last().unwrap().kind) { // The `if` followed by a tuple is currently creates a block around both in case // there was none to start with, so there is an extra block here. ExpressionKind::Block(block) => { assert_eq!(block.0.len(), 2); - assert!(matches!(unwrap_expr(&block.0[0]), ExpressionKind::If(_))); - assert!(matches!(unwrap_expr(&block.0[1]), ExpressionKind::Tuple(_))); + assert!(matches!(unwrap_expr(&block.0[0].kind), ExpressionKind::If(_))); + assert!(matches!(unwrap_expr(&block.0[1].kind), ExpressionKind::Tuple(_))); } _ => unreachable!(), } @@ -1859,9 +1872,9 @@ mod test { } /// Extract an Statement::Expression from a statement or panic - fn unwrap_expr(stmt: &Statement) -> &ExpressionKind { + fn unwrap_expr(stmt: &StatementKind) -> &ExpressionKind { match stmt { - Statement::Expression(expr) => &expr.kind, + StatementKind::Expression(expr) => &expr.kind, _ => unreachable!(), } } @@ -1959,7 +1972,7 @@ mod test { match parse_with(assertion(expression()), "assert(x == y, \"assertion message\")").unwrap() { - Statement::Constrain(ConstrainStatement(_, message)) => { + StatementKind::Constrain(ConstrainStatement(_, message)) => { assert_eq!(message, Some("assertion message".to_owned())); } _ => unreachable!(), @@ -1983,7 +1996,7 @@ mod test { match parse_with(assertion_eq(expression()), "assert_eq(x, y, \"assertion message\")") .unwrap() { - Statement::Constrain(ConstrainStatement(_, message)) => { + StatementKind::Constrain(ConstrainStatement(_, message)) => { assert_eq!(message, Some("assertion message".to_owned())); } _ => unreachable!(), diff --git a/tooling/nargo_cli/Cargo.toml b/tooling/nargo_cli/Cargo.toml index 0ae6073f0a1..d90aa0ca2ab 100644 --- a/tooling/nargo_cli/Cargo.toml +++ b/tooling/nargo_cli/Cargo.toml @@ -23,6 +23,7 @@ clap.workspace = true fm.workspace = true iter-extended.workspace = true nargo.workspace = true +nargo_fmt.workspace = true nargo_toml.workspace = true noir_lsp.workspace = true noirc_driver.workspace = true diff --git a/tooling/nargo_cli/src/cli/fmt_cmd.rs b/tooling/nargo_cli/src/cli/fmt_cmd.rs new file mode 100644 index 00000000000..bfe0b1948ad --- /dev/null +++ b/tooling/nargo_cli/src/cli/fmt_cmd.rs @@ -0,0 +1,73 @@ +use std::{fs::DirEntry, path::Path}; + +use clap::Args; +use fm::FileManager; +use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; +use noirc_errors::CustomDiagnostic; +use noirc_frontend::hir::def_map::parse_file; + +use crate::errors::CliError; + +use super::NargoConfig; + +#[derive(Debug, Clone, Args)] +pub(crate) struct FormatCommand {} + +pub(crate) fn run(_args: FormatCommand, config: NargoConfig) -> Result<(), CliError> { + let toml_path = get_package_manifest(&config.program_dir)?; + let workspace = resolve_workspace_from_toml(&toml_path, PackageSelection::All)?; + + let config = nargo_fmt::Config::read(&config.program_dir) + .map_err(|err| CliError::Generic(err.to_string()))?; + + for package in &workspace { + let mut file_manager = + FileManager::new(&package.root_dir, Box::new(|path| std::fs::read_to_string(path))); + + visit_noir_files(&package.root_dir.join("src"), &mut |entry| { + let file_id = file_manager.add_file(&entry.path()).expect("file exists"); + let (parsed_module, errors) = parse_file(&file_manager, file_id); + + if !errors.is_empty() { + let errors = errors + .into_iter() + .map(|error| { + let error: CustomDiagnostic = error.into(); + error.in_file(file_id) + }) + .collect(); + + let _ = super::compile_cmd::report_errors::<()>(Err(errors), &file_manager, false); + return Ok(()); + } + + let source = nargo_fmt::format( + file_manager.fetch_file(file_id).source(), + parsed_module, + &config, + ); + + std::fs::write(entry.path(), source) + }) + .map_err(|error| CliError::Generic(error.to_string()))?; + } + Ok(()) +} + +fn visit_noir_files( + dir: &Path, + cb: &mut dyn FnMut(&DirEntry) -> std::io::Result<()>, +) -> std::io::Result<()> { + if dir.is_dir() { + for entry in std::fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + visit_noir_files(&path, cb)?; + } else if entry.path().extension().map_or(false, |extension| extension == "nr") { + cb(&entry)?; + } + } + } + Ok(()) +} diff --git a/tooling/nargo_cli/src/cli/mod.rs b/tooling/nargo_cli/src/cli/mod.rs index 56d36095518..9e832317331 100644 --- a/tooling/nargo_cli/src/cli/mod.rs +++ b/tooling/nargo_cli/src/cli/mod.rs @@ -14,6 +14,7 @@ mod check_cmd; mod codegen_verifier_cmd; mod compile_cmd; mod execute_cmd; +mod fmt_cmd; mod info_cmd; mod init_cmd; mod lsp_cmd; @@ -52,6 +53,8 @@ pub(crate) struct NargoConfig { enum NargoCommand { Backend(backend_cmd::BackendCommand), Check(check_cmd::CheckCommand), + #[command(hide = true)] // Hidden while the feature has not been extensively tested + Fmt(fmt_cmd::FormatCommand), CodegenVerifier(codegen_verifier_cmd::CodegenVerifierCommand), #[command(alias = "build")] Compile(compile_cmd::CompileCommand), @@ -100,6 +103,7 @@ pub(crate) fn start_cli() -> eyre::Result<()> { NargoCommand::CodegenVerifier(args) => codegen_verifier_cmd::run(&backend, args, config), NargoCommand::Backend(args) => backend_cmd::run(args), NargoCommand::Lsp(args) => lsp_cmd::run(&backend, args, config), + NargoCommand::Fmt(args) => fmt_cmd::run(args, config), }?; Ok(()) diff --git a/tooling/nargo_fmt/Cargo.toml b/tooling/nargo_fmt/Cargo.toml new file mode 100644 index 00000000000..4b034ec7e3e --- /dev/null +++ b/tooling/nargo_fmt/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nargo_fmt" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +bytecount = "0.6.3" +noirc_frontend.workspace = true +serde.workspace = true +toml.workspace = true +thiserror.workspace = true diff --git a/tooling/nargo_fmt/src/config.rs b/tooling/nargo_fmt/src/config.rs new file mode 100644 index 00000000000..2d76c567e8c --- /dev/null +++ b/tooling/nargo_fmt/src/config.rs @@ -0,0 +1,60 @@ +use std::path::Path; + +use crate::errors::ConfigError; + +macro_rules! config { + ($($field_name:ident: $field_ty:ty, $default_value:expr, $description:expr );+ $(;)*) => ( + pub struct Config { + $( + #[doc = $description] + pub $field_name: $field_ty + ),+ + } + + impl Config { + pub fn fill_from_toml(&mut self, toml: TomlConfig) { + $( + if let Some(value) = toml.$field_name { + self.$field_name = value; + } + )+ + } + } + + impl Default for Config { + fn default() -> Self { + Self { + $( + $field_name: $default_value, + )+ + } + } + } + + #[derive(serde::Deserialize, serde::Serialize, Clone)] + pub struct TomlConfig { + $(pub $field_name: Option<$field_ty>),+ + } + ) +} + +config! { + tab_spaces: usize, 4, "Number of spaces per tab"; +} + +impl Config { + pub fn read(path: &Path) -> Result { + let mut config = Self::default(); + let config_path = path.join("noirfmt.toml"); + + let raw_toml = match std::fs::read_to_string(&config_path) { + Ok(t) => t, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => String::new(), + Err(cause) => return Err(ConfigError::ReadFailed(config_path, cause)), + }; + let toml = toml::from_str(&raw_toml).map_err(ConfigError::MalformedFile)?; + + config.fill_from_toml(toml); + Ok(config) + } +} diff --git a/tooling/nargo_fmt/src/errors.rs b/tooling/nargo_fmt/src/errors.rs new file mode 100644 index 00000000000..e0a1758ae0f --- /dev/null +++ b/tooling/nargo_fmt/src/errors.rs @@ -0,0 +1,12 @@ +use std::path::PathBuf; + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ConfigError { + #[error("Cannot read file {0} - {1}")] + ReadFailed(PathBuf, std::io::Error), + + #[error("noirfmt.toml is badly formed, could not parse.\n\n {0}")] + MalformedFile(#[from] toml::de::Error), +} diff --git a/tooling/nargo_fmt/src/lib.rs b/tooling/nargo_fmt/src/lib.rs new file mode 100644 index 00000000000..754449aa425 --- /dev/null +++ b/tooling/nargo_fmt/src/lib.rs @@ -0,0 +1,83 @@ +#![forbid(unsafe_code)] +#![warn(unused_crate_dependencies, unused_extern_crates)] +#![warn(unreachable_pub)] +#![warn(clippy::semicolon_if_nothing_returned)] +#![warn(unused_qualifications, clippy::use_self)] + +/// A Rust code formatting utility designed to manage and format untouched fragments of source code, +/// including comments, whitespace, and other characters. While the module doesn't directly address comments, +/// it treats them as unchanged fragments, ensuring their original placement and content remain preserved. +/// +/// Key methods include: +/// - `format_missing`: Addresses characters between the last processed position and a given end position, +/// capturing comments and other untouched sequences. +/// - `format_missing_indent`: Functions similarly to `format_missing`, but introduces added indentation. +/// - `format_missing_inner`: The core method for handling missing fragments, appending them to the output buffer. +/// Pure whitespace fragments might be replaced or adjusted based on context. +/// - `push_vertical_spaces`: Standardizes vertical spacing, eliminating potential excessive empty lines +/// or ensuring adequate vertical separation. +/// +/// By recognizing and properly handling these untouched fragments, the utility ensures comments remain intact +/// in both placement and content during the formatting process. +mod config; +pub mod errors; +mod visitor; + +use noirc_frontend::ParsedModule; +use visitor::FmtVisitor; + +pub use config::Config; + +pub fn format(source: &str, parsed_module: ParsedModule, config: &Config) -> String { + let mut fmt = FmtVisitor::new(source, config); + fmt.visit_module(parsed_module); + fmt.finish() +} + +#[cfg(test)] +mod tests { + use std::{ffi::OsStr, path::PathBuf}; + + use crate::Config; + + #[test] + fn test() { + let files = std::fs::read_dir("tests/input").unwrap(); + for file in files { + let file = file.unwrap(); + + let config = Config::default(); + + let source_path = file.path(); + let source = std::fs::read_to_string(&source_path).unwrap(); + + let (parsed_module, errors) = noirc_frontend::parse_program(&source); + let fmt_text = crate::format(&source, parsed_module, &config); + + assert!(errors.is_empty()); + + let target_path: PathBuf = source_path + .components() + .map(|component| { + if component.as_os_str() == "input" { + OsStr::new("expected") + } else { + component.as_os_str() + } + }) + .collect(); + + let target = match std::fs::read_to_string(&target_path) { + Ok(t) => t, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + std::fs::write(target_path, fmt_text.clone()).unwrap(); + fmt_text.clone() + } + Err(err) => unreachable!("{err}"), + }; + + // FIXME: better diff + assert_eq!(fmt_text, target); + } + } +} diff --git a/tooling/nargo_fmt/src/visitor.rs b/tooling/nargo_fmt/src/visitor.rs new file mode 100644 index 00000000000..cf2b4d9adfd --- /dev/null +++ b/tooling/nargo_fmt/src/visitor.rs @@ -0,0 +1,151 @@ +/// A macro to create a slice from a given data source, helping to avoid borrow checker errors. +#[macro_export] +macro_rules! slice { + ($this:ident, $start:expr, $end:expr) => { + &$this.source[$start as usize..$end as usize] + }; +} + +mod expr; +mod item; +mod stmt; + +use noirc_frontend::hir::resolution::errors::Span; + +use crate::config::Config; + +pub(crate) struct FmtVisitor<'me> { + config: &'me Config, + buffer: String, + source: &'me str, + block_indent: Indent, + last_position: u32, +} + +impl<'me> FmtVisitor<'me> { + pub(crate) fn new(source: &'me str, config: &'me Config) -> Self { + Self { + buffer: String::new(), + config, + source, + last_position: 0, + block_indent: Indent { block_indent: 0 }, + } + } + + pub(crate) fn finish(self) -> String { + self.buffer + } + + fn with_indent(&mut self, f: impl FnOnce(&mut Self) -> T) -> T { + self.block_indent.block_indent(self.config); + let ret = f(self); + self.block_indent.block_unindent(self.config); + ret + } + + fn at_start(&self) -> bool { + self.buffer.is_empty() + } + + fn push_str(&mut self, s: &str) { + self.buffer.push_str(s); + } + + #[track_caller] + fn push_rewrite(&mut self, s: String, span: Span) { + self.format_missing_indent(span.start(), true); + self.push_str(&s); + } + + fn format_missing(&mut self, end: u32) { + self.format_missing_inner(end, |this, slice, _| this.push_str(slice)); + } + + #[track_caller] + fn format_missing_indent(&mut self, end: u32, should_indent: bool) { + self.format_missing_inner(end, |this, last_slice, slice| { + this.push_str(last_slice.trim_end()); + + if last_slice == slice && !this.at_start() { + this.push_str("\n"); + } + + if should_indent { + let indent = this.block_indent.to_string(); + this.push_str(&indent); + } + }); + } + + #[track_caller] + fn format_missing_inner( + &mut self, + end: u32, + process_last_slice: impl Fn(&mut Self, &str, &str), + ) { + let start = self.last_position; + + if start == end { + if !self.at_start() { + process_last_slice(self, "", ""); + } + return; + } + + let slice = slice!(self, start, end); + self.last_position = end; + + if slice.trim().is_empty() && !self.at_start() { + self.push_vertical_spaces(slice); + process_last_slice(self, "", slice); + } else { + process_last_slice(self, slice, slice); + } + } + + fn push_vertical_spaces(&mut self, slice: &str) { + let newline_upper_bound = 2; + let newline_lower_bound = 1; + + let mut newline_count = bytecount::count(slice.as_bytes(), b'\n'); + let offset = self.buffer.chars().rev().take_while(|c| *c == '\n').count(); + + if newline_count + offset > newline_upper_bound { + if offset >= newline_upper_bound { + newline_count = 0; + } else { + newline_count = newline_upper_bound - offset; + } + } else if newline_count + offset < newline_lower_bound { + if offset >= newline_lower_bound { + newline_count = 0; + } else { + newline_count = newline_lower_bound - offset; + } + } + + let blank_lines = "\n".repeat(newline_count); + self.push_str(&blank_lines); + } +} + +#[derive(Clone, Copy)] +struct Indent { + block_indent: usize, +} + +impl Indent { + fn block_indent(&mut self, config: &Config) { + self.block_indent += config.tab_spaces; + } + + fn block_unindent(&mut self, config: &Config) { + self.block_indent -= config.tab_spaces; + } + + #[allow(clippy::inherent_to_string)] + fn to_string(self) -> String { + " ".repeat(self.block_indent) + } +} diff --git a/tooling/nargo_fmt/src/visitor/expr.rs b/tooling/nargo_fmt/src/visitor/expr.rs new file mode 100644 index 00000000000..a0128042e1d --- /dev/null +++ b/tooling/nargo_fmt/src/visitor/expr.rs @@ -0,0 +1,81 @@ +use noirc_frontend::{hir::resolution::errors::Span, BlockExpression, Expression, ExpressionKind}; + +use super::FmtVisitor; + +impl FmtVisitor<'_> { + pub(crate) fn visit_expr(&mut self, expr: Expression) { + let span = expr.span; + + let rewrite = self.format_expr(expr); + self.push_rewrite(rewrite, span); + + self.last_position = span.end(); + } + + fn format_expr(&self, Expression { kind, span }: Expression) -> String { + match kind { + ExpressionKind::Block(block) => { + let mut visitor = FmtVisitor::new(self.source, self.config); + + visitor.block_indent = self.block_indent; + visitor.visit_block(block, span, true); + + visitor.buffer + } + ExpressionKind::Prefix(prefix) => { + format!("{}{}", prefix.operator, self.format_expr(prefix.rhs)) + } + // TODO: + _expr => slice!(self, span.start(), span.end()).to_string(), + } + } + + pub(crate) fn visit_block( + &mut self, + block: BlockExpression, + block_span: Span, + should_indent: bool, + ) { + if block.is_empty() { + self.visit_empty_block(block_span, should_indent); + return; + } + + self.last_position = block_span.start() + 1; // `{` + self.push_str("{"); + + self.with_indent(|this| { + this.visit_stmts(block.0); + }); + + let slice = slice!(self, self.last_position, block_span.end() - 1).trim_end(); + self.push_str(slice); + + self.last_position = block_span.end(); + + self.push_str("\n"); + if should_indent { + self.push_str(&self.block_indent.to_string()); + } + self.push_str("}"); + } + + fn visit_empty_block(&mut self, block_span: Span, should_indent: bool) { + let slice = slice!(self, block_span.start(), block_span.end()); + let comment_str = slice[1..slice.len() - 1].trim(); + let block_str = if comment_str.is_empty() { + "{}".to_string() + } else { + self.block_indent.block_indent(self.config); + let open_indent = self.block_indent.to_string(); + self.block_indent.block_unindent(self.config); + let close_indent = + if should_indent { self.block_indent.to_string() } else { String::new() }; + + let ret = format!("{{\n{open_indent}{comment_str}\n{close_indent}}}"); + ret + }; + self.last_position = block_span.end(); + self.push_str(&block_str); + } +} diff --git a/tooling/nargo_fmt/src/visitor/item.rs b/tooling/nargo_fmt/src/visitor/item.rs new file mode 100644 index 00000000000..1e32ab22747 --- /dev/null +++ b/tooling/nargo_fmt/src/visitor/item.rs @@ -0,0 +1,33 @@ +use noirc_frontend::{ + parser::{Item, ItemKind}, + NoirFunction, ParsedModule, +}; + +impl super::FmtVisitor<'_> { + fn format_fn_before_block(&self, func: NoirFunction, start: u32) -> (String, bool) { + let slice = slice!(self, start, func.span().start()); + let force_brace_newline = slice.contains("//"); + (slice.trim_end().to_string(), force_brace_newline) + } + + pub(crate) fn visit_module(&mut self, module: ParsedModule) { + for Item { kind, span } in module.items { + match kind { + ItemKind::Function(func) => { + let (fn_before_block, force_brace_newline) = + self.format_fn_before_block(func.clone(), span.start()); + + self.format_missing_indent(span.start(), false); + + self.push_str(&fn_before_block); + self.push_str(if force_brace_newline { "\n" } else { " " }); + + self.visit_block(func.def.body, func.def.span, false); + } + _ => self.format_missing(span.end()), + } + } + + self.format_missing_indent(self.source.len() as u32, false); + } +} diff --git a/tooling/nargo_fmt/src/visitor/stmt.rs b/tooling/nargo_fmt/src/visitor/stmt.rs new file mode 100644 index 00000000000..973167fd19a --- /dev/null +++ b/tooling/nargo_fmt/src/visitor/stmt.rs @@ -0,0 +1,19 @@ +use noirc_frontend::{Statement, StatementKind}; + +impl super::FmtVisitor<'_> { + pub(crate) fn visit_stmts(&mut self, stmts: Vec) { + for Statement { kind, span } in stmts { + match kind { + StatementKind::Expression(expr) => self.visit_expr(expr), + StatementKind::Semi(expr) => { + self.visit_expr(expr); + self.push_str(";"); + } + StatementKind::Error => unreachable!(), + _ => self.format_missing(span.end()), + } + + self.last_position = span.end(); + } + } +} diff --git a/tooling/nargo_fmt/tests/expected/add.nr b/tooling/nargo_fmt/tests/expected/add.nr new file mode 100644 index 00000000000..6f2892942c1 --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/add.nr @@ -0,0 +1,7 @@ +fn main(mut x: u32, y: u32, z: u32) { + x += y; + assert(x == z); + + x *= 8; + assert(x>9); +} diff --git a/tooling/nargo_fmt/tests/expected/call.nr b/tooling/nargo_fmt/tests/expected/call.nr new file mode 100644 index 00000000000..ca4d5b82683 --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/call.nr @@ -0,0 +1,3 @@ +fn main() { + main(4, 3); +} diff --git a/tooling/nargo_fmt/tests/expected/comment.nr b/tooling/nargo_fmt/tests/expected/comment.nr new file mode 100644 index 00000000000..b6ac52a236a --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/comment.nr @@ -0,0 +1,27 @@ +fn comment1() { + // +} + +// random comment +fn comment2() { + // Test +} + +fn comment3() // some comment +{} + +fn comment4() +// some comment +{} + +fn comment5() // some comment +{} + +fn comment6() // some comment some comment some comment some comment some comment some comment so +{} + +fn comment7() +// some comment some comment some comment some comment some comment some comment some comment +{} + +fn comment8(/*test*/) {} diff --git a/tooling/nargo_fmt/tests/expected/empty.nr b/tooling/nargo_fmt/tests/expected/empty.nr new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tooling/nargo_fmt/tests/expected/expr.nr b/tooling/nargo_fmt/tests/expected/expr.nr new file mode 100644 index 00000000000..9497c4d831a --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/expr.nr @@ -0,0 +1,64 @@ +// Test some empty blocks. +fn qux() { + {} + + { + /* a block with a comment */ + } + {} + { + // A block with a comment. + } + + { + { + { + // A block with a comment. + } + } + } +} + +fn foo_return() { + "yay" +} + +fn fooblock() { + { + "inner-block" + } +} + +fn fooblock() { + { + { + { + "inner-block" + } + } + } +} + +fn comment() { + // this is a test comment + 1 +} + +fn only_comment() { + // Keep this here +} + +fn only_comments() { + // Keep this here +// Keep this here +} + +fn only_comments() { + // Keep this here + // Keep this here +} + +fn commnet() { + 1 + // +} diff --git a/tooling/nargo_fmt/tests/expected/fn.nr b/tooling/nargo_fmt/tests/expected/fn.nr new file mode 100644 index 00000000000..89484dc83c2 --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/fn.nr @@ -0,0 +1 @@ +fn main(x: pub u8, y: u8) {} diff --git a/tooling/nargo_fmt/tests/expected/infix.nr b/tooling/nargo_fmt/tests/expected/infix.nr new file mode 100644 index 00000000000..13b53170da6 --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/infix.nr @@ -0,0 +1,5 @@ +fn foo() { + 40 + 2; + !40 + 2; + 40 + 2 == 42; +} diff --git a/tooling/nargo_fmt/tests/expected/module.nr b/tooling/nargo_fmt/tests/expected/module.nr new file mode 100644 index 00000000000..e419543dbc4 --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/module.nr @@ -0,0 +1,23 @@ +mod a { + mod b { + struct Data { + a: Field + } + } + + fn data(a: Field) -> Data { + Data { a } + } + + fn data2(a: Field) -> Data2 { + Data2 { a } + } + + mod tests { + #[test] + fn test() { + data(1); + data2(1); + } + } +} diff --git a/tooling/nargo_fmt/tests/expected/nested-if-else.nr b/tooling/nargo_fmt/tests/expected/nested-if-else.nr new file mode 100644 index 00000000000..8aa120e3b18 --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/nested-if-else.nr @@ -0,0 +1,3 @@ +fn nested_if_else() { + if false { 1 } else if false { 2 } else { 3 } +} diff --git a/tooling/nargo_fmt/tests/expected/print.nr b/tooling/nargo_fmt/tests/expected/print.nr new file mode 100644 index 00000000000..e169f565455 --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/print.nr @@ -0,0 +1,5 @@ +use dep::std; + +fn main() { + std::println("Hello world"); +} diff --git a/tooling/nargo_fmt/tests/expected/print2.nr b/tooling/nargo_fmt/tests/expected/print2.nr new file mode 100644 index 00000000000..80284444af8 --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/print2.nr @@ -0,0 +1,5 @@ +use dep::std; + +fn main( ) { + std::println("Hello world"); +} diff --git a/tooling/nargo_fmt/tests/expected/read_array.nr b/tooling/nargo_fmt/tests/expected/read_array.nr new file mode 100644 index 00000000000..d2619884b5d --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/read_array.nr @@ -0,0 +1,6 @@ +fn read_array(x: [Field; 3]) { + assert(x[0] == 1); + let y = [1, 5, 27]; + + assert(y[x[0]] == 5); +} diff --git a/tooling/nargo_fmt/tests/expected/struct.nr b/tooling/nargo_fmt/tests/expected/struct.nr new file mode 100644 index 00000000000..5e3530e8364 --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/struct.nr @@ -0,0 +1,77 @@ +struct Foo { + bar: Field, + array: [Field; 2], +} + +struct Pair { + first: Foo, + second: Field, +} + +impl Foo { + fn default(x: Field,y: Field) -> Self { + Self { bar: 0, array: [x,y] } + } +} + +impl Pair { + fn foo(p: Self) -> Foo { + p.first + } + + fn bar(self) -> Field { + self.foo().bar + } +} + +struct Nested { + a: Field, + b: Field +} +struct MyStruct { + my_bool: bool, + my_int: u32, + my_nest: Nested, +} +fn test_struct_in_tuple(a_bool : bool,x:Field, y:Field) -> (MyStruct, bool) { + let my_struct = MyStruct { + my_bool: a_bool, + my_int: 5, + my_nest: Nested{a:x,b:y}, + }; + (my_struct, a_bool) +} + +struct Animal { + legs: Field, + eyes: u8, +} + +fn get_dog() -> Animal { + let dog = Animal { legs: 4, eyes: 2 }; + dog +} + +fn main(x: Field, y: Field) { + let first = Foo::default(x,y); + let p = Pair { first, second: 1 }; + + assert(p.bar() == x); + assert(p.second == y); + assert(p.first.array[0] != p.first.array[1]); + + // Nested structs + let (struct_from_tuple, a_bool) = test_struct_in_tuple(true,x,y); + assert(struct_from_tuple.my_bool == true); + assert(a_bool == true); + assert(struct_from_tuple.my_int == 5); + assert(struct_from_tuple.my_nest.a == 0); + + // Regression test for issue #670 + let Animal { legs, eyes } = get_dog(); + let six = legs + eyes as Field; + + assert(six == 6); + + let Animal { legs: _, eyes: _ } = get_dog(); +} diff --git a/tooling/nargo_fmt/tests/expected/unary_operators.nr b/tooling/nargo_fmt/tests/expected/unary_operators.nr new file mode 100644 index 00000000000..88140ac9a6e --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/unary_operators.nr @@ -0,0 +1,3 @@ +fn main() { + -1 +} diff --git a/tooling/nargo_fmt/tests/expected/vec.nr b/tooling/nargo_fmt/tests/expected/vec.nr new file mode 100644 index 00000000000..1c9a791961e --- /dev/null +++ b/tooling/nargo_fmt/tests/expected/vec.nr @@ -0,0 +1,60 @@ +struct Vec { + slice: [T] +} + +// A mutable vector type implemented as a wrapper around immutable slices. +// A separate type is technically not needed but helps differentiate which operations are mutable. +impl Vec { + pub fn new() -> Self { + Self { slice: [] } + } + + // Create a Vec containing each element from the given slice. + // Mutations to the resulting Vec will not affect the original slice. + pub fn from_slice(slice: [T]) -> Self { + Self { slice } + } + + /// Get an element from the vector at the given index. + /// Panics if the given index + /// points beyond the end of the vector. + pub fn get(self, index: Field) -> T { + self.slice[index] + } + + /// Push a new element to the end of the vector, returning a + /// new vector with a length one greater than the + /// original unmodified vector. + pub fn push(&mut self, elem: T) { + self.slice = self.slice.push_back(elem); + } + + /// Pop an element from the end of the given vector, returning + /// a new vector with a length of one less than the given vector, + /// as well as the popped element. + /// Panics if the given vector's length is zero. + pub fn pop(&mut self) -> T { + let (popped_slice, last_elem) = self.slice.pop_back(); + self.slice = popped_slice; + last_elem + } + + /// Insert an element at a specified index, shifting all elements + /// after it to the right + pub fn insert(&mut self, index: Field, elem: T) { + self.slice = self.slice.insert(index, elem); + } + + /// Remove an element at a specified index, shifting all elements + /// after it to the left, returning the removed element + pub fn remove(&mut self, index: Field) -> T { + let (new_slice, elem) = self.slice.remove(index); + self.slice = new_slice; + elem + } + + /// Returns the number of elements in the vector + pub fn len(self) -> Field { + self.slice.len() + } +} diff --git a/tooling/nargo_fmt/tests/input/add.nr b/tooling/nargo_fmt/tests/input/add.nr new file mode 100644 index 00000000000..6f2892942c1 --- /dev/null +++ b/tooling/nargo_fmt/tests/input/add.nr @@ -0,0 +1,7 @@ +fn main(mut x: u32, y: u32, z: u32) { + x += y; + assert(x == z); + + x *= 8; + assert(x>9); +} diff --git a/tooling/nargo_fmt/tests/input/call.nr b/tooling/nargo_fmt/tests/input/call.nr new file mode 100644 index 00000000000..8927ebc85db --- /dev/null +++ b/tooling/nargo_fmt/tests/input/call.nr @@ -0,0 +1,3 @@ +fn main() { + main(4, 3); +} \ No newline at end of file diff --git a/tooling/nargo_fmt/tests/input/comment.nr b/tooling/nargo_fmt/tests/input/comment.nr new file mode 100644 index 00000000000..0e203a82d66 --- /dev/null +++ b/tooling/nargo_fmt/tests/input/comment.nr @@ -0,0 +1,32 @@ +fn comment1() { + // +} + +// random comment + +fn comment2() { // Test +} + +fn comment3() // some comment +{ +} + +fn comment4() +// some comment +{ +} + +fn comment5() // some comment +{ +} + +fn comment6() // some comment some comment some comment some comment some comment some comment so +{ +} + +fn comment7() +// some comment some comment some comment some comment some comment some comment some comment +{ +} + +fn comment8(/*test*/) {} diff --git a/tooling/nargo_fmt/tests/input/empty.nr b/tooling/nargo_fmt/tests/input/empty.nr new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tooling/nargo_fmt/tests/input/expr.nr b/tooling/nargo_fmt/tests/input/expr.nr new file mode 100644 index 00000000000..9e3e4a35e1a --- /dev/null +++ b/tooling/nargo_fmt/tests/input/expr.nr @@ -0,0 +1,64 @@ +// Test some empty blocks. +fn qux() { + {} + + { /* a block with a comment */ } + { + + } + { + // A block with a comment. + } + + { + { + { + // A block with a comment. + } + } + } +} + +fn foo_return() { + "yay" +} + +fn fooblock() { + { + "inner-block" + } +} + +fn fooblock() { + { + { + { + "inner-block" + } + } + } +} + +fn comment() { + // this is a test comment + 1 +} + +fn only_comment() { + // Keep this here +} + +fn only_comments() { +// Keep this here +// Keep this here +} + +fn only_comments() { + // Keep this here + // Keep this here +} + +fn commnet() { + 1 + // +} \ No newline at end of file diff --git a/tooling/nargo_fmt/tests/input/fn.nr b/tooling/nargo_fmt/tests/input/fn.nr new file mode 100644 index 00000000000..89484dc83c2 --- /dev/null +++ b/tooling/nargo_fmt/tests/input/fn.nr @@ -0,0 +1 @@ +fn main(x: pub u8, y: u8) {} diff --git a/tooling/nargo_fmt/tests/input/infix.nr b/tooling/nargo_fmt/tests/input/infix.nr new file mode 100644 index 00000000000..13b53170da6 --- /dev/null +++ b/tooling/nargo_fmt/tests/input/infix.nr @@ -0,0 +1,5 @@ +fn foo() { + 40 + 2; + !40 + 2; + 40 + 2 == 42; +} diff --git a/tooling/nargo_fmt/tests/input/module.nr b/tooling/nargo_fmt/tests/input/module.nr new file mode 100644 index 00000000000..e419543dbc4 --- /dev/null +++ b/tooling/nargo_fmt/tests/input/module.nr @@ -0,0 +1,23 @@ +mod a { + mod b { + struct Data { + a: Field + } + } + + fn data(a: Field) -> Data { + Data { a } + } + + fn data2(a: Field) -> Data2 { + Data2 { a } + } + + mod tests { + #[test] + fn test() { + data(1); + data2(1); + } + } +} diff --git a/tooling/nargo_fmt/tests/input/nested-if-else.nr b/tooling/nargo_fmt/tests/input/nested-if-else.nr new file mode 100644 index 00000000000..8aa120e3b18 --- /dev/null +++ b/tooling/nargo_fmt/tests/input/nested-if-else.nr @@ -0,0 +1,3 @@ +fn nested_if_else() { + if false { 1 } else if false { 2 } else { 3 } +} diff --git a/tooling/nargo_fmt/tests/input/print.nr b/tooling/nargo_fmt/tests/input/print.nr new file mode 100644 index 00000000000..8afa562dada --- /dev/null +++ b/tooling/nargo_fmt/tests/input/print.nr @@ -0,0 +1,3 @@ +use dep::std; + +fn main() { std::println("Hello world"); } diff --git a/tooling/nargo_fmt/tests/input/print2.nr b/tooling/nargo_fmt/tests/input/print2.nr new file mode 100644 index 00000000000..07ef9dd0386 --- /dev/null +++ b/tooling/nargo_fmt/tests/input/print2.nr @@ -0,0 +1,5 @@ +use dep::std; + +fn main( ) { +std::println("Hello world"); +} diff --git a/tooling/nargo_fmt/tests/input/read_array.nr b/tooling/nargo_fmt/tests/input/read_array.nr new file mode 100644 index 00000000000..d2619884b5d --- /dev/null +++ b/tooling/nargo_fmt/tests/input/read_array.nr @@ -0,0 +1,6 @@ +fn read_array(x: [Field; 3]) { + assert(x[0] == 1); + let y = [1, 5, 27]; + + assert(y[x[0]] == 5); +} diff --git a/tooling/nargo_fmt/tests/input/struct.nr b/tooling/nargo_fmt/tests/input/struct.nr new file mode 100644 index 00000000000..5e3530e8364 --- /dev/null +++ b/tooling/nargo_fmt/tests/input/struct.nr @@ -0,0 +1,77 @@ +struct Foo { + bar: Field, + array: [Field; 2], +} + +struct Pair { + first: Foo, + second: Field, +} + +impl Foo { + fn default(x: Field,y: Field) -> Self { + Self { bar: 0, array: [x,y] } + } +} + +impl Pair { + fn foo(p: Self) -> Foo { + p.first + } + + fn bar(self) -> Field { + self.foo().bar + } +} + +struct Nested { + a: Field, + b: Field +} +struct MyStruct { + my_bool: bool, + my_int: u32, + my_nest: Nested, +} +fn test_struct_in_tuple(a_bool : bool,x:Field, y:Field) -> (MyStruct, bool) { + let my_struct = MyStruct { + my_bool: a_bool, + my_int: 5, + my_nest: Nested{a:x,b:y}, + }; + (my_struct, a_bool) +} + +struct Animal { + legs: Field, + eyes: u8, +} + +fn get_dog() -> Animal { + let dog = Animal { legs: 4, eyes: 2 }; + dog +} + +fn main(x: Field, y: Field) { + let first = Foo::default(x,y); + let p = Pair { first, second: 1 }; + + assert(p.bar() == x); + assert(p.second == y); + assert(p.first.array[0] != p.first.array[1]); + + // Nested structs + let (struct_from_tuple, a_bool) = test_struct_in_tuple(true,x,y); + assert(struct_from_tuple.my_bool == true); + assert(a_bool == true); + assert(struct_from_tuple.my_int == 5); + assert(struct_from_tuple.my_nest.a == 0); + + // Regression test for issue #670 + let Animal { legs, eyes } = get_dog(); + let six = legs + eyes as Field; + + assert(six == 6); + + let Animal { legs: _, eyes: _ } = get_dog(); +} diff --git a/tooling/nargo_fmt/tests/input/unary_operators.nr b/tooling/nargo_fmt/tests/input/unary_operators.nr new file mode 100644 index 00000000000..5805c7081f5 --- /dev/null +++ b/tooling/nargo_fmt/tests/input/unary_operators.nr @@ -0,0 +1,3 @@ +fn main() { + -1 +} \ No newline at end of file diff --git a/tooling/nargo_fmt/tests/input/vec.nr b/tooling/nargo_fmt/tests/input/vec.nr new file mode 100644 index 00000000000..1c9a791961e --- /dev/null +++ b/tooling/nargo_fmt/tests/input/vec.nr @@ -0,0 +1,60 @@ +struct Vec { + slice: [T] +} + +// A mutable vector type implemented as a wrapper around immutable slices. +// A separate type is technically not needed but helps differentiate which operations are mutable. +impl Vec { + pub fn new() -> Self { + Self { slice: [] } + } + + // Create a Vec containing each element from the given slice. + // Mutations to the resulting Vec will not affect the original slice. + pub fn from_slice(slice: [T]) -> Self { + Self { slice } + } + + /// Get an element from the vector at the given index. + /// Panics if the given index + /// points beyond the end of the vector. + pub fn get(self, index: Field) -> T { + self.slice[index] + } + + /// Push a new element to the end of the vector, returning a + /// new vector with a length one greater than the + /// original unmodified vector. + pub fn push(&mut self, elem: T) { + self.slice = self.slice.push_back(elem); + } + + /// Pop an element from the end of the given vector, returning + /// a new vector with a length of one less than the given vector, + /// as well as the popped element. + /// Panics if the given vector's length is zero. + pub fn pop(&mut self) -> T { + let (popped_slice, last_elem) = self.slice.pop_back(); + self.slice = popped_slice; + last_elem + } + + /// Insert an element at a specified index, shifting all elements + /// after it to the right + pub fn insert(&mut self, index: Field, elem: T) { + self.slice = self.slice.insert(index, elem); + } + + /// Remove an element at a specified index, shifting all elements + /// after it to the left, returning the removed element + pub fn remove(&mut self, index: Field) -> T { + let (new_slice, elem) = self.slice.remove(index); + self.slice = new_slice; + elem + } + + /// Returns the number of elements in the vector + pub fn len(self) -> Field { + self.slice.len() + } +}