@devaloop/devalang 0.0.1-alpha.7 → 0.0.1-alpha.9

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 (86) hide show
  1. package/Cargo.toml +1 -1
  2. package/README.md +31 -16
  3. package/docs/CHANGELOG.md +49 -2
  4. package/docs/ROADMAP.md +2 -2
  5. package/docs/SYNTAX.md +41 -7
  6. package/docs/TODO.md +3 -3
  7. package/examples/condition.deva +20 -0
  8. package/examples/group.deva +3 -3
  9. package/examples/index.deva +9 -8
  10. package/examples/loop.deva +10 -8
  11. package/examples/synth.deva +14 -0
  12. package/examples/variables.deva +2 -2
  13. package/out-tsc/bin/devalang.exe +0 -0
  14. package/out-tsc/scripts/version/fetch.js +1 -5
  15. package/package.json +1 -1
  16. package/project-version.json +3 -3
  17. package/rust/cli/build.rs +6 -1
  18. package/rust/core/audio/engine.rs +89 -12
  19. package/rust/core/audio/evaluator.rs +31 -0
  20. package/rust/core/audio/interpreter/arrow_call.rs +129 -0
  21. package/rust/core/audio/interpreter/call.rs +64 -0
  22. package/rust/core/audio/interpreter/condition.rs +69 -0
  23. package/rust/core/audio/interpreter/driver.rs +216 -0
  24. package/rust/core/audio/interpreter/let_.rs +19 -0
  25. package/rust/core/audio/interpreter/load.rs +18 -0
  26. package/rust/core/audio/interpreter/loop_.rs +67 -0
  27. package/rust/core/audio/interpreter/mod.rs +12 -0
  28. package/rust/core/audio/interpreter/sleep.rs +36 -0
  29. package/rust/core/audio/interpreter/spawn.rs +66 -0
  30. package/rust/core/audio/interpreter/tempo.rs +16 -0
  31. package/rust/core/audio/interpreter/trigger.rs +69 -0
  32. package/rust/core/audio/loader/mod.rs +1 -0
  33. package/rust/core/audio/{loader.rs → loader/trigger.rs} +3 -1
  34. package/rust/core/audio/mod.rs +2 -1
  35. package/rust/core/audio/renderer.rs +54 -0
  36. package/rust/core/builder/mod.rs +1 -1
  37. package/rust/core/debugger/lexer.rs +1 -1
  38. package/rust/core/debugger/mod.rs +1 -0
  39. package/rust/core/debugger/store.rs +25 -0
  40. package/rust/core/error/mod.rs +1 -1
  41. package/rust/core/lexer/handler/arrow.rs +31 -0
  42. package/rust/core/lexer/handler/driver.rs +226 -0
  43. package/rust/core/lexer/handler/identifier.rs +3 -0
  44. package/rust/core/lexer/handler/mod.rs +4 -227
  45. package/rust/core/lexer/handler/operator.rs +44 -0
  46. package/rust/core/lexer/mod.rs +25 -4
  47. package/rust/core/lexer/token.rs +40 -9
  48. package/rust/core/parser/driver.rs +331 -0
  49. package/rust/core/parser/handler/arrow_call.rs +126 -0
  50. package/rust/core/parser/handler/at.rs +3 -7
  51. package/rust/core/parser/handler/bank.rs +5 -2
  52. package/rust/core/parser/handler/condition.rs +74 -0
  53. package/rust/core/parser/handler/dot.rs +1 -1
  54. package/rust/core/parser/handler/identifier/call.rs +41 -0
  55. package/rust/core/parser/handler/identifier/group.rs +75 -0
  56. package/rust/core/parser/handler/identifier/let_.rs +133 -0
  57. package/rust/core/parser/handler/identifier/mod.rs +51 -0
  58. package/rust/core/parser/handler/identifier/sleep.rs +33 -0
  59. package/rust/core/parser/handler/identifier/spawn.rs +41 -0
  60. package/rust/core/parser/handler/identifier/synth.rs +65 -0
  61. package/rust/core/parser/handler/loop_.rs +25 -19
  62. package/rust/core/parser/handler/mod.rs +3 -1
  63. package/rust/core/parser/handler/tempo.rs +1 -1
  64. package/rust/core/parser/mod.rs +3 -237
  65. package/rust/core/parser/statement.rs +36 -35
  66. package/rust/core/preprocessor/loader.rs +64 -49
  67. package/rust/core/preprocessor/module.rs +3 -6
  68. package/rust/core/preprocessor/processor.rs +13 -4
  69. package/rust/core/preprocessor/resolver/call.rs +123 -0
  70. package/rust/core/preprocessor/resolver/condition.rs +92 -0
  71. package/rust/core/preprocessor/resolver/driver.rs +227 -0
  72. package/rust/core/preprocessor/resolver/group.rs +35 -87
  73. package/rust/core/preprocessor/resolver/let_.rs +31 -0
  74. package/rust/core/preprocessor/resolver/loop_.rs +62 -116
  75. package/rust/core/preprocessor/resolver/mod.rs +9 -153
  76. package/rust/core/preprocessor/resolver/spawn.rs +58 -0
  77. package/rust/core/preprocessor/resolver/synth.rs +50 -0
  78. package/rust/core/preprocessor/resolver/trigger.rs +51 -50
  79. package/rust/core/preprocessor/resolver/value.rs +78 -0
  80. package/rust/core/utils/path.rs +17 -32
  81. package/rust/core/utils/validation.rs +30 -28
  82. package/typescript/scripts/version/fetch.ts +1 -6
  83. package/rust/core/audio/interpreter.rs +0 -317
  84. package/rust/core/audio/render.rs +0 -53
  85. package/rust/core/lexer/handler/equal.rs +0 -32
  86. package/rust/core/parser/handler/identifier.rs +0 -260
