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#[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 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 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 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 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
121pub 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
130pub 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
139pub 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
150pub(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}