mirror of
https://github.com/AleoHQ/leo.git
synced 2024-12-02 12:22:08 +03:00
Implements working typed ast serialization
This commit is contained in:
parent
f6dcd7f9c2
commit
00c10870e2
@ -18,3 +18,5 @@ pest_derive = { version = "2.0" }
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = { version = "1.0" }
|
serde_json = { version = "1.0" }
|
||||||
thiserror = { version = "1.0" }
|
thiserror = { version = "1.0" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
1
ast/tests/mod.rs
Normal file
1
ast/tests/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
mod serialization;
|
91
ast/tests/serialization/expected_ast.json
Normal file
91
ast/tests/serialization/expected_ast.json
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"imports": [],
|
||||||
|
"circuits": [],
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"function_name": {
|
||||||
|
"value": "main",
|
||||||
|
"span": {
|
||||||
|
"input": "main",
|
||||||
|
"start": 9,
|
||||||
|
"end": 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [],
|
||||||
|
"returns": [],
|
||||||
|
"statements": [
|
||||||
|
{
|
||||||
|
"Return": {
|
||||||
|
"return_": {
|
||||||
|
"Single": {
|
||||||
|
"Binary": {
|
||||||
|
"operation": "Add",
|
||||||
|
"left": {
|
||||||
|
"Value": {
|
||||||
|
"Implicit": {
|
||||||
|
"number": {
|
||||||
|
"value": "1",
|
||||||
|
"span": {
|
||||||
|
"input": "1",
|
||||||
|
"start": 29,
|
||||||
|
"end": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"span": {
|
||||||
|
"input": "1",
|
||||||
|
"start": 29,
|
||||||
|
"end": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"right": {
|
||||||
|
"Value": {
|
||||||
|
"Implicit": {
|
||||||
|
"number": {
|
||||||
|
"value": "1",
|
||||||
|
"span": {
|
||||||
|
"input": "1",
|
||||||
|
"start": 33,
|
||||||
|
"end": 34
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"span": {
|
||||||
|
"input": "1",
|
||||||
|
"start": 33,
|
||||||
|
"end": 34
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"span": {
|
||||||
|
"input": "1 + 1",
|
||||||
|
"start": 29,
|
||||||
|
"end": 34
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"span": {
|
||||||
|
"input": "return 1 + 1",
|
||||||
|
"start": 22,
|
||||||
|
"end": 34
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"span": {
|
||||||
|
"input": "function main() {\n return 1 + 1\n}\n",
|
||||||
|
"start": 0,
|
||||||
|
"end": 37
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tests": [],
|
||||||
|
"eoi": null,
|
||||||
|
"span": {
|
||||||
|
"input": "function main() {\n return 1 + 1\n}\n",
|
||||||
|
"start": 0,
|
||||||
|
"end": 37
|
||||||
|
}
|
||||||
|
}
|
22
ast/tests/serialization/json.rs
Normal file
22
ast/tests/serialization/json.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use leo_ast::LeoAst;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialization() {
|
||||||
|
let mut program_filepath = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
program_filepath.push("tests/serialization/main.leo");
|
||||||
|
|
||||||
|
let expected = include_str!("./expected_ast.json");
|
||||||
|
|
||||||
|
// Loads the Leo code as a string from the given file path.
|
||||||
|
let program_string = LeoAst::load_file(&program_filepath).unwrap();
|
||||||
|
|
||||||
|
// Parses the Leo file and constructs an abstract syntax tree.
|
||||||
|
let ast = LeoAst::new(&program_filepath, &program_string).unwrap();
|
||||||
|
|
||||||
|
// Serializes the abstract syntax tree into JSON format.
|
||||||
|
let serialized_ast = LeoAst::to_json_string(&ast).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(expected, serialized_ast);
|
||||||
|
}
|
3
ast/tests/serialization/main.leo
Normal file
3
ast/tests/serialization/main.leo
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
function main() {
|
||||||
|
return 1 + 1
|
||||||
|
}
|
1
ast/tests/serialization/mod.rs
Normal file
1
ast/tests/serialization/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
mod json;
|
@ -1,11 +1,21 @@
|
|||||||
use crate::Span;
|
use crate::Span;
|
||||||
use leo_ast::common::Identifier as AstIdentifier;
|
use leo_ast::common::Identifier as AstIdentifier;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{
|
||||||
use std::fmt;
|
de::{self, Visitor},
|
||||||
|
Deserialize,
|
||||||
|
Deserializer,
|
||||||
|
Serialize,
|
||||||
|
Serializer,
|
||||||
|
};
|
||||||
|
use std::{collections::BTreeMap, fmt};
|
||||||
|
|
||||||
/// An identifier in the constrained program.
|
/// An identifier in the constrained program.
|
||||||
#[derive(Clone, Hash, Serialize, Deserialize)]
|
///
|
||||||
|
/// Attention - When adding or removing fields from this struct,
|
||||||
|
/// please remember to update it's Serialize and Deserialize implementation
|
||||||
|
/// to reflect the new struct instantiation.
|
||||||
|
#[derive(Clone, Hash)]
|
||||||
pub struct Identifier {
|
pub struct Identifier {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
@ -44,3 +54,61 @@ impl PartialEq for Identifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for Identifier {}
|
impl Eq for Identifier {}
|
||||||
|
|
||||||
|
impl Serialize for Identifier {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
// Converts an element that implements Serialize into a string.
|
||||||
|
fn to_json_string<E: Serialize, Error: serde::ser::Error>(element: &E) -> Result<String, Error> {
|
||||||
|
serde_json::to_string(&element).map_err(|e| Error::custom(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the struct elements into a BTreeMap (to preserve serialized ordering of keys).
|
||||||
|
let mut key: BTreeMap<String, String> = BTreeMap::new();
|
||||||
|
key.insert("name".to_string(), self.name.clone());
|
||||||
|
key.insert("span".to_string(), to_json_string(&self.span)?);
|
||||||
|
|
||||||
|
// Convert the serialized object into a string for use as a key.
|
||||||
|
serializer.serialize_str(&to_json_string(&key)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Identifier {
|
||||||
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
struct IdentifierVisitor;
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for IdentifierVisitor {
|
||||||
|
type Value = Identifier;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("a string encoding the typed Identifier struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementation for recovering a string that serializes Identifier.
|
||||||
|
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||||
|
// Converts a serialized string into an element that implements Deserialize.
|
||||||
|
fn to_json_string<'a, D: Deserialize<'a>, Error: serde::de::Error>(
|
||||||
|
serialized: &'a str,
|
||||||
|
) -> Result<D, Error> {
|
||||||
|
serde_json::from_str::<'a>(serialized).map_err(|e| Error::custom(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the serialized string into a BTreeMap to recover Identifier.
|
||||||
|
let key: BTreeMap<String, String> = to_json_string(value)?;
|
||||||
|
|
||||||
|
let name = match key.get("name") {
|
||||||
|
Some(name) => name.clone(),
|
||||||
|
None => return Err(E::custom("missing 'name' in serialized Identifier struct")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let span: Span = match key.get("span") {
|
||||||
|
Some(span) => to_json_string(span)?,
|
||||||
|
None => return Err(E::custom("missing 'span' in serialized Identifier struct")),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Identifier { name, span })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_str(IdentifierVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,7 +6,7 @@ use leo_ast::imports::Import as AstImport;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Import {
|
pub struct Import {
|
||||||
pub package: Package,
|
pub package: Package,
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
|
@ -4,7 +4,7 @@ use leo_ast::imports::ImportSymbol as AstImportSymbol;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct ImportSymbol {
|
pub struct ImportSymbol {
|
||||||
pub symbol: Identifier,
|
pub symbol: Identifier,
|
||||||
pub alias: Option<Identifier>,
|
pub alias: Option<Identifier>,
|
||||||
@ -31,7 +31,7 @@ impl fmt::Display for ImportSymbol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: remove this
|
// TODO (collin): remove this
|
||||||
impl fmt::Debug for ImportSymbol {
|
impl fmt::Debug for ImportSymbol {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
if self.alias.is_some() {
|
if self.alias.is_some() {
|
||||||
|
@ -4,7 +4,7 @@ use leo_ast::imports::Package as AstPackage;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Package {
|
pub struct Package {
|
||||||
pub name: Identifier,
|
pub name: Identifier,
|
||||||
pub access: PackageAccess,
|
pub access: PackageAccess,
|
||||||
|
@ -4,7 +4,7 @@ use leo_ast::imports::PackageAccess as AstPackageAccess;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum PackageAccess {
|
pub enum PackageAccess {
|
||||||
Star(Span),
|
Star(Span),
|
||||||
SubPackage(Box<Package>),
|
SubPackage(Box<Package>),
|
||||||
|
@ -38,6 +38,7 @@ use leo_ast::LeoAst;
|
|||||||
|
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub struct LeoTypedAst {
|
pub struct LeoTypedAst {
|
||||||
typed_ast: Program,
|
typed_ast: Program,
|
||||||
}
|
}
|
||||||
@ -55,8 +56,14 @@ impl LeoTypedAst {
|
|||||||
self.typed_ast
|
self.typed_ast
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serializes the abstract syntax tree into a JSON string.
|
/// Serializes the typed syntax tree into a JSON string.
|
||||||
pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
|
pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
|
||||||
Ok(serde_json::to_string_pretty(&self.typed_ast)?)
|
Ok(serde_json::to_string_pretty(&self.typed_ast)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deserializes the JSON string into a typed syntax tree.
|
||||||
|
pub fn from_json_string(json: &str) -> Result<Self, serde_json::Error> {
|
||||||
|
let typed_ast: Program = serde_json::from_str(json)?;
|
||||||
|
Ok(Self { typed_ast })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// A simple program with statement expressions, program arguments and program returns.
|
/// A simple program with statement expressions, program arguments and program returns.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Program {
|
pub struct Program {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub expected_inputs: Vec<FunctionInput>,
|
pub expected_inputs: Vec<FunctionInput>,
|
||||||
|
1
typed/tests/mod.rs
Normal file
1
typed/tests/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
mod serialization;
|
66
typed/tests/serialization/expected_typed_ast.json
Normal file
66
typed/tests/serialization/expected_typed_ast.json
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"name": "leo_typed_tree",
|
||||||
|
"expected_inputs": [],
|
||||||
|
"imports": [],
|
||||||
|
"circuits": {},
|
||||||
|
"functions": {
|
||||||
|
"{\"name\":\"main\",\"span\":\"{\\\"text\\\":\\\" function main() {\\\",\\\"line\\\":1,\\\"start\\\":10,\\\"end\\\":14}\"}": {
|
||||||
|
"function_name": "{\"name\":\"main\",\"span\":\"{\\\"text\\\":\\\" function main() {\\\",\\\"line\\\":1,\\\"start\\\":10,\\\"end\\\":14}\"}",
|
||||||
|
"inputs": [],
|
||||||
|
"returns": [],
|
||||||
|
"statements": [
|
||||||
|
{
|
||||||
|
"Return": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Add": [
|
||||||
|
{
|
||||||
|
"Implicit": [
|
||||||
|
"1",
|
||||||
|
{
|
||||||
|
"text": " return 1 + 1",
|
||||||
|
"line": 2,
|
||||||
|
"start": 12,
|
||||||
|
"end": 13
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Implicit": [
|
||||||
|
"1",
|
||||||
|
{
|
||||||
|
"text": " return 1 + 1",
|
||||||
|
"line": 2,
|
||||||
|
"start": 16,
|
||||||
|
"end": 17
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": " return 1 + 1",
|
||||||
|
"line": 2,
|
||||||
|
"start": 12,
|
||||||
|
"end": 17
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{
|
||||||
|
"text": " return 1 + 1",
|
||||||
|
"line": 2,
|
||||||
|
"start": 5,
|
||||||
|
"end": 17
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"span": {
|
||||||
|
"text": " function main() {",
|
||||||
|
"line": 1,
|
||||||
|
"start": 1,
|
||||||
|
"end": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tests": {}
|
||||||
|
}
|
79
typed/tests/serialization/json.rs
Normal file
79
typed/tests/serialization/json.rs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
use leo_ast::LeoAst;
|
||||||
|
use leo_typed::LeoTypedAst;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn to_typed_ast(program_filepath: &PathBuf) -> LeoTypedAst {
|
||||||
|
// Loads the Leo code as a string from the given file path.
|
||||||
|
let program_string = LeoAst::load_file(program_filepath).unwrap();
|
||||||
|
|
||||||
|
// Parses the Leo file and constructs an abstract syntax tree.
|
||||||
|
let ast = LeoAst::new(&program_filepath, &program_string).unwrap();
|
||||||
|
|
||||||
|
// Parse the abstract syntax tree and constructs a typed syntax tree.
|
||||||
|
let typed_ast = LeoTypedAst::new("leo_typed_tree", &ast);
|
||||||
|
|
||||||
|
typed_ast
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialize() {
|
||||||
|
// Construct a typed syntax tree from the given test file.
|
||||||
|
let typed_ast = {
|
||||||
|
let mut program_filepath = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
program_filepath.push("tests/serialization/main.leo");
|
||||||
|
|
||||||
|
to_typed_ast(&program_filepath)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Serializes the typed syntax tree into JSON format.
|
||||||
|
let serialized_typed_ast = typed_ast.to_json_string().unwrap();
|
||||||
|
|
||||||
|
// Load the expected typed syntax tree.
|
||||||
|
let expected = include_str!("expected_typed_ast.json");
|
||||||
|
|
||||||
|
println!("{}", serialized_typed_ast);
|
||||||
|
assert_eq!(expected, serialized_typed_ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize() {
|
||||||
|
// Load the expected typed syntax tree.
|
||||||
|
let expected_typed_ast = {
|
||||||
|
let mut program_filepath = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
program_filepath.push("tests/serialization/main.leo");
|
||||||
|
|
||||||
|
to_typed_ast(&program_filepath)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Construct a typed syntax tree by deserializing a typed syntax tree JSON file.
|
||||||
|
let serialized_typed_ast = include_str!("expected_typed_ast.json");
|
||||||
|
let typed_ast = LeoTypedAst::from_json_string(serialized_typed_ast).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(expected_typed_ast, typed_ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialize_deserialize_serialize() {
|
||||||
|
// Construct a typed syntax tree from the given test file.
|
||||||
|
let typed_ast = {
|
||||||
|
let mut program_filepath = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
program_filepath.push("tests/serialization/main.leo");
|
||||||
|
|
||||||
|
to_typed_ast(&program_filepath)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Serializes the typed syntax tree into JSON format.
|
||||||
|
let serialized_typed_ast = typed_ast.to_json_string().unwrap();
|
||||||
|
|
||||||
|
// Deserializes the typed syntax tree into a LeoTypedAst.
|
||||||
|
let typed_ast = LeoTypedAst::from_json_string(&serialized_typed_ast).unwrap();
|
||||||
|
|
||||||
|
// Reserializes the typed syntax tree into JSON format.
|
||||||
|
let reserialized_typed_ast = typed_ast.to_json_string().unwrap();
|
||||||
|
|
||||||
|
// Load the expected typed syntax tree.
|
||||||
|
let expected = include_str!("expected_typed_ast.json");
|
||||||
|
|
||||||
|
assert_eq!(serialized_typed_ast, reserialized_typed_ast);
|
||||||
|
}
|
3
typed/tests/serialization/main.leo
Normal file
3
typed/tests/serialization/main.leo
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
function main() {
|
||||||
|
return 1 + 1
|
||||||
|
}
|
1
typed/tests/serialization/mod.rs
Normal file
1
typed/tests/serialization/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
mod json;
|
Loading…
Reference in New Issue
Block a user