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, LocalSlot},
10    parser::{Parser, ParserDialect},
11};
12
13// REPL snippets need a small amount of extra state that normal source compilation
14// does not carry: the persisted binding table and which slots are live on entry.
15pub(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}