@devaloop/devalang 0.0.1-alpha.12 → 0.0.1-alpha.13

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 (62) hide show
  1. package/Cargo.toml +54 -53
  2. package/README.md +1 -14
  3. package/docs/CHANGELOG.md +26 -0
  4. package/docs/TODO.md +1 -1
  5. package/examples/index.deva +10 -13
  6. package/out-tsc/bin/devalang.exe +0 -0
  7. package/package.json +1 -1
  8. package/project-version.json +3 -3
  9. package/rust/cli/build.rs +25 -2
  10. package/rust/cli/check.rs +26 -3
  11. package/rust/cli/play.rs +1 -1
  12. package/rust/core/audio/engine.rs +126 -73
  13. package/rust/core/audio/interpreter/call.rs +72 -47
  14. package/rust/core/audio/interpreter/condition.rs +14 -12
  15. package/rust/core/audio/interpreter/driver.rs +84 -127
  16. package/rust/core/audio/interpreter/function.rs +21 -0
  17. package/rust/core/audio/interpreter/load.rs +1 -1
  18. package/rust/core/audio/interpreter/loop_.rs +24 -18
  19. package/rust/core/audio/interpreter/mod.rs +2 -1
  20. package/rust/core/audio/interpreter/sleep.rs +0 -6
  21. package/rust/core/audio/interpreter/spawn.rs +78 -60
  22. package/rust/core/audio/interpreter/trigger.rs +157 -70
  23. package/rust/core/audio/loader/trigger.rs +37 -4
  24. package/rust/core/audio/player.rs +20 -10
  25. package/rust/core/audio/renderer.rs +24 -25
  26. package/rust/core/debugger/mod.rs +2 -0
  27. package/rust/core/debugger/module.rs +47 -0
  28. package/rust/core/debugger/store.rs +25 -11
  29. package/rust/core/error/mod.rs +6 -0
  30. package/rust/core/lexer/handler/driver.rs +23 -1
  31. package/rust/core/lexer/handler/identifier.rs +1 -0
  32. package/rust/core/lexer/handler/mod.rs +1 -0
  33. package/rust/core/lexer/handler/parenthesis.rs +41 -0
  34. package/rust/core/lexer/token.rs +3 -0
  35. package/rust/core/parser/driver.rs +3 -1
  36. package/rust/core/parser/handler/dot.rs +64 -127
  37. package/rust/core/parser/handler/identifier/call.rs +69 -22
  38. package/rust/core/parser/handler/identifier/function.rs +92 -0
  39. package/rust/core/parser/handler/identifier/let_.rs +13 -19
  40. package/rust/core/parser/handler/identifier/mod.rs +1 -0
  41. package/rust/core/parser/handler/identifier/spawn.rs +74 -27
  42. package/rust/core/parser/statement.rs +16 -4
  43. package/rust/core/preprocessor/loader.rs +45 -29
  44. package/rust/core/preprocessor/module.rs +3 -1
  45. package/rust/core/preprocessor/processor.rs +26 -1
  46. package/rust/core/preprocessor/resolver/call.rs +61 -84
  47. package/rust/core/preprocessor/resolver/condition.rs +11 -6
  48. package/rust/core/preprocessor/resolver/driver.rs +52 -6
  49. package/rust/core/preprocessor/resolver/function.rs +78 -0
  50. package/rust/core/preprocessor/resolver/group.rs +43 -13
  51. package/rust/core/preprocessor/resolver/let_.rs +7 -10
  52. package/rust/core/preprocessor/resolver/mod.rs +2 -1
  53. package/rust/core/preprocessor/resolver/spawn.rs +64 -30
  54. package/rust/core/preprocessor/resolver/trigger.rs +7 -3
  55. package/rust/core/preprocessor/resolver/value.rs +10 -1
  56. package/rust/core/shared/value.rs +4 -1
  57. package/rust/core/store/function.rs +34 -0
  58. package/rust/core/store/global.rs +9 -10
  59. package/rust/core/store/mod.rs +2 -1
  60. package/rust/core/store/variable.rs +6 -0
  61. package/rust/lib.rs +10 -7
  62. package/rust/utils/mod.rs +45 -1
