feat: add minimal ast bindings (#1167)
* feat: add minimal ast bindings * feat: add recursive nodes
This commit is contained in:
parent
6fcde371a3
commit
8dd72723d1
|
@ -8,7 +8,7 @@ use crate::{
|
|||
BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection,
|
||||
EwasmOutputSelection,
|
||||
},
|
||||
CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData, GasEstimates,
|
||||
Ast, CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData, GasEstimates,
|
||||
LosslessAbi, Metadata, Offsets, Settings, StorageLayout, UserDoc,
|
||||
},
|
||||
ArtifactOutput, SolcConfig, SolcError, SourceFile,
|
||||
|
@ -52,8 +52,8 @@ pub struct ConfigurableContractArtifact {
|
|||
pub ir_optimized: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub ewasm: Option<Ewasm>,
|
||||
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
||||
pub ast: serde_json::Value,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub ast: Option<Ast>,
|
||||
}
|
||||
|
||||
impl ConfigurableContractArtifact {
|
||||
|
@ -303,7 +303,7 @@ impl ArtifactOutput for ConfigurableArtifacts {
|
|||
ir: artifact_ir,
|
||||
ir_optimized: artifact_ir_optimized,
|
||||
ewasm: artifact_ewasm,
|
||||
ast: source_file.map(|s| s.ast.clone()).unwrap_or_default(),
|
||||
ast: source_file.and_then(|s| s.ast.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
//! Bindings for solc's `ast` output field
|
||||
|
||||
use crate::artifacts::serde_helpers;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::BTreeMap, fmt, fmt::Write, str::FromStr};
|
||||
|
||||
/// Represents the AST field in the solc output
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Ast {
|
||||
#[serde(rename = "absolutePath")]
|
||||
pub absolute_path: String,
|
||||
pub id: usize,
|
||||
#[serde(default, rename = "exportedSymbols")]
|
||||
pub exported_symbols: BTreeMap<String, Vec<usize>>,
|
||||
#[serde(rename = "nodeType")]
|
||||
pub node_type: NodeType,
|
||||
#[serde(with = "serde_helpers::display_from_str")]
|
||||
pub src: SourceLocation,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub nodes: Vec<Node>,
|
||||
#[serde(flatten)]
|
||||
pub other: BTreeMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Node {
|
||||
pub id: usize,
|
||||
#[serde(rename = "nodeType")]
|
||||
pub node_type: NodeType,
|
||||
#[serde(with = "serde_helpers::display_from_str")]
|
||||
pub src: SourceLocation,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub nodes: Vec<Node>,
|
||||
#[serde(flatten)]
|
||||
pub other: BTreeMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
/// Represents the source location of a node : `<start>:<length>:<index>`
|
||||
///
|
||||
/// The `length` and `index` can be -1 which is represented as `None`
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SourceLocation {
|
||||
pub start: usize,
|
||||
pub length: Option<usize>,
|
||||
pub index: Option<usize>,
|
||||
}
|
||||
|
||||
impl FromStr for SourceLocation {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let invalid_location = move || format!("{} invalid source location", s);
|
||||
|
||||
let mut split = s.split(':');
|
||||
let start = split
|
||||
.next()
|
||||
.ok_or_else(invalid_location)?
|
||||
.parse::<usize>()
|
||||
.map_err(|_| invalid_location())?;
|
||||
let length = split
|
||||
.next()
|
||||
.ok_or_else(invalid_location)?
|
||||
.parse::<isize>()
|
||||
.map_err(|_| invalid_location())?;
|
||||
let index = split
|
||||
.next()
|
||||
.ok_or_else(invalid_location)?
|
||||
.parse::<isize>()
|
||||
.map_err(|_| invalid_location())?;
|
||||
|
||||
let length = if length < 0 { None } else { Some(length as usize) };
|
||||
let index = if index < 0 { None } else { Some(index as usize) };
|
||||
|
||||
Ok(Self { start, length, index })
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SourceLocation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.start.fmt(f)?;
|
||||
f.write_char(':')?;
|
||||
if let Some(length) = self.length {
|
||||
length.fmt(f)?;
|
||||
} else {
|
||||
f.write_str("-1")?;
|
||||
}
|
||||
f.write_char(':')?;
|
||||
if let Some(index) = self.index {
|
||||
index.fmt(f)?;
|
||||
} else {
|
||||
f.write_str("-1")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum NodeType {
|
||||
YulAssignment,
|
||||
YulBlock,
|
||||
YulExpressionStatement,
|
||||
YulForLoop,
|
||||
YulIf,
|
||||
YulVariableDeclaration,
|
||||
YulFunctionDefinition,
|
||||
SourceUnit,
|
||||
PragmaDirective,
|
||||
ContractDefinition,
|
||||
EventDefinition,
|
||||
ErrorDefinition,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn can_parse_ast() {
|
||||
let ast = r#"
|
||||
{
|
||||
"absolutePath": "input.sol",
|
||||
"exportedSymbols":
|
||||
{
|
||||
"Ballot":
|
||||
[
|
||||
2
|
||||
],
|
||||
"Ballot2":
|
||||
[
|
||||
3
|
||||
],
|
||||
"Ballot3":
|
||||
[
|
||||
4
|
||||
]
|
||||
},
|
||||
"id": 5,
|
||||
"nodeType": "SourceUnit",
|
||||
"nodes":
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"literals":
|
||||
[
|
||||
"solidity",
|
||||
">=",
|
||||
"0.4",
|
||||
".0"
|
||||
],
|
||||
"nodeType": "PragmaDirective",
|
||||
"src": "1:24:0"
|
||||
},
|
||||
{
|
||||
"abstract": false,
|
||||
"baseContracts": [],
|
||||
"canonicalName": "Ballot",
|
||||
"contractDependencies": [],
|
||||
"contractKind": "contract",
|
||||
"fullyImplemented": true,
|
||||
"id": 2,
|
||||
"linearizedBaseContracts":
|
||||
[
|
||||
2
|
||||
],
|
||||
"name": "Ballot",
|
||||
"nameLocation": "36:6:0",
|
||||
"nodeType": "ContractDefinition",
|
||||
"nodes": [],
|
||||
"scope": 5,
|
||||
"src": "27:20:0",
|
||||
"usedErrors": []
|
||||
},
|
||||
{
|
||||
"abstract": false,
|
||||
"baseContracts": [],
|
||||
"canonicalName": "Ballot2",
|
||||
"contractDependencies": [],
|
||||
"contractKind": "contract",
|
||||
"fullyImplemented": true,
|
||||
"id": 3,
|
||||
"linearizedBaseContracts":
|
||||
[
|
||||
3
|
||||
],
|
||||
"name": "Ballot2",
|
||||
"nameLocation": "58:7:0",
|
||||
"nodeType": "ContractDefinition",
|
||||
"nodes": [],
|
||||
"scope": 5,
|
||||
"src": "49:21:0",
|
||||
"usedErrors": []
|
||||
},
|
||||
{
|
||||
"abstract": false,
|
||||
"baseContracts": [],
|
||||
"canonicalName": "Ballot3",
|
||||
"contractDependencies": [],
|
||||
"contractKind": "contract",
|
||||
"fullyImplemented": true,
|
||||
"id": 4,
|
||||
"linearizedBaseContracts":
|
||||
[
|
||||
4
|
||||
],
|
||||
"name": "Ballot3",
|
||||
"nameLocation": "81:7:0",
|
||||
"nodeType": "ContractDefinition",
|
||||
"nodes": [],
|
||||
"scope": 5,
|
||||
"src": "72:21:0",
|
||||
"usedErrors": []
|
||||
}
|
||||
],
|
||||
"src": "1:92:0"
|
||||
}
|
||||
"#;
|
||||
let _ast: Ast = serde_json::from_str(ast).unwrap();
|
||||
|
||||
dbg!(serde_json::from_str::<serde_json::Value>("{}").unwrap());
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@ use crate::{compile::*, error::SolcIoError, remappings::Remapping, utils};
|
|||
|
||||
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
pub mod ast;
|
||||
pub use ast::*;
|
||||
pub mod bytecode;
|
||||
pub mod contract;
|
||||
pub mod output_selection;
|
||||
|
@ -1402,8 +1404,8 @@ pub struct SecondarySourceLocation {
|
|||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct SourceFile {
|
||||
pub id: u32,
|
||||
#[serde(default)]
|
||||
pub ast: serde_json::Value,
|
||||
#[serde(default, with = "serde_helpers::empty_json_object_opt")]
|
||||
pub ast: Option<Ast>,
|
||||
}
|
||||
|
||||
/// A wrapper type for a list of source files
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
use ethers_core::types::Bytes;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
pub fn deserialize_bytes<'de, D>(d: D) -> std::result::Result<Bytes, D::Error>
|
||||
pub fn deserialize_bytes<'de, D>(d: D) -> Result<Bytes, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
String::deserialize(d)?.parse::<Bytes>().map_err(|e| serde::de::Error::custom(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn deserialize_opt_bytes<'de, D>(d: D) -> std::result::Result<Option<Bytes>, D::Error>
|
||||
pub fn deserialize_opt_bytes<'de, D>(d: D) -> Result<Option<Bytes>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
|
@ -70,6 +70,43 @@ pub mod json_string_opt {
|
|||
}
|
||||
}
|
||||
|
||||
/// deserializes empty json object `{}` as `None`
|
||||
pub mod empty_json_object_opt {
|
||||
use serde::{
|
||||
de::{self, DeserializeOwned},
|
||||
ser, Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
|
||||
pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
T: Serialize,
|
||||
{
|
||||
if let Some(value) = value {
|
||||
let value = serde_json::to_string(value).map_err(ser::Error::custom)?;
|
||||
serializer.serialize_str(&value)
|
||||
} else {
|
||||
let empty = serde_json::Value::Object(Default::default());
|
||||
serde_json::Value::serialize(&empty, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let json = serde_json::Value::deserialize(deserializer)?;
|
||||
if json.is_null() {
|
||||
return Ok(None)
|
||||
}
|
||||
if json.as_object().map(|obj| obj.is_empty()).unwrap_or_default() {
|
||||
return Ok(None)
|
||||
}
|
||||
serde_json::from_value(json).map_err(de::Error::custom).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
/// serde support for string
|
||||
pub mod string_bytes {
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
@ -128,6 +165,28 @@ pub mod display_from_str_opt {
|
|||
}
|
||||
}
|
||||
|
||||
pub mod display_from_str {
|
||||
use serde::{de, Deserialize, Deserializer, Serializer};
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
T: fmt::Display,
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_str(value)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: FromStr,
|
||||
T::Err: fmt::Display,
|
||||
{
|
||||
String::deserialize(deserializer)?.parse().map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
/// (De)serialize vec of tuples as map
|
||||
pub mod tuple_vec_map {
|
||||
use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
|
|
@ -692,9 +692,9 @@ mod tests {
|
|||
assert_eq!(state.output.sources.len(), 3);
|
||||
for (f, source) in &state.output.sources {
|
||||
if f.ends_with("A.sol") {
|
||||
assert!(source.ast.is_object());
|
||||
assert!(source.ast.is_some());
|
||||
} else {
|
||||
assert!(source.ast.is_null());
|
||||
assert!(source.ast.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue