parse record type declaration

This commit is contained in:
collin 2022-06-23 22:34:18 -10:00
parent 9d0fd00072
commit d45ab61e40
5 changed files with 121 additions and 13 deletions

View File

@ -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<Identifier, Function>,
/// A map from circuit names to circuit definitions.
pub circuits: IndexMap<Identifier, Circuit>,
/// A map from record names to record definitions.
pub records: IndexMap<Identifier, Record>,
}
impl AsRef<Program> 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(),
}
}

View File

@ -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 {

View File

@ -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<Program> {
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<CircuitMember>`] 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<CircuitMember>, Span)> {
pub fn parse_circuit_members(&mut self) -> Result<(Vec<CircuitMember>, 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<CircuitMember> {
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<RecordVariable> {
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<RecordVariable> {
let (ident, type_) = self.parse_member()?;
Ok(RecordVariable::new(ident, type_))
}
/// Returns a [`Vec<RecordVariable>`] AST node if the next tokens represent one or more
/// user defined record variables.
pub fn parse_record_data(&mut self) -> Result<(Vec<RecordVariable>, 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<ParamMode> {
let public = self.eat(&Token::Public).then(|| self.prev_token.span);

View File

@ -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,
}
);

View File

@ -210,6 +210,8 @@ symbols! {
public,
private,
owner,
balance,
// todo: remove these.
CONTAINER_PSEUDO_CIRCUIT: "$InputContainer",