1pub mod ast;
10pub mod coverage;
11pub mod error;
12pub mod interpreter;
13pub mod lexer;
14pub mod lint;
15pub mod live;
16pub mod migration;
17pub mod module;
18pub mod parser;
19pub mod regex_cache;
20pub mod repl_common;
21pub mod repl_highlight;
22pub mod repl_simple;
23pub mod repl_tui;
24pub mod scaffold;
25pub mod serve;
26pub mod solidb_http;
27pub mod span;
28pub mod template;
29pub mod types;
30pub mod vm;
31
32use ast::expr::Argument;
33use error::SolilangError;
34use interpreter::Value;
35
36pub fn run(source: &str) -> Result<(), SolilangError> {
38 run_with_options(source, true)
39}
40
41pub fn run_with_type_check(source: &str, type_check: bool) -> Result<(), SolilangError> {
43 run_with_options(source, type_check)
44}
45
46pub fn run_with_options(source: &str, type_check: bool) -> Result<(), SolilangError> {
48 run_with_path(source, None, type_check)
49}
50
51pub fn run_file(path: &std::path::Path, type_check: bool) -> Result<(), SolilangError> {
53 let source = std::fs::read_to_string(path).map_err(|e| error::RuntimeError::General {
54 message: format!("Failed to read file '{}': {}", path.display(), e),
55 span: span::Span::new(0, 0, 1, 1),
56 })?;
57
58 run_with_path(&source, Some(path), type_check)
59}
60
61pub fn run_with_path(
63 source: &str,
64 source_path: Option<&std::path::Path>,
65 type_check: bool,
66) -> Result<(), SolilangError> {
67 let tokens = lexer::Scanner::new(source).scan_tokens()?;
69
70 let mut program = parser::Parser::new(tokens).parse()?;
72
73 if let Some(path) = source_path.filter(|_| has_imports(&program)) {
75 let base_dir = path.parent().unwrap_or(std::path::Path::new("."));
76 let mut resolver = module::ModuleResolver::new(base_dir);
77 program = resolver
78 .resolve(program, path)
79 .map_err(|e| error::RuntimeError::General {
80 message: format!("Module resolution error: {}", e),
81 span: span::Span::new(0, 0, 1, 1),
82 })?;
83 }
84
85 if type_check {
87 let mut checker = types::TypeChecker::new();
88 if let Err(errors) = checker.check(&program) {
89 return Err(errors.into_iter().next().unwrap().into());
90 }
91 }
92
93 let mut interpreter = interpreter::Interpreter::new();
95 interpreter.interpret(&program)?;
96
97 Ok(())
98}
99
100pub fn run_file_vm(path: &std::path::Path, type_check: bool) -> Result<(), SolilangError> {
102 let source = std::fs::read_to_string(path).map_err(|e| error::RuntimeError::General {
103 message: format!("Failed to read file '{}': {}", path.display(), e),
104 span: span::Span::new(0, 0, 1, 1),
105 })?;
106
107 run_vm(&source, Some(path), type_check)
108}
109
110pub fn run_vm(
112 source: &str,
113 source_path: Option<&std::path::Path>,
114 type_check: bool,
115) -> Result<(), SolilangError> {
116 let tokens = lexer::Scanner::new(source).scan_tokens()?;
118
119 let mut program = parser::Parser::new(tokens).parse()?;
121
122 if let Some(path) = source_path.filter(|_| has_imports(&program)) {
124 let base_dir = path.parent().unwrap_or(std::path::Path::new("."));
125 let mut resolver = module::ModuleResolver::new(base_dir);
126 program = resolver
127 .resolve(program, path)
128 .map_err(|e| error::RuntimeError::General {
129 message: format!("Module resolution error: {}", e),
130 span: span::Span::new(0, 0, 1, 1),
131 })?;
132 }
133
134 if type_check {
136 let mut checker = types::TypeChecker::new();
137 if let Err(errors) = checker.check(&program) {
138 return Err(errors.into_iter().next().unwrap().into());
139 }
140 }
141
142 let module = vm::Compiler::compile(&program).map_err(|e| error::RuntimeError::General {
144 message: format!("Compile error: {}", e),
145 span: span::Span::new(0, 0, 1, 1),
146 })?;
147
148 let mut vm_instance = vm::Vm::new();
150
151 use interpreter::value::{NativeFunction, Value as V};
153 vm_instance.globals.insert(
154 "print".to_string(),
155 V::NativeFunction(NativeFunction::new("print", None, |args| {
156 let output: Vec<String> = args.iter().map(|a| format!("{}", a)).collect();
157 println!("{}", output.join(" "));
158 Ok(V::Null)
159 })),
160 );
161 vm_instance.globals.insert(
162 "puts".to_string(),
163 V::NativeFunction(NativeFunction::new("puts", None, |args| {
164 let output: Vec<String> = args.iter().map(|a| format!("{}", a)).collect();
165 println!("{}", output.join(" "));
166 Ok(V::Null)
167 })),
168 );
169 vm_instance.globals.insert(
170 "len".to_string(),
171 V::NativeFunction(NativeFunction::new("len", Some(1), |args| match &args[0] {
172 V::String(s) => Ok(V::Int(s.len() as i64)),
173 V::Array(arr) => Ok(V::Int(arr.borrow().len() as i64)),
174 V::Hash(hash) => Ok(V::Int(hash.borrow().len() as i64)),
175 _ => Ok(V::Int(0)),
176 })),
177 );
178 vm_instance.globals.insert(
179 "str".to_string(),
180 V::NativeFunction(NativeFunction::new("str", Some(1), |args| {
181 Ok(V::String(format!("{}", args[0])))
182 })),
183 );
184 vm_instance.globals.insert(
185 "type_of".to_string(),
186 V::NativeFunction(NativeFunction::new("type_of", Some(1), |args| {
187 Ok(V::String(args[0].type_name().to_string()))
188 })),
189 );
190 vm_instance.globals.insert(
191 "clock".to_string(),
192 V::NativeFunction(NativeFunction::new("clock", Some(0), |_args| {
193 use std::time::{SystemTime, UNIX_EPOCH};
194 let now = SystemTime::now()
195 .duration_since(UNIX_EPOCH)
196 .unwrap_or_default();
197 Ok(V::Float(now.as_secs_f64()))
198 })),
199 );
200
201 vm_instance.execute(&module.main)?;
203
204 Ok(())
205}
206
207#[cfg(feature = "coverage")]
209pub fn run_with_path_and_coverage(
210 source: &str,
211 source_path: Option<&std::path::Path>,
212 type_check: bool,
213 coverage_tracker: Option<&std::rc::Rc<std::cell::RefCell<coverage::CoverageTracker>>>,
214 source_file_path: Option<&std::path::Path>,
215) -> Result<i64, SolilangError> {
216 interpreter::builtins::test_dsl::clear_test_suites();
218
219 let tokens = lexer::Scanner::new(source).scan_tokens()?;
221
222 let mut program = parser::Parser::new(tokens).parse()?;
224
225 if let Some(path) = source_path.filter(|_| has_imports(&program)) {
227 let base_dir = path.parent().unwrap_or(std::path::Path::new("."));
228 let mut resolver = module::ModuleResolver::new(base_dir);
229 program = resolver
230 .resolve(program, path)
231 .map_err(|e| error::RuntimeError::General {
232 message: format!("Module resolution error: {}", e),
233 span: span::Span::new(0, 0, 1, 1),
234 })?;
235 }
236
237 if type_check {
239 let mut checker = types::TypeChecker::new();
240 if let Err(errors) = checker.check(&program) {
241 return Err(errors.into_iter().next().unwrap().into());
242 }
243 }
244
245 let test_suites = extract_test_definitions(&program);
247
248 let mut interpreter = interpreter::Interpreter::new();
250 if let (Some(tracker), Some(path)) = (coverage_tracker, source_file_path) {
251 interpreter.set_coverage_tracker(tracker.clone());
252 interpreter.set_source_path(path.to_path_buf());
253 }
254 interpreter.interpret(&program)?;
255
256 let (failed_count, failed_tests) = execute_test_suites(&mut interpreter, &test_suites)?;
258
259 let assertion_count = interpreter::builtins::assertions::get_and_reset_assertion_count();
261
262 if failed_count > 0 {
264 let error_msg = if failed_tests.len() == 1 {
265 format!("Test failed: {}", failed_tests[0])
266 } else {
267 format!(
268 "{} tests failed:\n - {}",
269 failed_count,
270 failed_tests.join("\n - ")
271 )
272 };
273 return Err(SolilangError::Runtime(error::RuntimeError::General {
274 message: error_msg,
275 span: span::Span::new(0, 0, 1, 1),
276 }));
277 }
278
279 Ok(assertion_count)
280}
281
282#[cfg(not(feature = "coverage"))]
284pub fn run_with_path_and_coverage(
285 source: &str,
286 source_path: Option<&std::path::Path>,
287 type_check: bool,
288 _coverage_tracker: Option<&std::rc::Rc<std::cell::RefCell<coverage::CoverageTracker>>>,
289 source_file_path: Option<&std::path::Path>,
290) -> Result<i64, SolilangError> {
291 interpreter::builtins::test_dsl::clear_test_suites();
293
294 let tokens = lexer::Scanner::new(source).scan_tokens()?;
296
297 let mut program = parser::Parser::new(tokens).parse()?;
299
300 if let Some(path) = source_path.filter(|_| has_imports(&program)) {
302 let base_dir = path.parent().unwrap_or(std::path::Path::new("."));
303 let mut resolver = module::ModuleResolver::new(base_dir);
304 program = resolver
305 .resolve(program, path)
306 .map_err(|e| error::RuntimeError::General {
307 message: format!("Module resolution error: {}", e),
308 span: span::Span::new(0, 0, 1, 1),
309 })?;
310 }
311
312 if type_check {
314 let mut checker = types::TypeChecker::new();
315 if let Err(errors) = checker.check(&program) {
316 return Err(errors.into_iter().next().unwrap().into());
317 }
318 }
319
320 let test_suites = extract_test_definitions(&program);
322
323 let mut interpreter = interpreter::Interpreter::new();
325 if let Some(path) = source_file_path {
326 interpreter.set_source_path(path.to_path_buf());
327 }
328 interpreter.interpret(&program)?;
329
330 let (failed_count, failed_tests) = execute_test_suites(&mut interpreter, &test_suites)?;
332
333 let assertion_count = interpreter::builtins::assertions::get_and_reset_assertion_count();
335
336 if failed_count > 0 {
338 let error_msg = if failed_tests.len() == 1 {
339 format!("Test failed: {}", failed_tests[0])
340 } else {
341 format!(
342 "{} tests failed:\n - {}",
343 failed_count,
344 failed_tests.join("\n - ")
345 )
346 };
347 return Err(SolilangError::Runtime(error::RuntimeError::General {
348 message: error_msg,
349 span: span::Span::new(0, 0, 1, 1),
350 }));
351 }
352
353 Ok(assertion_count)
354}
355
356fn extract_test_definitions(
357 program: &ast::Program,
358) -> Vec<interpreter::builtins::test_dsl::TestSuite> {
359 let mut suites = Vec::new();
360 for stmt in &program.statements {
361 if let ast::StmtKind::Expression(expr) = &stmt.kind {
362 if let ast::ExprKind::Call { callee, arguments } = &expr.kind {
363 if let ast::ExprKind::Variable(name) = &callee.kind {
365 if name == "describe" || name == "context" {
366 if let Some(suite) = extract_suite_from_call(name, arguments, stmt.span) {
367 suites.push(suite);
368 }
369 }
370 }
371 }
372 }
373 }
374 suites
375}
376
377fn extract_suite_from_call(
378 _name: &str,
379 arguments: &[Argument],
380 _span: span::Span,
381) -> Option<interpreter::builtins::test_dsl::TestSuite> {
382 if arguments.len() < 2 {
383 return None;
384 }
385
386 let first_arg = match &arguments[0] {
388 Argument::Positional(expr) => expr,
389 Argument::Named(_) => return None,
390 Argument::Block(_) => return None,
391 };
392 let suite_name = match &first_arg.kind {
393 ast::ExprKind::StringLiteral(s) => s.clone(),
394 _ => return None,
395 };
396
397 let second_arg = match &arguments[1] {
399 Argument::Positional(expr) => expr,
400 Argument::Named(_) => return None,
401 Argument::Block(_) => return None,
402 };
403 let suite_body = match &second_arg.kind {
404 ast::ExprKind::Lambda { body, .. } => body.clone(),
405 _ => return None,
406 };
407
408 let mut suite = interpreter::builtins::test_dsl::TestSuite {
409 name: suite_name,
410 tests: Vec::new(),
411 before_each: None,
412 after_each: None,
413 before_all: None,
414 after_all: None,
415 nested_suites: Vec::new(),
416 };
417
418 extract_tests_from_block(&suite_body, &mut suite);
420
421 Some(suite)
422}
423
424fn extract_tests_from_block(
425 statements: &[ast::Stmt],
426 suite: &mut interpreter::builtins::test_dsl::TestSuite,
427) {
428 for stmt in statements {
429 if let ast::StmtKind::Expression(expr) = &stmt.kind {
430 if let ast::ExprKind::Call { callee, arguments } = &expr.kind {
431 if let ast::ExprKind::Variable(name) = &callee.kind {
432 if name == "test" || name == "it" || name == "specify" {
433 if let Some(test) = extract_test_from_call(arguments, stmt.span) {
434 suite.tests.push(test);
435 }
436 } else if name == "describe" || name == "context" {
437 if let Some(nested) = extract_suite_from_call(name, arguments, stmt.span) {
438 suite.nested_suites.push(nested);
439 }
440 } else if name == "before_each" {
441 if let Some(Argument::Positional(callback)) = arguments.first() {
442 suite.before_each = Some(ast_expr_to_value(callback));
443 }
444 } else if name == "after_each" {
445 if let Some(Argument::Positional(callback)) = arguments.first() {
446 suite.after_each = Some(ast_expr_to_value(callback));
447 }
448 } else if name == "before_all" {
449 if let Some(Argument::Positional(callback)) = arguments.first() {
450 suite.before_all = Some(ast_expr_to_value(callback));
451 }
452 } else if name == "after_all" {
453 if let Some(Argument::Positional(callback)) = arguments.first() {
454 suite.after_all = Some(ast_expr_to_value(callback));
455 }
456 }
457 }
458 }
459 }
460 }
461}
462
463fn extract_test_from_call(
464 arguments: &[Argument],
465 span: span::Span,
466) -> Option<interpreter::builtins::test_dsl::TestDefinition> {
467 if arguments.len() < 2 {
468 return None;
469 }
470
471 let first_arg = match &arguments[0] {
472 Argument::Positional(expr) => expr,
473 Argument::Named(_) => return None,
474 Argument::Block(_) => return None,
475 };
476 let test_name = match &first_arg.kind {
477 ast::ExprKind::StringLiteral(s) => s.clone(),
478 _ => return None,
479 };
480
481 let second_arg = match &arguments[1] {
482 Argument::Positional(expr) => expr,
483 Argument::Named(_) => return None,
484 Argument::Block(_) => return None,
485 };
486 let test_body = match &second_arg.kind {
487 ast::ExprKind::Lambda {
488 params,
489 return_type,
490 body,
491 } => create_function_value(params.clone(), return_type.clone(), body.clone(), span),
492 _ => return None,
493 };
494
495 Some(interpreter::builtins::test_dsl::TestDefinition {
496 name: test_name,
497 body: test_body,
498 })
499}
500
501fn create_function_value(
502 params: Vec<ast::stmt::Parameter>,
503 return_type: Option<ast::types::TypeAnnotation>,
504 body: Vec<ast::Stmt>,
505 span: span::Span,
506) -> Value {
507 use interpreter::value::Function;
508 use std::cell::RefCell;
509 use std::rc::Rc;
510
511 let mut env = interpreter::environment::Environment::new();
513 interpreter::builtins::register_builtins(&mut env, true);
514
515 let decl = ast::FunctionDecl {
516 name: "test_fn".to_string(),
517 params,
518 return_type,
519 body,
520 span,
521 };
522 let closure = Rc::new(RefCell::new(env));
523 Value::Function(Rc::new(Function::from_decl(&decl, closure, None)))
524}
525
526fn ast_expr_to_value(expr: &ast::Expr) -> Value {
527 match &expr.kind {
528 ast::ExprKind::Lambda {
529 params,
530 return_type,
531 body,
532 } => create_function_value(params.clone(), return_type.clone(), body.clone(), expr.span),
533 _ => Value::Null,
534 }
535}
536
537fn execute_test_suites(
538 interpreter: &mut interpreter::Interpreter,
539 suites: &[interpreter::builtins::test_dsl::TestSuite],
540) -> Result<(i64, Vec<String>), error::RuntimeError> {
541 let mut failed_count = 0i64;
542 let mut failed_tests = Vec::new();
543
544 for suite in suites {
545 if let Some(before_all) = &suite.before_all {
547 let rebound = rebind_closure(before_all, &interpreter.environment);
548 let _ = interpreter.call_value(rebound, Vec::new(), span::Span::new(0, 0, 1, 1));
549 }
550
551 for test in &suite.tests {
552 if let Some(before_each) = &suite.before_each {
554 let rebound = rebind_closure(before_each, &interpreter.environment);
555 let _ = interpreter.call_value(rebound, Vec::new(), span::Span::new(0, 0, 1, 1));
556 }
557
558 let test_body = rebind_closure(&test.body, &interpreter.environment);
561
562 let result = interpreter.call_value(test_body, Vec::new(), span::Span::new(0, 0, 1, 1));
564
565 if let Err(e) = result {
566 failed_count += 1;
567 failed_tests.push(format!("{}: {}", test.name, e));
568 }
569
570 if let Some(after_each) = &suite.after_each {
572 let rebound = rebind_closure(after_each, &interpreter.environment);
573 let _ = interpreter.call_value(rebound, Vec::new(), span::Span::new(0, 0, 1, 1));
574 }
575 }
576
577 let (nested_failed, mut nested_errors) =
579 execute_test_suites(interpreter, &suite.nested_suites)?;
580 failed_count += nested_failed;
581 failed_tests.append(&mut nested_errors);
582
583 if let Some(after_all) = &suite.after_all {
585 let rebound = rebind_closure(after_all, &interpreter.environment);
586 let _ = interpreter.call_value(rebound, Vec::new(), span::Span::new(0, 0, 1, 1));
587 }
588 }
589 Ok((failed_count, failed_tests))
590}
591
592fn rebind_closure(
595 value: &interpreter::value::Value,
596 env: &std::rc::Rc<std::cell::RefCell<interpreter::environment::Environment>>,
597) -> interpreter::value::Value {
598 use interpreter::value::{Function, Value};
599 match value {
600 Value::Function(func) => {
601 let mut new_func = Function {
602 name: func.name.clone(),
603 params: func.params.clone(),
604 body: func.body.clone(),
605 closure: env.clone(),
606 is_method: func.is_method,
607 span: func.span,
608 source_path: func.source_path.clone(),
609 defining_superclass: func.defining_superclass.clone(),
610 return_type: func.return_type.clone(),
611 };
612 new_func.closure = env.clone();
613 Value::Function(std::rc::Rc::new(new_func))
614 }
615 other => other.clone(),
616 }
617}
618
619pub(crate) fn has_imports(program: &ast::Program) -> bool {
621 program
622 .statements
623 .iter()
624 .any(|stmt| matches!(stmt.kind, ast::StmtKind::Import(_)))
625}
626
627pub fn parse(source: &str) -> Result<ast::Program, SolilangError> {
629 let tokens = lexer::Scanner::new(source).scan_tokens()?;
630 let program = parser::Parser::new(tokens).parse()?;
631 Ok(program)
632}
633
634pub fn lint(source: &str) -> Result<Vec<lint::LintDiagnostic>, SolilangError> {
636 let tokens = lexer::Scanner::new(source).scan_tokens()?;
637 let program = parser::Parser::new(tokens).parse()?;
638 Ok(lint::Linter::new(source).lint(&program))
639}
640
641pub fn type_check(source: &str) -> Result<(), Vec<error::TypeError>> {
643 let tokens = lexer::Scanner::new(source)
644 .scan_tokens()
645 .map_err(|_| Vec::new())?;
646 let program = parser::Parser::new(tokens)
647 .parse()
648 .map_err(|_| Vec::new())?;
649
650 let mut checker = types::TypeChecker::new();
651 checker.check(&program)
652}