circom_lsp_program_structure/abstract_syntax_tree/
ast.rs

1use crate::{file_definition::{FileLocation, FileID}, error_definition::Report, error_code::{ReportCode}};
2use num_bigint::BigInt;
3use serde_derive::{Deserialize, Serialize};
4
5#[derive(Clone)]
6pub enum Pragma {
7    Version(Meta, FileID, Version),
8    CustomGates(Meta ,FileID),
9    Unrecognized,
10}
11
12pub trait FillMeta {
13    fn fill(&mut self, file_id: usize, elem_id: &mut usize);
14}
15
16pub type MainComponent = (Vec<String>, Expression);
17pub fn build_main_component(public: Vec<String>, call: Expression) -> MainComponent {
18    (public, call)
19}
20
21pub type Version = (usize, usize, usize);
22
23#[derive(Clone)]
24pub struct Meta {
25    pub elem_id: usize,
26    pub start: usize,
27    pub end: usize,
28    pub location: FileLocation,
29    pub file_id: Option<usize>,
30    pub component_inference: Option<String>,
31    type_knowledge: TypeKnowledge,
32    memory_knowledge: MemoryKnowledge,
33}
34impl Meta {
35    pub fn new(start: usize, end: usize) -> Meta {
36        Meta {
37            end,
38            start,
39            elem_id: 0,
40            location: start..end,
41            file_id: Option::None,
42            component_inference: None,
43            type_knowledge: TypeKnowledge::default(),
44            memory_knowledge: MemoryKnowledge::default(),
45        }
46    }
47    pub fn change_location(&mut self, location: FileLocation, file_id: Option<usize>) {
48        self.location = location;
49        self.file_id = file_id;
50    }
51    pub fn get_start(&self) -> usize {
52        self.location.start
53    }
54    pub fn get_end(&self) -> usize {
55        self.location.end
56    }
57    pub fn get_file_id(&self) -> usize {
58        if let Option::Some(id) = self.file_id {
59            id
60        } else {
61            panic!("Empty file id accessed")
62        }
63    }
64    pub fn get_memory_knowledge(&self) -> &MemoryKnowledge {
65        &self.memory_knowledge
66    }
67    pub fn get_type_knowledge(&self) -> &TypeKnowledge {
68        &self.type_knowledge
69    }
70    pub fn get_mut_memory_knowledge(&mut self) -> &mut MemoryKnowledge {
71        &mut self.memory_knowledge
72    }
73    pub fn get_mut_type_knowledge(&mut self) -> &mut TypeKnowledge {
74        &mut self.type_knowledge
75    }
76    pub fn file_location(&self) -> FileLocation {
77        self.location.clone()
78    }
79    pub fn set_file_id(&mut self, file_id: usize) {
80        self.file_id = Option::Some(file_id);
81    }
82}
83
84#[derive(Clone)]
85pub struct AST {
86    pub meta: Meta,
87    pub compiler_version: Option<Version>,
88    pub custom_gates: bool,
89    pub custom_gates_declared: bool,
90    pub includes: Vec<String>,
91    pub definitions: Vec<Definition>,
92    pub main_component: Option<MainComponent>,
93}
94
95impl AST {
96    pub fn new(
97        meta: Meta,
98        pragmas: Vec<Pragma>,
99        includes: Vec<String>,
100        definitions: Vec<Definition>,
101        main_component: Option<MainComponent>,
102    ) -> (AST,Vec<Report>) {
103        let mut custom_gates = None;
104        let mut compiler_version = None;
105        let mut reports = Vec::new();
106        for p in pragmas {
107            match p {
108                // TODO: don't panic
109                Pragma::Version(location, file_id, ver) => match compiler_version {
110                    Some(_) => reports.push(produce_report(
111                            ReportCode::MultiplePragma,location.start..location.end, file_id)),
112                    None => compiler_version = Some(ver),
113                },
114                Pragma::CustomGates(location, file_id ) => match custom_gates {
115                    Some(_) => reports.push(produce_report(
116                        ReportCode::MultiplePragma, location.start..location.end, file_id)),
117                    None => custom_gates = Some(true),
118                },
119                Pragma::Unrecognized => {}, //This error is previously handled, and the
120                                            //parsing continues to catch more parsing errors.
121            }
122        }
123
124        let custom_gates_declared = definitions.iter().any(|definition| {
125            matches!(definition, Definition::Template { is_custom_gate: true, .. })
126        });
127
128        (AST {
129            meta,
130            compiler_version,
131            custom_gates: custom_gates.unwrap_or(false),
132            custom_gates_declared,
133            includes,
134            definitions,
135            main_component,
136        }, reports)
137    }
138}
139
140#[derive(Clone)]
141pub enum Definition {
142    Template {
143        meta: Meta,
144        name: String,
145        args: Vec<String>,
146        arg_location: FileLocation,
147        body: Statement,
148        parallel: bool,
149        is_custom_gate: bool,
150    },
151    Function {
152        meta: Meta,
153        name: String,
154        args: Vec<String>,
155        arg_location: FileLocation,
156        body: Statement,
157    },
158}
159pub fn build_template(
160    meta: Meta,
161    name: String,
162    args: Vec<String>,
163    arg_location: FileLocation,
164    body: Statement,
165    parallel: bool,
166    is_custom_gate: bool,
167) -> Definition {
168    Definition::Template { meta, name, args, arg_location, body, parallel, is_custom_gate }
169}
170
171pub fn build_function(
172    meta: Meta,
173    name: String,
174    args: Vec<String>,
175    arg_location: FileLocation,
176    body: Statement,
177) -> Definition {
178    Definition::Function { meta, name, args, arg_location, body }
179}
180
181#[derive(Clone)]
182pub enum Statement {
183    IfThenElse {
184        meta: Meta,
185        cond: Expression,
186        if_case: Box<Statement>,
187        else_case: Option<Box<Statement>>,
188    },
189    While {
190        meta: Meta,
191        cond: Expression,
192        stmt: Box<Statement>,
193    },
194    Return {
195        meta: Meta,
196        value: Expression,
197    },
198    InitializationBlock {
199        meta: Meta,
200        xtype: VariableType,
201        initializations: Vec<Statement>,
202    },
203    Declaration {
204        meta: Meta,
205        xtype: VariableType,
206        name: String,
207        dimensions: Vec<Expression>,
208        is_constant: bool,
209    },
210    Substitution {
211        meta: Meta,
212        var: String,
213        access: Vec<Access>,
214        op: AssignOp,
215        rhe: Expression,
216    },
217    MultSubstitution {
218        meta: Meta,
219        lhe: Expression,
220        op: AssignOp,
221        rhe: Expression,
222    },
223    UnderscoreSubstitution{
224        meta: Meta,
225        op: AssignOp,
226        rhe: Expression,
227    },
228    ConstraintEquality {
229        meta: Meta,
230        lhe: Expression,
231        rhe: Expression,
232    },
233    LogCall {
234        meta: Meta,
235        args: Vec<LogArgument>,
236    },
237    Block {
238        meta: Meta,
239        stmts: Vec<Statement>,
240    },
241    Assert {
242        meta: Meta,
243        arg: Expression,
244    },
245}
246
247#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
248pub enum SignalType {
249    Output,
250    Input,
251    Intermediate,
252}
253
254pub type TagList = Vec<String>;
255
256#[derive(Clone, PartialEq, Ord, PartialOrd, Eq)]
257pub enum VariableType {
258    Var,
259    Signal(SignalType, TagList),
260    Component,
261    AnonymousComponent,
262}
263
264#[derive(Clone)]
265pub enum Expression {
266    InfixOp {
267        meta: Meta,
268        lhe: Box<Expression>,
269        infix_op: ExpressionInfixOpcode,
270        rhe: Box<Expression>,
271    },
272    PrefixOp {
273        meta: Meta,
274        prefix_op: ExpressionPrefixOpcode,
275        rhe: Box<Expression>,
276    },
277    InlineSwitchOp {
278        meta: Meta,
279        cond: Box<Expression>,
280        if_true: Box<Expression>,
281        if_false: Box<Expression>,
282    },
283    ParallelOp {
284        meta: Meta,
285        rhe: Box<Expression>,
286    },
287    Variable {
288        meta: Meta,
289        name: String,
290        access: Vec<Access>,
291    },
292    Number(Meta, BigInt),
293    Call {
294        meta: Meta,
295        id: String,
296        args: Vec<Expression>,
297    },
298    AnonymousComp{
299        meta: Meta,
300        id: String,
301        is_parallel: bool,
302        params: Vec<Expression>,
303        signals: Vec<Expression>,
304        names: Option<Vec<(AssignOp,String)>>,
305    },
306    ArrayInLine {
307        meta: Meta,
308        values: Vec<Expression>,
309    },
310    Tuple {
311        meta: Meta,
312        values: Vec<Expression>,
313    },
314    UniformArray {
315        meta: Meta,
316        value: Box<Expression>,
317        dimension: Box<Expression>,
318    },
319}
320
321#[derive(Clone)]
322pub enum Access {
323    ComponentAccess(String),
324    ArrayAccess(Expression),
325}
326pub fn build_component_access(acc: String) -> Access {
327    Access::ComponentAccess(acc)
328}
329pub fn build_array_access(expr: Expression) -> Access {
330    Access::ArrayAccess(expr)
331}
332
333#[derive(Copy, Clone, Eq, PartialEq)]
334pub enum AssignOp {
335    AssignVar,
336    AssignSignal,
337    AssignConstraintSignal,
338}
339
340#[derive(Copy, Clone, PartialEq)]
341pub enum ExpressionInfixOpcode {
342    Mul,
343    Div,
344    Add,
345    Sub,
346    Pow,
347    IntDiv,
348    Mod,
349    ShiftL,
350    ShiftR,
351    LesserEq,
352    GreaterEq,
353    Lesser,
354    Greater,
355    Eq,
356    NotEq,
357    BoolOr,
358    BoolAnd,
359    BitOr,
360    BitAnd,
361    BitXor,
362}
363
364#[derive(Copy, Clone, PartialEq)]
365pub enum ExpressionPrefixOpcode {
366    Sub,
367    BoolNot,
368    Complement,
369}
370
371// Knowledge buckets
372
373#[derive(Copy, Clone, PartialOrd, PartialEq, Ord, Eq)]
374pub enum TypeReduction {
375    Variable,
376    Component,
377    Signal,
378    Tag,
379}
380
381#[derive(Clone)]
382pub enum LogArgument {
383    LogStr(String),
384    LogExp(Expression),
385}
386pub fn build_log_string(acc: String) -> LogArgument {
387    LogArgument::LogStr(acc)
388}
389pub fn build_log_expression(expr: Expression) -> LogArgument {
390    LogArgument::LogExp(expr)
391}
392
393
394#[derive(Default, Clone)]
395pub struct TypeKnowledge {
396    reduces_to: Option<TypeReduction>,
397}
398impl TypeKnowledge {
399    pub fn new() -> TypeKnowledge {
400        TypeKnowledge::default()
401    }
402    pub fn set_reduces_to(&mut self, reduces_to: TypeReduction) {
403        self.reduces_to = Option::Some(reduces_to);
404    }
405    pub fn get_reduces_to(&self) -> TypeReduction {
406        if let Option::Some(t) = &self.reduces_to {
407            *t
408        } else {
409            panic!("reduces_to knowledge is been look at without being initialized");
410        }
411    }
412    pub fn is_var(&self) -> bool {
413        self.get_reduces_to() == TypeReduction::Variable
414    }
415    pub fn is_component(&self) -> bool {
416        self.get_reduces_to() == TypeReduction::Component
417    }
418    pub fn is_signal(&self) -> bool {
419        self.get_reduces_to() == TypeReduction::Signal
420    }
421    pub fn is_tag(&self) -> bool {
422        self.get_reduces_to() == TypeReduction::Tag
423    }
424}
425
426#[derive(Default, Clone)]
427pub struct MemoryKnowledge {
428    concrete_dimensions: Option<Vec<usize>>,
429    full_length: Option<usize>,
430    abstract_memory_address: Option<usize>,
431}
432impl MemoryKnowledge {
433    pub fn new() -> MemoryKnowledge {
434        MemoryKnowledge::default()
435    }
436    pub fn set_concrete_dimensions(&mut self, value: Vec<usize>) {
437        self.full_length = Option::Some(value.iter().fold(1, |p, v| p * (*v)));
438        self.concrete_dimensions = Option::Some(value);
439    }
440    pub fn set_abstract_memory_address(&mut self, value: usize) {
441        self.abstract_memory_address = Option::Some(value);
442    }
443    pub fn get_concrete_dimensions(&self) -> &[usize] {
444        if let Option::Some(v) = &self.concrete_dimensions {
445            v
446        } else {
447            panic!("concrete dimensions was look at without being initialized");
448        }
449    }
450    pub fn get_full_length(&self) -> usize {
451        if let Option::Some(v) = &self.full_length {
452            *v
453        } else {
454            panic!("full dimension was look at without being initialized");
455        }
456    }
457    pub fn get_abstract_memory_address(&self) -> usize {
458        if let Option::Some(v) = &self.abstract_memory_address {
459            *v
460        } else {
461            panic!("abstract memory address was look at without being initialized");
462        }
463    }
464}
465
466 pub fn produce_report(error_code: ReportCode, location : FileLocation, file_id : FileID) -> Report {
467    use ReportCode::*;
468    let report  = match error_code {
469            UnclosedComment => {
470                let mut report =
471                    Report::error("unterminated /* */".to_string(), ReportCode::UnclosedComment);
472                report.add_primary(location, file_id, "Comment starts here".to_string());
473                report
474            }
475            NoMainFoundInProject => Report::error(
476                "No main specified in the project structure".to_string(),
477                ReportCode::NoMainFoundInProject,
478            ),
479            MultipleMain =>{
480                Report::error(
481                    "Multiple main components in the project structure".to_string(),
482                    ReportCode::MultipleMain,
483                )
484            }
485            MissingSemicolon => {
486                let mut report = Report::error(format!("Missing semicolon"), 
487                    ReportCode::MissingSemicolon);
488                report.add_primary(location, file_id, "A semicolon is needed here".to_string());
489                report
490            }
491            UnrecognizedInclude => {
492                let mut report =
493                Report::error("unrecognized argument in include directive".to_string(), ReportCode::UnrecognizedInclude);
494            report.add_primary(location, file_id, "this argument".to_string());
495            report
496
497            }
498            UnrecognizedPragma => {
499                let mut report =
500                Report::error("unrecognized argument in pragma directive".to_string(), ReportCode::UnrecognizedPragma);
501            report.add_primary(location, file_id, "this argument".to_string());
502            report
503
504            }        
505            UnrecognizedVersion => {
506                let mut report =
507                Report::error("unrecognized version argument in pragma directive".to_string(), ReportCode::UnrecognizedVersion);
508            report.add_primary(location, file_id, "this argument".to_string());
509            report
510            }      
511            IllegalExpression => {
512                let mut report =
513                Report::error("illegal expression".to_string(), ReportCode::IllegalExpression);
514            report.add_primary(location, file_id, "here".to_string());
515            report
516            }
517            MultiplePragma => {
518                let mut report =
519                Report::error("Multiple pragma directives".to_string(), ReportCode::MultiplePragma);
520            report.add_primary(location, file_id, "here".to_string());
521            report
522            },
523            ExpectedIdentifier => {
524                let mut report =
525                Report::error("An identifier is expected".to_string(), ReportCode::ExpectedIdentifier);
526            report.add_primary(location, file_id, "This should be an identifier".to_string());
527            report
528            },
529            _ => unreachable!(),    
530    };
531    report
532}
533
534pub fn produce_version_warning_report(path : String, version : Version) -> Report {
535    let mut r = Report::warning(
536        format!(
537            "File {} does not include pragma version. Assuming pragma version {:?}",
538            path, version
539        ),
540        ReportCode::NoCompilerVersionWarning,
541    );
542    r.add_note(format!("At the beginning of file {}, you should add the directive \"pragma circom <Version>\", to indicate which compiler version you are using.",path));
543    r
544}
545
546
547pub fn produce_report_with_message(error_code : ReportCode, msg : String) -> Report {
548    match error_code {
549        ReportCode::FileOs => {
550            Report::error(
551            format!("Could not open file {}", msg),
552            ReportCode::FileOs,
553            )
554        }
555        ReportCode::IncludeNotFound => {
556            let mut r = Report::error(
557                format!(" The file {} to be included has not been found", msg),
558                ReportCode::IncludeNotFound,
559                );
560                r.add_note("Consider using compilation option -l to indicate include paths".to_string());
561                r
562        },
563        _ => unreachable!()
564    }
565}
566
567pub fn produce_compiler_version_report(path : String, required_version : Version, version :  Version) -> Report {
568    let report = Report::error(
569        format!("File {} requires pragma version {:?} that is not supported by the compiler (version {:?})", path, required_version, version ),
570        ReportCode::CompilerVersionError,
571    );
572    report
573}
574
575pub fn anonymous_inside_condition_error(meta : Meta) -> Report {
576    let msg = "An anonymous component cannot be used inside a condition ".to_string();
577                let mut report = Report::error(
578                    format!("{}", msg),
579                    ReportCode::AnonymousCompError,
580                );
581                let file_id = meta.get_file_id().clone();
582                report.add_primary(
583                    meta.location,
584                    file_id,
585                    "This is an anonymous component used inside a condition".to_string(),
586                );
587                report
588}
589
590pub fn anonymous_general_error(meta : Meta, msg : String) -> Report {
591    let mut report = Report::error(
592                    format!("{}", msg),
593                    ReportCode::AnonymousCompError,
594                );
595                let file_id = meta.get_file_id().clone();
596                report.add_primary(
597                    meta.location,
598                    file_id,
599                    "This is the anonymous component whose use is not allowed".to_string(),
600                );
601                report
602}
603
604pub fn tuple_general_error(meta : Meta, msg : String) -> Report {
605    let mut report = Report::error(
606                    format!("{}", msg),
607                    ReportCode::TupleError,
608                );
609                let file_id = meta.get_file_id().clone();
610                report.add_primary(
611                    meta.location,
612                    file_id,
613                    "This is the tuple whose use is not allowed".to_string(),
614                );
615                report
616}