sizzle_parser/
pipeline.rs

1//! High-level logic for full-pipeline parsing.
2
3use std::{collections::HashMap, path::PathBuf};
4use thiserror::Error;
5
6use crate::{
7    SszSchema,
8    ast::{self, ModuleManager, ParseError},
9    schema::{self, SchemaError},
10    token::{self, TokenError},
11    token_tree::{self, ToktrError},
12    ty_resolver::{CrossModuleTypeMap, ModuleTypeMap, ResolverError},
13};
14
15/// Represents an error from any of the phases of parsing a raw schema.
16#[derive(Debug, Error)]
17pub enum SszError {
18    /// Error from the tokenizer.
19    #[error("tokenizer: {0}")]
20    Token(#[from] TokenError),
21
22    /// Error from the token tree parser.
23    #[error("treeizer: {0}")]
24    TokenTree(#[from] ToktrError),
25
26    /// Error from the AST parser.
27    #[error("parser: {0}")]
28    Parser(#[from] ParseError),
29
30    /// Error from the type resolver.
31    #[error("type resolution: {0}")]
32    TyResolver(#[from] ResolverError),
33
34    /// Error from the schema generator.
35    #[error("schema generation: {0}")]
36    SchemaGen(#[from] SchemaError),
37}
38
39/// High-level parse function.
40pub fn parse_str_schema(
41    files: &HashMap<PathBuf, String>,
42    external_modules: &[&str],
43) -> Result<(Vec<PathBuf>, HashMap<PathBuf, SszSchema>), SszError> {
44    let mut module_manager = ModuleManager::new(external_modules);
45
46    for (path, content) in files {
47        let chars = content.chars().collect::<Vec<_>>();
48        let tokens = token::parse_char_array_to_tokens(&chars)?;
49        let toktrs = token_tree::parse_tokens_to_toktrs(&tokens)?;
50
51        // TODO: Inserts at positon 0 in Vec, it's ok for now since we don't expect too many imports
52        // but if it becomes a bottleneck we can fix it.
53        module_manager.add_module_to_front(path.clone());
54        ast::parse_module_from_toktrs(&toktrs, path, &mut module_manager)?;
55    }
56
57    let mut schema_map = HashMap::new();
58    let mut cross_module_types = CrossModuleTypeMap::new();
59    let mut parsing_order = Vec::new();
60    while let Some((path, module)) = module_manager.pop_module() {
61        if module.is_external() {
62            cross_module_types.insert(path.clone(), ModuleTypeMap::External);
63            continue;
64        }
65        let (schema, idents) = schema::conv_module_to_schema(&module, &cross_module_types)?;
66        parsing_order.push(path.clone());
67        cross_module_types.insert(path.clone(), ModuleTypeMap::Internal(idents));
68        schema_map.insert(path, schema);
69    }
70
71    Ok((parsing_order, schema_map))
72}
73
74#[cfg(test)]
75mod tests {
76    use std::{collections::HashMap, path::Path};
77
78    use crate::pipeline::parse_str_schema;
79
80    /*fn make_ident(s: &str) -> Identifier {
81        Identifier::try_from(s.to_owned()).expect("test: make ident")
82    }*/
83
84    #[test]
85    fn test_pipeline_simple() {
86        const SCHEMA: &str = r"
87class Point2d(Container):
88  x_coord: uint32
89  y_coord: uint32
90";
91
92        let files = HashMap::from([(Path::new("").to_path_buf(), SCHEMA.to_string())]);
93        let schema = parse_str_schema(&files, &[]).expect("test: parse schema");
94
95        eprintln!("{schema:#?}");
96    }
97
98    #[test]
99    fn test_pipeline_beacon_deposit_request() {
100        // This is kinda bodging it, I just wanted to take a "real example".
101        const SCHEMA: &str = r"
102BLSPubkey = List[byte, 96]
103BLSSignature = List[byte, 96]
104Gwei = uint256
105
106class DepositRequest(Container):
107    pubkey: BLSPubkey
108    withdrawal_credentials: Bytes32
109    amount: Gwei
110    signature: BLSSignature
111    index: uint64
112";
113
114        let files = HashMap::from([(Path::new("").to_path_buf(), SCHEMA.to_string())]);
115        let schema = parse_str_schema(&files, &[]).expect("test: parse schema");
116
117        eprintln!("{schema:#?}");
118    }
119
120    #[test]
121    fn test_pipeline_aliases() {
122        const SCHEMA: &str = r"
123OMG = 3
124Epoch = uint32
125SomeVec = List[Epoch, 1337]
126
127class Header(Container):
128    slot: uint64
129    epoch: Epoch
130";
131
132        let files = HashMap::from([(Path::new("").to_path_buf(), SCHEMA.to_string())]);
133        let schema = parse_str_schema(&files, &[]).expect("test: parse schema");
134
135        eprintln!("{schema:#?}");
136    }
137
138    #[test]
139    fn test_pipeline_parent_aliases() {
140        // I don't even know if we want to support this, but hey we do now!
141        const SCHEMA: &str = r"
142MagicStable = StableContainer[32]
143
144class MagicFoo(MagicStable):
145    foo: Optional[uint32]
146    bar: Optional[uint64]
147";
148
149        let files = HashMap::from([(Path::new("").to_path_buf(), SCHEMA.to_string())]);
150        let schema = parse_str_schema(&files, &[]).expect("test: parse schema");
151
152        eprintln!("{schema:#?}");
153    }
154
155    #[test]
156    fn test_pipeline_imports() {
157        const SCHEMA_AS: &str = r"
158import import_test as test
159import ssz_external as external
160
161TestA = test.A
162TestB = test.B
163TestC = test.C
164TestD = external.D
165
166VAL_A = 12
167VAL_B = VAL_A
168TEST_CONST = test.D
169
170class Header(test.A):
171    a: Union[null, test.B]
172    b: test.B
173    c: test.C
174    d: uint8
175
176f = List[test.A, TEST_CONST]
177";
178
179        const SCHEMA: &str = r"
180import import_test
181import ssz_external.module_a
182
183TestA = import_test.A
184TestB = import_test.B
185TestC = import_test.C
186TestD = module_a.D
187
188VAL_A = 12
189VAL_B = VAL_A
190TEST_CONST = import_test.D
191
192class Header(import_test.A):
193    a: Union[null, import_test.B]
194    b: import_test.B
195    c: import_test.C
196    d: uint8
197
198f = List[import_test.A, TEST_CONST]
199";
200
201        let files = HashMap::from([(
202            Path::new("tests/non_existent").to_path_buf(),
203            SCHEMA_AS.to_string(),
204        )]);
205        let schema = parse_str_schema(&files, &["ssz_external"]).expect("test: parse schema");
206
207        eprintln!("{schema:#?}");
208
209        let files = HashMap::from([(
210            Path::new("tests/non_existent").to_path_buf(),
211            SCHEMA.to_string(),
212        )]);
213        let schema = parse_str_schema(&files, &["ssz_external"]).expect("test: parse schema");
214
215        eprintln!("{schema:#?}");
216    }
217}