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            original_item_removed: false,
74        })
75        .intern(self);
76        get_syntax_root_and_diagnostics(self, file, content.to_string().as_str())
77    }
78
79    /// Parses a token stream (based on whole file) and returns its syntax root.
80    /// It's very similar to [Self::parse_virtual_with_diagnostics], but instead of taking a content
81    /// as a string, it takes a type that implements [ToPrimitiveTokenStream] trait
82    pub fn parse_token_stream(
83        &self,
84        token_stream: &dyn ToPrimitiveTokenStream<Iter = impl Iterator<Item = PrimitiveToken>>,
85    ) -> (SyntaxNode, Diagnostics<ParserDiagnostic>) {
86        let (content, _offset) = primitive_token_stream_content_and_offset(token_stream);
87        let file_id = FileLongId::Virtual(VirtualFile {
88            parent: Default::default(),
89            name: "token_stream_file_parser_input".into(),
90            content: content.into(),
91            code_mappings: Default::default(),
92            kind: FileKind::Module,
93            original_item_removed: false,
94        })
95        .intern(self);
96        let mut diagnostics = DiagnosticsBuilder::default();
97
98        (
99            Parser::parse_token_stream(self, &mut diagnostics, file_id, token_stream)
100                .as_syntax_node(),
101            diagnostics.build(),
102        )
103    }
104
105    /// Parses a token stream (based on a single expression).
106    /// It's very similar to the [Self::parse_token_stream].
107    pub fn parse_token_stream_expr(
108        &self,
109        token_stream: &dyn ToPrimitiveTokenStream<Iter = impl Iterator<Item = PrimitiveToken>>,
110    ) -> (SyntaxNode, Diagnostics<ParserDiagnostic>) {
111        let (content, offset) = primitive_token_stream_content_and_offset(token_stream);
112        let vfs = VirtualFile {
113            parent: Default::default(),
114            name: "token_stream_expr_parser_input".into(),
115            content: content.into(),
116            code_mappings: Default::default(),
117            kind: FileKind::Module,
118            original_item_removed: false,
119        };
120        let content = vfs.content.clone();
121        let file_id = FileLongId::Virtual(vfs).intern(self);
122        let mut diagnostics = DiagnosticsBuilder::default();
123
124        (
125            Parser::parse_token_stream_expr(self, &mut diagnostics, file_id, &content, offset)
126                .as_syntax_node(),
127            diagnostics.build(),
128        )
129    }
130}
131
132/// Reads a cairo file to the db and return the syntax_root and diagnostic of its parsing.
133pub fn get_syntax_root_and_diagnostics_from_file(
134    db: &SimpleParserDatabase,
135    cairo_filepath: PathBuf,
136) -> (SyntaxNode, Diagnostics<ParserDiagnostic>) {
137    let file_id = FileId::new(db, cairo_filepath);
138    let contents = db.file_content(file_id).unwrap();
139    get_syntax_root_and_diagnostics(db, file_id, &contents)
140}
141
142/// Returns the syntax_root and diagnostic of a file in the db.
143pub fn get_syntax_root_and_diagnostics(
144    db: &SimpleParserDatabase,
145    file_id: FileId,
146    contents: &str,
147) -> (SyntaxNode, Diagnostics<ParserDiagnostic>) {
148    let (syntax_file, diagnostics) = get_syntax_file_and_diagnostics(db, file_id, contents);
149    (syntax_file.as_syntax_node(), diagnostics)
150}
151
152/// Returns the syntax_file and diagnostic of a file in the db.
153pub fn get_syntax_file_and_diagnostics(
154    db: &SimpleParserDatabase,
155    file_id: FileId,
156    contents: &str,
157) -> (SyntaxFile, Diagnostics<ParserDiagnostic>) {
158    let mut diagnostics = DiagnosticsBuilder::default();
159    let syntax_file = Parser::parse_file(db, &mut diagnostics, file_id, contents);
160    (syntax_file, diagnostics.build())
161}
162
163/// Collect content string and start offset from a struct implementing `[ToPrimitiveTokenStream`]
164/// interface. This basically means concatenation of all tokens supplied.
165pub(crate) fn primitive_token_stream_content_and_offset(
166    token_stream: &dyn ToPrimitiveTokenStream<Iter = impl Iterator<Item = PrimitiveToken>>,
167) -> (String, Option<TextOffset>) {
168    let mut primitive_stream = token_stream.to_primitive_token_stream();
169    let Some(first) = primitive_stream.next() else {
170        return ("".into(), None);
171    };
172    let start_offset = first
173        .span
174        .as_ref()
175        .map(|s| TextOffset::default().add_width(TextWidth::new_for_testing(s.start as u32)));
176    (chain!([first], primitive_stream).map(|t| t.content).collect(), start_offset)
177}