diff --git a/compiler/src/yul/mappers/module.rs b/compiler/src/yul/mappers/module.rs index b16ab3754d..bec0ae670d 100644 --- a/compiler/src/yul/mappers/module.rs +++ b/compiler/src/yul/mappers/module.rs @@ -22,6 +22,7 @@ pub fn module(context: &Context, module: &fe::Module) -> Result unimplemented!(), fe::ModuleStmt::FromImport { .. } => unimplemented!(), fe::ModuleStmt::SimpleImport { .. } => unimplemented!(), } diff --git a/compiler/tests/evm_contracts.rs b/compiler/tests/evm_contracts.rs index 7e58c622d4..36244204fe 100644 --- a/compiler/tests/evm_contracts.rs +++ b/compiler/tests/evm_contracts.rs @@ -909,6 +909,13 @@ fn sized_vals_in_sto() { }); } +#[test] +fn structs() { + with_executor(&|mut executor| { + let harness = deploy_contract(&mut executor, "structs.fe", "Foo", vec![]); + }); +} + #[test] fn erc20_token() { with_executor(&|mut executor| { diff --git a/compiler/tests/fixtures/structs.fe b/compiler/tests/fixtures/structs.fe new file mode 100644 index 0000000000..912f9a4ca1 --- /dev/null +++ b/compiler/tests/fixtures/structs.fe @@ -0,0 +1,7 @@ +struct House: + price: u256 + size: u256 + + +contract City: + pub house: House diff --git a/parser/src/ast.rs b/parser/src/ast.rs index 7585fc056b..706e355666 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -33,6 +33,11 @@ pub enum ModuleStmt<'a> { #[serde(borrow)] body: Vec>>, }, + StructDef { + name: Spanned<&'a str>, + #[serde(borrow)] + body: Vec>>, + }, } #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] @@ -108,12 +113,28 @@ pub enum ContractStmt<'a> { }, } +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum StructStmt<'a> { + StructField { + //qual: Option>, + #[serde(borrow)] + name: Spanned<&'a str>, + typ: Spanned>, + }, +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub enum ContractFieldQual { Const, Pub, } +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum StructFieldQual { + Const, + Pub, +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct EventField<'a> { pub qual: Option>, diff --git a/parser/src/ast_traits.rs b/parser/src/ast_traits.rs index 3e7ab13ffb..70a3402f07 100644 --- a/parser/src/ast_traits.rs +++ b/parser/src/ast_traits.rs @@ -24,6 +24,23 @@ impl TryFrom<&Token<'_>> for Spanned { } } +impl TryFrom<&Token<'_>> for Spanned { + type Error = &'static str; + + #[cfg_attr(tarpaulin, rustfmt::skip)] + fn try_from(tok: &Token) -> Result { + use StructFieldQual::*; + + let span = tok.span; + + Ok(match tok.string { + "const" => Spanned { node: Const, span }, + "pub" => Spanned { node: Pub, span }, + _ => return Err("unrecognized string"), + }) + } +} + impl TryFrom<&Token<'_>> for Spanned { type Error = &'static str; diff --git a/parser/src/parsers.rs b/parser/src/parsers.rs index bf921937b8..9eae0e721f 100644 --- a/parser/src/parsers.rs +++ b/parser/src/parsers.rs @@ -152,7 +152,7 @@ pub fn non_empty_file_input(input: Cursor) -> ParseResult> { /// Parse a module statement, such as a contract definition. pub fn module_stmt(input: Cursor) -> ParseResult> { - alt((import_stmt, type_def, contract_def))(input) + alt((import_stmt, type_def, contract_def, struct_def))(input) } /// Parse an import statement. @@ -470,6 +470,68 @@ pub fn contract_field(input: Cursor) -> ParseResult> { )) } +/// Parse a struct definition statement. +pub fn struct_def(input: Cursor) -> ParseResult> { + // "struct" name ":" NEWLINE + let (input, contract_kw) = name("struct")(input)?; + let (input, name_tok) = name_token(input)?; + let (input, _) = op(":")(input)?; + let (input, _) = newline_token(input)?; + + // INDENT struct_field+ DEDENT + let (input, _) = indent_token(input)?; + let (input, body) = many1(struct_field)(input)?; + let (input, _) = dedent_token(input)?; + + let last_stmt = body.last().unwrap(); + let span = Span::from_pair(contract_kw, last_stmt); + + Ok(( + input, + Spanned { + node: StructDef { + name: name_tok.into(), + body, + }, + span, + }, + )) +} + +/// Parse a struct field definition. +pub fn struct_field(input: Cursor) -> ParseResult> { + let (input, (qual, name_tok)) = alt(( + // Look for a qualifier and field name first... + map(pair(struct_field_qual, name_token), |res| { + let (qual, tok) = res; + (Some(qual), tok) + }), + // ...then fall back to just a field name + map(name_token, |tok| (None, tok)), + ))(input)?; + + let (input, _) = op(":")(input)?; + let (input, typ) = type_desc(input)?; + let (input, _) = newline_token(input)?; + + let span = match &qual { + Some(spanned) => Span::from_pair(spanned, &typ), + None => Span::from_pair(name_tok, &typ), + }; + + Ok(( + input, + Spanned { + node: StructStmt::StructField { + //qual, + name: name_tok.into(), + typ, + }, + span, + }, + )) +} + /// Parse an event definition statement. pub fn event_def(input: Cursor) -> ParseResult> { // "event" name ":" NEWLINE @@ -759,6 +821,11 @@ pub fn contract_field_qual(input: Cursor) -> ParseResult ParseResult> { + try_from_tok(name("pub"))(input) +} + /// Parse an event field qualifier keyword i.e. "idx". pub fn event_field_qual(input: Cursor) -> ParseResult> { try_from_tok(name("idx"))(input) diff --git a/parser/tests/fixtures/parsers/struct_def.ron b/parser/tests/fixtures/parsers/struct_def.ron new file mode 100644 index 0000000000..f5ebf1df8d --- /dev/null +++ b/parser/tests/fixtures/parsers/struct_def.ron @@ -0,0 +1,46 @@ +struct Foo: + x: address +--- +[ + Spanned( + node: StructDef( + name: Spanned( + node: "Foo", + span: Span( + start: 7, + end: 10, + ), + ), + body: [ + Spanned( + node: StructField( + name: Spanned( + node: "x", + span: Span( + start: 16, + end: 17, + ), + ), + typ: Spanned( + node: Base( + base: "address", + ), + span: Span( + start: 19, + end: 26, + ), + ), + ), + span: Span( + start: 16, + end: 26, + ), + ), + ], + ), + span: Span( + start: 0, + end: 26, + ), + ), +] diff --git a/parser/tests/test_parsers.rs b/parser/tests/test_parsers.rs index ec25144d36..7f651a3b3d 100644 --- a/parser/tests/test_parsers.rs +++ b/parser/tests/test_parsers.rs @@ -366,6 +366,12 @@ parser_fixture_tests! { write_contract_def, "fixtures/parsers/contract_def.ron", ), + ( + repeat(struct_def), + test_struct_def, + write_struct_def, + "fixtures/parsers/struct_def.ron", + ), ( repeat(contract_stmt), test_contract_stmt, diff --git a/semantics/src/traversal/module.rs b/semantics/src/traversal/module.rs index ef4877eefe..90d4b084af 100644 --- a/semantics/src/traversal/module.rs +++ b/semantics/src/traversal/module.rs @@ -20,6 +20,7 @@ pub fn module(context: Shared, module: &fe::Module) -> Result<(), Seman fe::ModuleStmt::ContractDef { .. } => { contracts::contract_def(Rc::clone(&scope), Rc::clone(&context), stmt)? } + fe::ModuleStmt::StructDef { .. } => unimplemented!(), fe::ModuleStmt::FromImport { .. } => unimplemented!(), fe::ModuleStmt::SimpleImport { .. } => unimplemented!(), }