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

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 (110) hide show
  1. package/.devalang +2 -3
  2. package/Cargo.toml +58 -54
  3. package/README.md +59 -27
  4. package/docs/CHANGELOG.md +99 -2
  5. package/docs/CONTRIBUTING.md +1 -0
  6. package/docs/ROADMAP.md +3 -3
  7. package/docs/TODO.md +5 -4
  8. package/examples/automation.deva +44 -0
  9. package/examples/bank.deva +2 -4
  10. package/examples/function.deva +15 -0
  11. package/examples/index.deva +41 -11
  12. package/examples/plugin.deva +15 -0
  13. package/out-tsc/bin/devalang.exe +0 -0
  14. package/package.json +6 -6
  15. package/project-version.json +3 -3
  16. package/rust/cli/bank.rs +16 -16
  17. package/rust/cli/build.rs +69 -30
  18. package/rust/cli/check.rs +46 -6
  19. package/rust/cli/driver.rs +40 -28
  20. package/rust/cli/install.rs +22 -7
  21. package/rust/cli/login.rs +134 -0
  22. package/rust/cli/mod.rs +2 -1
  23. package/rust/cli/play.rs +44 -19
  24. package/rust/cli/update.rs +1 -1
  25. package/rust/common/api.rs +5 -0
  26. package/rust/common/cdn.rs +3 -9
  27. package/rust/common/mod.rs +3 -1
  28. package/rust/common/sso.rs +5 -0
  29. package/rust/config/driver.rs +19 -1
  30. package/rust/config/loader.rs +56 -10
  31. package/rust/core/audio/engine.rs +314 -63
  32. package/rust/core/audio/evaluator.rs +101 -0
  33. package/rust/core/audio/interpreter/arrow_call.rs +60 -15
  34. package/rust/core/audio/interpreter/automate.rs +18 -0
  35. package/rust/core/audio/interpreter/call.rs +4 -4
  36. package/rust/core/audio/interpreter/condition.rs +3 -3
  37. package/rust/core/audio/interpreter/driver.rs +68 -30
  38. package/rust/core/audio/interpreter/let_.rs +14 -7
  39. package/rust/core/audio/interpreter/loop_.rs +39 -6
  40. package/rust/core/audio/interpreter/mod.rs +2 -1
  41. package/rust/core/audio/interpreter/sleep.rs +2 -4
  42. package/rust/core/audio/interpreter/spawn.rs +4 -4
  43. package/rust/core/audio/loader/trigger.rs +2 -5
  44. package/rust/core/audio/mod.rs +2 -1
  45. package/rust/core/audio/renderer.rs +1 -1
  46. package/rust/core/audio/special/easing.rs +120 -0
  47. package/rust/core/audio/special/env.rs +41 -0
  48. package/rust/core/audio/special/math.rs +92 -0
  49. package/rust/core/audio/special/mod.rs +9 -0
  50. package/rust/core/audio/special/modulator.rs +120 -0
  51. package/rust/core/builder/mod.rs +11 -6
  52. package/rust/core/debugger/store.rs +1 -1
  53. package/rust/core/error/mod.rs +4 -1
  54. package/rust/core/lexer/handler/arrow.rs +60 -9
  55. package/rust/core/lexer/handler/at.rs +4 -4
  56. package/rust/core/lexer/handler/brace.rs +8 -8
  57. package/rust/core/lexer/handler/colon.rs +4 -4
  58. package/rust/core/lexer/handler/comment.rs +2 -2
  59. package/rust/core/lexer/handler/dot.rs +4 -4
  60. package/rust/core/lexer/handler/driver.rs +42 -13
  61. package/rust/core/lexer/handler/identifier.rs +5 -4
  62. package/rust/core/lexer/handler/indent.rs +16 -2
  63. package/rust/core/lexer/handler/newline.rs +1 -1
  64. package/rust/core/lexer/handler/number.rs +3 -3
  65. package/rust/core/lexer/handler/operator.rs +3 -1
  66. package/rust/core/lexer/handler/parenthesis.rs +8 -8
  67. package/rust/core/lexer/handler/slash.rs +5 -5
  68. package/rust/core/lexer/handler/string.rs +1 -1
  69. package/rust/core/lexer/mod.rs +1 -1
  70. package/rust/core/lexer/token.rs +4 -0
  71. package/rust/core/mod.rs +2 -1
  72. package/rust/core/parser/driver.rs +134 -11
  73. package/rust/core/parser/handler/arrow_call.rs +141 -65
  74. package/rust/core/parser/handler/at.rs +1 -1
  75. package/rust/core/parser/handler/bank.rs +35 -7
  76. package/rust/core/parser/handler/dot.rs +43 -22
  77. package/rust/core/parser/handler/identifier/automate.rs +194 -0
  78. package/rust/core/parser/handler/identifier/function.rs +2 -3
  79. package/rust/core/parser/handler/identifier/let_.rs +16 -0
  80. package/rust/core/parser/handler/identifier/mod.rs +14 -10
  81. package/rust/core/parser/handler/identifier/print.rs +29 -0
  82. package/rust/core/parser/handler/identifier/sleep.rs +1 -1
  83. package/rust/core/parser/handler/identifier/synth.rs +7 -9
  84. package/rust/core/parser/handler/loop_.rs +60 -43
  85. package/rust/core/parser/statement.rs +5 -0
  86. package/rust/core/plugin/loader.rs +48 -0
  87. package/rust/core/plugin/mod.rs +1 -0
  88. package/rust/core/preprocessor/loader.rs +7 -5
  89. package/rust/core/preprocessor/processor.rs +4 -4
  90. package/rust/core/preprocessor/resolver/bank.rs +1 -2
  91. package/rust/core/preprocessor/resolver/call.rs +19 -18
  92. package/rust/core/preprocessor/resolver/driver.rs +7 -5
  93. package/rust/core/preprocessor/resolver/function.rs +3 -13
  94. package/rust/core/preprocessor/resolver/loop_.rs +31 -1
  95. package/rust/core/preprocessor/resolver/spawn.rs +3 -22
  96. package/rust/core/preprocessor/resolver/tempo.rs +1 -1
  97. package/rust/core/preprocessor/resolver/trigger.rs +2 -3
  98. package/rust/core/preprocessor/resolver/value.rs +6 -12
  99. package/rust/core/shared/bank.rs +1 -1
  100. package/rust/core/utils/path.rs +1 -1
  101. package/rust/core/utils/validation.rs +0 -1
  102. package/rust/installer/addon.rs +80 -0
  103. package/rust/installer/bank.rs +25 -15
  104. package/rust/installer/mod.rs +4 -1
  105. package/rust/installer/plugin.rs +55 -0
  106. package/rust/main.rs +32 -10
  107. package/rust/utils/error.rs +51 -0
  108. package/rust/utils/logger.rs +20 -0
  109. package/rust/utils/mod.rs +1 -44
  110. package/rust/utils/spinner.rs +3 -5
