1#![allow(clippy::module_inception)]
11#![allow(clippy::result_large_err)]
12#![allow(clippy::arc_with_non_send_sync)]
13#![allow(clippy::only_used_in_recursion)]
14#![allow(clippy::type_complexity)]
15#![allow(clippy::ptr_arg)]
16#![allow(clippy::new_without_default)]
17#![allow(clippy::collapsible_if)]
18#![allow(clippy::collapsible_else_if)]
19#![allow(clippy::collapsible_match)]
20#![allow(clippy::derivable_impls)]
21#![allow(clippy::unnecessary_cast)]
22#![allow(clippy::needless_borrow)]
23#![allow(clippy::wildcard_in_or_patterns)]
24#![allow(clippy::needless_borrows_for_generic_args)]
25#![allow(clippy::unnecessary_lazy_evaluations)]
26#![allow(clippy::len_zero)]
27#![allow(clippy::redundant_pattern_matching)]
28#![allow(clippy::trim_split_whitespace)]
29#![allow(clippy::to_string_in_format_args)]
30#![allow(clippy::while_let_on_iterator)]
31#![allow(clippy::manual_ok_err)]
32#![allow(clippy::unwrap_or_default)]
33#![allow(clippy::unnecessary_filter_map)]
34#![allow(clippy::manual_clamp)]
35#![allow(clippy::redundant_closure)]
36#![allow(clippy::unused_enumerate_index)]
37#![allow(clippy::too_many_arguments)]
38#![allow(clippy::let_underscore_future)]
39#![allow(clippy::double_ended_iterator_last)]
40#![allow(clippy::needless_late_init)]
41#![allow(clippy::manual_strip)]
42#![allow(clippy::never_loop)]
43
44pub mod ast;
45pub mod coverage;
46pub mod error;
47pub mod interpreter;
48pub mod lexer;
49pub mod live;
50pub mod migration;
51pub mod module;
52pub mod parser;
53pub mod scaffold;
54pub mod serve;
55pub mod solidb_http;
56pub mod span;
57pub mod template;
58pub mod types;
59
60use ast::expr::Argument;
61use error::SolilangError;
62use interpreter::Value;
63
64pub fn run(source: &str) -> Result<(), SolilangError> {
66 run_with_options(source, true)
67}
68
69pub fn run_with_type_check(source: &str, type_check: bool) -> Result<(), SolilangError> {
71 run_with_options(source, type_check)
72}
73
74pub fn run_with_options(
76 source: &str,
77 type_check: bool,
78) -> Result<(), SolilangError> {
79 run_with_path(source, None, type_check)
80}
81
82pub fn run_file(
84 path: &std::path::Path,
85 type_check: bool,
86) -> Result<(), SolilangError> {
87 let source = std::fs::read_to_string(path).map_err(|e| error::RuntimeError::General {
88 message: format!("Failed to read file '{}': {}", path.display(), e),
89 span: span::Span::new(0, 0, 1, 1),
90 })?;
91
92 run_with_path(&source, Some(path), type_check)
93}
94
95pub fn run_with_path(
97 source: &str,
98 source_path: Option<&std::path::Path>,
99 type_check: bool,
100) -> Result<(), SolilangError> {
101 let tokens = lexer::Scanner::new(source).scan_tokens()?;
103
104 let mut program = parser::Parser::new(tokens).parse()?;
106
107 if let Some(path) = source_path.filter(|_| has_imports(&program)) {
109 let base_dir = path.parent().unwrap_or(std::path::Path::new("."));
110 let mut resolver = module::ModuleResolver::new(base_dir);
111 program = resolver
112 .resolve(program, path)
113 .map_err(|e| error::RuntimeError::General {
114 message: format!("Module resolution error: {}", e),
115 span: span::Span::new(0, 0, 1, 1),
116 })?;
117 }
118
119 if type_check {
121 let mut checker = types::TypeChecker::new();
122 if let Err(errors) = checker.check(&program) {
123 return Err(errors.into_iter().next().unwrap().into());
124 }
125 }
126
127 let mut interpreter = interpreter::Interpreter::new();
129 interpreter.interpret(&program)?;
130
131 Ok(())
132}
133
134#[cfg(feature = "coverage")]
136pub fn run_with_path_and_coverage(
137 source: &str,
138 source_path: Option<&std::path::Path>,
139 type_check: bool,
140 coverage_tracker: Option<&std::rc::Rc<std::cell::RefCell<coverage::CoverageTracker>>>,
141 source_file_path: Option<&std::path::Path>,
142) -> Result<i64, SolilangError> {
143 interpreter::builtins::test_dsl::clear_test_suites();
145
146 let tokens = lexer::Scanner::new(source).scan_tokens()?;
148
149 let mut program = parser::Parser::new(tokens).parse()?;
151
152 if let Some(path) = source_path.filter(|_| has_imports(&program)) {
154 let base_dir = path.parent().unwrap_or(std::path::Path::new("."));
155 let mut resolver = module::ModuleResolver::new(base_dir);
156 program = resolver
157 .resolve(program, path)
158 .map_err(|e| error::RuntimeError::General {
159 message: format!("Module resolution error: {}", e),
160 span: span::Span::new(0, 0, 1, 1),
161 })?;
162 }
163
164 if type_check {
166 let mut checker = types::TypeChecker::new();
167 if let Err(errors) = checker.check(&program) {
168 return Err(errors.into_iter().next().unwrap().into());
169 }
170 }
171
172 let test_suites = extract_test_definitions(&program);
174
175 let mut interpreter = interpreter::Interpreter::new();
177 if let (Some(tracker), Some(path)) = (coverage_tracker, source_file_path) {
178 interpreter.set_coverage_tracker(tracker.clone());
179 interpreter.set_source_path(path.to_path_buf());
180 }
181 interpreter.interpret(&program)?;
182
183 let (failed_count, failed_tests) = execute_test_suites(&mut interpreter, &test_suites)?;
185
186 let assertion_count = interpreter::builtins::assertions::get_and_reset_assertion_count();
188
189 if failed_count > 0 {
191 let error_msg = if failed_tests.len() == 1 {
192 format!("Test failed: {}", failed_tests[0])
193 } else {
194 format!("{} tests failed:\n - {}", failed_count, failed_tests.join("\n - "))
195 };
196 return Err(SolilangError::Runtime(error::RuntimeError::General {
197 message: error_msg,
198 span: span::Span::new(0, 0, 1, 1),
199 }));
200 }
201
202 Ok(assertion_count)
203}
204
205#[cfg(not(feature = "coverage"))]
207pub fn run_with_path_and_coverage(
208 source: &str,
209 source_path: Option<&std::path::Path>,
210 type_check: bool,
211 _coverage_tracker: Option<&std::rc::Rc<std::cell::RefCell<coverage::CoverageTracker>>>,
212 source_file_path: Option<&std::path::Path>,
213) -> Result<i64, SolilangError> {
214 interpreter::builtins::test_dsl::clear_test_suites();
216
217 let tokens = lexer::Scanner::new(source).scan_tokens()?;
219
220 let mut program = parser::Parser::new(tokens).parse()?;
222
223 if let Some(path) = source_path.filter(|_| has_imports(&program)) {
225 let base_dir = path.parent().unwrap_or(std::path::Path::new("."));
226 let mut resolver = module::ModuleResolver::new(base_dir);
227 program = resolver
228 .resolve(program, path)
229 .map_err(|e| error::RuntimeError::General {
230 message: format!("Module resolution error: {}", e),
231 span: span::Span::new(0, 0, 1, 1),
232 })?;
233 }
234
235 if type_check {
237 let mut checker = types::TypeChecker::new();
238 if let Err(errors) = checker.check(&program) {
239 return Err(errors.into_iter().next().unwrap().into());
240 }
241 }
242
243 let test_suites = extract_test_definitions(&program);
245
246 let mut interpreter = interpreter::Interpreter::new();
248 if let Some(path) = source_file_path {
249 interpreter.set_source_path(path.to_path_buf());
250 }
251 interpreter.interpret(&program)?;
252
253 let (failed_count, failed_tests) = execute_test_suites(&mut interpreter, &test_suites)?;
255
256 let assertion_count = interpreter::builtins::assertions::get_and_reset_assertion_count();
258
259 if failed_count > 0 {
261 let error_msg = if failed_tests.len() == 1 {
262 format!("Test failed: {}", failed_tests[0])
263 } else {
264 format!("{} tests failed:\n - {}", failed_count, failed_tests.join("\n - "))
265 };
266 return Err(SolilangError::Runtime(error::RuntimeError::General {
267 message: error_msg,
268 span: span::Span::new(0, 0, 1, 1),
269 }));
270 }
271
272 Ok(assertion_count)
273}
274
275fn extract_test_definitions(program: &ast::Program) -> Vec<interpreter::builtins::test_dsl::TestSuite> {
276 let mut suites = Vec::new();
277 for stmt in &program.statements {
278 if let ast::StmtKind::Expression(expr) = &stmt.kind {
279 if let ast::ExprKind::Call { callee, arguments } = &expr.kind {
280 if let ast::ExprKind::Variable(name) = &callee.kind {
282 if name == "describe" || name == "context" {
283 if let Some(suite) = extract_suite_from_call(name, arguments, stmt.span) {
284 suites.push(suite);
285 }
286 }
287 }
288 }
289 }
290 }
291 suites
292}
293
294fn extract_suite_from_call(
295 _name: &str,
296 arguments: &[Argument],
297 _span: span::Span,
298) -> Option<interpreter::builtins::test_dsl::TestSuite> {
299 if arguments.len() < 2 {
300 return None;
301 }
302
303 let first_arg = match &arguments[0] {
305 Argument::Positional(expr) => expr,
306 Argument::Named(_) => return None,
307 };
308 let suite_name = match &first_arg.kind {
309 ast::ExprKind::StringLiteral(s) => s.clone(),
310 _ => return None,
311 };
312
313 let second_arg = match &arguments[1] {
315 Argument::Positional(expr) => expr,
316 Argument::Named(_) => return None,
317 };
318 let suite_body = match &second_arg.kind {
319 ast::ExprKind::Lambda { body, .. } => body.clone(),
320 _ => return None,
321 };
322
323 let mut suite = interpreter::builtins::test_dsl::TestSuite {
324 name: suite_name,
325 tests: Vec::new(),
326 before_each: None,
327 after_each: None,
328 before_all: None,
329 after_all: None,
330 nested_suites: Vec::new(),
331 };
332
333 extract_tests_from_block(&suite_body, &mut suite);
335
336 Some(suite)
337}
338
339fn extract_tests_from_block(
340 statements: &[ast::Stmt],
341 suite: &mut interpreter::builtins::test_dsl::TestSuite,
342) {
343 for stmt in statements {
344 if let ast::StmtKind::Expression(expr) = &stmt.kind {
345 if let ast::ExprKind::Call { callee, arguments } = &expr.kind {
346 if let ast::ExprKind::Variable(name) = &callee.kind {
347 if name == "test" || name == "it" || name == "specify" {
348 if let Some(test) = extract_test_from_call(arguments, stmt.span) {
349 suite.tests.push(test);
350 }
351 } else if name == "describe" || name == "context" {
352 if let Some(nested) = extract_suite_from_call(name, arguments, stmt.span) {
353 suite.nested_suites.push(nested);
354 }
355 } else if name == "before_each" {
356 if let Some(Argument::Positional(callback)) = arguments.first() {
357 suite.before_each = Some(ast_expr_to_value(callback));
358 }
359 } else if name == "after_each" {
360 if let Some(Argument::Positional(callback)) = arguments.first() {
361 suite.after_each = Some(ast_expr_to_value(callback));
362 }
363 } else if name == "before_all" {
364 if let Some(Argument::Positional(callback)) = arguments.first() {
365 suite.before_all = Some(ast_expr_to_value(callback));
366 }
367 } else if name == "after_all" {
368 if let Some(Argument::Positional(callback)) = arguments.first() {
369 suite.after_all = Some(ast_expr_to_value(callback));
370 }
371 }
372 }
373 }
374 }
375 }
376}
377
378fn extract_test_from_call(
379 arguments: &[Argument],
380 span: span::Span,
381) -> Option<interpreter::builtins::test_dsl::TestDefinition> {
382 if arguments.len() < 2 {
383 return None;
384 }
385
386 let first_arg = match &arguments[0] {
387 Argument::Positional(expr) => expr,
388 Argument::Named(_) => return None,
389 };
390 let test_name = match &first_arg.kind {
391 ast::ExprKind::StringLiteral(s) => s.clone(),
392 _ => return None,
393 };
394
395 let second_arg = match &arguments[1] {
396 Argument::Positional(expr) => expr,
397 Argument::Named(_) => return None,
398 };
399 let test_body = match &second_arg.kind {
400 ast::ExprKind::Lambda { params, return_type, body } => {
401 create_function_value(params.clone(), return_type.clone(), body.clone(), span)
402 }
403 _ => return None,
404 };
405
406 Some(interpreter::builtins::test_dsl::TestDefinition {
407 name: test_name,
408 body: test_body,
409 })
410}
411
412fn create_function_value(
413 params: Vec<ast::stmt::Parameter>,
414 return_type: Option<ast::types::TypeAnnotation>,
415 body: Vec<ast::Stmt>,
416 span: span::Span,
417) -> Value {
418 use interpreter::value::Function;
419 use std::rc::Rc;
420 use std::cell::RefCell;
421
422 let mut env = interpreter::environment::Environment::new();
424 interpreter::builtins::register_builtins(&mut env);
425
426 let decl = ast::FunctionDecl {
427 name: "test_fn".to_string(),
428 params,
429 return_type,
430 body,
431 span,
432 };
433 let closure = Rc::new(RefCell::new(env));
434 Value::Function(Rc::new(Function::from_decl(&decl, closure, None)))
435}
436
437fn ast_expr_to_value(expr: &ast::Expr) -> Value {
438 match &expr.kind {
439 ast::ExprKind::Lambda { params, return_type, body } => {
440 create_function_value(params.clone(), return_type.clone(), body.clone(), expr.span)
441 }
442 _ => Value::Null,
443 }
444}
445
446fn execute_test_suites(
447 interpreter: &mut interpreter::Interpreter,
448 suites: &[interpreter::builtins::test_dsl::TestSuite],
449) -> Result<(i64, Vec<String>), error::RuntimeError> {
450 let mut failed_count = 0i64;
451 let mut failed_tests = Vec::new();
452
453 for suite in suites {
454 if let Some(before_all) = &suite.before_all {
456 let _ = interpreter.call_value(before_all.clone(), Vec::new(), span::Span::new(0, 0, 1, 1));
457 }
458
459 for test in &suite.tests {
460 if let Some(before_each) = &suite.before_each {
462 let _ = interpreter.call_value(before_each.clone(), Vec::new(), span::Span::new(0, 0, 1, 1));
463 }
464
465 let result = interpreter.call_value(
467 test.body.clone(),
468 Vec::new(),
469 span::Span::new(0, 0, 1, 1),
470 );
471
472 if let Err(e) = result {
473 failed_count += 1;
474 failed_tests.push(format!("{}: {}", test.name, e));
475 }
476
477 if let Some(after_each) = &suite.after_each {
479 let _ = interpreter.call_value(after_each.clone(), Vec::new(), span::Span::new(0, 0, 1, 1));
480 }
481 }
482
483 let (nested_failed, mut nested_errors) = execute_test_suites(interpreter, &suite.nested_suites)?;
485 failed_count += nested_failed;
486 failed_tests.append(&mut nested_errors);
487
488 if let Some(after_all) = &suite.after_all {
490 let _ = interpreter.call_value(after_all.clone(), Vec::new(), span::Span::new(0, 0, 1, 1));
491 }
492 }
493 Ok((failed_count, failed_tests))
494}
495
496pub(crate) fn has_imports(program: &ast::Program) -> bool {
498 program
499 .statements
500 .iter()
501 .any(|stmt| matches!(stmt.kind, ast::StmtKind::Import(_)))
502}
503
504pub fn parse(source: &str) -> Result<ast::Program, SolilangError> {
506 let tokens = lexer::Scanner::new(source).scan_tokens()?;
507 let program = parser::Parser::new(tokens).parse()?;
508 Ok(program)
509}
510
511pub fn type_check(source: &str) -> Result<(), Vec<error::TypeError>> {
513 let tokens = lexer::Scanner::new(source)
514 .scan_tokens()
515 .map_err(|_| Vec::new())?;
516 let program = parser::Parser::new(tokens)
517 .parse()
518 .map_err(|_| Vec::new())?;
519
520 let mut checker = types::TypeChecker::new();
521 checker.check(&program)
522}