Skip to content

Commit

Permalink
Use bluebell parser instead of parsing scilla-fmt s-exp (#3)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
its-saeed and troelsfr authored Jan 8, 2024
1 parent 7ffc7c3 commit 538a3f5
Show file tree
Hide file tree
Showing 17 changed files with 831 additions and 275 deletions.
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
17 changes: 7 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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).
8 changes: 4 additions & 4 deletions src/ast/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl fmt::Display for NodeByteStr {
pub enum NodeTypeNameIdentifier {
/// Represents a byte string type
/// Example: `let x: ByStr = "type";`
ByteStringType(WithMetaData<NodeByteStr>),
ByteStringType(NodeByteStr),
/// Represents an event type
/// Example: `event e;`
EventType,
Expand All @@ -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) => {
Expand Down Expand Up @@ -210,9 +210,9 @@ pub enum NodeTypeMapValue {
/// Represents a map key value type
/// Example: `let x: Map (KeyType, (KeyType, ValueType)) = Emp;`
MapKeyValue(Box<WithMetaData<NodeTypeMapEntry>>),
/// Represents a map value paranthesized type
/// Represents a map value parenthesized type
/// Example: `let x: Map (KeyType, (ValueType)) = Emp;`
MapValueParanthesizedType(Box<WithMetaData<NodeTypeMapValueAllowingTypeArguments>>),
MapValueParenthesizedType(Box<WithMetaData<NodeTypeMapValueAllowingTypeArguments>>),
/// Represents a map value address type
/// Example: `let x: Map (KeyType, ByStr20) = Emp;`
MapValueAddressType(Box<WithMetaData<NodeAddressType>>),
Expand Down
2 changes: 1 addition & 1 deletion src/ast/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ impl AstVisitor for NodeTypeMapValue {

ret
}
NodeTypeMapValue::MapValueParanthesizedType(value) => {
NodeTypeMapValue::MapValueParenthesizedType(value) => {
let ret = value.visit(emitter);

ret
Expand Down
45 changes: 22 additions & 23 deletions src/contract.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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::<Contract>().unwrap();
///
/// let contract = contract.parse::<Contract>().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",
Expand All @@ -40,19 +45,12 @@ impl FromStr for Contract {
/// }
/// );
/// ```
fn from_str(sexp: &str) -> Result<Self, Self::Err> {
// 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<Self, Self::Err> {
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))
}
}

Expand All @@ -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",
Expand All @@ -79,7 +78,7 @@ impl Contract {
/// }
/// );
/// ```
pub fn from_path(contract_path: &Path) -> Result<Self, Error> {
run_scilla_fmt(contract_path)?.parse()
pub fn parse(contract_path: &Path) -> Result<Self, Error> {
std::fs::read_to_string(contract_path)?.parse()
}
}
24 changes: 17 additions & 7 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -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<L, T, E> From<ParseError<L, T, E>> for Error
where
L: fmt::Debug,
T: fmt::Debug,
E: fmt::Debug,
{
fn from(value: ParseError<L, T, E>) -> Self {
Self::ParseError(format!("{:?}", value))
}
}
39 changes: 6 additions & 33 deletions src/field.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<Self, Self::Error> {
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<Field>);

impl std::ops::Deref for FieldList {
Expand All @@ -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<Self, Self::Error> {
if !value.is_list() {
return Ok(FieldList::default());
}

let fields: Result<Vec<Field>, 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);
}
}
29 changes: 1 addition & 28 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<String, Error> {
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)?)
}
2 changes: 1 addition & 1 deletion src/parser/lexer.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
4 changes: 2 additions & 2 deletions src/parser/parser.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -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<NodeTypeNameIdentifier> = {
<start:@L> <node:ByteString> <end:@R> => WithMetaData::<NodeTypeNameIdentifier> {
node: NodeTypeNameIdentifier::EventType,
node: NodeTypeNameIdentifier::ByteStringType(node),
start,
end
},
Expand Down Expand Up @@ -615,7 +615,7 @@ pub TypeMapValue: WithMetaData<NodeTypeMapValue> = {
end
},
<start:@L> "(" <t:TypeMapValueAllowingTypeArguments> ")" <end:@R> => WithMetaData::<NodeTypeMapValue> {
node: NodeTypeMapValue::MapValueParanthesizedType(Box::new(t)),
node: NodeTypeMapValue::MapValueParenthesizedType(Box::new(t)),
start,
end
},
Expand Down
Loading

0 comments on commit 538a3f5

Please sign in to comment.