From 538a3f547981a9ea25256345a4d0f167344a21cb Mon Sep 17 00:00:00 2001 From: Saeed Dadkhah Date: Mon, 8 Jan 2024 12:38:01 +0330 Subject: [PATCH] Use bluebell parser instead of parsing scilla-fmt s-exp (#3) * Use bluebell parser instead of parsing scilla-fmt s-exp * Start implementing the AstConverting for Contract. * Pass the first test. * Pass the next test * Pass next test. * Simplify intermediate representation. * Rename * Support complex types * ByStr buggy contract. * Updating grammar * One more passing test ^_^ * Pass one more test! * Remove dependency to lexpr * Support map * Support complex map types * Pass one more test. * All tests passed. * All tests passed * Remove commented codes * Remove useless code * Refactor, remove useless code * Fix doc tests * Remove namespaces. * Remove useless code. * Update readme * Remove panic --------- Co-authored-by: Troels F. Roennow --- Cargo.toml | 1 - README.md | 17 +- src/ast/nodes.rs | 8 +- src/ast/visitor.rs | 2 +- src/contract.rs | 45 +- src/error.rs | 24 +- src/field.rs | 39 +- src/lib.rs | 29 +- src/parser/lexer.rs | 2 +- src/parser/parser.lalrpop | 4 +- src/simplified_representation/emitter.rs | 630 ++++++++++++++++++ src/simplified_representation/mod.rs | 2 + src/simplified_representation/primitives.rs | 70 ++ src/transition.rs | 41 +- src/type.rs | 137 +--- tests/contracts/chainid.scilla | 1 + ...{test_parser.rs => full_contract_tests.rs} | 54 +- 17 files changed, 831 insertions(+), 275 deletions(-) create mode 100644 src/simplified_representation/emitter.rs create mode 100644 src/simplified_representation/mod.rs create mode 100644 src/simplified_representation/primitives.rs rename tests/{test_parser.rs => full_contract_tests.rs} (98%) diff --git a/Cargo.toml b/Cargo.toml index cb0042a..2782633 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ documentation = "https://docs.rs/scilla-parser" [dependencies] lalrpop-util = "0.20.0" -lexpr = "0.2.7" regex = "1.10.2" thiserror = "1.0.50" diff --git a/README.md b/README.md index 72ae943..367540d 100644 --- a/README.md +++ b/README.md @@ -15,19 +15,16 @@ cargo add scilla_parser This will add the scilla_parser dependency to Cargo.toml as specified in the installation instructions above. # Usage -This library parses the s-expression of a contract. There are two options: -1. Use `Contract::from_path` and pass a contract path. This function will automatically call `scilla-fmt` through docker to generate the s-expression needed to parse the contract. -2. Parse a string (slice) to a contract. The string is supposed to have the s-expression of a contract. +This library parses a .scilla file. There are two options: +1. Use `Contract::parse` and pass a contract path. +2. Parse a string (slice) containing a scilla contract. ## To parse a Scilla file: Here is the code to parse [SendZil.scilla](./tests/contracts/SendZil.scilla) contract: ```rust - use std::{error::Error, path::PathBuf}; - use scilla_parser::{Contract, Field, FieldList, Transition, TransitionList, Type}; - let contract_path = PathBuf::from("tests/contracts/SendZil.scilla"); - let contract = Contract::from_path(&contract_path).unwrap(); + let contract = Contract::parse(&contract_path).unwrap(); assert_eq!( contract, @@ -86,10 +83,10 @@ Here is the code to parse [SendZil.scilla](./tests/contracts/SendZil.scilla) con ); ``` -## To parse a string containing the s-expression of a scilla contract: +## To parse a string containing a scilla contract: ```rust - let sexp: &str = "s-expression of the contract"; - let contract: Contract = sexp.parse().unwrap(); + let contract_code: &str = "contract HelloWorld"; + let contract: Contract = contract_code.parse().unwrap(); ``` For more examples, take a look at the [tests](./tests/test_parser.rs). \ No newline at end of file diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index e0931fb..c614c53 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -52,7 +52,7 @@ impl fmt::Display for NodeByteStr { pub enum NodeTypeNameIdentifier { /// Represents a byte string type /// Example: `let x: ByStr = "type";` - ByteStringType(WithMetaData), + ByteStringType(NodeByteStr), /// Represents an event type /// Example: `event e;` EventType, @@ -66,7 +66,7 @@ impl NodeTypeNameIdentifier { pub fn to_string(&self) -> String { match self { NodeTypeNameIdentifier::ByteStringType(byte_str) => { - format!("{}", byte_str.node.to_string()) + format!("{}", byte_str.to_string()) } NodeTypeNameIdentifier::EventType => format!("Event"), NodeTypeNameIdentifier::TypeOrEnumLikeIdentifier(custom_type) => { @@ -210,9 +210,9 @@ pub enum NodeTypeMapValue { /// Represents a map key value type /// Example: `let x: Map (KeyType, (KeyType, ValueType)) = Emp;` MapKeyValue(Box>), - /// Represents a map value paranthesized type + /// Represents a map value parenthesized type /// Example: `let x: Map (KeyType, (ValueType)) = Emp;` - MapValueParanthesizedType(Box>), + MapValueParenthesizedType(Box>), /// Represents a map value address type /// Example: `let x: Map (KeyType, ByStr20) = Emp;` MapValueAddressType(Box>), diff --git a/src/ast/visitor.rs b/src/ast/visitor.rs index 7360aad..2ac1e80 100644 --- a/src/ast/visitor.rs +++ b/src/ast/visitor.rs @@ -247,7 +247,7 @@ impl AstVisitor for NodeTypeMapValue { ret } - NodeTypeMapValue::MapValueParanthesizedType(value) => { + NodeTypeMapValue::MapValueParenthesizedType(value) => { let ret = value.visit(emitter); ret diff --git a/src/contract.rs b/src/contract.rs index 9b934d8..cdb2d5e 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,8 +1,12 @@ use std::{path::Path, str::FromStr}; -use crate::{run_scilla_fmt, Error, FieldList, TransitionList}; +use crate::{ + parser::{lexer::Lexer, parser}, + simplified_representation::emitter::SrEmitter, + Error, FieldList, TransitionList, +}; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Default)] /// The `Contract` struct represents a parsed contract in Rust, including its name, initialization /// parameters, fields, and transitions. pub struct Contract { @@ -23,15 +27,16 @@ impl FromStr for Contract { /// # Example /// ``` /// use std::{error::Error, path::PathBuf}; - /// use scilla_parser::{run_scilla_fmt, Contract, Field, FieldList, Transition, TransitionList, Type}; - /// let contract = run_scilla_fmt(&PathBuf::from("tests/contracts/chainid.scilla")).unwrap(); + /// use scilla_parser::{Contract, Field, FieldList, Transition, TransitionList, Type}; + /// let contract_path = PathBuf::from("tests/contracts/chainid.scilla"); + /// let contract_str = include_str!("../tests/contracts/chainid.scilla"); + /// let contract = contract_str.parse::().unwrap(); /// - /// let contract = contract.parse::().unwrap(); /// assert_eq!( /// contract, /// Contract { /// name: "ChainId".to_string(), - /// fields: FieldList::default(), + /// fields: FieldList(vec![Field::new("dummy_field", Type::Uint256)]), /// init_params: FieldList::default(), /// transitions: TransitionList(vec![Transition::new( /// "EventChainID", @@ -40,19 +45,12 @@ impl FromStr for Contract { /// } /// ); /// ``` - fn from_str(sexp: &str) -> Result { - // Bug in lexpr crate requires escaping backslashes - let v = lexpr::from_str(&sexp.replace("\\", ""))?; - let name = v["contr"][0]["cname"]["Ident"][0][1].to_string(); - let transitions = (&v["contr"][0]["ccomps"][0]).try_into()?; - let init_params = (&v["contr"][0]["cparams"][0]).try_into()?; - let fields = (&v["contr"][0]["cfields"][0]).try_into()?; - Ok(Contract { - name, - transitions, - init_params, - fields, - }) + fn from_str(contract: &str) -> Result { + let mut errors = vec![]; + let parsed = parser::ProgramParser::new().parse(&mut errors, Lexer::new(&contract))?; + + let emitter = SrEmitter::new(); + emitter.emit(&parsed).map_err(|e| Error::ParseError(e)) } } @@ -65,12 +63,13 @@ impl Contract { /// use std::{error::Error, path::PathBuf}; /// use scilla_parser::{Contract, Field, FieldList, Transition, TransitionList, Type}; /// let contract_path = PathBuf::from("tests/contracts/chainid.scilla"); - /// let contract = Contract::from_path(&contract_path).unwrap(); + /// let contract = Contract::parse(&contract_path).unwrap(); + /// /// assert_eq!( /// contract, /// Contract { /// name: "ChainId".to_string(), - /// fields: FieldList::default(), + /// fields: FieldList(vec![Field::new("dummy_field", Type::Uint256)]), /// init_params: FieldList::default(), /// transitions: TransitionList(vec![Transition::new( /// "EventChainID", @@ -79,7 +78,7 @@ impl Contract { /// } /// ); /// ``` - pub fn from_path(contract_path: &Path) -> Result { - run_scilla_fmt(contract_path)?.parse() + pub fn parse(contract_path: &Path) -> Result { + std::fs::read_to_string(contract_path)?.parse() } } diff --git a/src/error.rs b/src/error.rs index bf09540..e13c313 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,21 +1,31 @@ +use core::fmt; use std::string::FromUtf8Error; +use lalrpop_util::ParseError; use thiserror::Error as ThisError; #[derive(Debug, ThisError)] pub enum Error { - #[error("The requested entry {0} does not exist in the given S-expression")] - NoSuchEntryInSexp(String), + #[error("Failed to parse the contract. {0}")] + ParseError(String), - #[error("Comptype is not transition. It's {0}")] - CompTypeIsNotTransition(String), + #[error("Failed to visit AST {0}")] + AstVisitError(String), #[error(transparent)] IoError(#[from] std::io::Error), - #[error(transparent)] - LexprParseError(#[from] lexpr::parse::Error), - #[error(transparent)] FromUtf8Error(#[from] FromUtf8Error), } + +impl From> for Error +where + L: fmt::Debug, + T: fmt::Debug, + E: fmt::Debug, +{ + fn from(value: ParseError) -> Self { + Self::ParseError(format!("{:?}", value)) + } +} diff --git a/src/field.rs b/src/field.rs index 3ef924c..753e169 100644 --- a/src/field.rs +++ b/src/field.rs @@ -1,8 +1,6 @@ -use lexpr::Value; +use crate::Type; -use crate::{Error, Type}; - -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct Field { pub name: String, pub r#type: Type, @@ -32,19 +30,7 @@ impl Field { } } -impl TryFrom<&Value> for Field { - type Error = Error; - - /// Try to parse a field from a lexpr::Value - fn try_from(value: &Value) -> Result { - let name = value[0]["SimpleLocal"][0].to_string(); - let r#type = value[1].to_string().parse()?; - - Ok(Field { name, r#type }) - } -} - -#[derive(Debug, PartialEq, Default)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct FieldList(pub Vec); impl std::ops::Deref for FieldList { @@ -55,21 +41,8 @@ impl std::ops::Deref for FieldList { } } -impl TryFrom<&Value> for FieldList { - type Error = Error; - - /// Try to parse a list of fields from a lexpr::Value - fn try_from(value: &Value) -> Result { - if !value.is_list() { - return Ok(FieldList::default()); - } - - let fields: Result, Error> = value - .list_iter() - .unwrap() - .map(|elem| elem.try_into()) - .collect(); - - Ok(FieldList(fields?)) +impl FieldList { + pub fn push(&mut self, field: Field) { + self.0.push(field); } } diff --git a/src/lib.rs b/src/lib.rs index 83abe8a..31d9ad4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod contract; pub mod error; pub mod field; pub mod parser; +pub mod simplified_representation; pub mod transition; pub mod r#type; @@ -11,31 +12,3 @@ pub use error::Error; pub use field::*; pub use r#type::*; pub use transition::*; - -use std::{path::Path, process::Command}; - -/// Run the scilla-fmt command using docker to generate a s-expression out of a given scilla contract. -pub fn run_scilla_fmt(path: &Path) -> Result { - let volume = &format!( - "{}:/tmp/input.scilla", - path.canonicalize().unwrap().display() - ); - - let output = Command::new("docker") - .args([ - "run", - "--rm", - "-v", - volume, - "-i", - "zilliqa/scilla:v0.13.3", - "/scilla/0/bin/scilla-fmt", - "--sexp", - "--human-readable", - "-d", - "/tmp/input.scilla", - ]) - .output()?; - - Ok(String::from_utf8(output.stdout)?) -} diff --git a/src/parser/lexer.rs b/src/parser/lexer.rs index 323393c..7a14b78 100644 --- a/src/parser/lexer.rs +++ b/src/parser/lexer.rs @@ -1,4 +1,4 @@ -use std::{convert::From, fmt::Display, iter::Peekable, str::CharIndices, string::String}; +use std::{convert::From, iter::Peekable, str::CharIndices, string::String}; use regex::Regex; diff --git a/src/parser/parser.lalrpop b/src/parser/parser.lalrpop index 9c8033c..252dd22 100644 --- a/src/parser/parser.lalrpop +++ b/src/parser/parser.lalrpop @@ -111,7 +111,7 @@ pub ByteString : NodeByteStr = { // @return An identifier type name as a custom type name, a byte string type name, or an event type name. pub TypeNameIdentifier: WithMetaData = { => WithMetaData:: { - node: NodeTypeNameIdentifier::EventType, + node: NodeTypeNameIdentifier::ByteStringType(node), start, end }, @@ -615,7 +615,7 @@ pub TypeMapValue: WithMetaData = { end }, "(" ")" => WithMetaData:: { - node: NodeTypeMapValue::MapValueParanthesizedType(Box::new(t)), + node: NodeTypeMapValue::MapValueParenthesizedType(Box::new(t)), start, end }, diff --git a/src/simplified_representation/emitter.rs b/src/simplified_representation/emitter.rs new file mode 100644 index 0000000..b02ac5f --- /dev/null +++ b/src/simplified_representation/emitter.rs @@ -0,0 +1,630 @@ +use crate::{ + ast::{converting::AstConverting, nodes::*, visitor::AstVisitor}, + parser::lexer::SourcePosition, + simplified_representation::primitives::*, + Contract, Field, FieldList, Transition, +}; + +use crate::ast::{TraversalResult, TreeTraversalMode}; + +#[derive(Debug, Clone)] +enum StackObject { + IrIdentifier(SrIdentifier), + VariableDeclaration(Field), + TypeDefinition(SrType), +} + +/// The `SrEmitter` struct is used for bookkeeping during the conversion of a Scilla AST to a simplified representation. +/// It implements the `AstConverting` trait, which is a generic trait for AST conversions. +pub struct SrEmitter { + stack: Vec, + contract: Contract, +} + +impl SrEmitter { + pub fn new() -> Self { + SrEmitter { + stack: Vec::new(), + contract: Contract::default(), + } + } + + fn pop_ir_identifier(&mut self) -> Result { + let ret = if let Some(candidate) = self.stack.pop() { + match candidate { + StackObject::IrIdentifier(n) => n, + _ => { + return Err(format!("Expected symbol name, but found {:?}.", candidate)); + } + } + } else { + return Err("Expected symbol name, but found nothing.".to_string()); + }; + + Ok(ret) + } + + fn pop_variable_declaration(&mut self) -> Result { + let ret = if let Some(candidate) = self.stack.pop() { + match candidate { + StackObject::VariableDeclaration(n) => n, + _ => { + return Err(format!( + "Expected variable declaration, but found {:?}.", + candidate + )); + } + } + } else { + return Err("Expected variable declaration, but found nothing.".to_string()); + }; + + Ok(ret) + } + + fn pop_type_definition(&mut self) -> Result { + let ret = if let Some(candidate) = self.stack.pop() { + match candidate { + StackObject::TypeDefinition(n) => n, + _ => { + return Err(format!( + "Expected type definition, but found {:?}.", + candidate + )); + } + } + } else { + return Err("Expected type definition, but found nothing.".to_string()); + }; + + Ok(ret) + } + pub fn emit(mut self, node: &NodeProgram) -> Result { + node.contract_definition.visit(&mut self)?; + Ok(self.contract) + } +} + +impl AstConverting for SrEmitter { + fn push_source_position(&mut self, _start: &SourcePosition, _end: &SourcePosition) {} + + fn pop_source_position(&mut self) {} + + fn emit_byte_str( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeByteStr, + ) -> Result { + Ok(TraversalResult::Continue) + } + fn emit_type_name_identifier( + &mut self, + mode: TreeTraversalMode, + node: &NodeTypeNameIdentifier, + ) -> Result { + match mode { + TreeTraversalMode::Enter => match node { + NodeTypeNameIdentifier::ByteStringType(bytestr) => { + let symbol = SrIdentifier::new(bytestr.to_string(), SrIdentifierKind::Unknown); + + self.stack.push(StackObject::IrIdentifier(symbol)); + } + NodeTypeNameIdentifier::EventType => {} + NodeTypeNameIdentifier::TypeOrEnumLikeIdentifier(name) => { + let symbol = SrIdentifier::new(name.to_string(), SrIdentifierKind::Unknown); + + self.stack.push(StackObject::IrIdentifier(symbol)); + } + }, + TreeTraversalMode::Exit => (), + } + Ok(TraversalResult::Continue) + } + fn emit_imported_name( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeImportedName, + ) -> Result { + unimplemented!(); + } + fn emit_import_declarations( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeImportDeclarations, + ) -> Result { + unimplemented!(); + } + fn emit_meta_identifier( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeMetaIdentifier, + ) -> Result { + Ok(TraversalResult::Continue) + } + fn emit_variable_identifier( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeVariableIdentifier, + ) -> Result { + Ok(TraversalResult::SkipChildren) + } + fn emit_builtin_arguments( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeBuiltinArguments, + ) -> Result { + unimplemented!(); + } + fn emit_type_map_key( + &mut self, + _mode: TreeTraversalMode, + node: &NodeTypeMapKey, + ) -> Result { + match node { + NodeTypeMapKey::GenericMapKey(key) => key.visit(self)?, + NodeTypeMapKey::EnclosedGenericId(key) => key.visit(self)?, + NodeTypeMapKey::EnclosedAddressMapKeyType(key) => key.visit(self)?, + NodeTypeMapKey::AddressMapKeyType(key) => key.visit(self)?, + }; + Ok(TraversalResult::SkipChildren) + } + fn emit_type_map_value( + &mut self, + mode: TreeTraversalMode, + node: &NodeTypeMapValue, + ) -> Result { + match mode { + TreeTraversalMode::Enter => { + match node { + NodeTypeMapValue::MapValueTypeOrEnumLikeIdentifier(value) => { + value.visit(self)?; + let value = self.pop_ir_identifier()?; + let key = self.pop_ir_identifier()?; + let map = SrType { + main_type: "Map".to_string(), + sub_types: vec![key.into(), value.into()], + }; + self.stack.push(StackObject::TypeDefinition(map)); + } + NodeTypeMapValue::MapKeyValue(value) => { + value.visit(self)?; + } + NodeTypeMapValue::MapValueParenthesizedType(value) => { + value.visit(self)?; + let value = self.pop_type_definition()?; + let key = self.pop_ir_identifier()?; + let map = SrType { + main_type: "Map".to_string(), + sub_types: vec![key.into(), value], + }; + self.stack.push(StackObject::TypeDefinition(map)); + } + NodeTypeMapValue::MapValueAddressType(_value) => unimplemented!(), + }; + } + TreeTraversalMode::Exit => {} + } + Ok(TraversalResult::SkipChildren) + } + fn emit_type_argument( + &mut self, + _mode: TreeTraversalMode, + node: &NodeTypeArgument, + ) -> Result { + match node { + NodeTypeArgument::EnclosedTypeArgument(t) => { + let _ = t.visit(self)?; + } + NodeTypeArgument::GenericTypeArgument(n) => { + let _ = n.visit(self)?; + let identifier = self.pop_ir_identifier()?; + self.stack + .push(StackObject::TypeDefinition(identifier.into())); + } + NodeTypeArgument::TemplateTypeArgument(_) => { + unimplemented!(); + } + NodeTypeArgument::AddressTypeArgument(_) => { + unimplemented!(); + } + NodeTypeArgument::MapTypeArgument(_, _) => { + unimplemented!(); + } + } + Ok(TraversalResult::SkipChildren) + } + fn emit_scilla_type( + &mut self, + _mode: TreeTraversalMode, + node: &NodeScillaType, + ) -> Result { + match node { + NodeScillaType::GenericTypeWithArgs(lead, args) => { + let _ = lead.visit(self)?; + let identifier = self.pop_ir_identifier()?; + self.stack + .push(StackObject::TypeDefinition(identifier.into())); + if args.len() > 0 { + let mut main_type = self.pop_type_definition()?; + for arg in args { + let _ = arg.visit(self)?; + let sub_type = self.pop_type_definition()?; + main_type.push_sub_type(sub_type); + } + self.stack.push(StackObject::TypeDefinition(main_type)); + } + } + NodeScillaType::MapType(key, value) => { + let _ = key.visit(self)?; + let _ = value.visit(self)?; + } + NodeScillaType::FunctionType(_a, _b) => { + unimplemented!() + } + + NodeScillaType::PolyFunctionType(_name, _a) => { + unimplemented!() + } + NodeScillaType::EnclosedType(a) => { + let _ = (*a).visit(self)?; + } + NodeScillaType::ScillaAddresseType(a) => { + let _ = (*a).visit(self)?; + } + NodeScillaType::TypeVarType(_name) => { + unimplemented!() + } + }; + Ok(TraversalResult::SkipChildren) + } + + fn emit_type_map_entry( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeTypeMapEntry, + ) -> Result { + Ok(TraversalResult::Continue) + } + fn emit_address_type_field( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeAddressTypeField, + ) -> Result { + unimplemented!(); + } + fn emit_address_type( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeAddressType, + ) -> Result { + unimplemented!(); + } + + fn emit_full_expression( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeFullExpression, + ) -> Result { + Ok(TraversalResult::SkipChildren) + } + + fn emit_message_entry( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeMessageEntry, + ) -> Result { + unimplemented!(); + } + fn emit_pattern_match_expression_clause( + &mut self, + _mode: TreeTraversalMode, + _node: &NodePatternMatchExpressionClause, + ) -> Result { + unimplemented!(); + } + fn emit_atomic_expression( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeAtomicExpression, + ) -> Result { + unimplemented!(); + } + fn emit_contract_type_arguments( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeContractTypeArguments, + ) -> Result { + unimplemented!(); + } + fn emit_value_literal( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeValueLiteral, + ) -> Result { + Ok(TraversalResult::SkipChildren) + } + fn emit_map_access( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeMapAccess, + ) -> Result { + unimplemented!(); + } + fn emit_pattern( + &mut self, + _mode: TreeTraversalMode, + _node: &NodePattern, + ) -> Result { + Ok(TraversalResult::SkipChildren) + } + fn emit_argument_pattern( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeArgumentPattern, + ) -> Result { + unimplemented!(); + } + fn emit_pattern_match_clause( + &mut self, + _mode: TreeTraversalMode, + _node: &NodePatternMatchClause, + ) -> Result { + unimplemented!(); + } + fn emit_blockchain_fetch_arguments( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeBlockchainFetchArguments, + ) -> Result { + unimplemented!(); + } + + fn emit_statement( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeStatement, + ) -> Result { + Ok(TraversalResult::SkipChildren) + } + + fn emit_remote_fetch_statement( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeRemoteFetchStatement, + ) -> Result { + unimplemented!(); + } + fn emit_component_id( + &mut self, + _mode: TreeTraversalMode, + node: &NodeComponentId, + ) -> Result { + match node { + NodeComponentId::WithRegularId(name) => { + self.stack.push(StackObject::IrIdentifier(SrIdentifier { + unresolved: name.to_string(), + resolved: None, + type_reference: None, + kind: SrIdentifierKind::ComponentName, + is_definition: false, + })); + } + NodeComponentId::WithTypeLikeName(name) => { + self.stack.push(StackObject::IrIdentifier(SrIdentifier { + unresolved: name.to_string(), + resolved: None, + type_reference: None, + kind: SrIdentifierKind::ComponentName, + is_definition: false, + })); + } + } + + Ok(TraversalResult::SkipChildren) + } + + fn emit_component_parameters( + &mut self, + mode: TreeTraversalMode, + node: &NodeComponentParameters, + ) -> Result { + match mode { + TreeTraversalMode::Enter => { + for param in node.parameters.iter() { + let _ = param.visit(self)?; + let init_param = self.pop_variable_declaration()?; + self.contract.init_params.push(init_param); + } + } + TreeTraversalMode::Exit => {} + } + Ok(TraversalResult::Continue) + } + + fn emit_parameter_pair( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeParameterPair, + ) -> Result { + // Deliberate pass through + Ok(TraversalResult::Continue) + } + + fn emit_component_body( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeComponentBody, + ) -> Result { + Ok(TraversalResult::SkipChildren) + } + + fn emit_statement_block( + &mut self, + _node: TreeTraversalMode, + _mode: &NodeStatementBlock, + ) -> Result { + Ok(TraversalResult::Continue) + } + fn emit_typed_identifier( + &mut self, + _mode: TreeTraversalMode, + node: &NodeTypedIdentifier, + ) -> Result { + let name = node.identifier_name.clone(); + let _ = node.annotation.visit(self)?; + + let typename = self.pop_type_definition()?; + + let s = StackObject::VariableDeclaration(Field::new(&name.node, typename.into())); + self.stack.push(s); + + Ok(TraversalResult::SkipChildren) + } + fn emit_type_annotation( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeTypeAnnotation, + ) -> Result { + // Pass through + Ok(TraversalResult::Continue) + } + + fn emit_program( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeProgram, + ) -> Result { + Ok(TraversalResult::Continue) + } + + fn emit_library_definition( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeLibraryDefinition, + ) -> Result { + unimplemented!() + } + + fn emit_library_single_definition( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeLibrarySingleDefinition, + ) -> Result { + unimplemented!() + } + + fn emit_contract_definition( + &mut self, + _mode: TreeTraversalMode, + node: &NodeContractDefinition, + ) -> Result { + let _ = node.contract_name.visit(self)?; + self.contract.name = node.contract_name.to_string(); + + let _ = node.parameters.visit(self)?; + + if let Some(constraint) = &node.constraint { + let _ = constraint.visit(self)?; + } + + for field in node.fields.iter() { + let _ = field.visit(self)?; + } + + for component in node.components.iter() { + let _ = component.visit(self)?; + } + + Ok(TraversalResult::SkipChildren) + } + + fn emit_contract_field( + &mut self, + _mode: TreeTraversalMode, + node: &NodeContractField, + ) -> Result { + let _ = node.typed_identifier.visit(self)?; + + let field = self.pop_variable_declaration()?; + let _ = node.right_hand_side.visit(self)?; + + self.contract.fields.push(field); + + Ok(TraversalResult::SkipChildren) + } + fn emit_with_constraint( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeWithConstraint, + ) -> Result { + Ok(TraversalResult::Continue) + } + fn emit_component_definition( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeComponentDefinition, + ) -> Result { + Ok(TraversalResult::Continue) + } + fn emit_procedure_definition( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeProcedureDefinition, + ) -> Result { + Ok(TraversalResult::SkipChildren) + } + + fn emit_transition_definition( + &mut self, + _mode: TreeTraversalMode, + node: &NodeTransitionDefinition, + ) -> Result { + // Enter + let _ = node.name.visit(self)?; + + let arguments = node + .parameters + .node + .parameters + .iter() + .map(|arg| { + let _ = arg.visit(self)?; + self.pop_variable_declaration() + }) + .collect::, _>>()?; + + let mut function_name = self.pop_ir_identifier()?; + assert!(function_name.kind == SrIdentifierKind::ComponentName); + function_name.kind = SrIdentifierKind::TransitionName; + function_name.is_definition = true; + + self.contract.transitions.push(Transition::new( + &function_name.unresolved, + FieldList(arguments), + )); + + Ok(TraversalResult::SkipChildren) + } + + fn emit_type_alternative_clause( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeTypeAlternativeClause, + ) -> Result { + Ok(TraversalResult::SkipChildren) + } + fn emit_type_map_value_arguments( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeTypeMapValueArguments, + ) -> Result { + unimplemented!(); + } + fn emit_type_map_value_allowing_type_arguments( + &mut self, + _mode: TreeTraversalMode, + _node: &NodeTypeMapValueAllowingTypeArguments, + ) -> Result { + Ok(TraversalResult::Continue) + } +} diff --git a/src/simplified_representation/mod.rs b/src/simplified_representation/mod.rs new file mode 100644 index 0000000..5027f0d --- /dev/null +++ b/src/simplified_representation/mod.rs @@ -0,0 +1,2 @@ +pub mod emitter; +pub mod primitives; diff --git a/src/simplified_representation/primitives.rs b/src/simplified_representation/primitives.rs new file mode 100644 index 0000000..67b16e2 --- /dev/null +++ b/src/simplified_representation/primitives.rs @@ -0,0 +1,70 @@ +/// Enum representing the different kinds of identifiers in the simplified representation. +#[derive(Debug, Clone, PartialEq)] +pub enum SrIdentifierKind { + FunctionName, + StaticFunctionName, + TransitionName, + ProcedureName, + TemplateFunctionName, + ExternalFunctionName, + + TypeName, + ComponentName, + Event, + Namespace, + BlockLabel, + + ContextResource, + + // Storage and reference + VirtualRegister, + VirtualRegisterIntermediate, + Memory, + State, + + // More info needed to derive kind + Unknown, +} + +/// Struct representing an identifier in the simplified representation. +#[derive(Debug, Clone, PartialEq)] +pub struct SrIdentifier { + pub unresolved: String, + pub resolved: Option, + pub type_reference: Option, + pub kind: SrIdentifierKind, + pub is_definition: bool, +} + +#[derive(Debug, Clone)] +pub struct SrType { + pub main_type: String, + pub sub_types: Vec, +} + +impl SrType { + pub fn push_sub_type(&mut self, sub_type: SrType) { + self.sub_types.push(sub_type); + } +} + +impl From for SrType { + fn from(value: SrIdentifier) -> Self { + Self { + main_type: value.unresolved, + sub_types: vec![], + } + } +} + +impl SrIdentifier { + pub fn new(unresolved: String, kind: SrIdentifierKind) -> Self { + Self { + unresolved, + resolved: None, + type_reference: None, + kind, + is_definition: false, + } + } +} diff --git a/src/transition.rs b/src/transition.rs index 69aa2d7..4b60c08 100644 --- a/src/transition.rs +++ b/src/transition.rs @@ -1,6 +1,4 @@ -use lexpr::Value; - -use crate::{Error, FieldList}; +use crate::FieldList; #[derive(Debug, PartialEq)] pub struct Transition { @@ -24,24 +22,6 @@ impl Transition { } } -impl TryFrom<&Value> for Transition { - type Error = Error; - - /// Try to parse a lexpr::Value into a transition. - fn try_from(value: &Value) -> Result { - let comp_type = value["comp_type"][0].as_symbol().unwrap(); - if comp_type == "CompTrans" { - let transition_name = value["comp_name"][0]["SimpleLocal"][0].to_string(); - Ok(Transition { - name: transition_name, - params: (&value["comp_params"][0]).try_into()?, - }) - } else { - Err(Error::CompTypeIsNotTransition(comp_type.to_string())) - } - } -} - #[derive(Debug, PartialEq, Default)] pub struct TransitionList(pub Vec); @@ -53,21 +33,8 @@ impl std::ops::Deref for TransitionList { } } -impl TryFrom<&Value> for TransitionList { - type Error = Error; - - /// Try to parse a lexpr::Value into a list of transitions. - fn try_from(value: &Value) -> Result { - if !value.is_list() { - return Ok(TransitionList::default()); - } - - Ok(TransitionList( - value - .list_iter() - .unwrap() - .filter_map(|elem| Transition::try_from(elem).ok()) - .collect(), - )) +impl std::ops::DerefMut for TransitionList { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } diff --git a/src/type.rs b/src/type.rs index 937767a..83fe7f3 100644 --- a/src/type.rs +++ b/src/type.rs @@ -1,9 +1,9 @@ -use std::{fmt::Display, str::FromStr}; +use std::fmt::Display; -use crate::Error; +use crate::simplified_representation::primitives::SrType; /// Represents all different scilla types. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum Type { Int32, Int64, @@ -31,49 +31,6 @@ pub enum Type { Other(String), } -impl FromStr for Type { - type Err = Error; - - /// Try to parse a string slice to a Type. - fn from_str(s: &str) -> Result { - let v = lexpr::from_str(s)?; - match v[0].as_symbol() { - Some(t) => match t { - "PrimType" => match v[1].as_symbol().unwrap() { - "Int32" => Ok(Type::Int32), - "Int64" => Ok(Type::Int64), - "Int128" => Ok(Type::Int128), - "Int256" => Ok(Type::Int256), - "Uint32" => Ok(Type::Uint32), - "Uint64" => Ok(Type::Uint64), - "Uint128" => Ok(Type::Uint128), - "Uint256" => Ok(Type::Uint256), - "String" => Ok(Type::String), - "ByStr20" => Ok(Type::ByStr(20)), - "BNum" => Ok(Type::BNum), - _ => Ok(Type::Other(s.to_string())), - }, - "MapType" => Ok(Type::Map( - Box::new(v[1].to_string().parse()?), - Box::new(v[2].to_string().parse()?), - )), - "ADT" => match v["Ident"]["SimpleLocal"][0].as_symbol().unwrap() { - "Bool" => Ok(Type::Bool), - "Option" => Ok(Type::Option(Box::new(v[2][0].to_string().parse()?))), - "List" => Ok(Type::List(Box::new(v[2][0].to_string().parse()?))), - "Pair" => Ok(Type::Pair( - Box::new(v[2][0].to_string().parse()?), - Box::new(v[2][1].to_string().parse()?), - )), - _ => Ok(Type::Other(v["Ident"]["SimpleLocal"][0].to_string())), - }, - _ => Ok(Type::Other(s.to_string())), - }, - None => Ok(Self::Other(s.to_string())), - } - } -} - impl Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -98,65 +55,43 @@ impl Display for Type { } } +impl From for Type { + fn from(mut type_definition: SrType) -> Self { + match type_definition.main_type.as_str() { + "Int32" => Type::Int32, + "Int64" => Type::Int64, + "Int128" => Type::Int128, + "Int256" => Type::Int256, + "Uint32" => Type::Uint32, + "Uint64" => Type::Uint64, + "Uint128" => Type::Uint128, + "Uint256" => Type::Uint256, + "String" => Type::String, + "ByStr20" => Type::ByStr(20), + "BNum" => Type::BNum, + "Bool" => Type::Bool, + // TODO: Remove unwrap + "Option" => Type::Option(Box::new(type_definition.sub_types.pop().unwrap().into())), + "List" => Type::List(Box::new(type_definition.sub_types.pop().unwrap().into())), + "Pair" => { + let t2 = type_definition.sub_types.pop().unwrap(); + let t1 = type_definition.sub_types.pop().unwrap(); + Type::Pair(Box::new(t1.into()), Box::new(t2.into())) + } + "Map" => { + let value = type_definition.sub_types.pop().unwrap(); + let key = type_definition.sub_types.pop().unwrap(); + Type::Map(Box::new(key.into()), Box::new(value.into())) + } + _ => Type::Other(type_definition.main_type), + } + } +} + #[cfg(test)] mod tests { use super::*; - #[test] - fn test_prim_type() { - let prim = "(PrimType String)".parse::().unwrap(); - assert_eq!(prim, Type::String); - } - - #[test] - fn test_map_type() { - let map_type = "(MapType (PrimType ByStr20) (PrimType Uint128))" - .parse::() - .unwrap(); - - assert_eq!( - map_type, - Type::Map(Box::new(Type::ByStr(20)), Box::new(Type::Uint128)) - ); - } - - #[test] - fn test_complex_map_type() { - let map_type = - "(MapType (PrimType ByStr20) (MapType (PrimType ByStr20) (PrimType Uint128)))" - .parse::() - .unwrap(); - - assert_eq!( - map_type, - Type::Map( - Box::new(Type::ByStr(20)), - Box::new(Type::Map( - Box::new(Type::ByStr(20)), - Box::new(Type::Uint128) - )) - ) - ); - } - - #[test] - fn test_bool_type() { - let bool_type = "(ADT (Ident (SimpleLocal Bool) ((fname \"\") (lnum 0) (cnum 0))) ())" - .parse::() - .unwrap(); - - assert_eq!(bool_type, Type::Bool); - } - - #[test] - fn test_option_bool_type() { - let option_bool_type = r#"(ADT (Ident (SimpleLocal Option) ((fname "") (lnum 0) (cnum 0))) ((ADT (Ident (SimpleLocal Bool) ((fname "") (lnum 0) (cnum 0))) ())))"# - .parse::() - .unwrap(); - - assert_eq!(option_bool_type, Type::Option(Box::new(Type::Bool))); - } - #[test] fn test_type_to_string() { //(List (Pair ByStr20 (List (Pair ByStr20 Uint32)))) diff --git a/tests/contracts/chainid.scilla b/tests/contracts/chainid.scilla index db87d21..d1a3703 100644 --- a/tests/contracts/chainid.scilla +++ b/tests/contracts/chainid.scilla @@ -5,6 +5,7 @@ scilla_version 0 (***************************************************) contract ChainId() +field dummy_field : Uint256 = Uint256 0 transition EventChainID () cid <-& CHAINID; diff --git a/tests/test_parser.rs b/tests/full_contract_tests.rs similarity index 98% rename from tests/test_parser.rs rename to tests/full_contract_tests.rs index 7d916aa..789f4b4 100644 --- a/tests/test_parser.rs +++ b/tests/full_contract_tests.rs @@ -10,22 +10,41 @@ fn test_parse() -> Result<(), Box> { let path = entry.path(); if path.is_file() { println!("Parsing {}", path.display()); - Contract::from_path(&path)?; + Contract::parse(&path)?; } } Ok(()) } +#[test] +fn test_timestamp_contract_parse() { + let contract_path = PathBuf::from("tests/contracts/Timestamp.scilla"); + let contract = Contract::parse(&contract_path).unwrap(); + + assert_eq!( + contract, + Contract { + name: "Timestamp".to_string(), + init_params: FieldList::default(), + fields: FieldList::default(), + transitions: TransitionList(vec![Transition::new( + "EventTimestamp", + FieldList(vec![Field::new("bnum", Type::BNum)]) + )]) + } + ); +} + #[test] fn test_chain_id_contract_parse() { let contract_path = PathBuf::from("tests/contracts/chainid.scilla"); - let contract = Contract::from_path(&contract_path).unwrap(); + let contract = Contract::parse(&contract_path).unwrap(); assert_eq!( contract, Contract { name: "ChainId".to_string(), - fields: FieldList::default(), + fields: FieldList(vec![Field::new("dummy_field", Type::Uint256)]), init_params: FieldList::default(), transitions: TransitionList(vec![Transition::new( "EventChainID", @@ -38,7 +57,7 @@ fn test_chain_id_contract_parse() { #[test] fn test_hello_world_contract_parse() { let contract_path = PathBuf::from("tests/contracts/HelloWorld.scilla"); - let contract = Contract::from_path(&contract_path).unwrap(); + let contract = Contract::parse(&contract_path).unwrap(); assert_eq!( contract, @@ -57,7 +76,7 @@ fn test_hello_world_contract_parse() { #[test] fn test_send_zil_contract_parse() { let contract_path = PathBuf::from("tests/contracts/SendZil.scilla"); - let contract = Contract::from_path(&contract_path).unwrap(); + let contract = Contract::parse(&contract_path).unwrap(); assert_eq!( contract, @@ -116,29 +135,10 @@ fn test_send_zil_contract_parse() { ); } -#[test] -fn test_timestamp_contract_parse() { - let contract_path = PathBuf::from("tests/contracts/Timestamp.scilla"); - let contract = Contract::from_path(&contract_path).unwrap(); - - assert_eq!( - contract, - Contract { - name: "Timestamp".to_string(), - init_params: FieldList::default(), - fields: FieldList::default(), - transitions: TransitionList(vec![Transition::new( - "EventTimestamp", - FieldList(vec![Field::new("bnum", Type::BNum)]) - )]) - } - ); -} - #[test] fn test_fungible_token_parse() { let contract_path = PathBuf::from("tests/contracts/FungibleToken.scilla"); - let contract = Contract::from_path(&contract_path).unwrap(); + let contract = Contract::parse(&contract_path).unwrap(); assert_eq!( contract, Contract { @@ -212,7 +212,7 @@ fn test_fungible_token_parse() { #[test] fn test_staking_proxy_v2_parse() { let contract_path = PathBuf::from("tests/contracts/staking_proxy_v2.scilla"); - let contract = Contract::from_path(&contract_path).unwrap(); + let contract = Contract::parse(&contract_path).unwrap(); assert_eq!( contract, Contract { @@ -509,7 +509,7 @@ fn test_staking_proxy_v2_parse() { #[test] fn test_stzil_contract_parse() { let contract_path = PathBuf::from("tests/contracts/stzil.scilla"); - let contract = Contract::from_path(&contract_path).unwrap(); + let contract = Contract::parse(&contract_path).unwrap(); assert_eq!( contract,