From 683af78ee4ddfc2cc5df05b32168e340b7ab7ab7 Mon Sep 17 00:00:00 2001 From: Christoph Burgdorf Date: Mon, 2 May 2022 17:48:44 +0200 Subject: [PATCH] WIP --- crates/analyzer/src/db/queries/module.rs | 1 + crates/lowering/src/mappers/functions.rs | 2 + crates/lowering/src/mappers/module.rs | 1 + crates/parser/src/ast.rs | 36 ++++ crates/parser/src/grammar.rs | 1 + crates/parser/src/grammar/functions.rs | 97 +++++++++- crates/parser/src/grammar/module.rs | 2 + crates/parser/src/grammar/traits.rs | 55 ++++++ crates/parser/src/lexer/token.rs | 3 + crates/parser/tests/cases/parse_ast.rs | 2 + .../cases__errors__module_bad_stmt.snap | 2 +- .../snapshots/cases__parse_ast__fn_def.snap | 9 +- .../cases__parse_ast__fn_def_generic.snap | 172 ++++++++++++++++++ .../cases__parse_ast__fn_def_pub.snap | 9 +- .../cases__parse_ast__fn_def_pub_unsafe.snap | 9 +- .../cases__parse_ast__fn_def_unsafe.snap | 9 +- foo.fe | 7 + 17 files changed, 408 insertions(+), 9 deletions(-) create mode 100644 crates/parser/src/grammar/traits.rs create mode 100644 crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_generic.snap create mode 100644 foo.fe diff --git a/crates/analyzer/src/db/queries/module.rs b/crates/analyzer/src/db/queries/module.rs index 8c46a60583..c3d51af2b5 100644 --- a/crates/analyzer/src/db/queries/module.rs +++ b/crates/analyzer/src/db/queries/module.rs @@ -97,6 +97,7 @@ pub fn module_all_items(db: &dyn AnalyzerDb, module: ModuleId) -> Rc<[Item]> { })))) } ast::ModuleStmt::Pragma(_) => None, + ast::ModuleStmt::Trait(_) => None, ast::ModuleStmt::Use(_) => None, ast::ModuleStmt::Event(node) => Some(Item::Event(db.intern_event(Rc::new(Event { ast: node.clone(), diff --git a/crates/lowering/src/mappers/functions.rs b/crates/lowering/src/mappers/functions.rs index 048d9745c5..b3303be097 100644 --- a/crates/lowering/src/mappers/functions.rs +++ b/crates/lowering/src/mappers/functions.rs @@ -22,6 +22,7 @@ pub fn func_def(context: &mut ModuleContext, function: FunctionId) -> Node Node ast::Function { unsafe_: None, name: names::list_expr_generator_fn_name(array).into_node(), args, + generic_params: Vec::new().into_node(), return_type, body: [vec![var_decl], assignments, vec![return_stmt]].concat(), } diff --git a/crates/parser/src/ast.rs b/crates/parser/src/ast.rs index cc3af76dce..ebaaa5b053 100644 --- a/crates/parser/src/ast.rs +++ b/crates/parser/src/ast.rs @@ -21,6 +21,7 @@ pub enum ModuleStmt { Contract(Node), Constant(Node), Struct(Node), + Trait(Node), Function(Node), Event(Node), ParseError(Span), @@ -87,6 +88,12 @@ pub struct Struct { pub pub_qual: Option, } +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] +pub struct Trait { + pub name: Node, + pub pub_qual: Option, +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] pub enum TypeDesc { Unit, @@ -123,6 +130,24 @@ impl Spanned for GenericArg { } } +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] +pub enum GenericParameter { + Unbounded(Node), + Bounded { + name: Node, + bound: Node, + }, +} + +impl Spanned for GenericParameter { + fn span(&self) -> Span { + match self { + GenericParameter::Unbounded(node) => node.span, + GenericParameter::Bounded { name, bound } => name.span + bound.span, + } + } +} + /// struct or contract field, with optional 'pub' and 'const' qualifiers #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)] pub struct Field { @@ -153,6 +178,7 @@ pub struct Function { pub pub_: Option, pub unsafe_: Option, pub name: Node, + pub generic_params: Node>, pub args: Vec>, pub return_type: Option>, pub body: Vec>, @@ -406,6 +432,7 @@ impl Spanned for ModuleStmt { match self { ModuleStmt::Pragma(inner) => inner.span, ModuleStmt::Use(inner) => inner.span, + ModuleStmt::Trait(inner) => inner.span, ModuleStmt::TypeAlias(inner) => inner.span, ModuleStmt::Contract(inner) => inner.span, ModuleStmt::Constant(inner) => inner.span, @@ -437,6 +464,7 @@ impl fmt::Display for ModuleStmt { match self { ModuleStmt::Pragma(node) => write!(f, "{}", node.kind), ModuleStmt::Use(node) => write!(f, "{}", node.kind), + ModuleStmt::Trait(node) => write!(f, "{}", node.kind), ModuleStmt::TypeAlias(node) => write!(f, "{}", node.kind), ModuleStmt::Contract(node) => write!(f, "{}", node.kind), ModuleStmt::Constant(node) => write!(f, "{}", node.kind), @@ -504,6 +532,14 @@ impl fmt::Display for ConstantDecl { } } +impl fmt::Display for Trait { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "trait {}:", self.name.kind)?; + + Ok(()) + } +} + impl fmt::Display for TypeAlias { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "type {} = {}", self.name.kind, self.typ.kind) diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs index 00bfb7447f..0b8c906573 100644 --- a/crates/parser/src/grammar.rs +++ b/crates/parser/src/grammar.rs @@ -2,4 +2,5 @@ pub mod contracts; pub mod expressions; pub mod functions; pub mod module; +pub mod traits; pub mod types; diff --git a/crates/parser/src/grammar/functions.rs b/crates/parser/src/grammar/functions.rs index 99ef6085b2..2e5b55927f 100644 --- a/crates/parser/src/grammar/functions.rs +++ b/crates/parser/src/grammar/functions.rs @@ -2,11 +2,11 @@ use super::expressions::{parse_call_args, parse_expr}; use super::types::parse_type_desc; use crate::ast::{ - BinOperator, Expr, FuncStmt, Function, FunctionArg, RegularFunctionArg, VarDeclTarget, + BinOperator, Expr, FuncStmt, Function, FunctionArg, GenericParameter, RegularFunctionArg, + VarDeclTarget, }; -use crate::lexer::TokenKind; use crate::node::{Node, Span}; -use crate::{Label, ParseFailed, ParseResult, Parser}; +use crate::{Label, ParseFailed, ParseResult, Parser, TokenKind}; /// Parse a function definition. The optional `pub` qualifier must be parsed by /// the caller, and passed in. Next token must be `unsafe` or `fn`. @@ -28,7 +28,14 @@ pub fn parse_fn_def(par: &mut Parser, mut pub_qual: Option) -> ParseResult } let fn_tok = par.expect(TokenKind::Fn, "failed to parse function definition")?; let name = par.expect(TokenKind::Name, "failed to parse function definition")?; - let mut span = fn_tok.span + unsafe_qual + pub_qual + name.span; + + let generic_params = if par.peek() == Some(TokenKind::Lt) { + parse_generic_params(par)? + } else { + Node::new(vec![], name.span) + }; + + let mut span = fn_tok.span + unsafe_qual + pub_qual + name.span + generic_params.span; let args = match par.peek_or_err()? { TokenKind::ParenOpen => { @@ -87,6 +94,7 @@ pub fn parse_fn_def(par: &mut Parser, mut pub_qual: Option) -> ParseResult unsafe_: unsafe_qual, name: name.into(), args, + generic_params, return_type, body, }, @@ -94,6 +102,87 @@ pub fn parse_fn_def(par: &mut Parser, mut pub_qual: Option) -> ParseResult )) } +/// Parse a single generic function parameter (eg. `T:SomeTrait` in `fn foo(some_arg: u256) -> bool`). +/// # Panics +/// Panics if the first token isn't `Name`. +pub fn parse_generic_param(par: &mut Parser) -> ParseResult { + use TokenKind::*; + + let name = par.assert(Name); + match par.optional(Colon) { + Some(_) => { + let bound = par.assert(Name); + return Ok(GenericParameter::Bounded { + name: Node::new(name.text.into(), name.span), + bound: Node::new(bound.text.into(), bound.span), + }); + } + None => { + return Ok(GenericParameter::Unbounded(Node::new( + name.text.into(), + name.span, + ))) + } + } +} + +/// Parse an angle-bracket-wrapped list of generic arguments (eg. `` in `fn foo(some_arg: u256) -> bool`). +/// # Panics +/// Panics if the first token isn't `<`. +pub fn parse_generic_params(par: &mut Parser) -> ParseResult>> { + use TokenKind::*; + let mut span = par.assert(Lt).span; + + let mut args = vec![]; + + let expect_end = |par: &mut Parser| { + // If there's no comma, the next token must be `>` + match par.peek_or_err()? { + Gt => Ok(par.next()?.span), + _ => { + let tok = par.next()?; + par.unexpected_token_error( + tok.span, + "Unexpected token while parsing generic arg list", + vec![], + ); + Err(ParseFailed) + } + } + }; + + loop { + match par.peek_or_err()? { + Gt => { + span += par.next()?.span; + break; + } + Name => { + let typ = parse_generic_param(par)?; + args.push(typ); + if par.peek() == Some(Comma) { + par.next()?; + } else { + span += expect_end(par)?; + break; + } + } + + // Invalid generic argument. + _ => { + let tok = par.next()?; + par.unexpected_token_error( + tok.span, + "failed to parse list of generic function parameters", + vec![], + ); + return Err(ParseFailed); + } + } + } + Ok(Node::new(args, span)) +} + fn parse_fn_param_list(par: &mut Parser) -> ParseResult>>> { let mut span = par.assert(TokenKind::ParenOpen).span; let mut params = vec![]; diff --git a/crates/parser/src/grammar/module.rs b/crates/parser/src/grammar/module.rs index d7e49cd126..f429e3d8de 100644 --- a/crates/parser/src/grammar/module.rs +++ b/crates/parser/src/grammar/module.rs @@ -1,6 +1,7 @@ use super::contracts::parse_contract_def; use super::expressions::parse_expr; use super::functions::parse_fn_def; +use super::traits::parse_trait_def; use super::types::{ parse_event_def, parse_path_tail, parse_struct_def, parse_type_alias, parse_type_desc, }; @@ -44,6 +45,7 @@ pub fn parse_module_stmt(par: &mut Parser) -> ParseResult { TokenKind::Use => ModuleStmt::Use(parse_use(par)?), TokenKind::Contract => ModuleStmt::Contract(parse_contract_def(par, None)?), TokenKind::Struct => ModuleStmt::Struct(parse_struct_def(par, None)?), + TokenKind::Trait => ModuleStmt::Trait(parse_trait_def(par, None)?), TokenKind::Type => ModuleStmt::TypeAlias(parse_type_alias(par, None)?), TokenKind::Const => ModuleStmt::Constant(parse_constant(par, None)?), diff --git a/crates/parser/src/grammar/traits.rs b/crates/parser/src/grammar/traits.rs new file mode 100644 index 0000000000..147367aceb --- /dev/null +++ b/crates/parser/src/grammar/traits.rs @@ -0,0 +1,55 @@ +use crate::ast::Trait; +use crate::grammar::functions::parse_single_word_stmt; +use crate::node::{Node, Span}; +use crate::{ParseFailed, ParseResult, Parser, TokenKind}; + +/// Parse a trait definition. +/// # Panics +/// Panics if the next token isn't `trait`. +pub fn parse_trait_def(par: &mut Parser, trait_pub_qual: Option) -> ParseResult> { + let trait_tok = par.assert(TokenKind::Trait); + + // trait Event: + // pass + // + + let trait_name = par.expect_with_notes( + TokenKind::Name, + "failed to parse trait definition", + |_| vec!["Note: `trait` must be followed by a name, which must start with a letter and contain only letters, numbers, or underscores".into()], + )?; + + let header_span = trait_tok.span + trait_name.span; + par.enter_block(header_span, "trait definition")?; + + loop { + match par.peek() { + Some(TokenKind::Pass) => { + parse_single_word_stmt(par)?; + } + Some(TokenKind::Dedent) => { + par.next()?; + break; + } + None => break, + Some(_) => { + let tok = par.next()?; + par.unexpected_token_error( + tok.span, + "failed to parse trait definition body", + vec![], + ); + return Err(ParseFailed); + } + }; + } + + let span = header_span + trait_pub_qual; + Ok(Node::new( + Trait { + name: Node::new(trait_name.text.into(), trait_name.span), + pub_qual: trait_pub_qual, + }, + span, + )) +} diff --git a/crates/parser/src/lexer/token.rs b/crates/parser/src/lexer/token.rs index 4a76fe6001..d148c202dd 100644 --- a/crates/parser/src/lexer/token.rs +++ b/crates/parser/src/lexer/token.rs @@ -101,6 +101,8 @@ pub enum TokenKind { SelfValue, #[token("struct")] Struct, + #[token("trait")] + Trait, #[token("type")] Type, #[token("unsafe")] @@ -249,6 +251,7 @@ impl TokenKind { Revert => "keyword `revert`", SelfValue => "keyword `self`", Struct => "keyword `struct`", + Trait => "keyword `trait`", Type => "keyword `type`", Unsafe => "keyword `unsafe`", While => "keyword `while`", diff --git a/crates/parser/tests/cases/parse_ast.rs b/crates/parser/tests/cases/parse_ast.rs index 443342719a..9b29a1ead3 100644 --- a/crates/parser/tests/cases/parse_ast.rs +++ b/crates/parser/tests/cases/parse_ast.rs @@ -141,6 +141,8 @@ test_parse! { type_tuple, types::parse_type_desc, "(u8, u16, address, Map bool:\n false"} + +test_parse! { fn_def_generic, try_parse_module, "fn foo(this: T, that: R, _ val: u64) -> bool:\n false"} test_parse! { fn_def_pub, try_parse_module, "pub fn foo21(x: bool, y: address,) -> bool:\n x"} test_parse! { fn_def_unsafe, try_parse_module, "unsafe fn foo21(x: bool, y: address,) -> bool:\n x"} test_parse! { fn_def_pub_unsafe, try_parse_module, "pub unsafe fn foo21(x: bool, y: address,) -> bool:\n x"} diff --git a/crates/parser/tests/cases/snapshots/cases__errors__module_bad_stmt.snap b/crates/parser/tests/cases/snapshots/cases__errors__module_bad_stmt.snap index ec1698efa4..d401976178 100644 --- a/crates/parser/tests/cases/snapshots/cases__errors__module_bad_stmt.snap +++ b/crates/parser/tests/cases/snapshots/cases__errors__module_bad_stmt.snap @@ -9,6 +9,6 @@ error: failed to parse module 1 │ if x: │ ^^ unexpected token │ - = Note: expected import, contract, struct, type, const or event + = Note: expected import, contract, struct, trait, type, const or event diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def.snap index ebed258f4b..24be74401c 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(fn_def), try_parse_module,\n \"fn transfer(from sender: address, to recip: address, _ val: u64) -> bool:\\n false\")" +expression: "ast_string(stringify!(fn_def), try_parse_module,\n \"fn transfer(from sender: address, to recip: address, _ val: u64) -> bool:\\n false\")" --- Node( @@ -17,6 +17,13 @@ Node( end: 11, ), ), + generic_params: Node( + kind: [], + span: Span( + start: 3, + end: 11, + ), + ), args: [ Node( kind: Regular(RegularFunctionArg( diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_generic.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_generic.snap new file mode 100644 index 0000000000..15e16ae572 --- /dev/null +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_generic.snap @@ -0,0 +1,172 @@ +--- +source: crates/parser/tests/cases/parse_ast.rs +expression: "ast_string(stringify!(fn_def_generic), try_parse_module,\n \"fn foo(this: T, that: R, _ val: u64) -> bool:\\n false\")" + +--- +Node( + kind: Module( + body: [ + Function(Node( + kind: Function( + pub_: None, + unsafe_: None, + name: Node( + kind: "foo", + span: Span( + start: 3, + end: 6, + ), + ), + generic_params: Node( + kind: [ + Unbounded(Node( + kind: "T", + span: Span( + start: 7, + end: 8, + ), + )), + Bounded( + name: Node( + kind: "R", + span: Span( + start: 10, + end: 11, + ), + ), + bound: Node( + kind: "Event", + span: Span( + start: 13, + end: 18, + ), + ), + ), + ], + span: Span( + start: 6, + end: 19, + ), + ), + args: [ + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "this", + span: Span( + start: 20, + end: 24, + ), + ), + typ: Node( + kind: Base( + base: "T", + ), + span: Span( + start: 26, + end: 27, + ), + ), + )), + span: Span( + start: 20, + end: 27, + ), + ), + Node( + kind: Regular(RegularFunctionArg( + label: None, + name: Node( + kind: "that", + span: Span( + start: 29, + end: 33, + ), + ), + typ: Node( + kind: Base( + base: "R", + ), + span: Span( + start: 35, + end: 36, + ), + ), + )), + span: Span( + start: 29, + end: 36, + ), + ), + Node( + kind: Regular(RegularFunctionArg( + label: Some(Node( + kind: "_", + span: Span( + start: 38, + end: 39, + ), + )), + name: Node( + kind: "val", + span: Span( + start: 40, + end: 43, + ), + ), + typ: Node( + kind: Base( + base: "u64", + ), + span: Span( + start: 45, + end: 48, + ), + ), + )), + span: Span( + start: 40, + end: 48, + ), + ), + ], + return_type: Some(Node( + kind: Base( + base: "bool", + ), + span: Span( + start: 53, + end: 57, + ), + )), + body: [ + Node( + kind: Expr( + value: Node( + kind: Bool(false), + span: Span( + start: 60, + end: 65, + ), + ), + ), + span: Span( + start: 60, + end: 65, + ), + ), + ], + ), + span: Span( + start: 0, + end: 65, + ), + )), + ], + ), + span: Span( + start: 0, + end: 65, + ), +) diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub.snap index 1110f57e14..aba7965c16 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(fn_def_pub), try_parse_module,\n \"pub fn foo21(x: bool, y: address,) -> bool:\\n x\")" +expression: "ast_string(stringify!(fn_def_pub), try_parse_module,\n \"pub fn foo21(x: bool, y: address,) -> bool:\\n x\")" --- Node( @@ -20,6 +20,13 @@ Node( end: 12, ), ), + generic_params: Node( + kind: [], + span: Span( + start: 7, + end: 12, + ), + ), args: [ Node( kind: Regular(RegularFunctionArg( diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub_unsafe.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub_unsafe.snap index 4d3486bddf..23de356ec9 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub_unsafe.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_pub_unsafe.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(fn_def_pub_unsafe), try_parse_module,\n \"pub unsafe fn foo21(x: bool, y: address,) -> bool:\\n x\")" +expression: "ast_string(stringify!(fn_def_pub_unsafe), try_parse_module,\n \"pub unsafe fn foo21(x: bool, y: address,) -> bool:\\n x\")" --- Node( @@ -23,6 +23,13 @@ Node( end: 19, ), ), + generic_params: Node( + kind: [], + span: Span( + start: 14, + end: 19, + ), + ), args: [ Node( kind: Regular(RegularFunctionArg( diff --git a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_unsafe.snap b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_unsafe.snap index bad0eae436..c63c32db1e 100644 --- a/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_unsafe.snap +++ b/crates/parser/tests/cases/snapshots/cases__parse_ast__fn_def_unsafe.snap @@ -1,6 +1,6 @@ --- source: crates/parser/tests/cases/parse_ast.rs -expression: "ast_string(stringify!(fn_def_unsafe), try_parse_module,\n \"unsafe fn foo21(x: bool, y: address,) -> bool:\\n x\")" +expression: "ast_string(stringify!(fn_def_unsafe), try_parse_module,\n \"unsafe fn foo21(x: bool, y: address,) -> bool:\\n x\")" --- Node( @@ -20,6 +20,13 @@ Node( end: 15, ), ), + generic_params: Node( + kind: [], + span: Span( + start: 10, + end: 15, + ), + ), args: [ Node( kind: Regular(RegularFunctionArg( diff --git a/foo.fe b/foo.fe new file mode 100644 index 0000000000..59c3226881 --- /dev/null +++ b/foo.fe @@ -0,0 +1,7 @@ +trait Foo: + pass + +contract Meh: + + pub fn bar(x: T) -> bool: + return true \ No newline at end of file