cairo_lang_parser/
utils.rs

1use std::path::PathBuf;
2
3use cairo_lang_diagnostics::{Diagnostics, DiagnosticsBuilder};
4use cairo_lang_filesystem::db::{ExternalFiles, FilesDatabase, FilesGroup, init_files_group};
5use cairo_lang_filesystem::ids::{FileId, FileKind, FileLongId, VirtualFile};
6use cairo_lang_filesystem::span::{TextOffset, TextWidth};
7use cairo_lang_primitive_token::{PrimitiveToken, ToPrimitiveTokenStream};
8use cairo_lang_syntax::node::ast::SyntaxFile;
9use cairo_lang_syntax::node::db::{SyntaxDatabase, SyntaxGroup};
10use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode};
11use cairo_lang_utils::{Intern, Upcast};
12use itertools::chain;
13
14use crate::ParserDiagnostic;
15use crate::db::ParserDatabase;
16use crate::parser::Parser;
17
18/// A salsa database for parsing only.
19#[salsa::database(ParserDatabase, SyntaxDatabase, FilesDatabase)]
20pub struct SimpleParserDatabase {
21    storage: salsa::Storage<SimpleParserDatabase>,
22}
23impl salsa::Database for SimpleParserDatabase {}
24impl ExternalFiles for SimpleParserDatabase {}
25impl Default for SimpleParserDatabase {
26    fn default() -> Self {
27        let mut res = Self { storage: Default::default() };
28        init_files_group(&mut res);
29        res
30    }
31}
32
33impl Upcast<dyn SyntaxGroup> for SimpleParserDatabase {
34    fn upcast(&self) -> &(dyn SyntaxGroup + 'static) {
35        self
36    }
37}
38impl Upcast<dyn FilesGroup> for SimpleParserDatabase {
39    fn upcast(&self) -> &(dyn FilesGroup + 'static) {
40        self
41    }
42}
43
44impl SimpleParserDatabase {
45    /// Parses new file and returns its syntax root.
46    ///
47    /// This is similar to [Self::parse_virtual_with_diagnostics], but is more ergonomic in cases
48    /// when exact diagnostics do not matter at the usage place. If the parser has emitted error
49    /// diagnostics, this function will return an error. If no error diagnostics has been
50    /// emitted, the syntax root will be returned.
51    pub fn parse_virtual(
52        &self,
53        content: impl ToString,
54    ) -> Result<SyntaxNode, Diagnostics<ParserDiagnostic>> {
55        let (node, diagnostics) = self.parse_virtual_with_diagnostics(content);
56        if diagnostics.check_error_free().is_ok() { Ok(node) } else { Err(diagnostics) }
57    }
58
59    /// Parses new file and return its syntax root with diagnostics.
60    ///
61    /// This function creates new virtual file with the given content and parses it.
62    /// Diagnostics gathered by the parser are returned alongside the result.
63    pub fn parse_virtual_with_diagnostics(
64        &self,
65        content: impl ToString,
66    ) -> (SyntaxNode, Diagnostics<ParserDiagnostic>) {
67        let file = FileLongId::Virtual(VirtualFile {
68            parent: None,
69            name: "parser_input".into(),
70            content: content.to_string().into(),
71            code_mappings: [].into(),
72            kind: FileKind::Module,
73        })
74        .intern(self);
75        get_syntax_root_and_diagnostics(self, file, content.to_string().as_str())
76    }
77
78    /// Parses a token stream (based on whole file) and returns its syntax root.
79    /// It's very similar to [Self::parse_virtual_with_diagnostics], but instead of taking a content
80    /// as a string, it takes a type that implements [ToPrimitiveTokenStream] trait
81    pub fn parse_token_stream(
82        &self,
83        token_stream: &dyn ToPrimitiveTokenStream<Iter = impl Iterator<Item = PrimitiveToken>>,
84    ) -> (SyntaxNode, Diagnostics<ParserDiagnostic>) {
85        let (content, _offset) = primitive_token_stream_content_and_offset(token_stream);
86        let file_id = FileLongId::Virtual(VirtualFile {
87            parent: Default::default(),
88            name: "token_stream_file_parser_input".into(),
89            content: content.into(),
90            code_mappings: Default::default(),
91            kind: FileKind::Module,
92        })
93        .intern(self);
94        let mut diagnostics = DiagnosticsBuilder::default();
95
96        (
97            Parser::parse_token_stream(self, &mut diagnostics, file_id, token_stream)
98                .as_syntax_node(),
99            diagnostics.build(),
100        )
101    }
102
103    /// Parses a token stream (based on a single expression).
104    /// It's very similar to the [Self::parse_token_stream].
105    pub fn parse_token_stream_expr(
106        &self,
107        token_stream: &dyn ToPrimitiveTokenStream<Iter = impl Iterator<Item = PrimitiveToken>>,
108    ) -> (SyntaxNode, Diagnostics<ParserDiagnostic>) {
109        let file_id = FileLongId::Virtual(VirtualFile {
110            parent: Default::default(),
111            name: "token_stream_expr_parser_input".into(),
112            content: Default::default(),
113            code_mappings: Default::default(),
114            kind: FileKind::Module,
115        })
116        .intern(self);
117        let mut diagnostics = DiagnosticsBuilder::default();
118
119        (
120            Parser::parse_token_stream_expr(self, &mut diagnostics, file_id, token_stream)
121                .as_syntax_node(),
122            diagnostics.build(),
123        )
124    }
125}
126
127/// Reads a cairo file to the db and return the syntax_root and diagnostic of its parsing.
128pub fn get_syntax_root_and_diagnostics_from_file(
129    db: &SimpleParserDatabase,
130    cairo_filepath: PathBuf,
131) -> (SyntaxNode, Diagnostics<ParserDiagnostic>) {
132    let file_id = FileId::new(db, cairo_filepath);
133    let contents = db.file_content(file_id).unwrap();
134    get_syntax_root_and_diagnostics(db, file_id, &contents)
135}
136
137/// Returns the syntax_root and diagnostic of a file in the db.
138pub fn get_syntax_root_and_diagnostics(
139    db: &SimpleParserDatabase,
140    file_id: FileId,
141    contents: &str,
142) -> (SyntaxNode, Diagnostics<ParserDiagnostic>) {
143    let (syntax_file, diagnostics) = get_syntax_file_and_diagnostics(db, file_id, contents);
144    (syntax_file.as_syntax_node(), diagnostics)
145}
146
147/// Returns the syntax_file and diagnostic of a file in the db.
148pub fn get_syntax_file_and_diagnostics(
149    db: &SimpleParserDatabase,
150    file_id: FileId,
151    contents: &str,
152) -> (SyntaxFile, Diagnostics<ParserDiagnostic>) {
153    let mut diagnostics = DiagnosticsBuilder::default();
154    let syntax_file = Parser::parse_file(db, &mut diagnostics, file_id, contents);
155    (syntax_file, diagnostics.build())
156}
157
158/// Collect content string and start offset from a struct implementing `[ToPrimitiveTokenStream`]
159/// interface. This basically means concatenation of all tokens supplied.
160pub(crate) fn primitive_token_stream_content_and_offset(
161    token_stream: &dyn ToPrimitiveTokenStream<Iter = impl Iterator<Item = PrimitiveToken>>,
162) -> (String, Option<TextOffset>) {
163    let mut primitive_stream = token_stream.to_primitive_token_stream();
164    let Some(first) = primitive_stream.next() else {
165        return ("".into(), None);
166    };
167    let start_offset = first
168        .span
169        .as_ref()
170        .map(|s| TextOffset::default().add_width(TextWidth::new_for_testing(s.start as u32)));
171    (chain!([first], primitive_stream).map(|t| t.content).collect(), start_offset)
172}