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