swamp_script_compile/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/script
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5use regex::Regex;
6use seq_map::SeqMap;
7use std::env::current_dir;
8use std::io;
9use std::path::Path;
10use std::path::PathBuf;
11use std::rc::Rc;
12use std::str::FromStr;
13use swamp_script_analyzer::Analyzer;
14pub use swamp_script_analyzer::prelude::{Error, Program};
15use swamp_script_dep_loader::{
16    DependencyParser, ParsedAstModule, parse_local_modules_and_get_order, parse_single_module,
17    swamp_registry_path,
18};
19use swamp_script_error_report::{ScriptResolveError, prelude::show_script_resolve_error};
20use swamp_script_eval_loader::analyze_modules_in_order;
21use swamp_script_modules::modules::{ModuleRef, Modules};
22use swamp_script_modules::symtbl::{SymbolTable, SymbolTableRef};
23use swamp_script_pretty_print::{SourceMapDisplay, SymbolTableDisplay};
24use swamp_script_semantic::ProgramState;
25use swamp_script_source_map::SourceMap;
26use swamp_script_source_map_lookup::SourceMapWrapper;
27use time_dilation::ScopedTimer;
28use tiny_ver::TinyVersion;
29use tracing::{info, trace};
30
31pub const COMPILER_VERSION: &str = "0.0.0";
32pub const CORE_VERSION: &str = "core-0.0.0";
33
34/// # Errors
35///
36pub fn analyze_ast_module_skip_expression(
37    analyzer: &mut Analyzer,
38    parsed_ast_module: &ParsedAstModule,
39) -> Result<(), Error> {
40    for definition in &parsed_ast_module.ast_module.definitions {
41        analyzer.analyze_definition(definition)?;
42    }
43    Ok(())
44}
45
46pub fn analyze_single_module(
47    state: &mut ProgramState,
48    default_symbol_table: SymbolTable,
49    modules: &Modules,
50    core_symbol_table: SymbolTableRef,
51    parsed_ast_module: &ParsedAstModule,
52    source_map: &SourceMap,
53    versioned_module_path: &[String],
54) -> Result<SymbolTable, Error> {
55    let mut analyzer = Analyzer::new(
56        state,
57        modules,
58        core_symbol_table,
59        source_map,
60        versioned_module_path,
61        parsed_ast_module.file_id,
62    );
63
64    analyzer.shared.lookup_table = default_symbol_table;
65
66    //    trace!(lookup_table=?analyzer.shared.lookup_table, "analyzer lookup_table");
67
68    analyze_ast_module_skip_expression(&mut analyzer, parsed_ast_module)?;
69
70    Ok(analyzer.shared.definition_table)
71}
72
73pub fn create_source_map(registry_path: &Path, local_path: &Path) -> io::Result<SourceMap> {
74    trace!(?registry_path, ?local_path, "mounting source map");
75
76    let mut mounts = SeqMap::new();
77    mounts
78        .insert("crate".to_string(), local_path.to_path_buf())
79        .unwrap();
80
81    mounts
82        .insert("registry".to_string(), registry_path.to_path_buf())
83        .unwrap();
84
85    SourceMap::new(&mounts)
86}
87
88pub fn create_registry_source_map(registry_path: &Path) -> io::Result<SourceMap> {
89    trace!(?registry_path, "mounting registry path source map");
90
91    let mut mounts = SeqMap::new();
92    mounts
93        .insert("registry".to_string(), registry_path.to_path_buf())
94        .unwrap();
95
96    SourceMap::new(&mounts)
97}
98
99#[derive(Debug)]
100pub struct BootstrapResult {
101    pub program: Program,
102    pub core_module_path: Vec<String>,
103}
104
105/// Bootstraps the core and ffi modules and creates a default symbol table
106///
107/// # Errors
108///
109/// # Panics
110/// In theory it can panic, but should be safe.
111pub fn bootstrap_modules(
112    source_map: &mut SourceMap,
113) -> Result<BootstrapResult, ScriptResolveError> {
114    let compiler_version = TinyVersion::from_str(COMPILER_VERSION).unwrap();
115    trace!(%compiler_version, "booting up compiler");
116
117    let mut modules = Modules::new();
118
119    let mut core_module_with_intrinsics = swamp_script_core::create_module(&compiler_version);
120
121    let core_ast_module = parse_single_module(
122        source_map,
123        &core_module_with_intrinsics.symbol_table.module_path(),
124    )?;
125
126    let mut state = ProgramState::new();
127
128    let half_completed_core_symbol_table = core_module_with_intrinsics.symbol_table.clone();
129    let default_symbol_table_for_core_with_intrinsics = half_completed_core_symbol_table.clone();
130
131    let mut core_analyzed_definition_table = analyze_single_module(
132        &mut state,
133        default_symbol_table_for_core_with_intrinsics.clone(),
134        &modules,
135        half_completed_core_symbol_table.clone().into(),
136        &core_ast_module,
137        source_map,
138        &core_module_with_intrinsics.symbol_table.module_path(),
139    )?;
140    // Overwrite the default lookup table to the definition table
141    core_analyzed_definition_table
142        .extend_intrinsic_functions_from(&default_symbol_table_for_core_with_intrinsics);
143    core_analyzed_definition_table
144        .extend_basic_from(&default_symbol_table_for_core_with_intrinsics);
145
146    let source_map_lookup = SourceMapWrapper {
147        source_map,
148        current_dir: current_dir().unwrap(),
149    };
150    let pretty_printer = SourceMapDisplay {
151        source_map: &source_map_lookup,
152    };
153
154    let display_core_analyzed_definition_table = SymbolTableDisplay {
155        symbol_table: &core_analyzed_definition_table,
156        source_map_display: &pretty_printer,
157    };
158
159    //trace!(%display_core_analyzed_definition_table, "core analyzed symbol table");
160
161    core_module_with_intrinsics.symbol_table = core_analyzed_definition_table;
162
163    // core module is done, so add it read only to the modules
164    let core_module_ref = Rc::new(core_module_with_intrinsics);
165    modules.add(core_module_ref.clone());
166
167    let mut default_symbol_table_for_others = SymbolTable::new(&[]);
168
169    // Prelude for the core module
170    // Expose the basic primitive types, like `Int`, `String`, `Float`, `Bool`
171    // so they can be references without a `use core::{Int, String, Float, Bool}` statement.
172    default_symbol_table_for_others
173        .extend_alias_from(&core_module_ref.symbol_table)
174        .unwrap();
175
176    // Add `core` module without the version number, so they can be referenced from code
177    default_symbol_table_for_others
178        .add_package_version(swamp_script_core::PACKAGE_NAME, compiler_version)
179        .expect("should work");
180
181    let source_map_lookup = SourceMapWrapper {
182        source_map,
183        current_dir: current_dir().unwrap(),
184    };
185    let pretty_printer = SourceMapDisplay {
186        source_map: &source_map_lookup,
187    };
188
189    let symbol_table_display = SymbolTableDisplay {
190        symbol_table: &default_symbol_table_for_others,
191        source_map_display: &pretty_printer,
192    };
193
194    let program = Program::new(state, modules, default_symbol_table_for_others);
195
196    let result = BootstrapResult {
197        program,
198        core_module_path: core_module_ref.symbol_table.module_path().clone(),
199    };
200    Ok(result)
201}
202
203pub fn compile_and_analyze_all_modules(
204    module_path: &[String],
205    resolved_program: &mut Program,
206    source_map: &mut SourceMap,
207    core_symbol_table: SymbolTableRef,
208) -> Result<(), ScriptResolveError> {
209    let mut dependency_parser = DependencyParser::new();
210
211    let module_paths_in_order = parse_local_modules_and_get_order(
212        module_path.to_vec(),
213        &mut dependency_parser,
214        source_map,
215    )?;
216
217    analyze_modules_in_order(
218        &mut resolved_program.state,
219        &resolved_program.default_symbol_table,
220        &mut resolved_program.modules,
221        core_symbol_table,
222        source_map,
223        &module_paths_in_order,
224        &dependency_parser,
225    )?;
226
227    Ok(())
228}
229
230pub fn remove_version_from_package_name_regex(package_name_with_version: &str) -> String {
231    let re = Regex::new(
232        r"-(?P<version>[0-9]+(?:\.[0-9]+)*(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?)?(?:\+.*)?$",
233    )
234    .unwrap();
235    re.replace(package_name_with_version, "").to_string()
236}
237
238/// # Errors
239///
240/// # Panics
241///
242pub fn compile_analyze_and_link_without_version(
243    root_module_path: &[String],
244    resolved_program: &mut Program,
245    source_map: &mut SourceMap,
246    core_symbol_table: SymbolTableRef,
247) -> Result<(), ScriptResolveError> {
248    let mangrove_render_result = compile_and_analyze(
249        root_module_path,
250        resolved_program,
251        source_map,
252        core_symbol_table,
253    );
254
255    match mangrove_render_result {
256        Ok(..) => {}
257        Err(err) => {
258            show_script_resolve_error(&err, source_map, Path::new(""));
259            Err(err)?;
260        }
261    }
262    let mangrove_render_module = resolved_program.modules.get(root_module_path).unwrap();
263
264    let first_part = remove_version_from_package_name_regex(&root_module_path[0]);
265    let mut without_version_path: Vec<String> = root_module_path.to_vec();
266    without_version_path[0] = first_part;
267
268    resolved_program
269        .modules
270        .link_module(&without_version_path, mangrove_render_module.clone());
271
272    Ok(())
273}
274
275/// # Errors
276///
277pub fn compile_and_analyze(
278    root_module_path: &[String],
279    resolved_program: &mut Program,
280    source_map: &mut SourceMap,
281    core_symbol_table: SymbolTableRef,
282) -> Result<(), ScriptResolveError> {
283    compile_and_analyze_all_modules(
284        root_module_path,
285        resolved_program,
286        source_map,
287        core_symbol_table,
288    )
289}
290
291pub fn current_path() -> PathBuf {
292    current_dir().unwrap()
293}
294
295/// # Errors
296///
297/// # Panics
298///
299pub fn bootstrap_and_compile(
300    source_map: &mut SourceMap,
301    //root_dependencies: SeqMap<String, TinyVersion>,
302    root_path: &[String],
303) -> Result<Program, ScriptResolveError> {
304    let bootstrap_timer = ScopedTimer::new("bootstrap");
305    let bootstrap_result = bootstrap_modules(source_map).inspect_err(|err| {
306        show_script_resolve_error(err, source_map, &current_path());
307    })?;
308    drop(bootstrap_timer);
309
310    let mut program = bootstrap_result.program;
311
312    let core_symbol_table = program
313        .modules
314        .get(&bootstrap_result.core_module_path)
315        .unwrap()
316        .symbol_table
317        .clone();
318
319    /*
320    for (package_name, tiny_version) in root_dependencies {
321        program
322            .default_symbol_table
323            .add_package_version(&package_name, tiny_version)
324            .unwrap()
325    }
326
327     */
328
329    let compile_all_modules_timer = ScopedTimer::new("compile all modules");
330    compile_and_analyze_all_modules(
331        root_path,
332        &mut program,
333        source_map,
334        core_symbol_table.into(),
335    )
336    .inspect_err(|err| {
337        show_script_resolve_error(err, source_map, &current_path());
338    })?;
339
340    drop(compile_all_modules_timer);
341
342    // debug_all_modules(&program.modules, source_map);
343
344    Ok(program)
345}
346
347pub fn debug_all_modules(modules: &Modules, source_map: &SourceMap) {
348    for (_name, module) in modules.modules() {
349        debug_module(&module.symbol_table, source_map);
350    }
351}
352pub fn debug_module(symbol_table: &SymbolTable, source_map: &SourceMap) {
353    let source_map_lookup = SourceMapWrapper {
354        source_map,
355        current_dir: current_dir().unwrap(),
356    };
357    let pretty_printer = SourceMapDisplay {
358        source_map: &source_map_lookup,
359    };
360
361    let symbol_table_display = SymbolTableDisplay {
362        symbol_table: &symbol_table,
363        source_map_display: &pretty_printer,
364    };
365
366    info!(%symbol_table_display, "{:?}", symbol_table.module_path());
367}
368
369pub fn compile_string(script: &str) -> Result<(Program, ModuleRef, SourceMap), ScriptResolveError> {
370    let mut source_map = SourceMap::new(&SeqMap::default()).unwrap();
371    let file_id = 0xffff;
372
373    source_map
374        .add_mount("registry", &swamp_registry_path().unwrap())
375        .unwrap(); // registry must be present, since it reads core .swamp source file
376
377    source_map.add_mount("crate", Path::new("/tmp/")).unwrap();
378    source_map.add_to_cache("crate", Path::new("test.swamp"), script, file_id);
379
380    let resolved_path_str = vec!["crate".to_string(), "test".to_string()];
381
382    let program = bootstrap_and_compile(&mut source_map, &resolved_path_str)?;
383    let main_module = program.modules.get(&resolved_path_str).unwrap().clone();
384
385    Ok((program, main_module, source_map))
386}