@devaloop/devalang 0.0.1-alpha.3 → 0.0.1-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.devalang +1 -1
  2. package/Cargo.toml +9 -7
  3. package/README.md +49 -25
  4. package/docs/CHANGELOG.md +30 -0
  5. package/docs/COMMANDS.md +31 -0
  6. package/docs/CONFIG.md +6 -4
  7. package/docs/ROADMAP.md +4 -4
  8. package/docs/TODO.md +4 -4
  9. package/examples/index.deva +9 -2
  10. package/examples/samples/hat-808.wav +0 -0
  11. package/out-tsc/bin/devalang.exe +0 -0
  12. package/package.json +44 -42
  13. package/project-version.json +6 -6
  14. package/rust/audio/engine.rs +126 -0
  15. package/rust/audio/interpreter.rs +143 -0
  16. package/rust/audio/loader.rs +46 -0
  17. package/rust/audio/mod.rs +5 -0
  18. package/rust/audio/player.rs +54 -0
  19. package/rust/audio/render.rs +57 -0
  20. package/rust/cli/build.rs +17 -5
  21. package/rust/cli/check.rs +2 -1
  22. package/rust/cli/init.rs +4 -2
  23. package/rust/cli/mod.rs +29 -0
  24. package/rust/cli/play.rs +192 -0
  25. package/rust/cli/template.rs +2 -1
  26. package/rust/config/loader.rs +0 -1
  27. package/rust/config/mod.rs +3 -2
  28. package/rust/core/builder/mod.rs +54 -6
  29. package/rust/core/debugger/lexer.rs +20 -5
  30. package/rust/core/debugger/preprocessor.rs +9 -5
  31. package/rust/core/lexer/handler/mod.rs +2 -2
  32. package/rust/core/lexer/handler/newline.rs +5 -1
  33. package/rust/core/lexer/mod.rs +10 -5
  34. package/rust/core/parser/handler/loop_.rs +11 -0
  35. package/rust/core/parser/mod.rs +0 -1
  36. package/rust/core/preprocessor/loader.rs +89 -16
  37. package/rust/core/preprocessor/module.rs +2 -0
  38. package/rust/core/preprocessor/resolver/bank.rs +46 -0
  39. package/rust/core/preprocessor/resolver/loop_.rs +148 -0
  40. package/rust/core/preprocessor/resolver/mod.rs +151 -0
  41. package/rust/core/preprocessor/resolver/tempo.rs +49 -0
  42. package/rust/core/preprocessor/resolver/trigger.rs +114 -0
  43. package/rust/lib.rs +118 -0
  44. package/rust/main.rs +8 -0
  45. package/rust/utils/logger.rs +45 -6
  46. package/rust/utils/spinner.rs +2 -0
  47. package/rust/utils/watcher.rs +10 -2
  48. package/templates/minimal/.devalang +2 -1
  49. package/templates/minimal/README.md +202 -0
  50. package/templates/welcome/.devalang +2 -1
  51. package/templates/welcome/README.md +48 -31
  52. package/rust/core/preprocessor/resolver.rs +0 -372
@@ -1,7 +1,10 @@
1
- use crate::core::parser::statement::Statement;
1
+ use crate::{ audio::render::render_audio_with_modules, core::parser::statement::Statement };
2
+ use crate::core::store::global::GlobalStore;
2
3
  use std::{ collections::HashMap, fs::create_dir_all };
3
4
  use std::io::Write;
4
5
 
6
+ use crate::utils::logger::Logger;
7
+
5
8
  pub struct Builder {}
6
9
 
