diff --git a/sway-core/src/convert_parse_tree.rs b/sway-core/src/convert_parse_tree.rs index ae4e826606b..e0f5af85aae 100644 --- a/sway-core/src/convert_parse_tree.rs +++ b/sway-core/src/convert_parse_tree.rs @@ -19,10 +19,11 @@ use { AbiCastArgs, AngleBrackets, AsmBlock, Assignable, Braces, CodeBlockContents, Dependency, DoubleColonToken, Expr, ExprArrayDescriptor, ExprStructField, ExprTupleDescriptor, FnArg, FnArgs, FnSignature, GenericArgs, GenericParams, IfCondition, IfExpr, ImpureToken, - Instruction, Item, ItemAbi, ItemConst, ItemEnum, ItemFn, ItemImpl, ItemStorage, ItemStruct, - ItemTrait, ItemUse, LitInt, LitIntType, MatchBranchKind, PathExpr, PathExprSegment, - PathType, PathTypeSegment, Pattern, PatternStructField, Program, ProgramKind, PubToken, - QualifiedPathRoot, Statement, StatementLet, Traits, Ty, TypeField, UseTree, + Instruction, ItemAbi, ItemConst, ItemEnum, ItemFn, ItemImpl, ItemKind, ItemStorage, + ItemStruct, ItemTrait, ItemUse, LitInt, LitIntType, MatchBranchKind, PathExpr, + PathExprSegment, PathType, PathTypeSegment, Pattern, PatternStructField, Program, + ProgramKind, PubToken, QualifiedPathRoot, Statement, StatementLet, Traits, Ty, TypeField, + UseTree, }, sway_types::{Ident, Span}, thiserror::Error, @@ -263,7 +264,7 @@ pub fn program_to_sway_parse_tree( .collect() }; for item in program.items { - let ast_nodes = item_to_ast_nodes(ec, item)?; + let ast_nodes = item_to_ast_nodes(ec, item.kind)?; root_nodes.extend(ast_nodes); } root_nodes @@ -274,57 +275,57 @@ pub fn program_to_sway_parse_tree( }) } -fn item_to_ast_nodes(ec: &mut ErrorContext, item: Item) -> Result, ErrorEmitted> { +fn item_to_ast_nodes(ec: &mut ErrorContext, item: ItemKind) -> Result, ErrorEmitted> { let span = item.span(); let contents = match item { - Item::Use(item_use) => { + ItemKind::Use(item_use) => { let use_statements = item_use_to_use_statements(ec, item_use)?; use_statements .into_iter() .map(AstNodeContent::UseStatement) .collect() } - Item::Struct(item_struct) => { + ItemKind::Struct(item_struct) => { let struct_declaration = item_struct_to_struct_declaration(ec, item_struct)?; vec![AstNodeContent::Declaration(Declaration::StructDeclaration( struct_declaration, ))] } - Item::Enum(item_enum) => { + ItemKind::Enum(item_enum) => { let enum_declaration = item_enum_to_enum_declaration(ec, item_enum)?; vec![AstNodeContent::Declaration(Declaration::EnumDeclaration( enum_declaration, ))] } - Item::Fn(item_fn) => { + ItemKind::Fn(item_fn) => { let function_declaration = item_fn_to_function_declaration(ec, item_fn)?; vec![AstNodeContent::Declaration( Declaration::FunctionDeclaration(function_declaration), )] } - Item::Trait(item_trait) => { + ItemKind::Trait(item_trait) => { let trait_declaration = item_trait_to_trait_declaration(ec, item_trait)?; vec![AstNodeContent::Declaration(Declaration::TraitDeclaration( trait_declaration, ))] } - Item::Impl(item_impl) => { + ItemKind::Impl(item_impl) => { let declaration = item_impl_to_declaration(ec, item_impl)?; vec![AstNodeContent::Declaration(declaration)] } - Item::Abi(item_abi) => { + ItemKind::Abi(item_abi) => { let abi_declaration = item_abi_to_abi_declaration(ec, item_abi)?; vec![AstNodeContent::Declaration(Declaration::AbiDeclaration( abi_declaration, ))] } - Item::Const(item_const) => { + ItemKind::Const(item_const) => { let constant_declaration = item_const_to_constant_declaration(ec, item_const)?; vec![AstNodeContent::Declaration( Declaration::ConstantDeclaration(constant_declaration), )] } - Item::Storage(item_storage) => { + ItemKind::Storage(item_storage) => { let storage_declaration = item_storage_to_storage_declaration(ec, item_storage)?; vec![AstNodeContent::Declaration( Declaration::StorageDeclaration(storage_declaration), diff --git a/sway-parse/src/attribute.rs b/sway-parse/src/attribute.rs new file mode 100644 index 00000000000..7e7152c75a7 --- /dev/null +++ b/sway-parse/src/attribute.rs @@ -0,0 +1,63 @@ +use crate::priv_prelude::*; + +// Attributes can have any number of arguments: +// +// #[attribute] +// #[attribute()] +// #[attribute(value)] +// #[attribute(value0, value1, value2)] + +pub struct AttributeDecl { + pub hash_token: HashToken, + pub attribute: SquareBrackets, +} + +impl AttributeDecl { + pub fn span(&self) -> Span { + Span::join(self.hash_token.span(), self.attribute.span()) + } +} + +impl Parse for AttributeDecl { + fn parse(parser: &mut Parser) -> ParseResult { + let hash_token = parser.parse()?; + let attribute = parser.parse()?; + Ok(AttributeDecl { + hash_token, + attribute, + }) + } +} + +#[derive(Debug)] +pub struct Attribute { + pub name: Ident, + pub args: Option>>, +} + +impl Attribute { + pub fn span(&self) -> Span { + self.args + .as_ref() + .map(|args| Span::join(self.name.span().clone(), args.span())) + .unwrap_or_else(|| self.name.span().clone()) + } +} + +impl Parse for Attribute { + fn parse(parser: &mut Parser) -> ParseResult { + let name = parser.parse()?; + let args = Parens::try_parse(parser)?; + Ok(Attribute { name, args }) + } +} + +impl ParseToEnd for Attribute { + fn parse_to_end<'a, 'e>(mut parser: Parser<'a, 'e>) -> ParseResult<(Self, ParserConsumed<'a>)> { + let attrib = parser.parse()?; + match parser.check_empty() { + Some(consumed) => Ok((attrib, consumed)), + None => Err(parser.emit_error(ParseErrorKind::UnexpectedTokenAfterAttribute)), + } + } +} diff --git a/sway-parse/src/error.rs b/sway-parse/src/error.rs index 596c7ae8989..bda72a0ea19 100644 --- a/sway-parse/src/error.rs +++ b/sway-parse/src/error.rs @@ -58,6 +58,10 @@ pub enum ParseErrorKind { ExpectedKeyword { word: &'static str }, #[error("unexpected token after abi address")] UnexpectedTokenAfterAbiAddress, + #[error("expected an attribute")] + ExpectedAnAttribute, + #[error("unexpected token after an attribute")] + UnexpectedTokenAfterAttribute, } #[derive(Debug, Error, Clone, PartialEq, Hash)] diff --git a/sway-parse/src/item/mod.rs b/sway-parse/src/item/mod.rs index dac7e413bd9..0df0d4419d4 100644 --- a/sway-parse/src/item/mod.rs +++ b/sway-parse/src/item/mod.rs @@ -10,9 +10,41 @@ pub mod item_struct; pub mod item_trait; pub mod item_use; +pub struct Item { + pub attribute_list: Vec, + pub kind: ItemKind, +} + +impl Item { + pub fn span(&self) -> Span { + match self.attribute_list.first() { + Some(attr0) => Span::join(attr0.span(), self.kind.span()), + None => self.kind.span(), + } + } +} + +impl Parse for Item { + fn parse(parser: &mut Parser) -> ParseResult { + let mut attribute_list = Vec::new(); + loop { + if parser.peek::().is_some() { + attribute_list.push(parser.parse()?); + } else { + break; + } + } + let kind = parser.parse()?; + Ok(Item { + attribute_list, + kind, + }) + } +} + #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug)] -pub enum Item { +pub enum ItemKind { Use(ItemUse), Struct(ItemStruct), Enum(ItemEnum), @@ -24,37 +56,37 @@ pub enum Item { Storage(ItemStorage), } -impl Item { +impl ItemKind { pub fn span(&self) -> Span { match self { - Item::Use(item_use) => item_use.span(), - Item::Struct(item_struct) => item_struct.span(), - Item::Enum(item_enum) => item_enum.span(), - Item::Fn(item_fn) => item_fn.span(), - Item::Trait(item_trait) => item_trait.span(), - Item::Impl(item_impl) => item_impl.span(), - Item::Abi(item_abi) => item_abi.span(), - Item::Const(item_const) => item_const.span(), - Item::Storage(item_storage) => item_storage.span(), + ItemKind::Use(item_use) => item_use.span(), + ItemKind::Struct(item_struct) => item_struct.span(), + ItemKind::Enum(item_enum) => item_enum.span(), + ItemKind::Fn(item_fn) => item_fn.span(), + ItemKind::Trait(item_trait) => item_trait.span(), + ItemKind::Impl(item_impl) => item_impl.span(), + ItemKind::Abi(item_abi) => item_abi.span(), + ItemKind::Const(item_const) => item_const.span(), + ItemKind::Storage(item_storage) => item_storage.span(), } } } -impl Parse for Item { - fn parse(parser: &mut Parser) -> ParseResult { +impl Parse for ItemKind { + fn parse(parser: &mut Parser) -> ParseResult { if parser.peek::().is_some() || parser.peek2::().is_some() { let item_use = parser.parse()?; - return Ok(Item::Use(item_use)); + return Ok(ItemKind::Use(item_use)); } if parser.peek::().is_some() || parser.peek2::().is_some() { let item_struct = parser.parse()?; - return Ok(Item::Struct(item_struct)); + return Ok(ItemKind::Struct(item_struct)); } if parser.peek::().is_some() || parser.peek2::().is_some() { let item_enum = parser.parse()?; - return Ok(Item::Enum(item_enum)); + return Ok(ItemKind::Enum(item_enum)); } if parser.peek::().is_some() || parser.peek2::().is_some() @@ -62,29 +94,29 @@ impl Parse for Item { || parser.peek3::().is_some() { let item_fn = parser.parse()?; - return Ok(Item::Fn(item_fn)); + return Ok(ItemKind::Fn(item_fn)); } if parser.peek::().is_some() || parser.peek2::().is_some() { let item_trait = parser.parse()?; - return Ok(Item::Trait(item_trait)); + return Ok(ItemKind::Trait(item_trait)); } if parser.peek::().is_some() { let item_impl = parser.parse()?; - return Ok(Item::Impl(item_impl)); + return Ok(ItemKind::Impl(item_impl)); } if parser.peek::().is_some() { let item_abi = parser.parse()?; - return Ok(Item::Abi(item_abi)); + return Ok(ItemKind::Abi(item_abi)); } if parser.peek::().is_some() || parser.peek2::().is_some() { let item_const = parser.parse()?; - return Ok(Item::Const(item_const)); + return Ok(ItemKind::Const(item_const)); } if parser.peek::().is_some() { let item_storage = parser.parse()?; - return Ok(Item::Storage(item_storage)); + return Ok(ItemKind::Storage(item_storage)); } Err(parser.emit_error(ParseErrorKind::ExpectedAnItem)) } @@ -258,3 +290,303 @@ impl Parse for FnSignature { }) } } + +// ------------------------------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + fn parse_item(input: &str) -> Item { + let token_stream = crate::token::lex(&Arc::from(input), 0, input.len(), None).unwrap(); + let mut errors = Vec::new(); + let mut parser = Parser::new(&token_stream, &mut errors); + match Item::parse(&mut parser) { + Ok(item) => item, + Err(_) => { + //println!("Tokens: {:?}", token_stream); + panic!("Parse error: {:?}", errors); + } + } + } + + #[test] + fn parse_attributes_none() { + let item = parse_item( + r#" + fn f() -> bool { + false + } + "#, + ); + + assert!(matches!(item.kind, ItemKind::Fn(_))); + assert!(item.attribute_list.is_empty()); + } + + #[test] + fn parse_attributes_fn_basic() { + let item = parse_item( + r#" + #[foo] + fn f() -> bool { + false + } + "#, + ); + + assert!(matches!(item.kind, ItemKind::Fn(_))); + + assert_eq!(item.attribute_list.len(), 1); + + let attrib = item.attribute_list.get(0).unwrap(); + assert_eq!(attrib.attribute.get().name.as_str(), "foo"); + assert!(attrib.attribute.get().args.is_none()); + } + + #[test] + fn parse_attributes_fn_two_basic() { + let item = parse_item( + r#" + #[foo] + #[bar] + fn f() -> bool { + false + } + "#, + ); + + assert!(matches!(item.kind, ItemKind::Fn(_))); + + assert_eq!(item.attribute_list.len(), 2); + + let attrib = item.attribute_list.get(0).unwrap(); + assert_eq!(attrib.attribute.get().name.as_str(), "foo"); + assert!(attrib.attribute.get().args.is_none()); + + let attrib = item.attribute_list.get(1).unwrap(); + assert_eq!(attrib.attribute.get().name.as_str(), "bar"); + assert!(attrib.attribute.get().args.is_none()); + } + + #[test] + fn parse_attributes_fn_one_arg() { + let item = parse_item( + r#" + #[foo(one)] + fn f() -> bool { + false + } + "#, + ); + + assert!(matches!(item.kind, ItemKind::Fn(_))); + + assert_eq!(item.attribute_list.len(), 1); + + let attrib = item.attribute_list.get(0).unwrap(); + assert_eq!(attrib.attribute.get().name.as_str(), "foo"); + assert!(attrib.attribute.get().args.is_some()); + + let mut args = attrib + .attribute + .get() + .args + .as_ref() + .unwrap() + .get() + .into_iter(); + assert_eq!(args.next().map(|arg| arg.as_str()), Some("one")); + assert_eq!(args.next().map(|arg| arg.as_str()), None); + } + + #[test] + fn parse_attributes_fn_empty_parens() { + let item = parse_item( + r#" + #[foo()] + fn f() -> bool { + false + } + "#, + ); + + assert!(matches!(item.kind, ItemKind::Fn(_))); + + assert_eq!(item.attribute_list.len(), 1); + + let attrib = item.attribute_list.get(0).unwrap(); + assert_eq!(attrib.attribute.get().name.as_str(), "foo"); + + // Args are still parsed as 'some' but with an empty collection. + assert!(attrib.attribute.get().args.is_some()); + + let mut args = attrib + .attribute + .get() + .args + .as_ref() + .unwrap() + .get() + .into_iter(); + assert_eq!(args.next().map(|arg| arg.as_str()), None); + } + + #[test] + fn parse_attributes_fn_zero_and_one_arg() { + let item = parse_item( + r#" + #[bar] + #[foo(one)] + fn f() -> bool { + false + } + "#, + ); + + assert!(matches!(item.kind, ItemKind::Fn(_))); + + assert_eq!(item.attribute_list.len(), 2); + + let attrib = item.attribute_list.get(0).unwrap(); + assert_eq!(attrib.attribute.get().name.as_str(), "bar"); + assert!(attrib.attribute.get().args.is_none()); + + let attrib = item.attribute_list.get(1).unwrap(); + assert_eq!(attrib.attribute.get().name.as_str(), "foo"); + assert!(attrib.attribute.get().args.is_some()); + + let mut args = attrib + .attribute + .get() + .args + .as_ref() + .unwrap() + .get() + .into_iter(); + assert_eq!(args.next().map(|arg| arg.as_str()), Some("one")); + assert_eq!(args.next().map(|arg| arg.as_str()), None); + } + + #[test] + fn parse_attributes_fn_one_and_zero_arg() { + let item = parse_item( + r#" + #[foo(one)] + #[bar] + fn f() -> bool { + false + } + "#, + ); + + assert!(matches!(item.kind, ItemKind::Fn(_))); + + assert_eq!(item.attribute_list.len(), 2); + + let attrib = item.attribute_list.get(0).unwrap(); + assert_eq!(attrib.attribute.get().name.as_str(), "foo"); + assert!(attrib.attribute.get().args.is_some()); + + let mut args = attrib + .attribute + .get() + .args + .as_ref() + .unwrap() + .get() + .into_iter(); + assert_eq!(args.next().map(|arg| arg.as_str()), Some("one")); + assert_eq!(args.next().map(|arg| arg.as_str()), None); + + let attrib = item.attribute_list.get(1).unwrap(); + assert_eq!(attrib.attribute.get().name.as_str(), "bar"); + assert!(attrib.attribute.get().args.is_none()); + } + + #[test] + fn parse_attributes_fn_two_args() { + let item = parse_item( + r#" + #[foo(one, two)] + fn f() -> bool { + false + } + "#, + ); + + assert!(matches!(item.kind, ItemKind::Fn(_))); + + assert_eq!(item.attribute_list.len(), 1); + + let attrib = item.attribute_list.get(0).unwrap(); + assert_eq!(attrib.attribute.get().name.as_str(), "foo"); + assert!(attrib.attribute.get().args.is_some()); + + let mut args = attrib + .attribute + .get() + .args + .as_ref() + .unwrap() + .get() + .into_iter(); + assert_eq!(args.next().map(|arg| arg.as_str()), Some("one")); + assert_eq!(args.next().map(|arg| arg.as_str()), Some("two")); + assert_eq!(args.next().map(|arg| arg.as_str()), None); + } + + #[test] + fn parse_attributes_fn_zero_one_and_three_args() { + let item = parse_item( + r#" + #[bar] + #[foo(one)] + #[baz(two,three,four)] + fn f() -> bool { + false + } + "#, + ); + + assert!(matches!(item.kind, ItemKind::Fn(_))); + + assert_eq!(item.attribute_list.len(), 3); + + let attrib = item.attribute_list.get(0).unwrap(); + assert_eq!(attrib.attribute.get().name.as_str(), "bar"); + assert!(attrib.attribute.get().args.is_none()); + + let attrib = item.attribute_list.get(1).unwrap(); + assert_eq!(attrib.attribute.get().name.as_str(), "foo"); + assert!(attrib.attribute.get().args.is_some()); + + let mut args = attrib + .attribute + .get() + .args + .as_ref() + .unwrap() + .get() + .into_iter(); + assert_eq!(args.next().map(|arg| arg.as_str()), Some("one")); + assert_eq!(args.next().map(|arg| arg.as_str()), None); + + let attrib = item.attribute_list.get(2).unwrap(); + assert_eq!(attrib.attribute.get().name.as_str(), "baz"); + assert!(attrib.attribute.get().args.is_some()); + + let mut args = attrib + .attribute + .get() + .args + .as_ref() + .unwrap() + .get() + .into_iter(); + assert_eq!(args.next().map(|arg| arg.as_str()), Some("two")); + assert_eq!(args.next().map(|arg| arg.as_str()), Some("three")); + assert_eq!(args.next().map(|arg| arg.as_str()), Some("four")); + assert_eq!(args.next().map(|arg| arg.as_str()), None); + } +} diff --git a/sway-parse/src/keywords.rs b/sway-parse/src/keywords.rs index 8c14555e797..e700ddd7264 100644 --- a/sway-parse/src/keywords.rs +++ b/sway-parse/src/keywords.rs @@ -192,3 +192,4 @@ define_token!( ); define_token!(DoublePipeToken, "`||`", [Pipe, Pipe], [Pipe]); define_token!(UnderscoreToken, "`_`", [Underscore], [Underscore]); +define_token!(HashToken, "`#`", [Sharp], []); diff --git a/sway-parse/src/lib.rs b/sway-parse/src/lib.rs index 8ca86e89a00..72aecc56bb1 100644 --- a/sway-parse/src/lib.rs +++ b/sway-parse/src/lib.rs @@ -1,4 +1,5 @@ pub mod assignable; +pub mod attribute; pub mod brackets; pub mod dependency; mod error; @@ -41,7 +42,7 @@ pub use crate::{ item_struct::ItemStruct, item_trait::{ItemTrait, Traits}, item_use::{ItemUse, UseTree}, - FnArg, FnArgs, FnSignature, Item, TypeField, + FnArg, FnArgs, FnSignature, ItemKind, TypeField, }, keywords::{DoubleColonToken, ImpureToken, PubToken}, literal::{LitInt, LitIntType, Literal}, diff --git a/sway-parse/src/priv_prelude.rs b/sway-parse/src/priv_prelude.rs index 31ee39314a1..3a02eb3a251 100644 --- a/sway-parse/src/priv_prelude.rs +++ b/sway-parse/src/priv_prelude.rs @@ -1,6 +1,7 @@ pub use { crate::{ assignable::Assignable, + attribute::{Attribute, AttributeDecl}, brackets::{AngleBrackets, Braces, Parens, SquareBrackets}, dependency::Dependency, error::{ParseError, ParseErrorKind}, @@ -20,7 +21,7 @@ pub use { item_struct::ItemStruct, item_trait::{ItemTrait, Traits}, item_use::ItemUse, - FnSignature, Item, TypeField, + FnSignature, Item, ItemKind, TypeField, }, keywords::*, literal::{LitChar, LitInt, LitIntType, LitString, Literal}, diff --git a/sway-parse/src/statement.rs b/sway-parse/src/statement.rs index 7fc2f468ca1..7de36fcc397 100644 --- a/sway-parse/src/statement.rs +++ b/sway-parse/src/statement.rs @@ -4,7 +4,7 @@ use crate::priv_prelude::*; #[derive(Clone, Debug)] pub enum Statement { Let(StatementLet), - Item(Item), + Item(ItemKind), Expr { expr: Expr, semicolon_token_opt: Option, diff --git a/sway-parse/src/token.rs b/sway-parse/src/token.rs index 6ed9de551c0..64fee1cdce2 100644 --- a/sway-parse/src/token.rs +++ b/sway-parse/src/token.rs @@ -26,6 +26,7 @@ pub enum PunctKind { Pipe, Tilde, Underscore, + Sharp, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)] @@ -62,6 +63,7 @@ impl PunctKind { PunctKind::Pipe => '|', PunctKind::Tilde => '~', PunctKind::Underscore => '_', + PunctKind::Sharp => '#', } } } @@ -84,17 +86,14 @@ pub enum Delimiter { Parenthesis, Brace, Bracket, - //None, } impl Delimiter { - //pub fn as_open_char(self) -> Option { pub fn as_open_char(self) -> char { match self { Delimiter::Parenthesis => '(', Delimiter::Brace => '{', Delimiter::Bracket => '[', - //Delimiter::None => None, } } } @@ -232,6 +231,7 @@ impl CharExt for char { '|' => Some(PunctKind::Pipe), '~' => Some(PunctKind::Tilde), '_' => Some(PunctKind::Underscore), + '#' => Some(PunctKind::Sharp), _ => None, } }