@@ -0,0 +1,92 @@
1
+ use crate::core::store::variable::VariableTable;
2
+
3
+ // Parse comma-separated arguments at top level (no nested parentheses split)
4
+ fn parse_top_level_args(s: &str) -> Vec<&str> {
5
+ let mut args = Vec::new();
6
+ let mut depth = 0i32;
7
+ let mut start = 0usize;
8
+ for (i, ch) in s.char_indices() {
9
+ match ch {
10
+ '(' => depth += 1,
11
+ ')' => depth -= 1,
12
+ ',' if depth == 0 => {
13
+ args.push(s[start..i].trim());
14
+ start = i + 1;
15
+ }
16
+ _ => {}
17
+ }
18
+ }
19
+ let last = s[start..].trim();
20
+ if !last.is_empty() { args.push(last); }
21
+ args
22
+ }
23
+
24
+ fn eval_math_func(func: &str, args: &[f32], fallback_seed: f32) -> Option<f32> {
25
+ match func {
26
+ "sin" => args.get(0).copied().map(f32::sin),
27
+ "cos" => args.get(0).copied().map(f32::cos),
28
+ "random" => {
29
+ // deterministic pseudo-random based on provided seed or a fallback session seed
30
+ let seed = args.get(0).copied().unwrap_or(fallback_seed);
31
+ let x = (seed * 12.9898).sin() * 43758.5453;
32
+ Some((x.fract() * 2.0 - 1.0).clamp(-1.0, 1.0))
33
+ }
34
+ "lerp" => {
35
+ if args.len() >= 3 { Some(args[0] + (args[1] - args[0]) * args[2]) } else { None }
36
+ }
37
+ _ => None,
38
+ }
39
+ }
40
+
41
+ // Find and evaluate the first $math.<fn>(...) occurrence in the string, replacing it with a number.
42
+ // Supports multi-argument functions by splitting on top-level commas.
43
+ pub fn find_and_eval_first_math_call<EvalFn>(
44
+ s: &str,
45
+ eval: EvalFn,
46
+ vars: &VariableTable,
47
+ bpm: f32,
48
+ beat: f32,
49
+ ) -> Option<String>
50
+ where
51
+ EvalFn: Fn(&str, &VariableTable, f32, f32) -> Option<f32>,
52
+ {
53
+ let start = s.find("$math.")?;
54
+ let open_rel = s[start..].find('(')?;
55
+ let open = start + open_rel;
56
+ let func = &s[start + 6..open];
57
+
58
+ // Find matching close parenthesis, handling nesting
59
+ let mut depth: i32 = 0;
60
+ let mut close_abs: Option<usize> = None;
61
+ for (i, ch) in s[open..].char_indices() {
62
+ match ch {
63
+ '(' => depth += 1,
64
+ ')' => {
65
+ depth -= 1;
66
+ if depth == 0 {
67
+ close_abs = Some(open + i);
68
+ break;
69
+ }
70
+ }
71
+ _ => {}
72
+ }
73
+ }
74
+ let close = close_abs?;
75
+
76
+ let inner = &s[open + 1..close];
77
+ let raw_args = parse_top_level_args(inner);
78
+ let mut args: Vec<f32> = Vec::with_capacity(raw_args.len());
79
+ for a in raw_args {
80
+ if let Some(v) = eval(a, vars, bpm, beat) { args.push(v); } else { return None; }
81
+ }
82
+
83
+ // If no explicit seed is provided, use $env.seed via fallback
84
+ let fallback_seed = eval("$env.seed", vars, bpm, beat).unwrap_or(0.0);
85
+ let result = eval_math_func(func, &args, fallback_seed)?;
86
+
87
+ let mut replaced = String::new();
88
+ replaced.push_str(&s[..start]);
89
+ replaced.push_str(&result.to_string());
90
+ replaced.push_str(&s[close + 1..]);
91
+ Some(replaced)
92
+ }
@@ -0,0 +1,9 @@
1
+ pub mod env;
2
+ pub mod math;
3
+ pub mod easing;
4
+ pub mod modulator;
5
+
6
+ pub use env::resolve_env_atom;
7
+ pub use math::find_and_eval_first_math_call;
8
+ pub use easing::find_and_eval_first_easing_call;
9
+ pub use modulator::find_and_eval_first_mod_call;
@@ -0,0 +1,120 @@
1
+ use crate::core::store::variable::VariableTable;
2
+
3
+ fn lfo_sine(rate_per_beat: f32, beat: f32) -> f32 {
4
+ // Output in [-1,1]
5
+ (2.0 * std::f32::consts::PI * rate_per_beat * beat).sin()
6
+ }
7
+
8
+ fn lfo_triangle(rate_per_beat: f32, beat: f32) -> f32 {
9
+ // Triangle in [-1,1]
10
+ let phase = (rate_per_beat * beat).fract();
11
+ // Map [0,1]->[-1,1] tri
12
+ 4.0 * (phase - 0.5).abs() - 1.0
13
+ }
14
+
15
+ fn adsr_envelope_value_t(attack: f32, decay: f32, sustain: f32, release: f32, t: f32) -> f32 {
16
+ let a = attack.max(0.0);
17
+ let d = decay.max(0.0);
18
+ let r = release.max(0.0);
19
+ let s = sustain.clamp(0.0, 1.0);
20
+
21
+ // Normalize phases so that the whole ADSR spans t in [0,1]
22
+ let total = (a + d + r).max(1e-6);
23
+ let ap = a / total; let dp = d / total; let rp = r / total;
24
+
25
+ if t < ap {
26
+ // attack (0->1)
27
+ if ap > 0.0 { t / ap } else { 1.0 }
28
+ } else if t < ap + dp {
29
+ // decay (1->sustain)
30
+ let u = (t - ap) / dp.max(1e-6);
31
+ 1.0 - (1.0 - s) * u
32
+ } else if t < 1.0 - rp {
33
+ // sustain
34
+ s
35
+ } else {
36
+ // release (sustain->0)
37
+ let u = (t - (1.0 - rp)) / rp.max(1e-6);
38
+ s * (1.0 - u)
39
+ }
40
+ }
41
+
42
+ fn eval_mod_func(func: &str, args: &[f32], beat: f32) -> Option<f32> {
43
+ match func {
44
+ "lfo.sine" => {
45
+ let rate = args.get(0).copied().unwrap_or(1.0);
46
+ Some(lfo_sine(rate, beat))
47
+ }
48
+ "lfo.tri" | "lfo.triangle" => {
49
+ let rate = args.get(0).copied().unwrap_or(1.0);
50
+ Some(lfo_triangle(rate, beat))
51
+ }
52
+ // ADSR envelope normalized over t in [0,1]
53
+ // $mod.envelope(attack, decay, sustain, release, t)
54
+ "envelope" | "mod.envelope" => {
55
+ if args.len() >= 5 {
56
+ Some(adsr_envelope_value_t(args[0], args[1], args[2], args[3], args[4].clamp(0.0, 1.0)))
57
+ } else { None }
58
+ }
59
+ _ => None,
60
+ }
61
+ }
62
+
63
+ fn parse_top_level_args(s: &str) -> Vec<&str> {
64
+ let mut args = Vec::new();
65
+ let mut depth = 0i32;
66
+ let mut start = 0usize;
67
+ for (i, ch) in s.char_indices() {
68
+ match ch {
69
+ '(' => depth += 1,
70
+ ')' => depth -= 1,
71
+ ',' if depth == 0 => { args.push(s[start..i].trim()); start = i + 1; }
72
+ _ => {}
73
+ }
74
+ }
75
+ let last = s[start..].trim();
76
+ if !last.is_empty() { args.push(last); }
77
+ args
78
+ }
79
+
80
+ // Find and evaluate the first $mod.<fn>(...) occurrence in the string.
81
+ pub fn find_and_eval_first_mod_call<EvalFn>(
82
+ s: &str,
83
+ eval: EvalFn,
84
+ vars: &VariableTable,
85
+ bpm: f32,
86
+ beat: f32,
87
+ ) -> Option<String>
88
+ where
89
+ EvalFn: Fn(&str, &VariableTable, f32, f32) -> Option<f32>,
90
+ {
91
+ let start = s.find("$mod.")?;
92
+ let open_rel = s[start..].find('(')?;
93
+ let open = start + open_rel;
94
+ let func = &s[start + 5..open];
95
+
96
+ // matching close
97
+ let mut depth: i32 = 0;
98
+ let mut close_abs: Option<usize> = None;
99
+ for (i, ch) in s[open..].char_indices() {
100
+ match ch {
101
+ '(' => depth += 1,
102
+ ')' => { depth -= 1; if depth == 0 { close_abs = Some(open + i); break; } }
103
+ _ => {}
104
+ }
105
+ }
106
+ let close = close_abs?;
107
+
108
+ let inner = &s[open + 1..close];
109
+ let raw_args = parse_top_level_args(inner);
110
+ let mut args: Vec<f32> = Vec::with_capacity(raw_args.len());
111
+ for a in raw_args { args.push(eval(a, vars, bpm, beat)?); }
112
+
113
+ let result = eval_mod_func(func, &args, beat)?;
114
+
115
+ let mut replaced = String::new();
116
+ replaced.push_str(&s[..start]);
117
+ replaced.push_str(&result.to_string());
118
+ replaced.push_str(&s[close + 1..]);
119
+ Some(replaced)
120
+ }
@@ -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
  }
