1mod rustscript;
2
3use std::collections::HashMap;
4
5use crate::compiler::source_map::{LoweredSource, SourceMap};
6
7use super::{
8 CompileSourceFileOptions, ParseError, ReplLocalBinding, SharedParserOptions, SourceFlavor,
9 ir::{FrontendIr, LocalSlot},
10 parser::{Parser, ParserDialect},
11};
12
13pub(super) struct ParsedRustScriptReplSource {
16 pub ir: FrontendIr,
17 pub bindings: Vec<ReplLocalBinding>,
18 pub entry_definite_locals: Vec<LocalSlot>,
19}
20
21pub(super) fn parse_source(
22 source: &str,
23 flavor: SourceFlavor,
24 options: &CompileSourceFileOptions,
25) -> Result<FrontendIr, ParseError> {
26 match flavor {
27 SourceFlavor::RustScript => {
28 let lowered = rustscript::lower(source)?;
29 parse_lowered_with_mapping(source, lowered, false, false, true)
30 }
31 SourceFlavor::JavaScript | SourceFlavor::Lua => {
32 let Some(plugin) = options.source_plugin_for_flavor(flavor) else {
33 return Err(ParseError::new(format!(
34 "no frontend plugin registered for {flavor:?} source"
35 )));
36 };
37 plugin.parse_source(source)
38 }
39 }
40}
41
42pub(crate) fn parser_dialect_for_flavor(
43 flavor: SourceFlavor,
44 options: &CompileSourceFileOptions,
45) -> Option<&'static dyn ParserDialect> {
46 match flavor {
47 SourceFlavor::RustScript => Some(rustscript::parser_dialect()),
48 SourceFlavor::JavaScript | SourceFlavor::Lua => options
49 .source_plugin_for_flavor(flavor)
50 .and_then(|plugin| plugin.parser_dialect()),
51 }
52}
53
54pub fn parse_source_with_dialect(
55 source: &str,
56 dialect: &'static dyn ParserDialect,
57 options: SharedParserOptions,
58) -> Result<FrontendIr, ParseError> {
59 parse_with_parser(
60 source,
61 options.source_id,
62 options.allow_implicit_externs,
63 options.allow_implicit_semicolons,
64 options.enforce_mutable_bindings,
65 dialect,
66 )
67}
68
69pub(super) fn parse_rustscript_repl_source(
70 source: &str,
71 predefined_locals: &[ReplLocalBinding],
72) -> Result<ParsedRustScriptReplSource, ParseError> {
73 let lowered = rustscript::lower(source)?;
74 parse_lowered_repl_with_mapping(source, lowered, predefined_locals, false, false, true)
75}
76
77pub fn is_ident_start(ch: char) -> bool {
78 ch.is_ascii_alphabetic() || ch == '_'
79}
80
81pub fn is_ident_continue(ch: char) -> bool {
82 ch.is_ascii_alphanumeric() || ch == '_'
83}
84
85fn parse_with_parser(
86 source: &str,
87 source_id: u32,
88 allow_implicit_externs: bool,
89 allow_implicit_semicolons: bool,
90 enforce_mutable_bindings: bool,
91 dialect: &'static dyn ParserDialect,
92) -> Result<FrontendIr, ParseError> {
93 let mut parser = Parser::new(
94 source,
95 source_id,
96 allow_implicit_externs,
97 allow_implicit_semicolons,
98 enforce_mutable_bindings,
99 dialect,
100 )?;
101 let stmts = parser.parse_program()?;
102 Ok(FrontendIr {
103 stmts,
104 locals: parser.local_count(),
105 local_bindings: parser.local_bindings(),
106 struct_schemas: parser.struct_schemas(),
107 unknown_type_spans: parser.unknown_type_spans(),
108 functions: parser.function_decls(),
109 function_impls: parser.function_impls(),
110 stmt_sources: Vec::new(),
111 function_sources: HashMap::new(),
112 })
113}
114
115fn parse_repl_with_parser(
116 source: &str,
117 source_id: u32,
118 predefined_locals: &[ReplLocalBinding],
119 allow_implicit_externs: bool,
120 allow_implicit_semicolons: bool,
121 enforce_mutable_bindings: bool,
122 dialect: &'static dyn ParserDialect,
123) -> Result<ParsedRustScriptReplSource, ParseError> {
124 let mut parser = Parser::new_with_predeclared_locals(
125 source,
126 source_id,
127 allow_implicit_externs,
128 allow_implicit_semicolons,
129 enforce_mutable_bindings,
130 dialect,
131 predefined_locals,
132 )?;
133 let stmts = parser.parse_program()?;
134 let bindings = parser.local_bindings_with_mutability();
135 let predeclared = parser
136 .local_bindings()
137 .into_iter()
138 .filter_map(|(name, index)| {
139 predefined_locals
140 .iter()
141 .any(|binding| binding.name == name)
142 .then_some(index)
143 })
144 .collect::<Vec<_>>();
145 Ok(ParsedRustScriptReplSource {
146 ir: FrontendIr {
147 stmts,
148 locals: parser.local_count(),
149 local_bindings: parser.local_bindings(),
150 struct_schemas: parser.struct_schemas(),
151 unknown_type_spans: parser.unknown_type_spans(),
152 functions: parser.function_decls(),
153 function_impls: parser.function_impls(),
154 stmt_sources: Vec::new(),
155 function_sources: HashMap::new(),
156 },
157 bindings,
158 entry_definite_locals: predeclared,
159 })
160}
161
162fn parse_lowered_with_mapping(
163 original_source: &str,
164 lowered: LoweredSource,
165 allow_implicit_externs: bool,
166 allow_implicit_semicolons: bool,
167 enforce_mutable_bindings: bool,
168) -> Result<FrontendIr, ParseError> {
169 let mut source_map = SourceMap::new();
170 let original_source_id = source_map.add_source("<source>", original_source.to_string());
171 let lowered_source_id = source_map.add_source("<lowered>", lowered.text.clone());
172
173 match parse_with_parser(
174 &lowered.text,
175 lowered_source_id,
176 allow_implicit_externs,
177 allow_implicit_semicolons,
178 enforce_mutable_bindings,
179 rustscript::parser_dialect(),
180 ) {
181 Ok(mut ir) => {
182 map_spans_to_original_source(
183 &mut ir.unknown_type_spans,
184 &lowered,
185 &source_map,
186 lowered_source_id,
187 original_source_id,
188 );
189 Ok(ir)
190 }
191 Err(mut err) => {
192 err = err.with_line_span_from_source(&source_map, lowered_source_id);
193 let mapped_span = err.span.and_then(|span| {
194 lowered
195 .mapping
196 .map_span(&source_map, lowered_source_id, original_source_id, span)
197 });
198 if let Some(mapped) = mapped_span {
199 err.span = Some(mapped);
200 if let Some((line, _)) =
201 source_map.line_col_for_offset(original_source_id, mapped.lo)
202 {
203 err.line = line;
204 }
205 } else {
206 let mapped_line = lowered
207 .mapping
208 .lowered_to_original_line
209 .get(err.line.saturating_sub(1))
210 .copied()
211 .unwrap_or(err.line)
212 .max(1);
213 let original_line = source_map
214 .file(original_source_id)
215 .map(|file| mapped_line.min(file.line_count().max(1)))
216 .unwrap_or(mapped_line);
217 err.line = original_line;
218 err.span = source_map.line_span(original_source_id, original_line);
219 }
220 Err(err)
221 }
222 }
223}
224
225fn parse_lowered_repl_with_mapping(
226 original_source: &str,
227 lowered: LoweredSource,
228 predefined_locals: &[ReplLocalBinding],
229 allow_implicit_externs: bool,
230 allow_implicit_semicolons: bool,
231 enforce_mutable_bindings: bool,
232) -> Result<ParsedRustScriptReplSource, ParseError> {
233 let mut source_map = SourceMap::new();
234 let original_source_id = source_map.add_source("<source>", original_source.to_string());
235 let lowered_source_id = source_map.add_source("<lowered>", lowered.text.clone());
236
237 match parse_repl_with_parser(
238 &lowered.text,
239 lowered_source_id,
240 predefined_locals,
241 allow_implicit_externs,
242 allow_implicit_semicolons,
243 enforce_mutable_bindings,
244 rustscript::parser_dialect(),
245 ) {
246 Ok(mut parsed) => {
247 map_spans_to_original_source(
248 &mut parsed.ir.unknown_type_spans,
249 &lowered,
250 &source_map,
251 lowered_source_id,
252 original_source_id,
253 );
254 Ok(parsed)
255 }
256 Err(mut err) => {
257 err = err.with_line_span_from_source(&source_map, lowered_source_id);
258 let mapped_span = err.span.and_then(|span| {
259 lowered
260 .mapping
261 .map_span(&source_map, lowered_source_id, original_source_id, span)
262 });
263 if let Some(mapped) = mapped_span {
264 err.span = Some(mapped);
265 if let Some((line, _)) =
266 source_map.line_col_for_offset(original_source_id, mapped.lo)
267 {
268 err.line = line;
269 }
270 } else {
271 let mapped_line = lowered
272 .mapping
273 .lowered_to_original_line
274 .get(err.line.saturating_sub(1))
275 .copied()
276 .unwrap_or(err.line)
277 .max(1);
278 let original_line = source_map
279 .file(original_source_id)
280 .map(|file| mapped_line.min(file.line_count().max(1)))
281 .unwrap_or(mapped_line);
282 err.line = original_line;
283 err.span = source_map.line_span(original_source_id, original_line);
284 }
285 Err(err)
286 }
287 }
288}
289
290fn map_spans_to_original_source(
291 spans: &mut [crate::compiler::source_map::Span],
292 lowered: &LoweredSource,
293 source_map: &SourceMap,
294 lowered_source_id: u32,
295 original_source_id: u32,
296) {
297 for span in spans {
298 if let Some(mapped) =
299 lowered
300 .mapping
301 .map_span(source_map, lowered_source_id, original_source_id, *span)
302 {
303 *span = mapped;
304 }
305 }
306}