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 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 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 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 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 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 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}