@@ -27,7 +27,7 @@ pub fn write_function_log_file(output_dir: &str, file_name: &str, functions: Fun
27
27
  let log_directory = format!("{}/logs", output_dir);
28
28
  create_dir_all(&log_directory).expect("Failed to create log directory");
29
29
 
30
- for (index, function) in functions.functions {
30
+ for (_index, function) in functions.functions {
31
31
  content.push_str(
32
32
  &format!("'{}' = [{:?}] => {:?}\n", function.name, function.parameters, function.body)
33
33
  );
@@ -1,15 +1,18 @@
1
1
  use crate::core::parser::{ statement::{ Statement, StatementKind }, driver::Parser };
2
+ use serde::{Serialize, Deserialize};
2
3
 
3
4
  pub struct ErrorHandler {
4
5
  errors: Vec<Error>,
5
6
  }
6
7
 
8
+ #[derive(Serialize, Deserialize, Clone)]
7
9
  pub struct ErrorResult {
8
10
  pub message: String,
9
11
  pub line: usize,
10
12
  pub column: usize,
11
13
  }
12
14
 
15
+ #[derive(Clone)]
13
16
  pub struct Error {
14
17
  pub message: String,
15
18
  pub line: usize,
@@ -42,7 +45,7 @@ impl ErrorHandler {
42
45
  self.errors.clear();
43
46
  }
44
47
 
45
- pub fn detect_from_statements(&mut self, parser: &mut Parser, statements: &[Statement]) {
48
+ pub fn detect_from_statements(&mut self, _parser: &mut Parser, statements: &[Statement]) {
46
49
  for stmt in statements {
47
50
  match &stmt.kind {
48
51
  StatementKind::Unknown => {
@@ -1,29 +1,80 @@
1
1
  use crate::core::lexer::token::{ Token, TokenKind };
2
2
 
3
3
  pub fn handle_arrow_lexer(
4
- char: char,
4
+ ch: char,
5
5
  chars: &mut std::iter::Peekable<std::str::Chars>,
6
6
  current_indent: &mut usize,
7
- indent_stack: &mut Vec<usize>,
7
+ _indent_stack: &mut Vec<usize>,
8
8
  tokens: &mut Vec<Token>,
9
9
  line: &mut usize,
10
10
  column: &mut usize
11
11
  ) {
12
- let mut arrow_call = char.to_string();
13
-
14
- while let Some(&c) = chars.peek() {
12
+ // If next char is '>', this is an arrow '->'.
13
+ if let Some(&c) = chars.peek() {
15
14
  if c == '>' {
15
+ let mut arrow_call = ch.to_string();
16
16
  chars.next();
17
17
  arrow_call.push(c);
18
18
  *column += 1;
19
- } else {
20
- break;
19
+
20
+ tokens.push(Token {
21
+ kind: TokenKind::Arrow,
22
+ lexeme: arrow_call,
23
+ line: *line,
24
+ column: *column,
25
+ indent: *current_indent,
26
+ });
27
+ return;
28
+ }
29
+ }
30
+
31
+ // Otherwise, treat '-' as the start of a negative number if followed by digits.
32
+ let mut lexeme = String::from("-");
33
+ if let Some(&next) = chars.peek() {
34
+ if next.is_ascii_digit() {
35
+ // consume digits
36
+ while let Some(&d) = chars.peek() {
37
+ if d.is_ascii_digit() {
38
+ chars.next();
39
+ lexeme.push(d);
40
+ *column += 1;
41
+ } else {
42
+ break;
43
+ }
44
+ }
45
+ // optional decimal part
46
+ if let Some(&dot) = chars.peek() {
47
+ if dot == '.' {
48
+ chars.next();
49
+ lexeme.push(dot);
50
+ *column += 1;
51
+ while let Some(&d) = chars.peek() {
52
+ if d.is_ascii_digit() {
53
+ chars.next();
54
+ lexeme.push(d);
55
+ *column += 1;
56
+ } else {
57
+ break;
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ tokens.push(Token {
64
+ kind: TokenKind::Number,
65
+ lexeme,
66
+ line: *line,
67
+ column: *column,
68
+ indent: *current_indent,
69
+ });
70
+ return;
21
71
  }
22
72
  }
23
73
 
74
+ // Fallback: lone '-' not part of '->' or a number; emit Unknown to avoid mis-parsing as Arrow
24
75
  tokens.push(Token {
25
- kind: TokenKind::Arrow,
26
- lexeme: arrow_call,
76
+ kind: TokenKind::Unknown,
77
+ lexeme: "-".to_string(),
27
78
  line: *line,
28
79
  column: *column,
29
80
  indent: *current_indent,
@@ -1,17 +1,17 @@
1
1
  use crate::core::lexer::token::{Token, TokenKind};
2
2
 
3
3
  pub fn handle_at_lexer(
4
- char: char,
5
- chars: &mut std::iter::Peekable<std::str::Chars>,
4
+ ch: char,
5
+ _chars: &mut std::iter::Peekable<std::str::Chars>,
6
6
  current_indent: &mut usize,
7
- indent_stack: &mut Vec<usize>,
7
+ _indent_stack: &mut Vec<usize>,
8
8
  tokens: &mut Vec<Token>,
9
9
  line: &mut usize,
10
10
  column: &mut usize
11
11
  ) {
12
12
  tokens.push(Token {
13
13
  kind: TokenKind::At,
14
- lexeme: char.to_string(),
14
+ lexeme: ch.to_string(),
15
15
  line: *line,
16
16
  column: *column,
17
17
  indent: *current_indent,
@@ -1,17 +1,17 @@
1
1
  use crate::core::lexer::token::{ Token, TokenKind };
2
2
 
3
3
  pub fn handle_rbrace_lexer(
4
- char: char,
5
- chars: &mut std::iter::Peekable<std::str::Chars>,
4
+ ch: char,
5
+ _chars: &mut std::iter::Peekable<std::str::Chars>,
6
6
  current_indent: &mut usize,
7
- indent_stack: &mut Vec<usize>,
7
+ _indent_stack: &mut Vec<usize>,
8
8
  tokens: &mut Vec<Token>,
9
9
  line: &mut usize,
10
10
  column: &mut usize
11
11
  ) {
12
12
  tokens.push(Token {
13
13
  kind: TokenKind::RBrace,
14
- lexeme: char.to_string(),
14
+ lexeme: ch.to_string(),
15
15
  line: *line,
16
16
  column: *column,
17
17
  indent: *current_indent,
@@ -21,17 +21,17 @@ pub fn handle_rbrace_lexer(
21
21
  }
22
22
 
23
23
  pub fn handle_lbrace_lexer(
24
- char: char,
25
- chars: &mut std::iter::Peekable<std::str::Chars>,
24
+ ch: char,
25
+ _chars: &mut std::iter::Peekable<std::str::Chars>,
26
26
  current_indent: &mut usize,
27
- indent_stack: &mut Vec<usize>,
27
+ _indent_stack: &mut Vec<usize>,
28
28
  tokens: &mut Vec<Token>,
29
29
  line: &mut usize,
30
30
  column: &mut usize
31
31
  ) {
32
32
  tokens.push(Token {
33
33
  kind: TokenKind::LBrace,
34
- lexeme: char.to_string(),
34
+ lexeme: ch.to_string(),
35
35
  line: *line,
36
36
  column: *column,
37
37
  indent: *current_indent,
@@ -1,17 +1,17 @@
1
1
  use crate::core::lexer::token::{Token, TokenKind};
2
2
 
3
3
  pub fn handle_colon_lexer(
4
- char: char,
5
- chars: &mut std::iter::Peekable<std::str::Chars>,
4
+ ch: char,
5
+ _chars: &mut std::iter::Peekable<std::str::Chars>,
6
6
  current_indent: &mut usize,
7
- indent_stack: &mut Vec<usize>,
7
+ _indent_stack: &mut Vec<usize>,
8
8
  tokens: &mut Vec<Token>,
9
9
  line: &mut usize,
10
10
  column: &mut usize
11
11
  ) {
12
12
  tokens.push(Token {
13
13
  kind: TokenKind::Colon,
14
- lexeme: char.to_string(),
14
+ lexeme: ch.to_string(),
15
15
  line: *line,
16
16
  column: *column,
17
17
  indent: *current_indent,
@@ -1,10 +1,10 @@
1
1
  use crate::core::lexer::token::{Token, TokenKind};
2
2
 
3
3
  pub fn handle_comment_lexer(
4
- char: char,
4
+ _char: char,
5
5
  chars: &mut std::iter::Peekable<std::str::Chars>,
6
6
  current_indent: &mut usize,
7
- indent_stack: &mut Vec<usize>,
7
+ _indent_stack: &mut Vec<usize>,
8
8
  tokens: &mut Vec<Token>,
9
9
  line: &mut usize,
10
10
  column: &mut usize
@@ -1,17 +1,17 @@
1
1
  use crate::core::lexer::token::{ Token, TokenKind };
2
2
 
3
3
  pub fn handle_dot_lexer(
4
- char: char,
5
- chars: &mut std::iter::Peekable<std::str::Chars>,
4
+ ch: char,
5
+ _chars: &mut std::iter::Peekable<std::str::Chars>,
6
6
  current_indent: &mut usize,
7
- indent_stack: &mut Vec<usize>,
7
+ _indent_stack: &mut Vec<usize>,
8
8
  tokens: &mut Vec<Token>,
9
9
  line: &mut usize,
10
10
  column: &mut usize
11
11
  ) {
12
12
  tokens.push(Token {
13
13
  kind: TokenKind::Dot,
14
- lexeme: char.to_string(),
14
+ lexeme: ch.to_string(),
15
15
  line: *line,
16
16
  column: *column,
17
17
  indent: *current_indent,
@@ -7,7 +7,7 @@ use crate::core::lexer::{
7
7
 
8
8
  fn advance_char<I: Iterator<Item = char>>(
9
9
  chars: &mut std::iter::Peekable<I>,
10
- line: &mut usize,
10
+ _line: &mut usize,
11
11
  column: &mut usize
12
12
  ) -> Option<char> {
13
13
  while let Some(c) = chars.next() {
@@ -90,7 +90,7 @@ pub fn handle_content_lexing(content: String) -> Result<Vec<Token>, String> {
90
90
  &mut column
91
91
  );
92
92
  }
93
- '=' | '!' | '<' | '>' => {
93
+ '=' | '!' | '<' | '>' | '+' | '*' => {
94
94
  handle_operator_lexer(
95
95
  ch,
96
96
  &mut chars,
@@ -122,6 +122,20 @@ pub fn handle_content_lexing(content: String) -> Result<Vec<Token>, String> {
122
122
  &mut line,
123
123
  &mut column
124
124
  );
125
+ // If not parsed as arrow or number, fallback as Minus token
126
+ if let Some(last) = tokens.last() {
127
+ if last.kind == TokenKind::Unknown && last.lexeme == "-" {
128
+ // replace last with Minus
129
+ let _ = tokens.pop();
130
+ tokens.push(Token {
131
+ kind: TokenKind::Minus,
132
+ lexeme: "-".to_string(),
133
+ line,
134
+ column,
135
+ indent: current_indent,
136
+ });
137
+ }
138
+ }
125
139
  }
126
140
  '{' => {
127
141
  handle_lbrace_lexer(
@@ -145,6 +159,9 @@ pub fn handle_content_lexing(content: String) -> Result<Vec<Token>, String> {
145
159
  &mut column
146
160
  );
147
161
  }
162
+ '[' => { tokens.push(Token { kind: TokenKind::LBracket, lexeme: "[".to_string(), line, column, indent: current_indent }); }
163
+ ']' => { tokens.push(Token { kind: TokenKind::RBracket, lexeme: "]".to_string(), line, column, indent: current_indent }); }
164
+ ',' => { tokens.push(Token { kind: TokenKind::Comma, lexeme: ",".to_string(), line, column, indent: current_indent }); }
148
165
  '(' => {
149
166
  handle_lparen_lexer(
150
167
  ch,
@@ -189,37 +206,49 @@ pub fn handle_content_lexing(content: String) -> Result<Vec<Token>, String> {
189
206
  &mut column
190
207
  );
191
208
  }
192
- '\"' | '\'' => {
193
- handle_string_lexer(
209
+ '$' => {
210
+ // Treat `$` as start of a special identifier like `$env` or `$math`
211
+ let mut ident = String::from("$");
212
+ while let Some(&c) = chars.peek() {
213
+ if c.is_ascii_alphanumeric() || c == '_' {
214
+ ident.push(c);
215
+ chars.next();
216
+ column += 1;
217
+ } else { break; }
218
+ }
219
+ tokens.push(Token { kind: TokenKind::Identifier, lexeme: ident, line, column, indent: current_indent });
220
+ }
221
+ '0'..='9' => {
222
+ handle_number_lexer(
194
223
  ch,
195
224
  &mut chars,
196
225
  &mut current_indent,
197
226
  &mut indent_stack,
198
227
  &mut tokens,
199
228
  &mut line,
200
- &mut column
229
+ &mut column,
201
230
  );
202
231
  }
203
- c if c.is_ascii_digit() => {
204
- handle_number_lexer(
205
- c,
232
+ 'a'..='z' | 'A'..='Z' | '_' => {
233
+ handle_identifier_lexer(
234
+ ch,
206
235
  &mut chars,
207
236
  &mut current_indent,
208
237
  &mut indent_stack,
209
238
  &mut tokens,
210
239
  &mut line,
211
- &mut column
240
+ &mut column,
212
241
  );
213
242
  }
214
- c if c.is_ascii_alphabetic() => {
215
- handle_identifier_lexer(
216
- c,
243
+ '"' | '\'' => {
244
+ handle_string_lexer(
245
+ ch,
217
246
  &mut chars,
218
247
  &mut current_indent,
219
248
  &mut indent_stack,
220
249
  &mut tokens,
221
250
  &mut line,
222
- &mut column
251
+ &mut column,
223
252
  );
224
253
  }
225
254
  _ => {
@@ -1,15 +1,15 @@
1
1
  use crate::core::lexer::token::{ Token, TokenKind };
2
2
 
3
3
  pub fn handle_identifier_lexer(
4
- char: char,
4
+ ch: char,
5
5
  chars: &mut std::iter::Peekable<std::str::Chars>,
6
6
  current_indent: &mut usize,
7
- indent_stack: &mut Vec<usize>,
7
+ _indent_stack: &mut Vec<usize>,
8
8
  tokens: &mut Vec<Token>,
9
9
  line: &mut usize,
10
10
  column: &mut usize
11
11
  ) {
12
- let mut ident = char.to_string();
12
+ let mut ident = ch.to_string();
13
13
 
14
14
  while let Some(&c) = chars.peek() {
15
15
  if c.is_ascii_alphanumeric() || c == '_' {
@@ -26,7 +26,8 @@ pub fn handle_identifier_lexer(
26
26
  "else" => TokenKind::Else,
27
27
  "bank" => TokenKind::Bank,
28
28
  "bpm" => TokenKind::Tempo,
29
- "loop" => TokenKind::Loop,
29
+ "loop" => TokenKind::Loop,
30
+ "for" => TokenKind::Loop,
30
31
  "synth" => TokenKind::Synth,
31
32
  "fn" => TokenKind::Function,
32
33
  _ => TokenKind::Identifier,