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

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 (93) hide show
  1. package/.devalang +8 -9
  2. package/Cargo.toml +8 -3
  3. package/README.md +36 -34
  4. package/docs/CHANGELOG.md +65 -1
  5. package/docs/CONTRIBUTING.md +1 -0
  6. package/docs/ROADMAP.md +2 -2
  7. package/docs/TODO.md +6 -5
  8. package/examples/bank.deva +2 -4
  9. package/examples/function.deva +15 -0
  10. package/examples/index.deva +25 -14
  11. package/out-tsc/bin/devalang.exe +0 -0
  12. package/package.json +6 -6
  13. package/project-version.json +3 -3
  14. package/rust/cli/bank.rs +2 -1
  15. package/rust/cli/build.rs +76 -14
  16. package/rust/cli/check.rs +71 -8
  17. package/rust/cli/driver.rs +40 -28
  18. package/rust/cli/install.rs +22 -7
  19. package/rust/cli/login.rs +134 -0
  20. package/rust/cli/mod.rs +2 -1
  21. package/rust/cli/play.rs +45 -20
  22. package/rust/common/api.rs +8 -0
  23. package/rust/common/cdn.rs +2 -5
  24. package/rust/common/mod.rs +3 -1
  25. package/rust/common/sso.rs +8 -0
  26. package/rust/config/driver.rs +19 -1
  27. package/rust/config/loader.rs +56 -10
  28. package/rust/core/audio/engine.rs +254 -91
  29. package/rust/core/audio/interpreter/arrow_call.rs +34 -15
  30. package/rust/core/audio/interpreter/call.rs +72 -47
  31. package/rust/core/audio/interpreter/condition.rs +14 -12
  32. package/rust/core/audio/interpreter/driver.rs +90 -128
  33. package/rust/core/audio/interpreter/function.rs +21 -0
  34. package/rust/core/audio/interpreter/load.rs +1 -1
  35. package/rust/core/audio/interpreter/loop_.rs +24 -18
  36. package/rust/core/audio/interpreter/mod.rs +2 -1
  37. package/rust/core/audio/interpreter/sleep.rs +0 -6
  38. package/rust/core/audio/interpreter/spawn.rs +78 -60
  39. package/rust/core/audio/interpreter/trigger.rs +157 -70
  40. package/rust/core/audio/loader/trigger.rs +37 -4
  41. package/rust/core/audio/player.rs +20 -10
  42. package/rust/core/audio/renderer.rs +24 -25
  43. package/rust/core/builder/mod.rs +11 -6
  44. package/rust/core/debugger/mod.rs +2 -0
  45. package/rust/core/debugger/module.rs +47 -0
  46. package/rust/core/debugger/store.rs +25 -11
  47. package/rust/core/error/mod.rs +6 -0
  48. package/rust/core/lexer/handler/driver.rs +23 -1
  49. package/rust/core/lexer/handler/identifier.rs +1 -0
  50. package/rust/core/lexer/handler/indent.rs +16 -2
  51. package/rust/core/lexer/handler/mod.rs +1 -0
  52. package/rust/core/lexer/handler/parenthesis.rs +41 -0
  53. package/rust/core/lexer/token.rs +4 -0
  54. package/rust/core/mod.rs +2 -1
  55. package/rust/core/parser/driver.rs +47 -4
  56. package/rust/core/parser/handler/arrow_call.rs +78 -18
  57. package/rust/core/parser/handler/bank.rs +35 -7
  58. package/rust/core/parser/handler/dot.rs +81 -123
  59. package/rust/core/parser/handler/identifier/call.rs +69 -22
  60. package/rust/core/parser/handler/identifier/function.rs +92 -0
  61. package/rust/core/parser/handler/identifier/let_.rs +13 -19
  62. package/rust/core/parser/handler/identifier/mod.rs +1 -0
  63. package/rust/core/parser/handler/identifier/spawn.rs +74 -27
  64. package/rust/core/parser/statement.rs +16 -4
  65. package/rust/core/plugin/loader.rs +48 -0
  66. package/rust/core/plugin/mod.rs +1 -0
  67. package/rust/core/preprocessor/loader.rs +50 -32
  68. package/rust/core/preprocessor/module.rs +3 -1
  69. package/rust/core/preprocessor/processor.rs +26 -1
  70. package/rust/core/preprocessor/resolver/call.rs +61 -84
  71. package/rust/core/preprocessor/resolver/condition.rs +11 -6
  72. package/rust/core/preprocessor/resolver/driver.rs +52 -6
  73. package/rust/core/preprocessor/resolver/function.rs +78 -0
  74. package/rust/core/preprocessor/resolver/group.rs +43 -13
  75. package/rust/core/preprocessor/resolver/let_.rs +7 -10
  76. package/rust/core/preprocessor/resolver/mod.rs +2 -1
  77. package/rust/core/preprocessor/resolver/spawn.rs +64 -30
  78. package/rust/core/preprocessor/resolver/trigger.rs +7 -3
  79. package/rust/core/preprocessor/resolver/value.rs +10 -1
  80. package/rust/core/shared/value.rs +4 -1
  81. package/rust/core/store/function.rs +34 -0
  82. package/rust/core/store/global.rs +9 -10
  83. package/rust/core/store/mod.rs +2 -1
  84. package/rust/core/store/variable.rs +6 -0
  85. package/rust/installer/addon.rs +80 -0
  86. package/rust/installer/bank.rs +24 -14
  87. package/rust/installer/mod.rs +4 -1
  88. package/rust/installer/plugin.rs +55 -0
  89. package/rust/lib.rs +10 -7
  90. package/rust/main.rs +32 -9
  91. package/rust/utils/logger.rs +16 -0
  92. package/rust/utils/mod.rs +45 -1
  93. package/rust/utils/spinner.rs +2 -4
@@ -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
@@ -3,7 +3,6 @@ use crate::core::parser::statement::Statement;
3
3
  use crate::core::store::global::GlobalStore;
4
4
  use std::{ collections::HashMap, fs::create_dir_all };
5
5
  use std::io::Write;
6
-
7
6
  use crate::utils::logger::Logger;
8
7
 
9
8
  pub struct Builder {}
@@ -13,7 +12,12 @@ impl Builder {
13
12
  Builder {}
14
13
  }
15
14
 
16
- pub fn build_ast(&self, modules: &HashMap<String, Vec<Statement>>, out_dir: &str) {
15
+ pub fn build_ast(
16
+ &self,
17
+ modules: &HashMap<String, Vec<Statement>>,
18
+ out_dir: &str,
19
+ compress: bool
20
+ ) {
17
21
  for (name, statements) in modules {
18
22
  let formatted_name = name.split("/").last().unwrap_or(name);
19
23
  let formatted_name = formatted_name.replace(".deva", "");
@@ -22,10 +26,11 @@ impl Builder {
22
26
 
23
27
  let file_path = format!("{}/ast/{}.json", out_dir, formatted_name);
24
28
  let mut file = std::fs::File::create(file_path).expect("Failed to create AST file");
25
-
26
- let content = serde_json
27
- ::to_string_pretty(&statements)
28
- .expect("Failed to serialize AST");
29
+ let content = if compress {
30
+ serde_json::to_string(&statements).expect("Failed to serialize AST")
31
+ } else {
32
+ serde_json::to_string_pretty(&statements).expect("Failed to serialize AST")
33
+ };
29
34
 
30
35
  file.write_all(content.as_bytes()).expect("Failed to write AST to file");
31
36
  }
@@ -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
 
@@ -16,10 +16,24 @@ pub fn handle_indent_lexer(
16
16
  *current_indent += 1;
17
17
  chars.next();
18
18
  col += 1;
19
+ tokens.push(Token {
20
+ kind: TokenKind::Whitespace,
21
+ lexeme: " ".to_string(),
22
+ line: *line,
23
+ column: col,
24
+ indent: *current_indent,
25
+ });
19
26
  } else if c == '\t' {
20
27
  *current_indent += 4;
21
28
  chars.next();
22
29
  col += 4;
30
+ tokens.push(Token {
31
+ kind: TokenKind::Whitespace,
32
+ lexeme: "\t".to_string(),
33
+ line: *line,
34
+ column: col,
35
+ indent: *current_indent,
36
+ });
23
37
  } else {
24
38
  break;
25
39
  }
@@ -32,7 +46,7 @@ pub fn handle_indent_lexer(
32
46
  indent_stack.push(*current_indent);
33
47
  tokens.push(Token {
34
48
  kind: TokenKind::Indent,
35
- lexeme: String::new(),
49
+ lexeme: String::from("<INDENT>"),
36
50
  line: *line,
37
51
  column: *column,
38
52
  indent: *current_indent,
@@ -42,7 +56,7 @@ pub fn handle_indent_lexer(
42
56
  indent_stack.pop();
43
57
  tokens.push(Token {
44
58
  kind: TokenKind::Dedent,
45
- lexeme: String::new(),
59
+ lexeme: String::from("<DEDENT>"),
46
60
  line: *line,
47
61
  column: *column,
48
62
  indent: *current_indent,
@@ -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
+ }
@@ -29,6 +29,7 @@ pub enum TokenKind {
29
29
  Tempo,
30
30
  Bank,
31
31
  Loop,
32
+ Function,
32
33
 
33
34
  // ───── Instruments ─────
34
35
  Synth,
@@ -64,6 +65,8 @@ pub enum TokenKind {
64
65
  RBrace, // }
65
66
  LBracket, // [
66
67
  RBracket, // ]
68
+ LParen, // (
69
+ RParen, // )
67
70
 
68
71
  // ───── Quotes ─────
69
72
  Quote, // '
@@ -81,6 +84,7 @@ pub enum TokenKind {
81
84
  ElseIf,
82
85
 
83
86
  // ───── Special / Internal ─────
87
+ Whitespace,
84
88
  Unknown,
85
89
  Error(String),
86
90
  EOF,
package/rust/core/mod.rs CHANGED
@@ -7,4 +7,5 @@ pub mod debugger;
7
7
  pub mod utils;
8
8
  pub mod builder;
9
9
  pub mod error;
10
- pub mod audio;
10
+ pub mod audio;
11
+ pub mod plugin;
@@ -7,7 +7,7 @@ use crate::core::{
7
7
  bank::parse_bank_token,
8
8
  condition::parse_condition_token,
9
9
  dot::parse_dot_token,
10
- identifier::parse_identifier_token,
10
+ identifier::{ function::parse_function_token, parse_identifier_token },
11
11
  loop_::parse_loop_token,
12
12
  tempo::parse_tempo_token,
13
13
  },
@@ -107,7 +107,11 @@ impl Parser {
107
107
  tokens: Vec<Token>,
108
108
  global_store: &mut GlobalStore
109
109
  ) -> Vec<Statement> {
110
- self.tokens = tokens;
110
+ // Filtrer les tokens Whitespace et Newline avant parsing
111
+ self.tokens = tokens
112
+ .into_iter()
113
+ .filter(|t| t.kind != TokenKind::Whitespace && t.kind != TokenKind::Newline)
114
+ .collect();
111
115
  self.token_index = 0;
112
116
 
113
117
  let mut statements = Vec::new();
@@ -120,6 +124,11 @@ impl Parser {
120
124
  }
121
125
  };
122
126
 
127
+ if token.kind == TokenKind::Newline {
128
+ self.advance();
129
+ continue;
130
+ }
131
+
123
132
  let statement = match &token.kind {
124
133
  TokenKind::At => parse_at_token(self, global_store),
125
134
  TokenKind::Identifier => {
@@ -138,6 +147,7 @@ impl Parser {
138
147
  TokenKind::Bank => parse_bank_token(self, global_store),
139
148
  TokenKind::Loop => parse_loop_token(self, global_store),
140
149
  TokenKind::If => parse_condition_token(self, global_store),
150
+ TokenKind::Function => parse_function_token(self, global_store),
141
151
 
142
152
  | TokenKind::Else // Ignore else, already handled in `parse_condition_token`
143
153
  | TokenKind::Comment
@@ -148,7 +158,6 @@ impl Parser {
148
158
  | TokenKind::LBrace
149
159
  | TokenKind::RBrace
150
160
  | TokenKind::Comma
151
- | TokenKind::Newline
152
161
  | TokenKind::Dedent
153
162
  | TokenKind::Indent => {
154
163
  self.advance();
@@ -188,17 +197,50 @@ impl Parser {
188
197
  let mut map = std::collections::HashMap::new();
189
198
 
190
199
  while !self.check_token(TokenKind::RBrace) && !self.is_eof() {
200
+ // Skip newlines, whitespace, indent, dedent before the key
201
+ while
202
+ self.check_token(TokenKind::Newline) ||
203
+ self.check_token(TokenKind::Whitespace) ||
204
+ self.check_token(TokenKind::Indent) ||
205
+ self.check_token(TokenKind::Dedent)
206
+ {
207
+ self.advance();
208
+ }
209
+
210
+ // Check if we are at the closing brace of the map
211
+ if self.check_token(TokenKind::RBrace) {
212
+ break;
213
+ }
214
+
191
215
  let key = if let Some(token) = self.advance() {
192
- token.lexeme.clone()
216
+ match token.kind {
217
+ | TokenKind::Whitespace
218
+ | TokenKind::Indent
219
+ | TokenKind::Dedent
220
+ | TokenKind::Newline => {
221
+ continue;
222
+ }
223
+ _ => token.lexeme.clone(),
224
+ }
193
225
  } else {
194
226
  break;
195
227
  };
196
228
 
229
+ // Skip newlines and whitespace before colon
230
+ while self.check_token(TokenKind::Newline) || self.check_token(TokenKind::Whitespace) {
231
+ self.advance();
232
+ }
233
+
197
234
  if !self.match_token(TokenKind::Colon) {
198
235
  println!("Expected ':' after map key '{}'", key);
199
236
  break;
200
237
  }
201
238
 
239
+ // Skip newlines and whitespace before value
240
+ while self.check_token(TokenKind::Newline) || self.check_token(TokenKind::Whitespace) {
241
+ self.advance();
242
+ }
243
+
202
244
  let value = if let Some(token) = self.peek_clone() {
203
245
  match token.kind {
204
246
  TokenKind::String => {
@@ -303,6 +345,7 @@ impl Parser {
303
345
  }
304
346
  collected.push(self.advance().unwrap().clone());
305
347
  }
348
+
306
349
  collected
307
350
  }
308
351
 
@@ -54,11 +54,43 @@ pub fn parse_arrow_call(parser: &mut Parser, global_store: &mut GlobalStore) ->
54
54
  parser.advance(); // method
55
55
 
56
56
  let mut args = Vec::new();
57
+ let mut paren_depth = 0;
58
+ let mut map_depth = 0;
57
59
 
58
60
  while let Some(token) = parser.peek_clone() {
59
61
  if token.kind == TokenKind::Newline || token.kind == TokenKind::EOF {
60
62
  break;
61
63
  }
64
+ if token.kind == TokenKind::LParen {
65
+ paren_depth += 1;
66
+ }
67
+ if token.kind == TokenKind::RParen {
68
+ if paren_depth > 0 {
69
+ paren_depth -= 1;
70
+ parser.advance();
71
+ if paren_depth == 0 {
72
+ break;
73
+ }
74
+ continue;
75
+ } else {
76
+ break;
77
+ }
78
+ }
79
+ if token.kind == TokenKind::LBrace {
80
+ map_depth += 1;
81
+ }
82
+ if token.kind == TokenKind::RBrace {
83
+ if map_depth > 0 {
84
+ map_depth -= 1;
85
+ parser.advance();
86
+ if map_depth == 0 {
87
+ continue;
88
+ }
89
+ continue;
90
+ } else {
91
+ break;
92
+ }
93
+ }
62
94
 
63
95
  parser.advance();
64
96
 
@@ -79,49 +111,68 @@ pub fn parse_arrow_call(parser: &mut Parser, global_store: &mut GlobalStore) ->
79
111
  }
80
112
  parser.advance(); // consume key token
81
113
  let key = inner_token.lexeme.clone();
82
-
83
114
  if let Some(colon_token) = parser.peek_clone() {
84
115
  if colon_token.kind == TokenKind::Colon {
85
116
  parser.advance(); // consume colon
86
117
  if let Some(value_token) = parser.peek_clone() {
87
118
  parser.advance(); // consume value token
88
119
  let value = match value_token.kind {
89
- TokenKind::Identifier =>
90
- Value::Identifier(value_token.lexeme.clone()),
120
+ TokenKind::Identifier => {
121
+ // Interpret bare true/false as booleans
122
+ if value_token.lexeme == "true" {
123
+ Value::Boolean(true)
124
+ } else if value_token.lexeme == "false" {
125
+ Value::Boolean(false)
126
+ } else {
127
+ Value::Identifier(value_token.lexeme.clone())
128
+ }
129
+ },
91
130
  TokenKind::String => Value::String(value_token.lexeme.clone()),
92
131
  TokenKind::Number => {
132
+ // Support decimals (e.g., 0.8) and beats (e.g., 1/4)
93
133
  if let Some(TokenKind::Slash) = parser.peek_kind() {
94
- parser.advance(); // consume slash
134
+ // Beat fraction
135
+ parser.advance(); // consume '/'
95
136
  if let Some(denominator_token) = parser.peek_clone() {
96
137
  if denominator_token.kind == TokenKind::Number {
97
138
  parser.advance(); // consume denominator
98
- let denominator =
99
- denominator_token.lexeme.clone();
100
- Value::Beat(
101
- format!(
102
- "{}/{}",
103
- value_token.lexeme,
104
- denominator
105
- )
106
- )
139
+ let denominator = denominator_token.lexeme.clone();
140
+ Value::Beat(format!("{}/{}", value_token.lexeme, denominator))
107
141
  } else {
108
142
  Value::Unknown
109
143
  }
110
144
  } else {
111
145
  Value::Unknown
112
146
  }
147
+ } else if let Some(next) = parser.peek_clone() {
148
+ // Decimal number handling: NUMBER '.' NUMBER -> f32
149
+ if next.kind == TokenKind::Dot {
150
+ // consume '.'
151
+ parser.advance();
152
+ if let Some(after_dot) = parser.peek_clone() {
153
+ if after_dot.kind == TokenKind::Number {
154
+ parser.advance(); // consume fractional digits
155
+ let combined = format!("{}.{}", value_token.lexeme, after_dot.lexeme);
156
+ Value::Number(combined.parse::<f32>().unwrap_or(0.0))
157
+ } else {
158
+ // Lone dot without number, fallback to integer part
159
+ Value::Number(value_token.lexeme.parse::<f32>().unwrap_or(0.0))
160
+ }
161
+ } else {
162
+ Value::Number(value_token.lexeme.parse::<f32>().unwrap_or(0.0))
163
+ }
164
+ } else {
165
+ // Regular integer number
166
+ Value::Number(value_token.lexeme.parse::<f32>().unwrap_or(0.0))
167
+ }
113
168
  } else {
114
- // Regular number without slash
115
- Value::Number(
116
- value_token.lexeme.parse::<f32>().unwrap_or(0.0)
117
- )
169
+ Value::Number(value_token.lexeme.parse::<f32>().unwrap_or(0.0))
118
170
  }
119
171
  }
120
172
  TokenKind::Boolean =>
121
173
  Value::Boolean(
122
174
  value_token.lexeme.parse::<bool>().unwrap_or(false)
123
175
  ),
124
-
125
176
  _ => Value::Unknown,
126
177
  };
127
178
  map.insert(key, value);
@@ -135,6 +186,15 @@ pub fn parse_arrow_call(parser: &mut Parser, global_store: &mut GlobalStore) ->
135
186
  };
136
187
 
137
188
  args.push(value);
189
+
190
+ // Stop if we reach the end of the statement
191
+ if
192
+ paren_depth == 0 &&
193
+ map_depth == 0 &&
194
+ (token.kind == TokenKind::RParen || token.kind == TokenKind::RBrace)
195
+ {
196
+ break;
197
+ }
138
198
  }
139
199
 
140
200
  Statement {