Skip to main content

mysz_core/
compiler.rs

1use crate::backend::clback;
2use crate::backend::llvmback::LlvmBackend;
3use crate::ir::irgen::IRGen;
4use crate::ir::tac::Instruction;
5use crate::lex::lexer::Lexer;
6use crate::parse::parser::Parser as myszparser;
7use crate::parse::parsing::{Identifier, Parameter, Program, Stmt, Type};
8use crate::semantics::analyser::{Analyser, AnalyserError};
9use crate::semantics::analysis::FunctionSignature;
10use crate::utils::ctx::{CompilerCtx, CompilerTarget};
11use clap::builder::OsStr;
12use cranelift::codegen::Context as clContext;
13use cranelift_frontend::FunctionBuilderContext as clFunctionBuilderContext;
14use inkwell::OptimizationLevel;
15use inkwell::context::Context as inkContext;
16use inkwell::targets::{
17    CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetMachine,
18};
19use serde_derive::Serialize;
20use std::collections::{HashMap, HashSet};
21use std::fs::File;
22use std::io::{Read, Write};
23use std::path::{Path, PathBuf};
24use std::rc::Rc;
25
26#[derive(Serialize)]
27struct JsonError {
28    file: String,
29    line: usize,
30    column: usize,
31    message: String,
32    severity: String,
33}
34
35fn json_error_from_parser_error(err: &crate::parse::parsing::ParserError) -> JsonError {
36    JsonError {
37        file: err.location.file.to_string(),
38        line: err.location.line,
39        column: err.location.col,
40        message: err.message.clone(),
41        severity: "error".to_string(),
42    }
43}
44
45fn json_error_from_string(file: &str, message: &str) -> JsonError {
46    JsonError {
47        file: file.to_string(),
48        line: 0,
49        column: 0,
50        message: message.to_string(),
51        severity: "error".to_string(),
52    }
53}
54
55fn json_error_from_analyser_error(err: &AnalyserError) -> JsonError {
56    let (location, message) = match err {
57        AnalyserError::TypeError { location, message } => (location, message),
58        AnalyserError::SemanticError { location, message } => (location, message),
59    };
60    JsonError {
61        file: location.file.to_string(),
62        line: location.line,
63        column: location.col,
64        message: message.clone(),
65        severity: "error".to_string(),
66    }
67}
68
69type SourceMap = HashMap<String, String>;
70
71fn find_module_file(module_path: &[String], search_paths: &[PathBuf]) -> Option<PathBuf> {
72    let mut relative_path = PathBuf::new();
73    for segment in module_path {
74        relative_path.push(segment);
75    }
76    relative_path.set_extension("mysz");
77
78    for base in search_paths {
79        let full_path = base.join(&relative_path);
80        if full_path.exists() && full_path.is_file() {
81            return Some(full_path);
82        }
83    }
84
85    let cwd = std::env::current_dir().ok()?;
86    let full_path = cwd.join(&relative_path);
87    if full_path.exists() && full_path.is_file() {
88        return Some(full_path);
89    }
90
91    None
92}
93
94fn format_error_with_location(
95    file_path: &str,
96    line_num: usize,
97    column: usize,
98    message: &str,
99    source: Option<&str>,
100) -> String {
101    let source_lines: Vec<&str> = source.map(|s| s.lines().collect()).unwrap_or_default();
102    let source_line = if line_num > 0 && line_num <= source_lines.len() {
103        source_lines[line_num - 1]
104    } else {
105        ""
106    };
107
108    let column_offset = if column > 0 { column - 1 } else { 0 };
109
110    format!(
111        "  --> {}:{}:{}\n      {}\n      {}{}\n      {}",
112        file_path,
113        line_num,
114        column,
115        source_line,
116        " ".repeat(column_offset),
117        "^",
118        message
119    )
120}
121
122fn format_simple_error(file_path: &Path, message: &str) -> String {
123    format!("  --> {}\n      {}", file_path.display(), message)
124}
125
126fn format_module_error(module_path: &str, message: &str) -> String {
127    format!("  --> module '{}'\n      {}", module_path, message)
128}
129
130fn format_parser_errors(errors: &[crate::parse::parsing::ParserError], source: &str) -> String {
131    let source_lines: Vec<&str> = source.lines().collect();
132    let mut error_messages = Vec::new();
133
134    for err in errors {
135        let location = &err.location;
136        let line_num = location.line;
137        let column = location.col;
138        let file = location.file.clone();
139        let message = &err.message;
140
141        let source_line = if line_num > 0 && line_num <= source_lines.len() {
142            source_lines[line_num - 1]
143        } else {
144            ""
145        };
146
147        let column_offset = if column > 0 { column - 1 } else { 0 };
148
149        error_messages.push(format!(
150            "  --> {}:{}:{}\n      {}\n      {}{}\n      {}",
151            file,
152            line_num,
153            column,
154            source_line,
155            " ".repeat(column_offset),
156            "^",
157            message
158        ));
159    }
160
161    error_messages.join("\n")
162}
163
164fn parse_and_flatten<P: AsRef<Path>>(
165    input_path: P,
166    custom_search_paths: &[PathBuf],
167    json_output: bool,
168) -> Result<(Program, SourceMap), String> {
169    let input_path = input_path.as_ref().canonicalize().map_err(|e| {
170        if json_output {
171            let json_err = json_error_from_string(
172                &input_path.as_ref().display().to_string(),
173                &format!("Failed to canonicalize path: {}", e),
174            );
175            serde_json::to_string(&json_err).unwrap()
176        } else {
177            format_simple_error(
178                input_path.as_ref(),
179                &format!("Failed to canonicalize path: {}", e),
180            )
181        }
182    })?;
183
184    let mut search_paths = Vec::new();
185    if let Some(parent) = input_path.parent() {
186        search_paths.push(parent.to_path_buf());
187    }
188    search_paths.extend_from_slice(custom_search_paths);
189
190    let (source, tokens) = read_and_lex_file(&input_path, json_output)?;
191
192    let mut parser = myszparser::new(tokens);
193    parser.parse();
194
195    if !parser.parser_errs.is_empty() {
196        if json_output {
197            let json_errors: Vec<JsonError> = parser
198                .parser_errs
199                .iter()
200                .map(json_error_from_parser_error)
201                .collect();
202            return Err(serde_json::to_string(&json_errors).unwrap());
203        } else {
204            let error_report = format_parser_errors(&parser.parser_errs, &source);
205            return Err(format!("Parser errors:\n{}", error_report));
206        }
207    }
208
209    let mut visiting = HashSet::new();
210    let mut processed = HashSet::new();
211    visiting.insert(input_path.clone());
212
213    let mut sources: SourceMap = HashMap::new();
214    sources.insert(input_path.display().to_string(), source.clone());
215
216    let flattened_statements = flatten_program_statements(
217        parser.ast.statements,
218        &search_paths,
219        &mut visiting,
220        &mut processed,
221        &mut sources,
222        json_output,
223        &input_path,
224    )?;
225
226    let program = Program {
227        statements: flattened_statements,
228    };
229
230    Ok((program, sources))
231}
232
233pub fn check_root_file<'a, P: AsRef<Path>>(ctx: CompilerCtx<'a, P>) -> Result<(), String> {
234    let (program, sources) = parse_and_flatten(&ctx.input_path, ctx.search_paths, ctx.output_json)?;
235    let file_path = ctx.input_path.as_ref().canonicalize().unwrap();
236    let root_source = sources
237        .get(&file_path.display().to_string())
238        .map(|s| s.as_str());
239
240    let mut analyser = Analyser::new();
241    if let Err(err) = analyser.analyse(&program) {
242        if ctx.output_json {
243            let json_err = json_error_from_analyser_error(&err);
244            return Err(serde_json::to_string(&json_err).unwrap());
245        } else {
246            let formatted = format_analyser_error(&err, &sources, root_source);
247            return Err(format!("Semantic error:\n{}", formatted));
248        }
249    }
250
251    Ok(())
252}
253
254fn format_analyser_error(
255    err: &AnalyserError,
256    sources: &SourceMap,
257    root_source: Option<&str>,
258) -> String {
259    let (location, message) = match err {
260        AnalyserError::TypeError { location, message } => (location, message),
261        AnalyserError::SemanticError { location, message } => (location, message),
262    };
263    let file_path_str = location.file.as_ref();
264    let source = sources
265        .get(file_path_str)
266        .map(|s| s.as_str())
267        .or(root_source);
268    format_error_with_location(file_path_str, location.line, location.col, message, source)
269}
270
271fn read_and_lex_file(
272    file_path: &Path,
273    json_output: bool,
274) -> Result<(String, Vec<crate::lex::lexing::Token>), String> {
275    let mut file = File::open(file_path).map_err(|e| {
276        if json_output {
277            let json_err = json_error_from_string(
278                &file_path.display().to_string(),
279                &format!("Failed to open file: {}", e),
280            );
281            serde_json::to_string(&json_err).unwrap()
282        } else {
283            format_simple_error(file_path, &format!("Failed to open file: {}", e))
284        }
285    })?;
286    let mut source = String::new();
287    file.read_to_string(&mut source).map_err(|e| {
288        if json_output {
289            let json_err = json_error_from_string(
290                &file_path.display().to_string(),
291                &format!("Failed to read file: {}", e),
292            );
293            serde_json::to_string(&json_err).unwrap()
294        } else {
295            format_simple_error(file_path, &format!("Failed to read file: {}", e))
296        }
297    })?;
298
299    let file_id: Rc<str> = Rc::from(file_path.display().to_string());
300    let mut lexer = Lexer::new(source.clone(), file_id);
301    let res = lexer.lex();
302
303    if let Err(err) = res {
304        if json_output {
305            let json_err = json_error_from_string(
306                &file_path.display().to_string(),
307                &format!("Lexer error: {}", err),
308            );
309            return Err(serde_json::to_string(&json_err).unwrap());
310        } else {
311            return Err(format_simple_error(
312                file_path,
313                &format!("Lexer error: {}", err),
314            ));
315        }
316    }
317
318    Ok((source, lexer.tokens))
319}
320
321fn flatten_program_statements(
322    statements: Vec<Stmt>,
323    search_paths: &[PathBuf],
324    visiting: &mut HashSet<PathBuf>,
325    processed: &mut HashSet<PathBuf>,
326    sources: &mut SourceMap,
327    json_output: bool,
328    root_file_path: &Path,
329) -> Result<Vec<Stmt>, String> {
330    let mut flattened = Vec::new();
331
332    for stmt in statements {
333        if let Stmt::Use { path } = stmt {
334            let module_path_str = path.join("::");
335            let resolved_path = find_module_file(&path, search_paths).ok_or_else(|| {
336                let msg = format!(
337                    "Could not find module '{}' in search paths or CWD.",
338                    module_path_str
339                );
340                if json_output {
341                    let json_err =
342                        json_error_from_string(&root_file_path.display().to_string(), &msg);
343                    serde_json::to_string(&json_err).unwrap()
344                } else {
345                    format_module_error(&module_path_str, &msg)
346                }
347            })?;
348
349            if visiting.contains(&resolved_path) {
350                let msg = "Cyclic dependency detected! Module imports itself.".to_string();
351                if json_output {
352                    let json_err =
353                        json_error_from_string(&root_file_path.display().to_string(), &msg);
354                    return Err(serde_json::to_string(&json_err).unwrap());
355                } else {
356                    return Err(format_module_error(&module_path_str, &msg));
357                }
358            }
359
360            if processed.contains(&resolved_path) {
361                continue;
362            }
363
364            visiting.insert(resolved_path.clone());
365
366            let (source, tokens) = read_and_lex_file(&resolved_path, json_output)?;
367            sources.insert(resolved_path.display().to_string(), source.clone());
368
369            let mut parser = myszparser::new(tokens);
370            parser.parse();
371
372            if !parser.parser_errs.is_empty() {
373                if json_output {
374                    let json_errors: Vec<JsonError> = parser
375                        .parser_errs
376                        .iter()
377                        .map(json_error_from_parser_error)
378                        .collect();
379                    return Err(serde_json::to_string(&json_errors).unwrap());
380                } else {
381                    let error_report = format_parser_errors(&parser.parser_errs, &source);
382                    return Err(format!(
383                        "Parser errors in module '{}':\n{}",
384                        module_path_str, error_report
385                    ));
386                }
387            }
388
389            let module_stmts = flatten_program_statements(
390                parser.ast.statements,
391                search_paths,
392                visiting,
393                processed,
394                sources,
395                json_output,
396                root_file_path,
397            )?;
398
399            flattened.extend(module_stmts);
400            visiting.remove(&resolved_path);
401            processed.insert(resolved_path);
402        } else {
403            flattened.push(stmt);
404        }
405    }
406
407    Ok(flattened)
408}
409
410pub fn compile_root_file<'a, P: AsRef<Path>>(
411    ctx: CompilerCtx<'a, P>,
412    output_filename: &str,
413) -> Result<(), String> {
414    let input_path = ctx.input_path.as_ref().canonicalize().map_err(|e| {
415        format_simple_error(
416            ctx.input_path.as_ref(),
417            &format!("Failed to canonicalize path: {}", e),
418        )
419    })?;
420
421    let mut search_paths = Vec::new();
422    if let Some(parent) = input_path.parent() {
423        search_paths.push(parent.to_path_buf());
424    }
425    search_paths.extend_from_slice(ctx.search_paths);
426
427    let (source, tokens) = read_and_lex_file(&input_path, ctx.output_json)?;
428
429    let mut parser = myszparser::new(tokens);
430    parser.parse();
431
432    if !parser.parser_errs.is_empty() {
433        if ctx.output_json {
434            let json_errors: Vec<JsonError> = parser
435                .parser_errs
436                .iter()
437                .map(json_error_from_parser_error)
438                .collect();
439            return Err(serde_json::to_string(&json_errors).unwrap());
440        } else {
441            let error_report = format_parser_errors(&parser.parser_errs, &source);
442            return Err(format!("Parser errors:\n{}", error_report));
443        }
444    }
445
446    let mut visiting = HashSet::new();
447    let mut processed = HashSet::new();
448    visiting.insert(input_path.clone());
449
450    let mut sources: SourceMap = HashMap::new();
451    sources.insert(input_path.display().to_string(), source.clone());
452
453    let flattened_statements = flatten_program_statements(
454        parser.ast.statements,
455        &search_paths,
456        &mut visiting,
457        &mut processed,
458        &mut sources,
459        ctx.output_json,
460        input_path.as_path(),
461    )?;
462
463    let program = Program {
464        statements: flattened_statements,
465    };
466
467    compile_ast_program(
468        &program,
469        output_filename,
470        &sources,
471        &input_path,
472        ctx.output_json,
473        &ctx.target,
474    )
475}
476
477pub fn compile_ast_program(
478    program: &Program,
479    output_filename: &str,
480    sources: &SourceMap,
481    file_path: &Path,
482    json_output: bool,
483    target: &CompilerTarget,
484) -> Result<(), String> {
485    let root_source = sources
486        .get(&file_path.display().to_string())
487        .map(|s| s.as_str());
488
489    let filename: Rc<str> = Rc::from(
490        file_path
491            .file_name()
492            .unwrap_or(&OsStr::default())
493            .to_string_lossy()
494            .as_ref(),
495    );
496
497    // ----------------------------
498    // Semantic analysis
499    // ----------------------------
500
501    let mut analyser = Analyser::new();
502
503    if let Err(err) = analyser.analyse(program) {
504        if json_output {
505            let location = match err.clone() {
506                AnalyserError::SemanticError { location, .. }
507                | AnalyserError::TypeError { location, .. } => location,
508            };
509
510            let message = match err.clone() {
511                AnalyserError::SemanticError { message, .. }
512                | AnalyserError::TypeError { message, .. } => message,
513            };
514
515            let json_err = JsonError {
516                file: location.file.to_string(),
517                line: location.line,
518                column: location.col,
519                message: message.to_string(),
520                severity: "error".to_string(),
521            };
522
523            let json_str = serde_json::to_string(&json_err)
524                .map_err(|e| format!("Failed to serialize error: {}", e))?;
525
526            return Err(json_str);
527        } else {
528            let formatted = format_analyser_error(&err, sources, root_source);
529
530            return Err(format!("Semantic error:\n{}", formatted));
531        }
532    }
533
534    // ----------------------------
535    // IR generation
536    // ----------------------------
537
538    let mut irgen = IRGen::new();
539    irgen.analyser_constants = analyser.constants.clone();
540
541    for (name, sig) in &analyser.structs {
542        if !sig.generic_params.is_empty() {
543            let fields_vec: Vec<Parameter> = sig
544                .fields
545                .iter()
546                .map(|(fname, ftype)| Parameter {
547                    name: Identifier {
548                        value: fname.clone(),
549                        location: crate::utils::location::Location::new_with_file(
550                            0,
551                            0,
552                            filename.clone(),
553                        ),
554                    },
555                    ptype: Some(ftype.clone()),
556                    is_variadic: false,
557                })
558                .collect();
559
560            irgen
561                .struct_blueprints
562                .insert(name.clone(), (sig.generic_params.clone(), fields_vec));
563        }
564    }
565
566    irgen.gen_program(program);
567
568    // irgen.dump();
569
570    // ----------------------------
571    // Remove duplicate definitions
572    // ----------------------------
573
574    let mut tac_instructions = Vec::new();
575    let mut seen_labels = HashSet::new();
576    let mut skip_current_duplicate = false;
577
578    for inst in irgen.code.iter().cloned() {
579        match &inst {
580            Instruction::FunctionLabel(name) => {
581                if seen_labels.contains(name) {
582                    skip_current_duplicate = true;
583                } else {
584                    seen_labels.insert(name.clone());
585                    skip_current_duplicate = false;
586                    tac_instructions.push(inst);
587                }
588            }
589
590            _ => {
591                if !skip_current_duplicate {
592                    tac_instructions.push(inst);
593                }
594            }
595        }
596    }
597
598    // ----------------------------
599    // Public functions
600    // ----------------------------
601
602    let mut public_functions = HashSet::new();
603
604    for stmt in &program.statements {
605        if let Stmt::Function { name, public, .. } = stmt
606            && *public
607        {
608            public_functions.insert(name.value.clone());
609        }
610    }
611
612    // ----------------------------
613    // Backend selection
614    // ----------------------------
615
616    match target {
617        CompilerTarget::Cranelift => compile_with_cranelift(
618            irgen,
619            analyser.functions.clone(),
620            tac_instructions,
621            public_functions,
622            file_path,
623            output_filename,
624        ),
625
626        CompilerTarget::Llvm => compile_with_llvm(
627            irgen,
628            analyser.functions.clone(),
629            tac_instructions,
630            public_functions,
631            file_path,
632            output_filename,
633        ),
634    }
635}
636
637fn compile_with_cranelift(
638    irgen: IRGen,
639    functions: HashMap<String, FunctionSignature>,
640    tac_instructions: Vec<Instruction>,
641    public_functions: HashSet<String>,
642    file_path: &Path,
643    output_filename: &str,
644) -> Result<(), String> {
645    let mut unique_function_names = HashSet::new();
646
647    for inst in &tac_instructions {
648        if let Instruction::FunctionLabel(name) = inst {
649            unique_function_names.insert(name.clone());
650        }
651    }
652
653    let mut backend = clback::CraneliftBackend::new(irgen.struct_defs, functions);
654
655    backend.register_defined_functions(unique_function_names.iter().cloned());
656
657    backend.scan_externs(&tac_instructions);
658
659    let instruction_refs: Vec<&Instruction> = tac_instructions.iter().collect();
660
661    backend.pre_declare_strings(&instruction_refs);
662
663    for func_name in unique_function_names {
664        let is_public = public_functions.contains(&func_name);
665
666        let func_instructions: Vec<&Instruction> = tac_instructions
667            .iter()
668            .skip_while(|inst| {
669                !matches!(
670                    inst,
671                    Instruction::FunctionLabel(name)
672                        if name == &func_name
673                )
674            })
675            .skip(1)
676            .take_while(|inst| !matches!(inst, Instruction::FunctionLabel(_)))
677            .collect();
678
679        if !func_instructions.is_empty() {
680            let mut ctx = clContext::new();
681            let mut func_ctx = clFunctionBuilderContext::new();
682
683            backend.compile_function(
684                &func_name,
685                is_public,
686                &func_instructions,
687                &mut ctx,
688                &mut func_ctx,
689                &irgen.var_types,
690            );
691        }
692    }
693
694    let product = backend.finish();
695
696    let emit_result = product.emit().map_err(|e| {
697        format_simple_error(file_path, &format!("Failed to emit object code: {}", e))
698    })?;
699
700    write_output_file(file_path, output_filename, &emit_result)
701}
702
703fn is_generic_type(ty: &Type) -> bool {
704    matches!(
705        ty,
706        Type::GenericParam(_)
707            | Type::GenericInstance { .. }
708            | Type::VariadicPack { .. }
709            | Type::Any
710    )
711}
712
713#[allow(unused)]
714fn compile_with_llvm(
715    irgen: IRGen,
716    functions: HashMap<String, FunctionSignature>,
717    tac_instructions: Vec<Instruction>,
718    public_functions: HashSet<String>,
719    file_path: &Path,
720    output_filename: &str,
721) -> Result<(), String> {
722
723    let concrete_functions: HashMap<String, FunctionSignature> = functions
724        .into_iter()
725        .filter(|(_, sig)| {
726            !sig.param_types.iter().any(is_generic_type) && !is_generic_type(&sig.return_type)
727        })
728        .collect();
729
730    let context = inkContext::create();
731
732    Target::initialize_native(&InitializationConfig::default()).map_err(|e| e.to_string())?;
733
734    let modname = file_path.file_name();
735
736    if modname.is_none() {
737        return Err("filepath doesn't containe a file".to_string());
738    }
739
740    let mut backend = LlvmBackend::new(
741        &context,
742        &modname.unwrap().to_string_lossy(),
743        irgen.var_types,
744        irgen.struct_defs,
745        concrete_functions,
746    );
747
748    backend.compile(&tac_instructions)?;
749
750    backend.verify()?;
751
752    let target_triple = TargetMachine::get_default_triple();
753
754    let target = Target::from_triple(&target_triple).map_err(|e| e.to_string())?;
755
756    let target_machine = target
757        .create_target_machine(
758            &target_triple,
759            "generic",
760            "",
761            OptimizationLevel::None,
762            RelocMode::PIC,
763            CodeModel::Default,
764        )
765        .ok_or_else(|| "failed to create LLVM target machine".to_string())?;
766
767    target_machine
768        .write_to_file(
769            backend.module(),
770            FileType::Object,
771            Path::new(&output_filename),
772        )
773        .map_err(|e| e.to_string())?;
774
775    // backend.print_ir();
776
777    Ok(())
778}
779
780fn write_output_file(file_path: &Path, output_filename: &str, bytes: &[u8]) -> Result<(), String> {
781    let mut file = File::create(output_filename).map_err(|e| {
782        format_simple_error(
783            file_path,
784            &format!("Failed to create output file '{}': {}", output_filename, e),
785        )
786    })?;
787
788    file.write_all(bytes).map_err(|e| {
789        format_simple_error(
790            file_path,
791            &format!(
792                "Failed to write to output file '{}': {}",
793                output_filename, e
794            ),
795        )
796    })?;
797
798    Ok(())
799}