Skip to main content

cairo_lang_parser/
utils.rs

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