diff --git a/compiler/ast/src/program.rs b/compiler/ast/src/program.rs index 132b97b4c6..6e69dd66c0 100644 --- a/compiler/ast/src/program.rs +++ b/compiler/ast/src/program.rs @@ -17,7 +17,7 @@ //! A Leo program consists of import, circuit, and function definitions. //! Each defined type consists of ast statements and expressions. -use crate::{Circuit, Function, FunctionInput, Identifier}; +use crate::{Circuit, Function, FunctionInput, Identifier, Record}; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; @@ -36,6 +36,8 @@ pub struct Program { pub functions: IndexMap, /// A map from circuit names to circuit definitions. pub circuits: IndexMap, + /// A map from record names to record definitions. + pub records: IndexMap, } impl AsRef for Program { @@ -50,6 +52,14 @@ impl fmt::Display for Program { function.fmt(f)?; writeln!(f,)?; } + for (_, circuit) in self.circuits.iter() { + circuit.fmt(f)?; + writeln!(f,)?; + } + for (_, record) in self.records.iter() { + record.fmt(f)?; + writeln!(f,)?; + } write!(f, "") } } @@ -62,6 +72,7 @@ impl Program { expected_input: vec![], functions: IndexMap::new(), circuits: IndexMap::new(), + records: IndexMap::new(), } } diff --git a/compiler/ast/src/records/record_variable.rs b/compiler/ast/src/records/record_variable.rs index 6671ea9149..e0de64a571 100644 --- a/compiler/ast/src/records/record_variable.rs +++ b/compiler/ast/src/records/record_variable.rs @@ -19,15 +19,20 @@ use crate::{Identifier, Type}; use serde::{Deserialize, Serialize}; use std::fmt; -#[allow(clippy::large_enum_variant)] /// A variable definition in a record; -/// For example: `foobar: u8;`. +/// For example: `owner: address;`. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct RecordVariable { /// The identifier of the constant. ident: Identifier, /// The type the constant has. - type_: Type + type_: Type, +} + +impl RecordVariable { + pub fn new(ident: Identifier, type_: Type) -> Self { + Self { ident, type_ } + } } impl fmt::Display for RecordVariable { diff --git a/compiler/parser/src/parser/file.rs b/compiler/parser/src/parser/file.rs index d8a8c221c1..272bb7fcde 100644 --- a/compiler/parser/src/parser/file.rs +++ b/compiler/parser/src/parser/file.rs @@ -17,13 +17,14 @@ use super::*; use leo_errors::{ParserError, ParserWarning, Result}; -use leo_span::sym; +use leo_span::{sym, Symbol}; 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 functions = IndexMap::new(); let mut circuits = IndexMap::new(); + let mut records = IndexMap::new(); while self.has_next() { match &self.token.token { @@ -36,11 +37,14 @@ impl ParserContext<'_> { functions.insert(id, function); } Token::Ident(sym::test) => return Err(ParserError::test_function(self.token.span).into()), - // Const functions share the first token with the global Const. Token::Function => { let (id, function) = self.parse_function()?; functions.insert(id, function); } + Token::Record => { + let (id, record) = self.parse_record()?; + records.insert(id, record); + } _ => return Err(Self::unexpected_item(&self.token).into()), } @@ -50,6 +54,7 @@ impl ParserContext<'_> { expected_input: Vec::new(), functions, circuits, + records, }) } @@ -65,9 +70,9 @@ impl ParserContext<'_> { ) } - /// Returns a [`CircuitMember`] AST node if the next tokens represent a circuit member variable + /// Returns a [`Vec`] AST node if the next tokens represent a circuit member variable /// or circuit member function or circuit member constant. - pub fn parse_circuit_declaration(&mut self) -> Result<(Vec, Span)> { + pub fn parse_circuit_members(&mut self) -> Result<(Vec, Span)> { let mut members = Vec::new(); let (mut semi_colons, mut commas) = (false, false); @@ -106,7 +111,7 @@ impl ParserContext<'_> { } /// Parses `IDENT: TYPE`. - fn parse_typed_field_name(&mut self) -> Result<(Identifier, Type)> { + fn parse_member(&mut self) -> Result<(Identifier, Type)> { let name = self.expect_ident()?; self.expect(&Token::Colon)?; let type_ = self.parse_all_types()?.0; @@ -120,7 +125,7 @@ impl ParserContext<'_> { self.expect(&Token::Const)?; // `IDENT: TYPE = EXPR`: - let (_name, _type_) = self.parse_typed_field_name()?; + let (_name, _type_) = self.parse_member()?; self.expect(&Token::Assign)?; let expr = self.parse_expression()?; @@ -134,7 +139,7 @@ impl ParserContext<'_> { /// Returns a [`CircuitMember`] AST node if the next tokens represent a circuit member variable. pub fn parse_member_variable_declaration(&mut self) -> Result { - let (name, type_) = self.parse_typed_field_name()?; + let (name, type_) = self.parse_member()?; Ok(CircuitMember::CircuitVariable(name, type_)) } @@ -152,13 +157,13 @@ impl ParserContext<'_> { } } - /// Returns an [`(Identifier, Function)`] ast node if the next tokens represent a circuit declaration. + /// Returns an [`(Identifier, Circuit)`] ast node if the next tokens represent a circuit declaration. pub(super) fn parse_circuit(&mut self) -> Result<(Identifier, Circuit)> { let start = self.expect(&Token::Circuit)?; let circuit_name = self.expect_ident()?; self.expect(&Token::LeftCurly)?; - let (members, end) = self.parse_circuit_declaration()?; + let (members, end) = self.parse_circuit_members()?; Ok(( circuit_name.clone(), @@ -170,6 +175,84 @@ impl ParserContext<'_> { )) } + /// Parse the member name and type or emit an error. + /// Only used for `record` type. + /// We complete this check at parse time rather than during type checking to enforce + /// ordering, naming, and type as early as possible for program records. + fn parse_record_variable_exact(&mut self, expected_name: Symbol, expected_type: Type) -> Result { + let actual_name = self.expect_ident()?; + self.expect(&Token::Colon)?; + let actual_type = self.parse_all_types()?.0; + + if expected_name != actual_name.name || expected_type != actual_type { + return Err(ParserError::required_record_variable(expected_name, expected_type, actual_name.span()).into()); + } + + Ok(RecordVariable::new(actual_name, actual_type)) + } + + /// Returns a [`RecordVariable`] AST node if the next tokens represent a record variable. + pub fn parse_record_variable(&mut self) -> Result { + let (ident, type_) = self.parse_member()?; + + Ok(RecordVariable::new(ident, type_)) + } + + /// Returns a [`Vec`] AST node if the next tokens represent one or more + /// user defined record variables. + pub fn parse_record_data(&mut self) -> Result<(Vec, Span)> { + let mut data = Vec::new(); + + let (mut semi_colons, mut commas) = (false, false); + while !self.check(&Token::RightCurly) { + data.push({ + let variable = self.parse_record_variable()?; + + if self.eat(&Token::Semicolon) { + if commas { + self.emit_err(ParserError::mixed_commas_and_semicolons(self.token.span)); + } + semi_colons = true; + } + + if self.eat(&Token::Comma) { + if semi_colons { + self.emit_err(ParserError::mixed_commas_and_semicolons(self.token.span)); + } + commas = true; + } + + variable + }); + } + + let span = self.expect(&Token::RightCurly)?; + + Ok((data, span)) + } + + /// Returns an [`(Identifier, Circuit)`] ast node if the next tokens represent a record declaration. + pub(super) fn parse_record(&mut self) -> Result<(Identifier, Record)> { + let start = self.expect(&Token::Record)?; + let record_name = self.expect_ident()?; + + self.expect(&Token::LeftCurly)?; + let owner = self.parse_record_variable_exact(sym::owner, Type::Address)?; + let balance = self.parse_record_variable_exact(sym::balance, Type::IntegerType(IntegerType::U64))?; + let (data, end) = self.parse_record_data()?; + + Ok(( + record_name.clone(), + Record { + identifier: record_name, + owner, + balance, + data, + span: start + end, + }, + )) + } + /// Returns a [`ParamMode`] AST node if the next tokens represent a function parameter mode. pub(super) fn parse_function_parameter_mode(&mut self) -> Result { let public = self.eat(&Token::Public).then(|| self.prev_token.span); diff --git a/leo/errors/src/errors/parser/parser_errors.rs b/leo/errors/src/errors/parser/parser_errors.rs index 52bddb044f..fae9635289 100644 --- a/leo/errors/src/errors/parser/parser_errors.rs +++ b/leo/errors/src/errors/parser/parser_errors.rs @@ -389,4 +389,11 @@ create_messages!( msg: format!("Invalid associated access call to circuit {name}."), help: Some("Double colon `::` syntax is only supported for core circuits in Leo for testnet3.".to_string()), } + + @formatted + required_record_variable { + args: (name: impl Display, type_: impl Display), + msg: format!("The `record` type requires the variable `{name}: {type_}` and enforces ordering."), + help: None, + } ); diff --git a/leo/span/src/symbol.rs b/leo/span/src/symbol.rs index d6bc07d680..912c024cb6 100644 --- a/leo/span/src/symbol.rs +++ b/leo/span/src/symbol.rs @@ -210,6 +210,8 @@ symbols! { public, private, + owner, + balance, // todo: remove these. CONTAINER_PSEUDO_CIRCUIT: "$InputContainer",