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