@@ -0,0 +1,66 @@
1
+ use crate::core::{
2
+ audio::{ engine::AudioEngine, interpreter::driver::execute_audio_block },
3
+ parser::statement::Statement,
4
+ shared::value::Value,
5
+ store::variable::VariableTable,
6
+ };
7
+
8
+ pub fn interprete_spawn_statement(
9
+ stmt: &Statement,
10
+ audio_engine: AudioEngine,
11
+ variable_table: &VariableTable,
12
+ base_bpm: f32,
13
+ base_duration: f32,
14
+ cursor_time: f32,
15
+ max_end_time: f32
16
+ ) -> Option<(f32, f32, AudioEngine)> {
17
+ match &stmt.value {
18
+ Value::String(identifier) | Value::Identifier(identifier) => {
19
+ handle_spawn_identifier(
20
+ identifier,
21
+ audio_engine,
22
+ variable_table,
23
+ base_bpm,
24
+ base_duration,
25
+ cursor_time,
26
+ max_end_time
27
+ )
28
+ }
29
+
30
+ _ => {
31
+ eprintln!("❌ Invalid spawn statement: expected identifier, found {:?}", stmt.value);
32
+ None
33
+ }
34
+ }
35
+ }
36
+
37
+ fn handle_spawn_identifier(
38
+ identifier: &str,
39
+ audio_engine: AudioEngine,
40
+ variable_table: &VariableTable,
41
+ base_bpm: f32,
42
+ base_duration: f32,
43
+ cursor_time: f32,
44
+ max_end_time: f32
45
+ ) -> Option<(f32, f32, AudioEngine)> {
46
+ if let Some(Value::Map(map)) = variable_table.get(identifier) {
47
+ if let Some(Value::Block(block)) = map.get("body") {
48
+ let (eng, _, end_time) = execute_audio_block(
49
+ audio_engine.clone(),
50
+ variable_table.clone(),
51
+ block.clone(),
52
+ base_bpm,
53
+ base_duration,
54
+ max_end_time,
55
+ cursor_time
56
+ );
57
+ return Some((max_end_time.max(end_time), end_time, eng));
58
+ } else {
59
+ eprintln!("❌ Spawn group '{}' has no 'body' block", identifier);
60
+ }
61
+ } else {
62
+ eprintln!("❌ Spawn group '{}' not found or not a map", identifier);
63
+ }
64
+
65
+ None
66
+ }
@@ -0,0 +1,16 @@
1
+ use crate::core::{ parser::statement::{ Statement, StatementKind }, shared::value::Value };
2
+
3
+ pub fn interprete_tempo_statement(stmt: &Statement) -> Option<(f32, f32)> {
4
+ if let StatementKind::Tempo = &stmt.kind {
5
+ if let Value::Number(bpm) = &stmt.value {
6
+ let bpm = *bpm as f32;
7
+ let duration = 60.0 / bpm;
8
+
9
+ return Some((bpm, duration));
10
+ } else {
11
+ eprintln!("❌ Invalid tempo value: {:?}", stmt.value);
12
+ }
13
+ }
14
+
15
+ None
16
+ }
@@ -0,0 +1,69 @@
1
+ use crate::core::{
2
+ audio::{ engine::AudioEngine, loader::trigger::load_trigger },
3
+ parser::statement::{ Statement, StatementKind },
4
+ shared::{ duration::Duration, value::Value },
5
+ store::variable::VariableTable,
6
+ };
7
+
8
+ pub fn interprete_trigger_statement(
9
+ stmt: &Statement,
10
+ audio_engine: &mut AudioEngine,
11
+ variable_table: &VariableTable,
12
+ base_duration: f32,
13
+ cursor_time: f32,
14
+ max_end_time: f32
15
+ ) -> Option<(f32, f32, AudioEngine)> {
16
+ if let StatementKind::Trigger { entity, duration } = &stmt.kind {
17
+ if let Some(trigger_val) = variable_table.get(entity) {
18
+ let duration_secs = match duration {
19
+ Duration::Number(n) => *n,
20
+
21
+ Duration::Identifier(id) => {
22
+ if id == "auto" {
23
+ 1.0
24
+ } else {
25
+ match variable_table.get(id) {
26
+ Some(Value::Number(n)) => *n,
27
+ Some(Value::Identifier(other)) if other == "auto" => 1.0,
28
+ Some(other) => {
29
+ eprintln!(
30
+ "❌ Invalid duration reference '{}': expected number, got {:?}",
31
+ id,
32
+ other
33
+ );
34
+ return None;
35
+ }
36
+ None => {
37
+ eprintln!("❌ Duration identifier '{}' not found", id);
38
+ return None;
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ Duration::Auto => 1.0,
45
+ };
46
+
47
+ let duration_final = duration_secs * base_duration;
48
+
49
+ let (src, _) = load_trigger(
50
+ trigger_val,
51
+ duration,
52
+ base_duration,
53
+ variable_table.clone()
54
+ );
55
+
56
+ let mut updated_engine = audio_engine.clone();
57
+ updated_engine.insert_sample(&src, cursor_time, duration_final, None);
58
+
59
+ let new_cursor_time = cursor_time + duration_final;
60
+ let new_max_end_time = new_cursor_time.max(max_end_time);
61
+
62
+ return Some((new_cursor_time, new_max_end_time, updated_engine));
63
+ } else {
64
+ eprintln!("❌ Unknown trigger entity: {}", entity);
65
+ }
66
+ }
67
+
68
+ None
69
+ }
@@ -0,0 +1 @@
1
+ pub mod trigger;
@@ -26,7 +26,9 @@ pub fn load_trigger(
26
26
  duration_as_secs = *num;
27
27
  } else if let Some(Value::String(num_str)) = variable_table.get(duration_identifier) {
28
28
  duration_as_secs = num_str.parse::<f32>().unwrap_or(base_duration);
29
- } else if let Some(Value::Identifier(num_str)) = variable_table.get(duration_identifier) {
29
+ } else if
30
+ let Some(Value::Identifier(num_str)) = variable_table.get(duration_identifier)
31
+ {
30
32
  duration_as_secs = num_str.parse::<f32>().unwrap_or(base_duration);
31
33
  } else {
32
34
  eprintln!("❌ Invalid duration identifier: {}", duration_identifier);
@@ -2,4 +2,5 @@ pub mod engine;
2
2
  pub mod interpreter;
3
3
  pub mod loader;
4
4
  pub mod player;
5
- pub mod render;
5
+ pub mod renderer;
6
+ pub mod evaluator;
@@ -0,0 +1,54 @@
1
+ use std::collections::HashMap;
2
+
3
+ use crate::{
4
+ core::{
5
+ audio::{ engine::AudioEngine, interpreter::driver::run_audio_program },
6
+ parser::statement::Statement,
7
+ store::global::GlobalStore,
8
+ },
9
+ utils::logger::{ LogLevel, Logger },
10
+ };
11
+
12
+ pub fn render_audio_with_modules(
13
+ modules: HashMap<String, Vec<Statement>>,
14
+ output_dir: &str,
15
+ global_store: &mut GlobalStore
16
+ ) -> HashMap<String, AudioEngine> {
17
+ let mut result = HashMap::new();
18
+
19
+ for (module_name, statements) in modules {
20
+ let mut global_max_end_time: f32 = 0.0;
21
+ let mut initial_engine = AudioEngine::new(module_name.clone());
22
+
23
+ // Apply global variables to the initial engine
24
+ 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)
42
+ );
43
+ continue;
44
+ }
45
+
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);
49
+
50
+ result.insert(module_name, updated_engine);
51
+ }
52
+
53
+ result
54
+ }
@@ -1,4 +1,4 @@
1
- use crate::core::audio::render::render_audio_with_modules;
1
+ use crate::core::audio::renderer::render_audio_with_modules;
2
2
  use crate::core::parser::statement::Statement;
3
3
  use crate::core::store::global::GlobalStore;
4
4
  use std::{ collections::HashMap, fs::create_dir_all };
@@ -1,5 +1,5 @@
1
1
  use std::{ collections::HashMap, fs::create_dir_all };
2
- use crate::core::{ debugger::Debugger, lexer::token::Token, parser::statement::Statement };
2
+ use crate::core::{ debugger::Debugger, lexer::token::Token };
3
3
 
4
4
  pub fn write_lexer_log_file(
5
5
  output_dir: &str,
@@ -1,5 +1,6 @@
1
1
  pub mod preprocessor;
2
2
  pub mod lexer;
3
+ pub mod store;
3
4
 
4
5
  use std::io::Write;
5
6
 
@@ -0,0 +1,25 @@
1
+ use std::{ collections::HashMap, fs::create_dir_all };
2
+ use crate::core::{ debugger::Debugger, preprocessor::module::Module };
3
+
4
+ pub fn write_store_log_file(output_dir: &str, file_name: &str, modules: HashMap<String, Module>) {
5
+ let debugger = Debugger::new();
6
+ let mut content = String::new();
7
+
8
+ let log_directory = format!("{}/logs", output_dir);
9
+ create_dir_all(&log_directory).expect("Failed to create log directory");
10
+
11
+ for (path, module) in modules {
12
+ content.push_str(&format!("--- Module: {} ---\n", path));
13
+
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;
17
+
18
+ content.push_str(&format!("{}: {:?} = {:?}\n", index + 1, var_name, var_data));
19
+ }
20
+
21
+ content.push_str("\n");
22
+ }
23
+
24
+ debugger.write_log_file(&log_directory, file_name, &content);
25
+ }
@@ -1,4 +1,4 @@
1
- use crate::core::parser::{ statement::{ Statement, StatementKind }, Parser };
1
+ use crate::core::parser::{ statement::{ Statement, StatementKind }, driver::Parser };
2
2
 
3
3
  pub struct ErrorHandler {
4
4
  errors: Vec<Error>,
@@ -0,0 +1,31 @@
1
+ use crate::core::lexer::token::{ Token, TokenKind };
2
+
3
+ pub fn handle_arrow_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
+ let mut arrow_call = char.to_string();
13
+
14
+ while let Some(&c) = chars.peek() {
15
+ if c == '>' {
16
+ chars.next();
17
+ arrow_call.push(c);
18
+ *column += 1;
19
+ } else {
20
+ break;
21
+ }
22
+ }
23
+
24
+ tokens.push(Token {
25
+ kind: TokenKind::Arrow,
26
+ lexeme: arrow_call,
27
+ line: *line,
28
+ column: *column,
29
+ indent: *current_indent,
30
+ });
31
+ }
@@ -0,0 +1,226 @@
1
+ use crate::core::lexer::{
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, string::handle_string_lexer
4
+ },
5
+ token::{ Token, TokenKind },
6
+ };
7
+
8
+ fn advance_char<I: Iterator<Item = char>>(
9
+ chars: &mut std::iter::Peekable<I>,
10
+ line: &mut usize,
11
+ column: &mut usize
12
+ ) -> Option<char> {
13
+ let c = chars.next()?;
14
+ if c == '\n' {
15
+ // Do not increment column on newline here
16
+ } else {
17
+ *column += 1;
18
+ }
19
+ Some(c)
20
+ }
21
+
22
+ pub fn handle_content_lexing(content: String) -> Result<Vec<Token>, String> {
23
+ let mut tokens = Vec::new();
24
+
25
+ let mut line = 1;
26
+ let mut column = 1;
27
+
28
+ let mut indent_stack: Vec<usize> = vec![0];
29
+ let mut current_indent = 0;
30
+ let mut at_line_start = true;
31
+
32
+ let mut chars = content.chars().peekable();
33
+
34
+ while chars.peek().is_some() {
35
+ if at_line_start {
36
+ handle_indent_lexer(
37
+ &mut chars,
38
+ &mut current_indent,
39
+ &mut indent_stack,
40
+ &mut tokens,
41
+ &mut line,
42
+ &mut column
43
+ );
44
+
45
+ at_line_start = false;
46
+ }
47
+
48
+ let Some(ch) = advance_char(&mut chars, &mut line, &mut column) else {
49
+ break;
50
+ };
51
+
52
+ match ch {
53
+ '\n' => {
54
+ handle_newline_lexer(
55
+ ch,
56
+ &mut chars,
57
+ &mut tokens,
58
+ &mut line,
59
+ &mut column,
60
+ &mut at_line_start,
61
+ &mut current_indent
62
+ );
63
+ }
64
+ ' ' | '\t' => {
65
+ // Already handled by indent_lexer
66
+ }
67
+ '#' => {
68
+ handle_comment_lexer(
69
+ ch,
70
+ &mut chars,
71
+ &mut current_indent,
72
+ &mut indent_stack,
73
+ &mut tokens,
74
+ &mut line,
75
+ &mut column
76
+ );
77
+ }
78
+ ':' => {
79
+ handle_colon_lexer(
80
+ ch,
81
+ &mut chars,
82
+ &mut current_indent,
83
+ &mut indent_stack,
84
+ &mut tokens,
85
+ &mut line,
86
+ &mut column
87
+ );
88
+ }
89
+ '=' | '!' | '<' | '>' => {
90
+ handle_operator_lexer(
91
+ ch,
92
+ &mut chars,
93
+ &mut current_indent,
94
+ &mut indent_stack,
95
+ &mut tokens,
96
+ &mut line,
97
+ &mut column
98
+ );
99
+ }
100
+ '-' => {
101
+ handle_arrow_lexer(
102
+ ch,
103
+ &mut chars,
104
+ &mut current_indent,
105
+ &mut indent_stack,
106
+ &mut tokens,
107
+ &mut line,
108
+ &mut column
109
+ );
110
+ }
111
+ '{' => {
112
+ handle_lbrace_lexer(
113
+ ch,
114
+ &mut chars,
115
+ &mut current_indent,
116
+ &mut indent_stack,
117
+ &mut tokens,
118
+ &mut line,
119
+ &mut column
120
+ );
121
+ }
122
+ '}' => {
123
+ handle_rbrace_lexer(
124
+ ch,
125
+ &mut chars,
126
+ &mut current_indent,
127
+ &mut indent_stack,
128
+ &mut tokens,
129
+ &mut line,
130
+ &mut column
131
+ );
132
+ }
133
+ '.' => {
134
+ handle_dot_lexer(
135
+ ch,
136
+ &mut chars,
137
+ &mut current_indent,
138
+ &mut indent_stack,
139
+ &mut tokens,
140
+ &mut line,
141
+ &mut column
142
+ );
143
+ }
144
+ '@' => {
145
+ handle_at_lexer(
146
+ ch,
147
+ &mut chars,
148
+ &mut current_indent,
149
+ &mut indent_stack,
150
+ &mut tokens,
151
+ &mut line,
152
+ &mut column
153
+ );
154
+ }
155
+ '\"' | '\'' => {
156
+ handle_string_lexer(
157
+ ch,
158
+ &mut chars,
159
+ &mut current_indent,
160
+ &mut indent_stack,
161
+ &mut tokens,
162
+ &mut line,
163
+ &mut column
164
+ );
165
+ }
166
+ c if c.is_ascii_digit() => {
167
+ handle_number_lexer(
168
+ c,
169
+ &mut chars,
170
+ &mut current_indent,
171
+ &mut indent_stack,
172
+ &mut tokens,
173
+ &mut line,
174
+ &mut column
175
+ );
176
+ }
177
+ c if c.is_ascii_alphabetic() => {
178
+ handle_identifier_lexer(
179
+ c,
180
+ &mut chars,
181
+ &mut current_indent,
182
+ &mut indent_stack,
183
+ &mut tokens,
184
+ &mut line,
185
+ &mut column
186
+ );
187
+ }
188
+ _ => {
189
+ // Ignore unknown char
190
+ }
191
+ }
192
+ }
193
+
194
+ while indent_stack.len() > 1 {
195
+ indent_stack.pop();
196
+ current_indent = *indent_stack.last().unwrap();
197
+ tokens.push(Token {
198
+ kind: TokenKind::Dedent,
199
+ lexeme: String::new(),
200
+ line,
201
+ column,
202
+ indent: current_indent,
203
+ });
204
+ }
205
+
206
+ tokens.push(Token {
207
+ kind: TokenKind::EOF,
208
+ lexeme: String::new(),
209
+ line: line + 1,
210
+ column: 0,
211
+ indent: 0,
212
+ });
213
+
214
+ // NOTE: Debug only
215
+ // for token in &tokens {
216
+ // println!(
217
+ // "{:?} @ line {}, col {}, indent {}",
218
+ // token.kind,
219
+ // token.line,
220
+ // token.column,
221
+ // token.indent
222
+ // );
223
+ // }
224
+
225
+ Ok(tokens)
226
+ }
@@ -22,9 +22,12 @@ pub fn handle_identifier_lexer(
22
22
  }
23
23
 
24
24
  let kind = match ident.as_str() {
25
+ "if" => TokenKind::If,
26
+ "else" => TokenKind::Else,
25
27
  "bank" => TokenKind::Bank,
26
28
  "bpm" => TokenKind::Tempo,
27
29
  "loop" => TokenKind::Loop,
30
+ "synth" => TokenKind::Synth,
28
31
  _ => TokenKind::Identifier,
29
32
  };
30
33