cairo_lang_parser/
utils.rs

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