diff --git a/compiler/parser/src/parser/expression.rs b/compiler/parser/src/parser/expression.rs index a6c5e9a16b..fa5a8d27b9 100644 --- a/compiler/parser/src/parser/expression.rs +++ b/compiler/parser/src/parser/expression.rs @@ -429,6 +429,25 @@ impl ParserContext<'_> { self.parse_paren_comma_list(|p| p.parse_expression().map(Some)) } + // Parses an externa function call `credits.aleo/transfer()` or `board.leo/make_move()` + fn parse_external_call(&mut self, expr: Expression) -> Result { + // Eat an external function call. + self.eat(&Token::Div); // todo: Make `/` a more general token. + + // Parse function name. + let name = self.expect_identifier()?; + + // Parse the function call. + let (arguments, _, span) = self.parse_paren_comma_list(|p| p.parse_expression().map(Some))?; + Ok(Expression::Call(CallExpression { + span: expr.span() + span, + function: Box::new(Expression::Identifier(name)), + external: Some(Box::new(expr)), + arguments, + id: self.node_builder.next_id(), + })) + } + /// Returns an [`Expression`] AST node if the next tokens represent an /// array access, struct member access, function call, or static function call expression. /// @@ -449,22 +468,8 @@ impl ParserContext<'_> { span, id: self.node_builder.next_id(), })) - } else if self.eat(&Token::Leo) { - // Eat an external function call. - self.eat(&Token::Div); // todo: Make `/` a more general token. - - // Parse function name. - let name = self.expect_identifier()?; - - // Parse the function call. - let (arguments, _, span) = self.parse_paren_comma_list(|p| p.parse_expression().map(Some))?; - expr = Expression::Call(CallExpression { - span: expr.span() + span, - function: Box::new(Expression::Identifier(name)), - external: Some(Box::new(expr)), - arguments, - id: self.node_builder.next_id(), - }); + } else if self.eat(&Token::Leo) || self.eat(&Token::Aleo) { + expr = self.parse_external_call(expr)?; } else { // Parse identifier name. let name = self.expect_identifier()?; diff --git a/compiler/parser/src/parser/file.rs b/compiler/parser/src/parser/file.rs index 5c3c9c31b8..3282df4132 100644 --- a/compiler/parser/src/parser/file.rs +++ b/compiler/parser/src/parser/file.rs @@ -25,6 +25,7 @@ impl ParserContext<'_> { /// Returns a [`Program`] AST if all tokens can be consumed and represent a valid Leo program. pub fn parse_program(&mut self) -> Result { let mut imports = IndexMap::new(); + let mut stubs: IndexMap = IndexMap::new(); let mut program_scopes = IndexMap::new(); // TODO: Remove restrictions on multiple program scopes @@ -36,6 +37,10 @@ impl ParserContext<'_> { let (id, import) = self.parse_import()?; imports.insert(id, import); } + Token::Stub => { + let (id, stub) = self.parse_stub()?; + stubs.insert(id, stub); + } Token::Program => { match parsed_program_scope { // Only one program scope is allowed per file. @@ -56,7 +61,7 @@ impl ParserContext<'_> { return Err(ParserError::missing_program_scope(self.token.span).into()); } - Ok(Program { imports, program_scopes }) + Ok(Program { imports, stubs, program_scopes }) } fn unexpected_item(token: &SpannedToken, expected: &[Token]) -> ParserError { @@ -114,25 +119,20 @@ impl ParserContext<'_> { Ok((import_name.name, (program_ast.into_repr(), start + end))) } - /// Parsers a program scope `program foo.aleo { ... }`. - fn parse_program_scope(&mut self) -> Result { - // Parse `program` keyword. - let start = self.expect(&Token::Program)?; - + /// Parses a program body `credits.aleo { ... }` + fn parse_program_body(&mut self, start: Span) -> Result { // Parse the program name. let name = self.expect_identifier()?; // Parse the program network. self.expect(&Token::Dot)?; - let network = self.expect_identifier()?; + + // Otherwise throw parser error + self.expect(&Token::Aleo).or_else(|_| Err(ParserError::invalid_network(self.token.span)))?; // Construct the program id. - let program_id = ProgramId { name, network }; - - // Check that the program network is valid. - if network.name != sym::aleo { - return Err(ParserError::invalid_network(network.span).into()); - } + let program_id = + ProgramId { name, network: Identifier::new(Symbol::intern("aleo"), self.node_builder.next_id()) }; // Parse `{`. self.expect(&Token::LeftCurly)?; @@ -183,6 +183,23 @@ impl ParserContext<'_> { Ok(ProgramScope { program_id, consts, functions, structs, mappings, span: start + end }) } + /// Parses a stub `stub credits.aleo { ... }`. + fn parse_stub(&mut self) -> Result<(Symbol, Stub)> { + // Parse `stub` keyword. + let start = self.expect(&Token::Stub)?; + let stub = Stub::from(self.parse_program_body(start)?); + + Ok((stub.stub_id.name.name, stub)) + } + + /// Parses a program scope `program foo.aleo { ... }`. + fn parse_program_scope(&mut self) -> Result { + // Parse `program` keyword. + let start = self.expect(&Token::Program)?; + + self.parse_program_body(start) + } + /// Returns a [`Vec`] AST node if the next tokens represent a struct member. fn parse_struct_members(&mut self) -> Result<(Vec, Span)> { let mut members = Vec::new(); @@ -433,13 +450,25 @@ impl ParserContext<'_> { } }; - // Parse the function body. - let block = self.parse_block()?; + // Parse the function body. Allow empty blocks. `fn foo(a:u8);` + let (has_empty_block, block) = match &self.token.token { + Token::LeftCurly => (false, self.parse_block()?), + Token::Semicolon => { + let semicolon = self.expect(&Token::Semicolon)?; + (true, Block { statements: Vec::new(), span: semicolon, id: self.node_builder.next_id() }) + } + _ => self.unexpected("block or semicolon")?, + }; // Parse the `finalize` block if it exists. let finalize = match self.eat(&Token::Finalize) { false => None, true => { + // Make sure has function body. Don't want `fn foo(); finalize foo { ... }` to be valid parsing. + if has_empty_block { + return Err(ParserError::empty_function_cannot_have_finalize(self.token.span).into()); + } + // Get starting span. let start = self.prev_token.span; diff --git a/compiler/parser/src/tokenizer/lexer.rs b/compiler/parser/src/tokenizer/lexer.rs index fb9d120c4b..6f11c720c2 100644 --- a/compiler/parser/src/tokenizer/lexer.rs +++ b/compiler/parser/src/tokenizer/lexer.rs @@ -379,6 +379,7 @@ impl Token { match &*identifier { x if x.starts_with("aleo1") => Token::AddressLit(identifier), "address" => Token::Address, + "aleo" => Token::Aleo, "as" => Token::As, "assert" => Token::Assert, "assert_eq" => Token::AssertEq, diff --git a/compiler/parser/src/tokenizer/token.rs b/compiler/parser/src/tokenizer/token.rs index 117cc509a0..a38a0f701a 100644 --- a/compiler/parser/src/tokenizer/token.rs +++ b/compiler/parser/src/tokenizer/token.rs @@ -139,6 +139,7 @@ pub enum Token { Transition, // Meta Tokens + Aleo, Block, Eof, Leo, @@ -150,6 +151,7 @@ pub enum Token { /// because true and false are also boolean literals, which are different tokens from keywords. pub const KEYWORD_TOKENS: &[Token] = &[ Token::Address, + Token::Aleo, Token::As, Token::Assert, Token::AssertEq, @@ -207,6 +209,7 @@ impl Token { pub fn keyword_to_symbol(&self) -> Option { Some(match self { Token::Address => sym::address, + Token::Aleo => sym::aleo, Token::As => sym::As, Token::Assert => sym::assert, Token::AssertEq => sym::assert_eq, @@ -344,6 +347,7 @@ impl fmt::Display for Token { U128 => write!(f, "u128"), Record => write!(f, "record"), + Aleo => write!(f, "aleo"), As => write!(f, "as"), Assert => write!(f, "assert"), AssertEq => write!(f, "assert_eq"), diff --git a/errors/src/errors/parser/parser_errors.rs b/errors/src/errors/parser/parser_errors.rs index 734c2f1fd6..beed52cfed 100644 --- a/errors/src/errors/parser/parser_errors.rs +++ b/errors/src/errors/parser/parser_errors.rs @@ -292,6 +292,14 @@ create_messages!( help: None, } + /// Enforce that empty functions cannot have finalize functions attached to them + @formatted + empty_function_cannot_have_finalize { + args: (), + msg: format!("Empty functions cannot have finalize functions attached to them."), + help: None, + } + @formatted array_must_have_at_least_one_element { args: (kind: impl Display),