@@ -15,105 +15,192 @@ pub fn interprete_trigger_statement(
15
15
  cursor_time: f32,
16
16
  max_end_time: f32
17
17
  ) -> Option<(f32, f32, AudioEngine)> {
18
- if let StatementKind::Trigger { entity, duration } = &stmt.kind {
19
- if let Some(trigger_val) = resolve_namespaced_variable(entity, variable_table) {
20
- let duration_secs = match duration {
21
- Duration::Number(n) => *n,
22
-
23
- Duration::Identifier(id) => {
24
- if id == "auto" {
25
- 1.0
18
+ if let StatementKind::Trigger { entity, duration, effects } = &stmt.kind {
19
+ let mut trigger_val = Value::String(entity.clone());
20
+ let mut trigger_src = String::new();
21
+
22
+ match variable_table.variables.get(entity) {
23
+ Some(Value::Identifier(id)) => {
24
+ // Get real value from global variable table
25
+ if let Some(global_table) = &variable_table.parent {
26
+ if let Some(val) = global_table.get(id) {
27
+ trigger_val = val.clone();
26
28
  } else {
27
- match variable_table.get(id) {
28
- Some(Value::Number(n)) => *n,
29
- Some(Value::Identifier(other)) if other == "auto" => 1.0,
30
- Some(other) => {
31
- eprintln!(
32
- "❌ Invalid duration reference '{}': expected number, got {:?}",
33
- id,
34
- other
35
- );
36
- return None;
37
- }
38
- None => {
39
- eprintln!("❌ Duration identifier '{}' not found", id);
40
- return None;
41
- }
42
- }
29
+ eprintln!("❌ Trigger entity '{}' not found in global variable table", id);
30
+ return None;
43
31
  }
32
+ } else if let Some(val) = variable_table.get(id) {
33
+ trigger_val = val.clone();
34
+ } else {
35
+ eprintln!("❌ Trigger entity '{}' not found in variable table", id);
36
+ return None;
44
37
  }
38
+ }
39
+ Some(Value::Sample(sample_src)) => {
40
+ // If the entity is a sample, we use its path directly
41
+ trigger_src = sample_src.clone();
42
+ }
43
+ Some(Value::Map(map)) => {
44
+ // If the entity is a map, we assume it contains an "entity" key
45
+ if let Some(Value::String(src)) = map.get("entity") {
46
+ trigger_val = Value::String(src.clone());
47
+ } else if let Some(Value::Identifier(src)) = map.get("entity") {
48
+ trigger_val = Value::Identifier(src.clone());
49
+ } else {
50
+ eprintln!(
51
+ "❌ Trigger map must contain an 'entity' key with a string or identifier value."
52
+ );
53
+ return None;
54
+ }
55
+ }
56
+ _ => {
57
+ trigger_val = Value::String(entity.clone());
58
+ }
59
+ }
45
60
 
46
- Duration::Beat(beat_str) => {
47
- // Assuming beat_str is in the format "numerator/denominator"
48
- let parts: Vec<&str> = beat_str.split('/').collect();
49
-
50
- if parts.len() != 2 {
51
- eprintln!("❌ Invalid beat duration format: {}", beat_str);
52
- return None;
61
+ let duration_secs = match duration {
62
+ Duration::Number(n) => *n,
63
+
64
+ Duration::Identifier(id) => {
65
+ if id == "auto" {
66
+ 1.0
67
+ } else {
68
+ match variable_table.get(id) {
69
+ Some(Value::Number(n)) => *n,
70
+ Some(Value::Identifier(other)) if other == "auto" => 1.0,
71
+ Some(other) => {
72
+ eprintln!(
73
+ "❌ Invalid duration reference '{}': expected number, got {:?}",
74
+ id,
75
+ other
76
+ );
77
+ return None;
78
+ }
79
+ None => {
80
+ eprintln!("❌ Duration identifier '{}' not found", id);
81
+ return None;
82
+ }
53
83
  }
54
-
55
- let numerator: f32 = parts[0].parse().unwrap_or(1.0);
56
- let denominator: f32 = parts[1].parse().unwrap_or(1.0);
57
- numerator / denominator
58
84
  }
85
+ }
59
86
 
60
- Duration::Auto => 1.0,
61
- };
87
+ Duration::Beat(beat_str) => {
88
+ let parts: Vec<&str> = beat_str.split('/').collect();
89
+ if parts.len() != 2 {
90
+ eprintln!("❌ Invalid beat duration format: {}", beat_str);
91
+ return None;
92
+ }
62
93
 
63
- let duration_final = duration_secs * base_duration;
94
+ let numerator: f32 = parts[0].parse().unwrap_or(1.0);
95
+ let denominator: f32 = parts[1].parse().unwrap_or(4.0);
64
96
 
65
- let (src, _) = load_trigger(
66
- trigger_val,
67
- duration,
68
- base_duration,
69
- variable_table.clone()
70
- );
97
+ let beats = (numerator / denominator) * 4.0;
71
98
 
72
- if let Some(effects) = extract_effects(stmt.value.clone()) {
73
- audio_engine.insert_sample(&src, cursor_time, duration_final, Some(effects));
74
- } else {
75
- audio_engine.insert_sample(&src, cursor_time, duration_final, None);
99
+ beats * base_duration
76
100
  }
77
101
 
78
- let mut updated_engine = audio_engine.clone();
102
+ Duration::Auto => base_duration,
103
+ };
79
104
 
80
- let new_cursor_time = cursor_time + duration_final;
81
- let new_max_end_time = new_cursor_time.max(max_end_time);
82
-
83
- return Some((new_cursor_time, new_max_end_time, updated_engine));
105
+ let final_variable_table = if let Some(parent) = &variable_table.parent {
106
+ VariableTable {
107
+ variables: parent.variables.clone(),
108
+ parent: None,
109
+ }
84
110
  } else {
85
- eprintln!("❌ Unknown trigger entity: {}", entity);
111
+ variable_table.clone()
112
+ };
113
+
114
+ let (src, sample_length) = load_trigger(
115
+ &trigger_val,
116
+ duration,
117
+ effects,
118
+ base_duration,
119
+ final_variable_table.clone()
120
+ );
121
+
122
+ if trigger_src.is_empty() {
123
+ trigger_src = src;
86
124
  }
87
- }
88
-
89
- None
90
- }
91
125
 
92
- fn resolve_namespaced_variable<'a>(path: &str, variables: &'a VariableTable) -> Option<&'a Value> {
93
- let mut current: Option<&Value> = None;
126
+ let effects = extract_effects(stmt.value.clone());
127
+ let one_shot = effects
128
+ .as_ref()
129
+ .and_then(|map| map.get("one_shot"))
130
+ .and_then(|v| {
131
+ match v {
132
+ Value::Identifier(id) if id == "true" => Some(true),
133
+ Value::String(s) if s == "true" => Some(true),
134
+ _ => None,
135
+ }
136
+ })
137
+ .unwrap_or(false);
94
138
 
95
- for (i, part) in path.split('.').enumerate() {
96
- if i == 0 {
97
- current = variables.get(part);
139
+ let play_length = if one_shot {
140
+ sample_length // play entire sample
98
141
  } else {
99
- current = match current {
100
- Some(Value::Map(map)) => map.get(part),
101
- _ => {
142
+ duration_secs.min(sample_length)
143
+ };
144
+
145
+ let trigger_src = match trigger_val.get("entity") {
146
+ Some(Value::String(src)) => src.clone(),
147
+ Some(Value::Identifier(id)) => id.clone(),
148
+ Some(Value::Statement(stmt)) => {
149
+ if let StatementKind::Trigger { entity, .. } = &stmt.kind {
150
+ entity.clone()
151
+ } else {
152
+ eprintln!("❌ Invalid trigger statement in map: expected 'Trigger' kind");
102
153
  return None;
103
154
  }
104
- };
155
+ }
156
+ _ => trigger_src,
157
+ };
158
+
159
+ if let Some(effects_map) = effects {
160
+ audio_engine.insert_sample(
161
+ &trigger_src,
162
+ cursor_time,
163
+ play_length,
164
+ Some(effects_map),
165
+ &final_variable_table
166
+ );
167
+ } else {
168
+ audio_engine.insert_sample(
169
+ &trigger_src,
170
+ cursor_time,
171
+ play_length,
172
+ None,
173
+ &final_variable_table
174
+ );
105
175
  }
176
+
177
+ let new_cursor_time = cursor_time + duration_secs; // advance by beat duration
178
+ let new_max_end_time = (cursor_time + play_length).max(max_end_time);
179
+
180
+ let updated_engine = audio_engine.clone();
181
+
182
+ return Some((new_cursor_time, new_max_end_time, updated_engine));
106
183
  }
107
184
 
108
- current
185
+ None
109
186
  }
110
187
 
111
188
  fn extract_effects(value: Value) -> Option<HashMap<String, Value>> {
112
- if let Value::Map(map) = value.clone() {
189
+ if let Value::Map(map) = value {
113
190
  let mut effects = HashMap::new();
114
191
 
115
192
  for (key, val) in map {
116
- effects.insert(key.clone(), val);
193
+ if key == "effects" {
194
+ if let Value::Map(effect_map) = val {
195
+ for (effect_key, effect_val) in effect_map {
196
+ effects.insert(effect_key, effect_val);
197
+ }
198
+ } else {
199
+ return None; // effects must be a map
200
+ }
201
+ } else {
202
+ return Some(effects);
203
+ }
117
204
  }
118
205
 
119
206
  Some(effects)
@@ -1,8 +1,13 @@
1
- use crate::core::{ shared::{ duration::Duration, value::Value }, store::variable::VariableTable };
1
+ use crate::core::{
2
+ parser::statement::StatementKind,
3
+ shared::{ duration::Duration, value::Value },
4
+ store::variable::VariableTable,
5
+ };
2
6
 
3
7
  pub fn load_trigger(
4
8
  trigger: &Value,
5
9
  duration: &Duration,
10
+ effects: &Option<Value>,
6
11
  base_duration: f32,
7
12
  variable_table: VariableTable
8
13
  ) -> (String, f32) {
@@ -13,8 +18,36 @@ pub fn load_trigger(
13
18
  Value::String(src) => {
14
19
  trigger_path = src.to_string();
15
20
  }
21
+ Value::Identifier(src) => {
22
+ trigger_path = src.to_string();
23
+ }
24
+
25
+ Value::Map(map) => {
26
+ if let Some(Value::String(src)) = map.get("entity") {
27
+ trigger_path = format!("devalang://bank/{}", src.to_string());
28
+ } else if let Some(Value::Identifier(src)) = map.get("entity") {
29
+ trigger_path = format!("devalang://bank/{}", src.to_string());
30
+ } else {
31
+ eprintln!(
32
+ "❌ Trigger map must contain an 'entity' key with a string or identifier value."
33
+ );
34
+ }
35
+ }
36
+ Value::Sample(src) => {
37
+ trigger_path = src.to_string();
38
+ }
39
+ Value::Statement(stmt) => {
40
+ if let StatementKind::Trigger { entity, duration, effects } = &stmt.kind {
41
+ trigger_path = entity.clone();
42
+ } else {
43
+ eprintln!("❌ Trigger statement must be of type 'Trigger', found: {:?}", stmt.kind);
44
+ }
45
+ }
16
46
  _ => {
17
- eprintln!("❌ Invalid trigger type. Expected a text variable.");
47
+ eprintln!(
48
+ "❌ Invalid trigger type. Expected a string or identifier variable, found: {:?}",
49
+ trigger
50
+ );
18
51
  }
19
52
  }
20
53
 
@@ -45,11 +78,11 @@ pub fn load_trigger(
45
78
 
46
79
  Duration::Beat(beat_str) => {
47
80
  let parts: Vec<&str> = beat_str.split('/').collect();
48
-
81
+
49
82
  if parts.len() == 2 {
50
83
  let numerator: f32 = parts[0].parse().unwrap_or(1.0);
51
84
  let denominator: f32 = parts[1].parse().unwrap_or(1.0);
52
- duration_as_secs = numerator / denominator * base_duration;
85
+ duration_as_secs = (numerator / denominator) * base_duration;
53
86
  } else {
54
87
  eprintln!("❌ Invalid beat duration format: {}", beat_str);
55
88
  }
@@ -21,23 +21,33 @@ impl AudioPlayer {
21
21
  }
22
22
  }
23
23
 
24
- fn load_source(&self, path: &str) -> impl Source<Item = f32> + Send + 'static {
25
- let file = File::open(path).unwrap();
26
- let reader = BufReader::new(file);
27
-
28
- Decoder::new(reader).unwrap().convert_samples()
24
+ fn load_source(&self, path: &str) -> Option<impl Source<Item = f32> + Send + 'static> {
25
+ if let Ok(file) = File::open(path) {
26
+ let reader = BufReader::new(file);
27
+ match Decoder::new(reader) {
28
+ Ok(decoder) => Some(decoder.convert_samples()),
29
+ Err(e) => {
30
+ eprintln!("❌ Failed to decode audio file '{}': {}", path, e);
31
+ None
32
+ }
33
+ }
34
+ } else {
35
+ eprintln!("❌ Could not open audio file: {}", path);
36
+ None
37
+ }
29
38
  }
30
39
 
31
40
  pub fn play_file_once(&mut self, path: &str) {
32
41
  self.sink.stop();
33
42
  self.sink = Sink::try_new(&self.handle).unwrap();
34
-
35
43
  self.sink.set_volume(1.0);
36
44
 
37
- let source = self.load_source(path);
38
-
39
- self.sink.append(source);
40
- self.last_path = Some(path.to_string());
45
+ if let Some(source) = self.load_source(path) {
46
+ self.sink.append(source);
47
+ self.last_path = Some(path.to_string());
48
+ } else {
49
+ eprintln!("⚠️ Skipping playback: failed to load '{}'", path);
50
+ }
41
51
  }
42
52
 
43
53
  pub fn replay_last(&mut self) {
@@ -1,5 +1,4 @@
1
1
  use std::collections::HashMap;
2
-
3
2
  use crate::{
4
3
  core::{
5
4
  audio::{ engine::AudioEngine, interpreter::driver::run_audio_program },
@@ -18,36 +17,36 @@ pub fn render_audio_with_modules(
18
17
 
19
18
  for (module_name, statements) in modules {
20
19
  let mut global_max_end_time: f32 = 0.0;
21
- let mut initial_engine = AudioEngine::new(module_name.clone());
20
+ let mut audio_engine = AudioEngine::new(module_name.clone());
22
21
 
23
22
  // Apply global variables to the initial engine
24
23
  if let Some(module) = global_store.get_module(&module_name) {
25
- initial_engine.set_variables(module.variable_table.clone());
26
- }
27
-
28
- // interprete statements to fill the audio buffer
29
- let (mut updated_engine, _bpm, module_max_end_time) = run_audio_program(
30
- &statements,
31
- initial_engine,
32
- module_name.clone(),
33
- output_dir.to_string()
34
- );
35
-
36
- // Verify if the buffer is silent (all samples are zero)
37
- if updated_engine.buffer.iter().all(|&s| s == 0) {
38
- let logger = Logger::new();
39
- logger.log_message(
40
- LogLevel::Warning,
41
- &format!("Module '{}' ignored: silent buffer (no non-zero samples)", module_name)
24
+ // interprete statements to fill the audio buffer
25
+ let (module_max_end_time, cursor_time) = run_audio_program(
26
+ &statements,
27
+ &mut audio_engine,
28
+ module_name.clone(),
29
+ output_dir.to_string(),
30
+ module.variable_table.clone(),
31
+ module.function_table.clone(),
32
+ global_store
42
33
  );
43
- continue;
44
- }
45
34
 
46
- // Determines the maximum end time for the module
47
- global_max_end_time = global_max_end_time.max(module_max_end_time);
48
- updated_engine.set_duration(global_max_end_time);
35
+ // Verify if the buffer is silent (all samples are zero)
36
+ if audio_engine.buffer.iter().all(|&s| s == 0) {
37
+ let logger = Logger::new();
38
+ logger.log_message(
39
+ LogLevel::Warning,
40
+ &format!("Module '{}' ignored: silent buffer (no non-zero samples)", module_name)
41
+ );
42
+ }
43
+
44
+ // Determines the maximum end time for the module
45
+ global_max_end_time = global_max_end_time.max(module_max_end_time);
46
+ audio_engine.set_duration(global_max_end_time);
49
47
 
50
- result.insert(module_name, updated_engine);
48
+ result.insert(module_name, audio_engine);
49
+ }
51
50
  }
52
51
 
53
52
  result
@@ -1,6 +1,7 @@
1
1
  pub mod preprocessor;
2
2
  pub mod lexer;
3
3
  pub mod store;
4
+ pub mod module;
4
5
 
5
6
  use std::io::Write;
6
7
 
@@ -13,6 +14,7 @@ impl Debugger {
13
14
 
14
15
  pub fn write_log_file(&self, path: &str, filename: &str, content: &str) {
15
16
  std::fs::create_dir_all(path).expect("Failed to create directory");
17
+
16
18
  let file_path = format!("{}/{}", path, filename);
17
19
  let mut file = std::fs::File::create(file_path).expect("Failed to create file");
18
20
 
@@ -0,0 +1,47 @@
1
+ use std::{ fs::create_dir_all };
2
+ use crate::core::{
3
+ debugger::Debugger,
4
+ store::{ function::FunctionTable, variable::VariableTable },
5
+ };
6
+
7
+ pub fn write_module_variable_log_file(
8
+ output_dir: &str,
9
+ module_path: &str,
10
+ variable_table: &VariableTable
11
+ ) {
12
+ let debugger = Debugger::new();
13
+ let mut content = String::new();
14
+ let module_name = module_path.split('/').last().unwrap_or("index").replace(".deva", "");
15
+
16
+ let log_directory = format!("{}/logs/modules/{}", output_dir, module_name);
17
+ create_dir_all(&log_directory).expect("Failed to create log directory");
18
+
19
+ for (var_name, var_data) in &variable_table.variables {
20
+ content.push_str(&format!("{:?} = {:?}\n", var_name, var_data));
21
+ }
22
+
23
+ content.push_str("\n");
24
+
25
+ debugger.write_log_file(&log_directory, "variables.log", &content);
26
+ }
27
+
28
+ pub fn write_module_function_log_file(
29
+ output_dir: &str,
30
+ module_path: &str,
31
+ function_table: &FunctionTable
32
+ ) {
33
+ let debugger = Debugger::new();
34
+ let mut content = String::new();
35
+ let module_name = module_path.split('/').last().unwrap_or("index").replace(".deva", "");
36
+
37
+ let log_directory = format!("{}/logs/modules/{}", output_dir, module_name);
38
+ create_dir_all(&log_directory).expect("Failed to create log directory");
39
+
40
+ for (func_name, func_data) in &function_table.functions {
41
+ content.push_str(&format!("{:?} = {:?}\n", func_name, func_data));
42
+ }
43
+
44
+ content.push_str("\n");
45
+
46
+ debugger.write_log_file(&log_directory, "functions.log", &content);
47
+ }
@@ -1,25 +1,39 @@
1
- use std::{ collections::HashMap, fs::create_dir_all };
2
- use crate::core::{ debugger::Debugger, preprocessor::module::Module };
1
+ use std::{ fs::create_dir_all };
2
+ use crate::core::{
3
+ debugger::Debugger,
4
+ store::{ function::FunctionTable, variable::VariableTable },
5
+ };
3
6
 
4
- pub fn write_store_log_file(output_dir: &str, file_name: &str, modules: HashMap<String, Module>) {
7
+ pub fn write_variables_log_file(output_dir: &str, file_name: &str, variables: VariableTable) {
5
8
  let debugger = Debugger::new();
6
9
  let mut content = String::new();
7
10
 
8
11
  let log_directory = format!("{}/logs", output_dir);
9
12
  create_dir_all(&log_directory).expect("Failed to create log directory");
10
13
 
11
- for (path, module) in modules {
12
- content.push_str(&format!("--- Module: {} ---\n", path));
14
+ for (var_name, var_data) in variables.variables {
15
+ content.push_str(&format!("{:?} = {:?}\n", var_name, var_data));
16
+ }
17
+
18
+ content.push_str("\n");
19
+
20
+ debugger.write_log_file(&log_directory, file_name, &content);
21
+ }
13
22
 
14
- for (index, var_value) in module.variable_table.variables.iter().enumerate() {
15
- let var_name = var_value.0.clone();
16
- let var_data = var_value.1;
23
+ pub fn write_function_log_file(output_dir: &str, file_name: &str, functions: FunctionTable) {
24
+ let debugger = Debugger::new();
25
+ let mut content = String::new();
17
26
 
18
- content.push_str(&format!("{}: {:?} = {:?}\n", index + 1, var_name, var_data));
19
- }
27
+ let log_directory = format!("{}/logs", output_dir);
28
+ create_dir_all(&log_directory).expect("Failed to create log directory");
20
29
 
21
- content.push_str("\n");
30
+ for (index, function) in functions.functions {
31
+ content.push_str(
32
+ &format!("'{}' = [{:?}] => {:?}\n", function.name, function.parameters, function.body)
33
+ );
22
34
  }
23
35
 
36
+ content.push_str("\n");
37
+
24
38
  debugger.write_log_file(&log_directory, file_name, &content);
25
39
  }
@@ -4,6 +4,12 @@ pub struct ErrorHandler {
4
4
  errors: Vec<Error>,
5
5
  }
6
6
 
7
+ pub struct ErrorResult {
8
+ pub message: String,
9
+ pub line: usize,
10
+ pub column: usize,
11
+ }
12
+
7
13
  pub struct Error {
8
14
  pub message: String,
9
15
  pub line: usize,
@@ -1,6 +1,6 @@
1
1
  use crate::core::lexer::{
2
2
  handler::{
3
- arrow::handle_arrow_lexer, at::handle_at_lexer, brace::{ handle_lbrace_lexer, handle_rbrace_lexer }, colon::handle_colon_lexer, comment::handle_comment_lexer, dot::handle_dot_lexer, identifier::handle_identifier_lexer, indent::handle_indent_lexer, newline::handle_newline_lexer, number::handle_number_lexer, operator::handle_operator_lexer, slash::handle_slash_lexer, string::handle_string_lexer
3
+ arrow::handle_arrow_lexer, at::handle_at_lexer, brace::{ handle_lbrace_lexer, handle_rbrace_lexer }, colon::handle_colon_lexer, comment::handle_comment_lexer, dot::handle_dot_lexer, identifier::handle_identifier_lexer, indent::handle_indent_lexer, newline::handle_newline_lexer, number::handle_number_lexer, operator::handle_operator_lexer, parenthesis::{handle_lparen_lexer, handle_rparen_lexer}, slash::handle_slash_lexer, string::handle_string_lexer
4
4
  },
5
5
  token::{ Token, TokenKind },
6
6
  };
@@ -145,6 +145,28 @@ pub fn handle_content_lexing(content: String) -> Result<Vec<Token>, String> {
145
145
  &mut column
146
146
  );
147
147
  }
148
+ '(' => {
149
+ handle_lparen_lexer(
150
+ ch,
151
+ &mut chars,
152
+ &mut current_indent,
153
+ &mut indent_stack,
154
+ &mut tokens,
155
+ &mut line,
156
+ &mut column
157
+ );
158
+ }
159
+ ')' => {
160
+ handle_rparen_lexer(
161
+ ch,
162
+ &mut chars,
163
+ &mut current_indent,
164
+ &mut indent_stack,
165
+ &mut tokens,
166
+ &mut line,
167
+ &mut column
168
+ );
169
+ }
148
170
  '.' => {
149
171
  handle_dot_lexer(
150
172
  ch,
@@ -28,6 +28,7 @@ pub fn handle_identifier_lexer(
28
28
  "bpm" => TokenKind::Tempo,
29
29
  "loop" => TokenKind::Loop,
30
30
  "synth" => TokenKind::Synth,
31
+ "fn" => TokenKind::Function,
31
32
  _ => TokenKind::Identifier,
32
33
  };
33
34
 
@@ -13,3 +13,4 @@ pub mod indent;
13
13
  pub mod string;
14
14
  pub mod arrow;
15
15
  pub mod slash;
16
+ pub mod parenthesis;
@@ -0,0 +1,41 @@
1
+ use crate::core::lexer::token::{ Token, TokenKind };
2
+
3
+ pub fn handle_rparen_lexer(
4
+ char: char,
5
+ chars: &mut std::iter::Peekable<std::str::Chars>,
6
+ current_indent: &mut usize,
7
+ indent_stack: &mut Vec<usize>,
8
+ tokens: &mut Vec<Token>,
9
+ line: &mut usize,
10
+ column: &mut usize
11
+ ) {
12
+ tokens.push(Token {
13
+ kind: TokenKind::RParen,
14
+ lexeme: char.to_string(),
15
+ line: *line,
16
+ column: *column,
17
+ indent: *current_indent,
18
+ });
19
+
20
+ *column += 1;
21
+ }
22
+
23
+ pub fn handle_lparen_lexer(
24
+ char: char,
25
+ chars: &mut std::iter::Peekable<std::str::Chars>,
26
+ current_indent: &mut usize,
27
+ indent_stack: &mut Vec<usize>,
28
+ tokens: &mut Vec<Token>,
29
+ line: &mut usize,
30
+ column: &mut usize
31
+ ) {
32
+ tokens.push(Token {
33
+ kind: TokenKind::LParen,
34
+ lexeme: char.to_string(),
35
+ line: *line,
36
+ column: *column,
37
+ indent: *current_indent,
38
+ });
39
+
40
+ *column += 1;
41
+ }