swamp_compile/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/swamp
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5pub mod prelude;
6
7use regex::Regex;
8use seq_map::SeqMap;
9use source_map_cache::SourceMap;
10use source_map_cache::SourceMapWrapper;
11use std::env::current_dir;
12use std::io;
13use std::path::Path;
14use std::path::PathBuf;
15use std::str::FromStr;
16pub use swamp_analyzer::prelude::Program;
17use swamp_analyzer::{Analyzer, AnalyzerOptions};
18use swamp_core::text::core_text;
19use swamp_dep_loader::{
20    DependencyParser, ParsedAstModule, RunMode, parse_local_modules_and_get_order,
21    parse_single_module_from_text, swamp_registry_path,
22};
23use swamp_error_report::analyze::show_analyzer_error;
24use swamp_error_report::prelude::Kind;
25use swamp_error_report::{ScriptResolveError, prelude::show_script_resolve_error};
26use swamp_modules::modules::{ModuleRef, Modules};
27use swamp_modules::prelude::Module;
28use swamp_modules::symtbl::{DefinitionTable, SymbolTableRef};
29use swamp_pretty_print::{ImplsDisplay, SourceMapDisplay, SymbolTableDisplay};
30use swamp_program_analyzer::analyze_modules_in_order;
31use swamp_semantic::err::Error;
32use swamp_semantic::{AssociatedImpls, ProgramState, formal_module_name};
33use swamp_std::std_text;
34use swamp_types::prelude::print_types;
35use time_dilation::ScopedTimer;
36use tiny_ver::TinyVersion;
37use tracing::{info, trace};
38
39pub const COMPILER_VERSION: &str = "0.0.0";
40pub const CORE_VERSION: &str = "core-0.0.0";
41
42/// # Errors
43///
44pub fn analyze_ast_module_skip_expression(
45    analyzer: &mut Analyzer,
46    parsed_ast_module: &ParsedAstModule,
47) {
48    for definition in &parsed_ast_module.ast_module.definitions {
49        analyzer.analyze_definition(definition);
50    }
51}
52
53#[must_use]
54pub fn analyze_single_module(
55    state: &mut ProgramState,
56    default_symbol_table: DefinitionTable,
57    modules: &Modules,
58    core_symbol_table: SymbolTableRef,
59    parsed_ast_module: &ParsedAstModule,
60    source_map: &SourceMap,
61    versioned_module_path: &[String],
62    options: AnalyzerOptions,
63) -> DefinitionTable {
64    let mut analyzer = Analyzer::new(
65        state,
66        modules,
67        core_symbol_table,
68        source_map,
69        versioned_module_path,
70        parsed_ast_module.file_id,
71        options,
72    );
73
74    analyzer.shared.lookup_table = default_symbol_table;
75
76    //    trace!(lookup_table=?analyzer.shared.lookup_table, "analyzer lookup_table");
77
78    analyze_ast_module_skip_expression(&mut analyzer, parsed_ast_module);
79
80    analyzer.shared.definition_table
81}
82
83pub fn create_source_map(registry_path: &Path, local_path: &Path) -> io::Result<SourceMap> {
84    trace!(?registry_path, ?local_path, "mounting source map");
85
86    let mut mounts = SeqMap::new();
87    mounts
88        .insert("crate".to_string(), local_path.to_path_buf())
89        .unwrap();
90
91    mounts
92        .insert("registry".to_string(), registry_path.to_path_buf())
93        .unwrap();
94
95    SourceMap::new(&mounts)
96}
97
98pub fn create_default_source_map(local_path: &Path) -> io::Result<SourceMap> {
99    create_source_map(&local_path.join("packages/"), &local_path.join("scripts/"))
100}
101
102pub fn create_default_source_map_from_scripts_dir(local_path: &Path) -> io::Result<SourceMap> {
103    let root = local_path.join("..");
104    create_default_source_map(&root)
105}
106
107pub fn create_default_source_map_crate_only(local_path: &Path) -> io::Result<SourceMap> {
108    let mut mounts = SeqMap::new();
109    mounts
110        .insert("crate".to_string(), local_path.to_path_buf())
111        .unwrap();
112
113    SourceMap::new(&mounts)
114}
115
116pub fn create_registry_source_map(registry_path: &Path) -> io::Result<SourceMap> {
117    trace!(?registry_path, "mounting registry path source map");
118
119    let mut mounts = SeqMap::new();
120    mounts
121        .insert("registry".to_string(), registry_path.to_path_buf())
122        .unwrap();
123
124    SourceMap::new(&mounts)
125}
126
127#[derive(Debug)]
128pub struct BootstrapResult {
129    pub program: Program,
130    pub core_module_path: Vec<String>,
131}
132
133/// Bootstraps the core and ffi modules and creates a default symbol table
134///
135/// # Errors
136///
137/// # Panics
138/// In theory it can panic, but should be safe.
139pub fn bootstrap_modules(
140    source_map: &mut SourceMap,
141) -> Result<BootstrapResult, ScriptResolveError> {
142    let compiler_version = TinyVersion::from_str(COMPILER_VERSION).unwrap();
143    trace!(%compiler_version, "booting up compiler");
144    let mut state = ProgramState::new();
145
146    let mut modules = Modules::new();
147
148    let core_module_with_intrinsics =
149        swamp_core::create_module(&compiler_version, &mut state.types, 0);
150
151    let core_path = core_module_with_intrinsics.definition_table.module_path();
152    let core_parsed_ast_module =
153        parse_single_module_from_text(source_map, &core_path, &core_text())?;
154
155    let half_completed_core_symbol_table = core_module_with_intrinsics.definition_table.clone();
156    let default_symbol_table_for_core_with_intrinsics = half_completed_core_symbol_table.clone();
157
158    let mut analyzed_core_symbol_table = analyze_single_module(
159        &mut state,
160        default_symbol_table_for_core_with_intrinsics.clone(),
161        &modules,
162        half_completed_core_symbol_table.clone().into(),
163        &core_parsed_ast_module,
164        source_map,
165        &core_module_with_intrinsics.definition_table.module_path(),
166        AnalyzerOptions {
167            allow_unsafe: false,
168        },
169    );
170
171    // After this the core symbol table is done
172    analyzed_core_symbol_table
173        .extend_basic_from(&core_module_with_intrinsics.definition_table)
174        .expect("couldn't extend core");
175    let mut default_module = swamp_core::create_module_with_name(&[], 0);
176    default_module
177        .definition_table
178        .extend_alias_from(&analyzed_core_symbol_table)
179        .expect("extend basic alias and functions from core");
180
181    let core_module = Module::new(analyzed_core_symbol_table, vec![], None, 0);
182    modules.add(ModuleRef::from(core_module));
183
184    // ---------
185
186    let std_path = &["std".to_string()];
187    let std_module_with_intrinsics = swamp_core::create_module_with_name(std_path, 0);
188    let std_ast_module = parse_single_module_from_text(source_map, std_path, &std_text())?;
189    let analyzed_std_symbol_table = analyze_single_module(
190        &mut state,
191        default_symbol_table_for_core_with_intrinsics,
192        &modules,
193        half_completed_core_symbol_table.into(),
194        &std_ast_module,
195        source_map,
196        &std_module_with_intrinsics.definition_table.module_path(),
197        AnalyzerOptions {
198            allow_unsafe: false,
199        },
200    );
201    default_module
202        .definition_table
203        .extend_basic_from(&analyzed_std_symbol_table)
204        .expect("extend basics from core");
205
206    let analyzed_std_module = Module::new(analyzed_std_symbol_table, vec![], None, 0);
207
208    modules.add(ModuleRef::from(analyzed_std_module));
209
210    let bootstrap_program = Program::new(state, modules, default_module.definition_table);
211
212    let result = BootstrapResult {
213        program: bootstrap_program,
214        core_module_path: core_path,
215    };
216
217    Ok(result)
218}
219
220pub fn compile_and_analyze_all_modules(
221    module_path: &[String],
222    resolved_program: &mut Program,
223    source_map: &mut SourceMap,
224    core_symbol_table: SymbolTableRef,
225    options: AnalyzerOptions,
226) -> Result<(), ScriptResolveError> {
227    let mut dependency_parser = DependencyParser::new();
228
229    let module_paths_in_order =
230        parse_local_modules_and_get_order(module_path, &mut dependency_parser, source_map)?;
231
232    analyze_modules_in_order(
233        &mut resolved_program.state,
234        &resolved_program.default_symbol_table,
235        &mut resolved_program.modules,
236        &core_symbol_table,
237        source_map,
238        &module_paths_in_order,
239        &dependency_parser,
240        options,
241    )?;
242
243    Ok(())
244}
245
246#[must_use]
247pub fn remove_version_from_package_name_regex(package_name_with_version: &str) -> String {
248    let re = Regex::new(
249        r"-(?P<version>[0-9]+(?:\.[0-9]+)*(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?)?(?:\+.*)?$",
250    )
251    .unwrap();
252    re.replace(package_name_with_version, "").to_string()
253}
254
255#[must_use]
256pub fn current_path() -> PathBuf {
257    current_dir().unwrap()
258}
259
260pub struct CompileOptions {
261    pub show_semantic: bool,
262    pub show_modules: bool,
263    pub show_errors: bool,
264    pub show_warnings: bool,
265    pub show_hints: bool,
266    pub show_information: bool,
267    pub show_types: bool,
268    pub allow_unsafe: bool,
269}
270
271/// # Errors
272///
273/// # Panics
274///
275pub fn bootstrap_and_compile(
276    source_map: &mut SourceMap,
277    root_path: &[String],
278    options: &CompileOptions,
279) -> Result<Program, ScriptResolveError> {
280    let bootstrap_timer = ScopedTimer::new("bootstrap");
281    let bootstrap_result = bootstrap_modules(source_map).inspect_err(|err| {
282        show_script_resolve_error(err, source_map, &current_path());
283    })?;
284    drop(bootstrap_timer);
285    let mut program = bootstrap_result.program;
286
287    let core_symbol_table = program
288        .modules
289        .get(&bootstrap_result.core_module_path)
290        .unwrap()
291        .definition_table
292        .clone();
293
294    let compile_all_modules_timer = ScopedTimer::new("compile all modules");
295    compile_and_analyze_all_modules(
296        root_path,
297        &mut program,
298        source_map,
299        core_symbol_table.into(),
300        AnalyzerOptions {
301            allow_unsafe: options.allow_unsafe,
302        },
303    )
304    .inspect_err(|err| {
305        show_script_resolve_error(err, source_map, &current_path());
306    })?;
307
308    drop(compile_all_modules_timer);
309
310    if options.show_modules {
311        debug_all_modules(&program.modules, source_map);
312    }
313
314    if options.show_semantic {
315        debug_all_impl_functions(&program.state.associated_impls, source_map);
316    }
317
318    if options.show_types {
319        let mut str = String::new();
320        print_types(&mut str, &program.state.types).expect("should work");
321        eprintln!("{str}");
322        //info!(str, "types");
323    }
324
325    let source_map_wrapper = SourceMapWrapper {
326        source_map,
327        current_dir: current_dir().unwrap(),
328    };
329
330    if options.show_errors {
331        show_errors(&program.state.errors, &source_map_wrapper);
332    }
333
334    if options.show_warnings {
335        /* TODO: Not working perfectly yet
336        for x in program.state.symbols.iter_all() {
337            if !program.state.refs.is_used(&x.id) {
338                let name = source_map_wrapper.get_text(&x.name);
339                if x.name.span.file_id <= 2 {
340                    eprintln!("name: {name} {:?} {}", x.kind, x.name.span.file_id);
341                    continue;
342                }
343                let line_info = source_map_wrapper.get_line(&x.name.span);
344                eprintln!("not used: '{}' ({:?}) file://{}:{}:{}\n{}", name, x.kind, line_info.relative_file_name, line_info.row, line_info.col, line_info.line);
345            }
346        }
347
348         */
349    }
350
351    if options.show_hints {
352        show_hints(&program.state.hints, &source_map_wrapper);
353    }
354
355    if options.show_information {
356        show_information(&program.state.infos, &source_map_wrapper);
357    }
358
359    Ok(program)
360}
361
362fn show_errors(errors: &[Error], source_map_wrapper: &SourceMapWrapper) {
363    for err in errors.iter().take(4) {
364        show_analyzer_error(
365            err,
366            Kind::Error,
367            source_map_wrapper.source_map,
368            &source_map_wrapper.current_dir,
369        );
370    }
371}
372
373fn show_hints(hints: &[Error], source_map_wrapper: &SourceMapWrapper) {
374    for err in hints {
375        show_analyzer_error(
376            err,
377            Kind::Warning,
378            source_map_wrapper.source_map,
379            &source_map_wrapper.current_dir,
380        );
381    }
382}
383
384fn show_information(infos: &[Error], source_map_wrapper: &SourceMapWrapper) {
385    for err in infos {
386        show_analyzer_error(
387            err,
388            Kind::Help,
389            source_map_wrapper.source_map,
390            &source_map_wrapper.current_dir,
391        );
392    }
393}
394
395pub fn debug_all_modules(modules: &Modules, source_map: &SourceMap) {
396    for (_name, module) in modules.modules() {
397        debug_module(&module.definition_table, source_map);
398    }
399}
400pub fn debug_module(symbol_table: &DefinitionTable, source_map: &SourceMap) {
401    let source_map_lookup = SourceMapWrapper {
402        source_map,
403        current_dir: current_dir().unwrap(),
404    };
405    let pretty_printer = SourceMapDisplay {
406        source_map: &source_map_lookup,
407    };
408
409    let symbol_table_display = SymbolTableDisplay {
410        symbol_table,
411        source_map_display: &pretty_printer,
412    };
413
414    info!(
415        "module: {}{}",
416        formal_module_name(&symbol_table.module_path()),
417        symbol_table_display
418    );
419}
420
421fn debug_all_impl_functions(all_impls: &AssociatedImpls, source_map: &mut SourceMap) {
422    let source_map_lookup = SourceMapWrapper {
423        source_map,
424        current_dir: current_dir().unwrap(),
425    };
426    let pretty_printer = SourceMapDisplay {
427        source_map: &source_map_lookup,
428    };
429
430    let symbol_table_display = ImplsDisplay {
431        all_impls,
432        source_map: &pretty_printer,
433    };
434
435    info!("impls: {}", symbol_table_display);
436}
437
438#[must_use]
439pub fn compile_string(script: &str, run_mode: &RunMode) -> (Program, ModuleRef, SourceMap) {
440    let mut source_map = SourceMap::new(&SeqMap::default()).unwrap();
441    let file_id = 0xffff;
442
443    if let Some(swamp_home) = swamp_registry_path(run_mode) {
444        source_map.add_mount("registry", &swamp_home).unwrap();
445    }
446
447    source_map.add_mount("crate", Path::new("/tmp/")).unwrap();
448    source_map.add_to_cache("crate", Path::new("test.swamp"), script, file_id);
449
450    let resolved_path_str = vec!["crate".to_string(), "test".to_string()];
451    let compile_options = CompileOptions {
452        show_semantic: false,
453        show_modules: false,
454        show_errors: true,
455        show_warnings: true,
456        show_hints: false,
457        show_information: false,
458        show_types: false,
459        allow_unsafe: false,
460    };
461    let program =
462        bootstrap_and_compile(&mut source_map, &resolved_path_str, &compile_options).unwrap();
463    let main_module = program.modules.get(&resolved_path_str).unwrap().clone();
464
465    (program, main_module, source_map)
466}