1use std::{
20 collections::HashMap,
21 path::PathBuf,
22 sync::{Arc, RwLock},
23};
24
25use onion_vm::{
26 GC,
27 lambda::{
28 runnable::{Runnable, RuntimeError, StepResult},
29 scheduler::scheduler::Scheduler,
30 },
31 types::{
32 lambda::{
33 definition::{LambdaBody, LambdaType, OnionLambdaDefinition},
34 launcher::OnionLambdaRunnableLauncher,
35 parameter::LambdaParameter,
36 vm_instructions::{
37 instruction_set::VMInstructionPackage,
38 ir::{DebugInfo, Functions, IR},
39 ir_translator::{IRTranslator, IRTranslatorError},
40 },
41 },
42 object::{OnionObject, OnionObjectCell, OnionStaticObject},
43 tuple::OnionTuple,
44 },
45 unwrap_object,
46 utils::fastmap::{OnionFastMap, OnionKeyPool},
47};
48
49use crate::{
50 diagnostics::{Diagnostic, SourceLocation, collector::DiagnosticCollector},
51 ir_generator::ir_generator::{IRGenerator, NameSpace},
52 parser::{
53 Source,
54 analyzer::{analyze_ast, auto_capture_and_rebuild},
55 ast::{ASTNode, ASTNodeType},
56 comptime::{OnionASTObject, ast_bindings, native::wrap_native_function},
57 },
58 utils::cycle_detector::CycleDetector,
59};
60
61#[derive(Debug)]
65pub struct ComptimeState {
66 builtin_definitions: Arc<HashMap<String, OnionStaticObject>>,
67 user_definitions: Arc<RwLock<HashMap<String, OnionStaticObject>>>,
68}
69
70impl ComptimeState {
71 pub fn inject_state(
79 &self,
80 capture: &mut OnionFastMap<Box<str>, OnionObject>,
81 ) -> Result<(), RuntimeError> {
82 for (key, value) in self
85 .user_definitions
86 .read()
87 .map_err(|e| RuntimeError::BorrowError(e.to_string().into()))?
88 .iter()
89 {
90 capture.push(key.as_str(), value.weak().clone());
91 }
92
93 for (key, value) in self.builtin_definitions.iter() {
94 capture.push(key.as_str(), value.weak().clone());
95 }
96
97 Ok(())
98 }
99}
100
101#[derive(Debug, Clone)]
105pub enum ComptimeDiagnostic {
106 RuntimeError(Option<SourceLocation>, RuntimeError),
108 BorrowError(Option<SourceLocation>, String),
110 IRTranslatorError(Option<SourceLocation>, IRTranslatorError),
112 CustomError(Option<SourceLocation>, String),
114 CustomWarning(Option<SourceLocation>, String),
116}
117
118impl Diagnostic for ComptimeDiagnostic {
119 fn severity(&self) -> crate::diagnostics::ReportSeverity {
120 match self {
121 ComptimeDiagnostic::RuntimeError(_, _) => crate::diagnostics::ReportSeverity::Error,
122 ComptimeDiagnostic::BorrowError(_, _) => crate::diagnostics::ReportSeverity::Error,
123 ComptimeDiagnostic::IRTranslatorError(_, _) => {
124 crate::diagnostics::ReportSeverity::Error
125 }
126 ComptimeDiagnostic::CustomError(_, _) => crate::diagnostics::ReportSeverity::Error,
127 ComptimeDiagnostic::CustomWarning(_, _) => crate::diagnostics::ReportSeverity::Warning,
128 }
129 }
130
131 fn title(&self) -> String {
132 match self {
133 ComptimeDiagnostic::RuntimeError(_, _) => "Runtime Error".to_string(),
134 ComptimeDiagnostic::BorrowError(_, _) => "Borrow Error".to_string(),
135 ComptimeDiagnostic::IRTranslatorError(_, _) => "IR Translator Error".to_string(),
136 ComptimeDiagnostic::CustomError(_, _) => "Custom Error".to_string(),
137 ComptimeDiagnostic::CustomWarning(_, _) => "Custom Warning".to_string(),
138 }
139 }
140
141 fn message(&self) -> String {
142 match self {
143 ComptimeDiagnostic::RuntimeError(_, err) => format!("Runtime Error: {}", err),
144 ComptimeDiagnostic::BorrowError(_, msg) => format!("Borrow Error: {}", msg),
145 ComptimeDiagnostic::IRTranslatorError(_, err) => {
146 format!("IR Translator Error: {}", err)
147 }
148 ComptimeDiagnostic::CustomError(_, msg) => msg.clone(),
149 ComptimeDiagnostic::CustomWarning(_, msg) => msg.clone(),
150 }
151 }
152
153 fn location(&self) -> Option<crate::diagnostics::SourceLocation> {
154 match self {
155 ComptimeDiagnostic::RuntimeError(loc, _) => loc.clone(),
156 ComptimeDiagnostic::BorrowError(loc, _) => loc.clone(),
157 ComptimeDiagnostic::IRTranslatorError(loc, _) => loc.clone(),
158 ComptimeDiagnostic::CustomError(loc, _) => loc.clone(),
159 ComptimeDiagnostic::CustomWarning(loc, _) => loc.clone(),
160 }
161 }
162
163 fn help(&self) -> Option<String> {
164 None
165 }
166
167 fn copy(&self) -> Box<dyn Diagnostic> {
168 Box::new(self.clone())
169 }
170}
171
172#[derive(Debug)]
177pub struct ComptimeSolver {
178 state: ComptimeState,
179 diagnostics: Arc<RwLock<DiagnosticCollector>>,
180}
181
182impl ComptimeSolver {
183 pub fn new(
192 user_definitions: Arc<RwLock<HashMap<String, OnionStaticObject>>>,
193 import_cycle_detector: CycleDetector<PathBuf>,
194 ) -> Self {
195 let user_definitions_ref = user_definitions.clone();
196
197 let diagnostics = Arc::new(RwLock::new(DiagnosticCollector::new()));
198 let diagnostics_ref = diagnostics.clone();
199
200 let mut builtin_definitions = HashMap::new();
201 builtin_definitions.insert(
202 "def".into(),
203 wrap_native_function(
204 LambdaParameter::top("def"),
205 OnionFastMap::default(),
206 "comptime::def",
207 OnionKeyPool::create(vec!["def".into()]),
208 Arc::new(
209 move |argument: &OnionFastMap<Box<str>, OnionStaticObject>,
210 _gc: &mut GC<OnionObjectCell>|
211 -> Result<OnionStaticObject, RuntimeError> {
212 match argument.get("def") {
213 Some(v) => v.weak().with_data(|v| match v {
214 OnionObject::Pair(pair) => {
215 let k = pair.get_key().to_string(&vec![])?;
216 let v = pair.get_value().stabilize();
217 let mut state = user_definitions_ref.write().map_err(|e| {
218 RuntimeError::BorrowError(e.to_string().into())
219 })?;
220 state.insert(k, v);
221 Ok(OnionObject::Undefined(None).stabilize())
222 }
223 _ => Err(RuntimeError::InvalidType("Expect Pair for 'def'".into())),
224 }),
225 None => {
226 Err(RuntimeError::DetailedError("No 'def' in arguments".into()))
227 }
228 }
229 },
230 ),
231 ),
232 );
233
234 let user_definitions_ref = user_definitions.clone();
235 builtin_definitions.insert(
236 "undef".into(),
237 wrap_native_function(
238 LambdaParameter::top("name"),
239 OnionFastMap::default(),
240 "comptime::undef",
241 OnionKeyPool::create(vec!["name".into()]),
242 Arc::new(
243 move |argument: &OnionFastMap<Box<str>, OnionStaticObject>,
244 _gc: &mut GC<OnionObjectCell>|
245 -> Result<OnionStaticObject, RuntimeError> {
246 match argument.get("name") {
247 Some(v) => v.weak().with_data(|v| match v {
248 OnionObject::String(name) => {
249 let mut state = user_definitions_ref.write().map_err(|e| {
250 RuntimeError::BorrowError(e.to_string().into())
251 })?;
252 state.remove(name.as_ref());
253 Ok(OnionObject::Undefined(None).stabilize())
254 }
255 _ => Err(RuntimeError::InvalidType(
256 "Expect String for 'name'".into(),
257 )),
258 }),
259 None => {
260 Err(RuntimeError::DetailedError("No 'name' in arguments".into()))
261 }
262 }
263 },
264 ),
265 ),
266 );
267
268 let user_definitions_ref = user_definitions.clone();
269 builtin_definitions.insert(
270 "ifdef".into(),
271 wrap_native_function(
272 LambdaParameter::top("name"),
273 OnionFastMap::default(),
274 "comptime::ifdef",
275 OnionKeyPool::create(vec!["name".into()]),
276 Arc::new(
277 move |argument: &OnionFastMap<Box<str>, OnionStaticObject>,
278 _gc: &mut GC<OnionObjectCell>|
279 -> Result<OnionStaticObject, RuntimeError> {
280 match argument.get("name") {
281 Some(v) => v.weak().with_data(|v| match v {
282 OnionObject::String(name) => {
283 let state = user_definitions_ref.read().map_err(|e| {
284 RuntimeError::BorrowError(e.to_string().into())
285 })?;
286 Ok(OnionObject::Boolean(state.contains_key(name.as_ref()))
287 .stabilize())
288 }
289 _ => Err(RuntimeError::InvalidType(
290 "Expect String for 'name'".into(),
291 )),
292 }),
293 None => {
294 Err(RuntimeError::DetailedError("No 'name' in arguments".into()))
295 }
296 }
297 },
298 ),
299 ),
300 );
301
302 builtin_definitions.insert(
303 "required".into(),
304 wrap_native_function(
305 LambdaParameter::top("name"),
306 OnionFastMap::default(),
307 "comptime::required",
308 OnionKeyPool::create(vec!["name".into()]),
309 Arc::new(
310 move |argument: &OnionFastMap<Box<str>, OnionStaticObject>,
311 _gc: &mut GC<OnionObjectCell>|
312 -> Result<OnionStaticObject, RuntimeError> {
313 match argument.get("name") {
314 Some(v) => v.weak().with_data(|v| match v {
315 OnionObject::String(name) => Ok(OnionObject::Custom(Arc::new(
316 OnionASTObject::new(ASTNode {
317 node_type: ASTNodeType::Required(name.to_string()),
318 source_location: None,
319 children: vec![],
320 }),
321 ))
322 .stabilize()),
323 _ => Err(RuntimeError::InvalidType(
324 "Expect String for 'name'".into(),
325 )),
326 }),
327 None => {
328 Err(RuntimeError::DetailedError("No 'name' in arguments".into()))
329 }
330 }
331 },
332 ),
333 ),
334 );
335
336 let cloned_ref = user_definitions.clone();
337 builtin_definitions.insert(
338 "include".into(),
339 wrap_native_function(
340 LambdaParameter::top("path"),
341 OnionFastMap::default(),
342 "comptime::include",
343 OnionKeyPool::create(vec!["path".into()]),
344 Arc::new(
345 move |argument: &OnionFastMap<Box<str>, OnionStaticObject>,
346 _gc: &mut GC<OnionObjectCell>|
347 -> Result<OnionStaticObject, RuntimeError> {
348 match argument.get("path") {
349 Some(v) => v.weak().with_data(|v| match v {
350 OnionObject::String(path) => {
351 let abs_path =
352 match import_cycle_detector.last() {
353 Some(file) => {
354 let dir = file.parent().unwrap_or(file.as_path());
355 let mut target_path = PathBuf::from(path.as_ref());
356 if !target_path.is_absolute() {
357 target_path = dir.join(&target_path);
358 }
359 target_path.canonicalize().map_err(|e| {
360 RuntimeError::DetailedError(
361 format!("Failed to canonicalize path: {e}")
362 .into(),
363 )
364 })?
365 }
366 None => {
367 let mut target_path = PathBuf::from(path.as_ref());
368 if !target_path.is_absolute() {
369 target_path =
370 std::env::current_dir()
371 .map_err(|e| {
372 RuntimeError::DetailedError(
373 format!("Failed to get current dir: {e}").into(),
374 )
375 })?
376 .join(&target_path);
377 }
378 target_path.canonicalize().map_err(|e| {
379 RuntimeError::DetailedError(
380 format!("Failed to canonicalize path: {e}")
381 .into(),
382 )
383 })?
384 }
385 };
386 let source = Source::from_file(&abs_path).map_err(|e| {
388 RuntimeError::DetailedError(
389 format!(
390 "Failed to read file {}: {e}",
391 abs_path.display()
392 )
393 .into(),
394 )
395 })?;
396
397 use crate::parser::ast::{ast_token_stream, build_ast};
399 use crate::parser::lexer::tokenizer;
400 let tokens = tokenizer::tokenize(&source);
401 let tokens = tokenizer::reject_comment(&tokens);
402 let gathered = ast_token_stream::from_stream(&tokens);
403
404 let mut collector = diagnostics_ref.write().map_err(|e| {
405 RuntimeError::BorrowError(e.to_string().into())
406 })?;
407
408 let ast =
409 build_ast(&mut collector, gathered).map_err(|_| {
410 RuntimeError::DetailedError(
411 "Solver failed to build AST".into(),
412 )
413 })?;
414
415 if collector.has_errors() {
416 return Err(RuntimeError::DetailedError(
417 "Solver produced errors while building AST".into(),
418 ));
419 }
420
421 let mut sub_solver = ComptimeSolver::new(
422 cloned_ref.clone(),
423 import_cycle_detector.enter(abs_path).map_err(|path| {
424 RuntimeError::DetailedError(
425 format!("Cyclic reference detected: {:?}", path)
426 .into(),
427 )
428 })?,
429 );
430
431 let result = sub_solver.solve(&ast);
432 for diagnostic in sub_solver
433 .diagnostics
434 .read()
435 .map_err(|e| {
436 RuntimeError::BorrowError(e.to_string().into())
437 })?
438 .diagnostics()
439 {
440 collector.report(diagnostic.copy());
441 }
442
443 if result.is_err() {
444 return Err(RuntimeError::DetailedError(
445 "Sub-solver failed".into(),
446 ));
447 }
448
449 if sub_solver
450 .diagnostics
451 .read()
452 .map_err(|e| {
453 RuntimeError::BorrowError(e.to_string().into())
454 })?
455 .has_errors()
456 {
457 return Err(RuntimeError::DetailedError(
458 "Sub-solver produced errors while executing expression"
459 .into(),
460 ));
461 }
462
463 Ok(OnionObject::Custom(Arc::new(OnionASTObject::new(
464 result.unwrap(),
465 )))
466 .consume_and_stabilize())
467 }
468 _ => Err(RuntimeError::InvalidType(
469 "Expect String for 'path'".into(),
470 )),
471 }),
472 None => {
473 Err(RuntimeError::DetailedError("No 'path' in arguments".into()))
474 }
475 }
476 },
477 ),
478 ),
479 );
480
481 let diagnostics_ref_for_error = diagnostics.clone();
482 builtin_definitions.insert(
483 "error".into(),
484 wrap_native_function(
485 LambdaParameter::top("message"),
486 OnionFastMap::default(),
487 "comptime::error",
488 OnionKeyPool::create(vec!["message".into()]),
489 Arc::new(
490 move |argument: &OnionFastMap<Box<str>, OnionStaticObject>,
491 _gc: &mut GC<OnionObjectCell>|
492 -> Result<OnionStaticObject, RuntimeError> {
493 match argument.get("message") {
494 Some(v) => v.weak().with_data(|v| match v {
495 OnionObject::String(message) => {
496 let mut collector =
497 diagnostics_ref_for_error.write().map_err(|e| {
498 RuntimeError::BorrowError(e.to_string().into())
499 })?;
500 collector.report(ComptimeDiagnostic::CustomError(
501 None, message.to_string(),
503 ));
504 Err(RuntimeError::DetailedError(
505 format!("Comptime error: {}", message).into(),
506 ))
507 }
508 _ => Err(RuntimeError::InvalidType(
509 "Expect String for 'message'".into(),
510 )),
511 }),
512 None => Err(RuntimeError::DetailedError(
513 "No 'message' in arguments".into(),
514 )),
515 }
516 },
517 ),
518 ),
519 );
520
521 let diagnostics_ref_for_warning = diagnostics.clone();
522 builtin_definitions.insert(
523 "warning".into(),
524 wrap_native_function(
525 LambdaParameter::top("message"),
526 OnionFastMap::default(),
527 "comptime::warning",
528 OnionKeyPool::create(vec!["message".into()]),
529 Arc::new(
530 move |argument: &OnionFastMap<Box<str>, OnionStaticObject>,
531 _gc: &mut GC<OnionObjectCell>|
532 -> Result<OnionStaticObject, RuntimeError> {
533 match argument.get("message") {
534 Some(v) => v.weak().with_data(|v| match v {
535 OnionObject::String(message) => {
536 let mut collector =
537 diagnostics_ref_for_warning.write().map_err(|e| {
538 RuntimeError::BorrowError(e.to_string().into())
539 })?;
540 collector.report(ComptimeDiagnostic::CustomWarning(
541 None, message.to_string(),
543 ));
544 Ok(OnionObject::Undefined(None).stabilize())
545 }
546 _ => Err(RuntimeError::InvalidType(
547 "Expect String for 'message'".into(),
548 )),
549 }),
550 None => Err(RuntimeError::DetailedError(
551 "No 'message' in arguments".into(),
552 )),
553 }
554 },
555 ),
556 ),
557 );
558
559 builtin_definitions.insert("ast".to_string(), ast_bindings::build_module());
560
561 ComptimeSolver {
562 state: ComptimeState {
563 builtin_definitions: Arc::new(builtin_definitions),
564 user_definitions,
565 },
566 diagnostics: diagnostics,
567 }
568 }
569
570 pub fn diagnostics(&self) -> &Arc<RwLock<DiagnosticCollector>> {
572 &self.diagnostics
573 }
574
575 #[stacksafe::stacksafe]
583 pub fn solve(&mut self, ast: &ASTNode) -> Result<ASTNode, ()> {
584 let mut iteration_result = ast.clone();
585 loop {
586 let mut children = Vec::new();
587 for child in &iteration_result.children {
588 children.push(self.solve(child)?);
589 }
590 let result = ASTNode {
591 node_type: iteration_result.node_type.clone(),
592 source_location: iteration_result.source_location.clone(),
593 children,
594 };
595 match self.expand(&result)? {
596 Some(expanded_ast) => {
597 if expanded_ast == iteration_result {
598 return Ok(expanded_ast);
599 } else {
600 iteration_result = expanded_ast;
601 }
602 }
603 None => {
604 return Ok(result);
605 }
606 }
607 }
608 }
609
610 pub fn expand(&mut self, ast: &ASTNode) -> Result<Option<ASTNode>, ()> {
620 let ASTNodeType::Comptime = ast.node_type else {
621 return Ok(None);
622 };
623
624 let mut collector = self
625 .diagnostics
626 .write()
627 .expect("Failed to lock diagnostics collector");
628 let mut context = vec![];
629 context.extend(
630 self.state
631 .builtin_definitions
632 .iter()
633 .map(|(name, _)| ASTNode {
634 node_type: ASTNodeType::Required(name.clone()),
635 source_location: None,
636 children: vec![],
637 })
638 .collect::<Vec<_>>(),
639 );
640 context.extend(
641 self.state
642 .user_definitions
643 .read()
644 .map_err(|e| {
645 collector.report(ComptimeDiagnostic::BorrowError(
646 ast.source_location.clone(),
647 e.to_string(),
648 ));
649 ()
650 })?
651 .iter()
652 .map(|(name, _)| ASTNode {
653 node_type: ASTNodeType::Required(name.clone()),
654 source_location: None,
655 children: vec![],
656 })
657 .collect::<Vec<_>>(),
658 );
659 context.extend(ast.children.iter().cloned());
660
661 let ast_with_context = ASTNode {
662 node_type: ASTNodeType::Expressions,
663 source_location: ast.source_location.clone(),
664 children: context,
665 };
666
667 let (_required_vars, rebuilt_ast) = auto_capture_and_rebuild(&ast_with_context);
668
669 let _ = analyze_ast(&rebuilt_ast, &mut collector, &None)?;
670
671 let namespace = NameSpace::new("Main".to_string(), None);
672 let mut functions = Functions::new();
673 let mut ir_generator = IRGenerator::new(&mut functions, namespace);
674
675 let mut ir = ir_generator.generate(&mut collector, &rebuilt_ast)?;
676
677 ir.push((DebugInfo::new((0, 0)), IR::Return));
678 functions.append("__main__".to_string(), ir);
679
680 let package = functions.build_instructions(None);
681 let mut translator = IRTranslator::new(&package);
682
683 let byte_code = match translator.translate() {
684 Ok(_) => translator.get_result(),
685 Err(e) => {
686 collector.report(ComptimeDiagnostic::IRTranslatorError(
687 ast.source_location.clone(),
688 e,
689 ));
690 return Err(());
691 }
692 };
693
694 drop(collector); let result = match self.execute(&byte_code) {
696 Ok(result) => result,
697 Err(err) => {
698 let mut collector = self
699 .diagnostics
700 .write()
701 .expect("Failed to lock diagnostics collector");
702 collector.report(ComptimeDiagnostic::RuntimeError(
703 ast.source_location.clone(),
704 err,
705 ));
706 return Err(());
707 }
708 };
709 match OnionASTObject::from_onion(result.weak()) {
710 Ok(ast_object) => Ok(Some(ast_object)),
711 Err(err) => {
712 let mut collector = self
713 .diagnostics
714 .write()
715 .expect("Failed to lock diagnostics collector");
716 collector.report(ComptimeDiagnostic::RuntimeError(
717 ast.source_location.clone(),
718 err,
719 ));
720 Err(())
721 }
722 }
723 }
724
725 fn execute(
733 &mut self,
734 vm_instructions_package: &VMInstructionPackage,
735 ) -> Result<OnionStaticObject, RuntimeError> {
736 let mut gc = GC::new_with_memory_threshold(1024 * 1024); if let Err(e) = VMInstructionPackage::validate(vm_instructions_package) {
739 return Err(RuntimeError::DetailedError(
740 format!("Invalid VM instruction package: {e}").into(),
741 ));
742 }
743 let mut capture = OnionFastMap::new(vm_instructions_package.create_key_pool());
744
745 self.state.inject_state(&mut capture)?;
746
747 let lambda = OnionLambdaDefinition::new_static(
749 LambdaParameter::Multiple(Box::new([])),
750 LambdaBody::Instruction(Arc::new(vm_instructions_package.clone())),
751 capture,
752 "__main__".into(),
753 LambdaType::Atomic,
754 );
755
756 let args = OnionTuple::new_static(vec![]);
757
758 let mut scheduler: Box<dyn Runnable> = Box::new(Scheduler::new(vec![Box::new(
759 OnionLambdaRunnableLauncher::new(lambda.weak(), args, Ok)?,
760 )]));
761 loop {
763 match scheduler.step(&mut gc) {
764 StepResult::Continue => {
765 }
767 StepResult::SpawnRunnable(_) => {
768 return Err(RuntimeError::DetailedError(
769 "Cannot spawn async task in sync context".into(),
770 ));
771 }
772 StepResult::Error(ref error) => {
773 if let RuntimeError::Pending = error {
774 continue;
776 }
777 return Err(error.clone());
778 }
779 StepResult::NewRunnable(_) => {
780 unreachable!()
781 }
782 StepResult::ReplaceRunnable(_) => {
783 unreachable!()
784 }
785 StepResult::Return(ref result) => {
786 let result_borrowed = result.weak();
787 let result = unwrap_object!(result_borrowed, OnionObject::Pair)?;
788 let success = *unwrap_object!(result.get_key(), OnionObject::Boolean)?;
789 if !success {
790 let value_text = result.get_value().with_data(|data| match data {
791 OnionObject::Undefined(Some(str)) => Ok(str.to_string()),
792 _ => Ok(data.to_string(&vec![]).unwrap_or_else(|e| {
793 format!("[Failed to convert value to string: {e}]")
794 })),
795 })?;
796
797 let mut error_text =
798 format!("{} {}", "Execution returned a failure value:", value_text);
799
800 error_text.push_str(&format!("\n{}", "Context at Time of Failure Return:"));
802 error_text.push_str(&scheduler.format_context());
803
804 return Err(RuntimeError::DetailedError(error_text.into()));
805 }
806 return Ok(result.get_value().stabilize());
807 }
808 }
809 }
810 }
811}