7
10
  impl Builder {
@@ -9,16 +12,14 @@ impl Builder {
9
12
  Builder {}
10
13
  }
11
14
 
12
- pub fn build_ast(&self, modules: &HashMap<String, Vec<Statement>>) {
13
- let output_path = "./output";
14
-
15
+ pub fn build_ast(&self, modules: &HashMap<String, Vec<Statement>>, out_dir: &str) {
15
16
  for (name, statements) in modules {
16
17
  let formatted_name = name.split("/").last().unwrap_or(name);
17
18
  let formatted_name = formatted_name.replace(".deva", "");
18
19
 
19
- create_dir_all(format!("{}/ast", output_path)).expect("Failed to create AST directory");
20
+ create_dir_all(format!("{}/ast", out_dir)).expect("Failed to create AST directory");
20
21
 
21
- let file_path = format!("{}/ast/{}.json", output_path, formatted_name);
22
+ let file_path = format!("{}/ast/{}.json", out_dir, formatted_name);
22
23
  let mut file = std::fs::File::create(file_path).expect("Failed to create AST file");
23
24
 
24
25
  let content = serde_json
@@ -28,4 +29,51 @@ impl Builder {
28
29
  file.write_all(content.as_bytes()).expect("Failed to write AST to file");
29
30
  }
30
31
  }
32
+
33
+ pub fn build_audio(
34
+ &self,
35
+ modules: &HashMap<String, Vec<Statement>>,
36
+ normalized_output_dir: &str,
37
+ global_store: &mut GlobalStore
38
+ ) {
39
+ let logger = Logger::new();
40
+
41
+ let audio_engines = render_audio_with_modules(
42
+ modules.clone(),
43
+ &normalized_output_dir,
44
+ global_store
45
+ );
46
+
47
+ create_dir_all(format!("{}/audio", normalized_output_dir)).expect(
48
+ "Failed to create audio directory"
49
+ );
50
+
51
+ for (module_name, mut audio_engine) in audio_engines {
52
+ let formatted_module_name = module_name
53
+ .split('/')
54
+ .last()
55
+ .unwrap_or(&module_name)
56
+ .replace(".deva", "");
57
+
58
+ let output_path = format!(
59
+ "{}/audio/{}.wav",
60
+ normalized_output_dir,
61
+ formatted_module_name
62
+ );
63
+
64
+ match audio_engine.generate_wav_file(&output_path) {
65
+ Ok(_) => {}
66
+ Err(msg) => {
67
+ logger.log_error_with_stacktrace(
68
+ &format!(
69
+ "Unable to generate WAV file for module '{}': {}",
70
+ formatted_module_name,
71
+ msg
72
+ ),
73
+ &module_name
74
+ );
75
+ }
76
+ }
77
+ }
78
+ }
31
79
  }
@@ -1,12 +1,27 @@
1
- use crate::core::{ debugger::Debugger, lexer::token::Token };
1
+ use std::{ collections::HashMap, fs::create_dir_all };
2
+ use crate::core::{ debugger::Debugger, lexer::token::Token, parser::statement::Statement };
2
3
 
3
- pub fn write_lexer_log_file(output_dir: &str, file_name: &str, tokens: Vec<Token>) {
4
+ pub fn write_lexer_log_file(
5
+ output_dir: &str,
6
+ file_name: &str,
7
+ modules: HashMap<String, Vec<Token>>
8
+ ) {
4
9
  let debugger = Debugger::new();
5
10
  let mut content = String::new();
6
11
 
7
- for token in tokens {
8
- content.push_str(&format!("{:?}\n", token));
12
+ let log_directory = format!("{}/logs", output_dir);
13
+
14
+ create_dir_all(&log_directory).expect("Failed to create log directory");
15
+
16
+ for (path, tokens) in modules {
17
+ content.push_str(&format!("--- Resolved Tokens for {} ---\n", path));
18
+
19
+ for token in tokens {
20
+ content.push_str(&format!("{:?}\n", token));
21
+ }
22
+
23
+ content.push_str("\n");
9
24
  }
10
25
 
11
- debugger.write_log_file(output_dir, file_name, &content);
26
+ debugger.write_log_file(&log_directory, file_name, &content);
12
27
  }
@@ -1,17 +1,21 @@
1
- use std::collections::HashMap;
1
+ use std::{ collections::HashMap, fs::create_dir_all };
2
2
  use crate::core::{ debugger::Debugger, parser::statement::Statement };
3
3
 
4
4
  pub fn write_preprocessor_log_file(
5
5
  output_dir: &str,
6
6
  file_name: &str,
7
- statements: HashMap<String, Vec<Statement>>
7
+ modules: HashMap<String, Vec<Statement>>
8
8
  ) {
9
9
  let debugger = Debugger::new();
10
10
  let mut content = String::new();
11
11
 
12
- for (path, stmts) in statements {
12
+ let log_directory = format!("{}/logs", output_dir);
13
+
14
+ create_dir_all(&log_directory).expect("Failed to create log directory");
15
+
16
+ for (path, stmts) in modules {
13
17
  content.push_str(&format!("--- Resolved Statements for {} ---\n", path));
14
-
18
+
15
19
  for stmt in stmts {
16
20
  content.push_str(&format!("{:?}\n", stmt));
17
21
  }
@@ -19,5 +23,5 @@ pub fn write_preprocessor_log_file(
19
23
  content.push_str("\n");
20
24
  }
21
25
 
22
- debugger.write_log_file(output_dir, file_name, &content);
26
+ debugger.write_log_file(&log_directory, file_name, &content);
23
27
  }
@@ -42,7 +42,7 @@ fn advance_char<I: Iterator<Item = char>>(
42
42
  Some(c)
43
43
  }
44
44
 
45
- pub fn handle_content_lexing(content: String) -> Vec<Token> {
45
+ pub fn handle_content_lexing(content: String) -> Result<Vec<Token>, String> {
46
46
  let mut tokens = Vec::new();
47
47
 
48
48
  let mut line = 1;
@@ -234,5 +234,5 @@ pub fn handle_content_lexing(content: String) -> Vec<Token> {
234
234
  // );
235
235
  // }
236
236
 
237
- tokens
237
+ Ok(tokens)
238
238
  }
@@ -14,6 +14,10 @@ pub fn handle_newline_lexer(
14
14
  lexeme: ch.to_string(),
15
15
  line: *line,
16
16
  column: 0,
17
- indent: 0,
17
+ indent: *current_indent,
18
18
  });
19
+
20
+ *line += 1;
21
+ *column = 1;
22
+ *at_line_start = true;
19
23
  }
@@ -2,7 +2,10 @@ pub mod handler;
2
2
  pub mod token;
3
3
 
4
4
  use std::fs;
5
- use crate::core::{ lexer::{ handler::handle_content_lexing, token::Token }, utils::path::normalize_path };
5
+ use crate::core::{
6
+ lexer::{ handler::handle_content_lexing, token::Token },
7
+ utils::path::normalize_path,
8
+ };
6
9
 
7
10
  pub struct Lexer {}
8
11
 
@@ -11,14 +14,16 @@ impl Lexer {
11
14
  Lexer {}
12
15
  }
13
16
 
17
+ pub fn lex_from_source(&self, source: &str) -> Result<Vec<Token>, String> {
18
+ handle_content_lexing(source.to_string())
19
+ }
20
+
14
21
  pub fn lex_tokens(&self, entrypoint: &str) -> Vec<Token> {
15
22
  let path = normalize_path(entrypoint);
16
23
 
17
- let file_content = fs
18
- ::read_to_string(&path)
19
- .expect("Failed to read the entrypoint file");
24
+ let file_content = fs::read_to_string(&path).expect("Failed to read the entrypoint file");
20
25
 
21
- let tokens = handle_content_lexing(file_content);
26
+ let tokens = handle_content_lexing(file_content).expect("Failed to lex the content");
22
27
 
23
28
  tokens
24
29
  }
@@ -40,6 +40,17 @@ pub fn parse_loop_token(parser: &mut Parser, global_store: &mut GlobalStore) ->
40
40
  );
41
41
  let loop_body = parser.parse_block(tokens.clone(), global_store);
42
42
 
43
+ // Peek for dedent
44
+ if let Some(token) = parser.peek() {
45
+ if token.kind == TokenKind::Dedent {
46
+ parser.advance();
47
+ } else {
48
+ // Unexpected token after loop body
49
+ }
50
+ } else {
51
+ // EOF or unexpected end of input
52
+ }
53
+
43
54
  let mut value_map = HashMap::new();
44
55
 
45
56
  value_map.insert("iterator".to_string(), Value::Identifier(iterator_name));
@@ -225,7 +225,6 @@ impl Parser {
225
225
  break;
226
226
  }
227
227
  if condition(token) {
228
- self.advance(); // Consume the token that matches the condition
229
228
  break;
230
229
  }
231
230
  collected.push(self.advance().unwrap().clone());
@@ -1,10 +1,8 @@
1
1
  use std::collections::HashMap;
2
-
3
2
  use crate::{
4
3
  core::{
5
- debugger::{ lexer::write_lexer_log_file, preprocessor::write_preprocessor_log_file },
6
4
  error::ErrorHandler,
7
- lexer::Lexer,
5
+ lexer::{ token::Token, Lexer },
8
6
  parser::{ statement::{ Statement, StatementKind }, Parser },
9
7
  preprocessor::{
10
8
  module::Module,
@@ -12,8 +10,9 @@ use crate::{
12
10
  resolver::{ resolve_all_modules, resolve_and_flatten_all_modules },
13
11
  },
14
12
  store::global::GlobalStore,
13
+ utils::path::normalize_path,
15
14
  },
16
- utils::logger::{ LogLevel, Logger },
15
+ utils::logger::Logger,
17
16
  };
18
17
 
19
18
  pub struct ModuleLoader {
@@ -29,26 +28,90 @@ impl ModuleLoader {
29
28
  }
30
29
  }
31
30
 
32
- pub fn load_all(&self, global_store: &mut GlobalStore) -> HashMap<String, Vec<Statement>> {
31
+ pub fn from_raw_source(
32
+ entry_path: &str,
33
+ output_path: &str,
34
+ content: &str,
35
+ global_store: &mut GlobalStore
36
+ ) -> Self {
37
+ let normalized_entry_path = normalize_path(entry_path);
38
+
39
+ let mut module = Module::new(&entry_path);
40
+ module.content = content.to_string();
41
+
42
+ println!("Loading module from raw source: {}", normalized_entry_path);
43
+
44
+ global_store.insert_module(normalized_entry_path.to_string(), module);
45
+
46
+ Self {
47
+ entry: normalized_entry_path.to_string(),
48
+ output: output_path.to_string(),
49
+ }
50
+ }
51
+
52
+ pub fn extract_statements_map(
53
+ &self,
54
+ global_store: &GlobalStore
55
+ ) -> HashMap<String, Vec<Statement>> {
56
+ global_store.modules
57
+ .iter()
58
+ .map(|(path, module)| (path.clone(), module.statements.clone()))
59
+ .collect()
60
+ }
61
+
62
+ pub fn load_single_module(&self, global_store: &mut GlobalStore) -> Result<Module, String> {
63
+ let mut module = global_store.modules
64
+ .remove(&self.entry)
65
+ .ok_or_else(|| format!("Module not found in store for path: {}", self.entry))?;
66
+
67
+ // SECTION Lexing the module content
68
+ let lexer = Lexer::new();
69
+ let tokens = lexer
70
+ .lex_from_source(&module.content)
71
+ .map_err(|e| format!("Lexer failed: {}", e))?;
72
+
73
+ module.tokens = tokens.clone();
74
+
75
+ // SECTION Parsing tokens into statements
76
+ let mut parser = Parser::new();
77
+ parser.set_current_module(self.entry.clone());
78
+ let statements = parser.parse_tokens(tokens, global_store);
79
+ module.statements = statements;
80
+
81
+ // SECTION Error handling
82
+ let mut error_handler = ErrorHandler::new();
83
+ error_handler.detect_from_statements(&mut parser, &module.statements);
84
+
85
+ global_store.modules.insert(self.entry.clone(), module.clone());
86
+
87
+ Ok(module)
88
+ }
89
+
90
+ #[cfg(feature = "cli")]
91
+ pub fn load_all_modules(
92
+ &self,
93
+ global_store: &mut GlobalStore
94
+ ) -> (HashMap<String, Vec<Token>>, HashMap<String, Vec<Statement>>) {
33
95
  // SECTION Load the entry module and its dependencies
34
- self.load_module_recursively(&self.entry, global_store);
96
+ let tokens_by_module = self.load_module_recursively(&self.entry, global_store);
35
97
 
36
98
  // SECTION Process and resolve modules
37
99
  process_modules(self, global_store);
38
100
  resolve_all_modules(self, global_store);
39
101
 
40
- let resolved = resolve_and_flatten_all_modules(global_store);
41
-
42
- // SECTION Write resolved statements to log file
43
- write_preprocessor_log_file(&self.output, "resolved_statements.log", resolved.clone());
102
+ let statemnts_by_module = resolve_and_flatten_all_modules(global_store);
44
103
 
45
- // Return the resolved statements
46
- resolved
104
+ (tokens_by_module, statemnts_by_module)
47
105
  }
48
106
 
49
- fn load_module_recursively(&self, path: &str, global_store: &mut GlobalStore) {
107
+ #[cfg(feature = "cli")]
108
+ fn load_module_recursively(
109
+ &self,
110
+ path: &str,
111
+ global_store: &mut GlobalStore
112
+ ) -> HashMap<String, Vec<Token>> {
50
113
  if global_store.modules.contains_key(path) {
51
- return;
114
+ return HashMap::new();
52
115
  }
53
116
 
54
117
  let lexer = Lexer::new();
@@ -75,15 +138,25 @@ impl ModuleLoader {
75
138
 
76
139
  // SECTION Module creation
77
140
  let mut module = Module::new(path);
78
- module.tokens = tokens;
79
- module.statements = statements;
141
+ module.tokens = tokens.clone();
142
+ module.statements = statements.clone();
80
143
 
81
144
  global_store.insert_module(path.to_string(), module);
82
145
 
83
146
  // Then load the imports recursively
84
147
  self.load_module_imports(&path.to_string(), global_store);
148
+
149
+ // Return all tokens by module
150
+ let mut tokens_by_module = HashMap::new();
151
+
152
+ global_store.modules.iter().for_each(|(path, module)| {
153
+ tokens_by_module.insert(path.clone(), module.tokens.clone());
154
+ });
155
+
156
+ tokens_by_module
85
157
  }
86
158
 
159
+ #[cfg(feature = "cli")]
87
160
  fn load_module_imports(&self, path: &String, global_store: &mut GlobalStore) {
88
161
  let imports = global_store.modules
89
162
  .get(path)
@@ -18,6 +18,7 @@ pub struct Module {
18
18
  pub variable_table: VariableTable,
19
19
  pub export_table: ExportTable,
20
20
  pub import_table: ImportTable,
21
+ pub content: String,
21
22
  }
22
23
 
23
24
  impl Module {
@@ -30,6 +31,7 @@ impl Module {
30
31
  export_table: ExportTable::new(),
31
32
  import_table: ImportTable::new(),
32
33
  resolved: false,
34
+ content: String::new(),
33
35
  }
34
36
  }
35
37
 
@@ -0,0 +1,46 @@
1
+ use crate::{core::{
2
+ parser::statement::{Statement, StatementKind},
3
+ preprocessor::module::Module,
4
+ shared::value::Value,
5
+ store::global::GlobalStore,
6
+ }, utils::logger::Logger};
7
+
8
+ pub fn resolve_bank(
9
+ stmt: &Statement,
10
+ module: &Module,
11
+ path: &str,
12
+ _global_store: &GlobalStore
13
+ ) -> Statement {
14
+ let mut new_stmt = stmt.clone();
15
+ let logger = Logger::new();
16
+
17
+ match &stmt.value {
18
+ Value::Identifier(ident) => {
19
+ if let Some(val) = module.variable_table.get(ident) {
20
+ new_stmt.value = val.clone();
21
+ } else {
22
+ let message = format!("Bank identifier '{ident}' not found in variable table");
23
+ logger.log_error_with_stacktrace(&message, &module.path);
24
+ new_stmt.kind = StatementKind::Error {
25
+ message: message.clone(),
26
+ };
27
+ new_stmt.value = Value::Null;
28
+ }
29
+ }
30
+
31
+ Value::String(_) => {
32
+ // Pas de résolution nécessaire
33
+ }
34
+
35
+ other => {
36
+ let message = format!("Expected a string or identifier for bank, found {:?}", other);
37
+ logger.log_error_with_stacktrace(&message, &module.path);
38
+ new_stmt.kind = StatementKind::Error {
39
+ message: "Expected a string or identifier for bank".to_string(),
40
+ };
41
+ new_stmt.value = Value::Null;
42
+ }
43
+ }
44
+
45
+ new_stmt
46
+ }
@@ -0,0 +1,148 @@
1
+ use crate::{
2
+ core::{
3
+ parser::statement::{ Statement, StatementKind },
4
+ preprocessor::{ module::Module, resolver::trigger::resolve_trigger },
5
+ shared::value::Value,
6
+ store::global::GlobalStore,
7
+ },
8
+ utils::logger::Logger,
9
+ };
10
+
11
+ pub fn resolve_loop(
12
+ stmt: &Statement,
13
+ module: &Module,
14
+ path: &str,
15
+ global_store: &GlobalStore
16
+ ) -> Statement {
17
+ let logger = Logger::new();
18
+
19
+ // Vérifie que stmt.value est bien une Map
20
+ if let Value::Map(value_map) = &stmt.value {
21
+ // Résolution de l'iterator
22
+ let iterator_value = match value_map.get("iterator") {
23
+ Some(Value::Identifier(ident)) => {
24
+ match module.variable_table.get(ident) {
25
+ Some(Value::Number(n)) => Value::Number(*n),
26
+ Some(_) => {
27
+ log_type_error(
28
+ &logger,
29
+ module,
30
+ stmt,
31
+ format!("Loop iterator '{ident}' must resolve to a number")
32
+ );
33
+ Value::Null
34
+ }
35
+ None => {
36
+ // Value is not a variable so we assume it's a number
37
+ if let Ok(n) = ident.parse::<f32>() {
38
+ Value::Number(n)
39
+ } else {
40
+ log_type_error(
41
+ &logger,
42
+ module,
43
+ stmt,
44
+ format!("Loop iterator '{ident}' is not a valid number")
45
+ );
46
+ Value::Null
47
+ }
48
+ }
49
+ }
50
+ }
51
+ Some(Value::Number(n)) => Value::Number(*n),
52
+ Some(other) => {
53
+ log_type_error(
54
+ &logger,
55
+ module,
56
+ stmt,
57
+ format!("Unexpected value for loop iterator: {:?}", other)
58
+ );
59
+ Value::Null
60
+ }
61
+ None => {
62
+ log_type_error(
63
+ &logger,
64
+ module,
65
+ stmt,
66
+ "Missing 'iterator' key in loop statement map".to_string()
67
+ );
68
+ Value::Null
69
+ }
70
+ };
71
+
72
+ // Résolution du body
73
+ let body_value = match value_map.get("body") {
74
+ Some(Value::Block(block)) => {
75
+ let mut resolved_block = Vec::new();
76
+ for ref statement in block.clone() {
77
+ match &statement.kind {
78
+ StatementKind::Trigger { entity, duration } => {
79
+ let resolved = resolve_trigger(
80
+ &mut statement.clone(),
81
+ &entity,
82
+ &mut duration.clone(),
83
+ module,
84
+ path,
85
+ global_store
86
+ );
87
+ resolved_block.push(resolved);
88
+ }
89
+ _ => {
90
+ println!("Unhandled loop body statement: {:?}", statement);
91
+ }
92
+ }
93
+ }
94
+ Value::Block(resolved_block)
95
+ }
96
+ Some(other) => {
97
+ log_type_error(
98
+ &logger,
99
+ module,
100
+ stmt,
101
+ format!("Unexpected value for loop body: {:?}", other)
102
+ );
103
+ Value::Null
104
+ }
105
+ None => {
106
+ log_type_error(
107
+ &logger,
108
+ module,
109
+ stmt,
110
+ "Missing 'body' key in loop statement map".to_string()
111
+ );
112
+ Value::Null
113
+ }
114
+ };
115
+
116
+ // ✅ Reconstruit proprement la valeur résolue
117
+ let mut resolved_map = std::collections::HashMap::new();
118
+ resolved_map.insert("iterator".to_string(), iterator_value);
119
+ resolved_map.insert("body".to_string(), body_value);
120
+
121
+ // ✅ Reconstruit le StatementLoop à partir des éléments résolus
122
+ Statement {
123
+ kind: StatementKind::Loop,
124
+ value: Value::Map(resolved_map),
125
+ ..stmt.clone()
126
+ }
127
+ } else {
128
+ log_type_error(
129
+ &logger,
130
+ module,
131
+ stmt,
132
+ format!("Expected a map for loop value, found {:?}", stmt.value)
133
+ );
134
+
135
+ Statement {
136
+ kind: StatementKind::Error {
137
+ message: "Expected a map for loop value".to_string(),
138
+ },
139
+ value: Value::Null,
140
+ ..stmt.clone()
141
+ }
142
+ }
143
+ }
144
+
145
+ fn log_type_error(logger: &Logger, module: &Module, stmt: &Statement, message: String) {
146
+ let stacktrace = format!("{}:{}:{}", module.path, stmt.line, stmt.column);
147
+ logger.log_error_with_stacktrace(&message, &stacktrace);
148
+ }