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#[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 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 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 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 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
122pub 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
131pub 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
140pub 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
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}