Skip to main content

vm/compiler/frontends/
mod.rs

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
13// REPL snippets carry the persisted binding table alongside the parsed IR.
14pub(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}