takomusic 1.2.0

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 (335) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +230 -0
  3. package/dist/__tests__/checker.test.d.ts +2 -0
  4. package/dist/__tests__/checker.test.d.ts.map +1 -0
  5. package/dist/__tests__/checker.test.js +316 -0
  6. package/dist/__tests__/checker.test.js.map +1 -0
  7. package/dist/__tests__/compiler.test.d.ts +2 -0
  8. package/dist/__tests__/compiler.test.d.ts.map +1 -0
  9. package/dist/__tests__/compiler.test.js +260 -0
  10. package/dist/__tests__/compiler.test.js.map +1 -0
  11. package/dist/__tests__/generators.test.d.ts +2 -0
  12. package/dist/__tests__/generators.test.d.ts.map +1 -0
  13. package/dist/__tests__/generators.test.js +283 -0
  14. package/dist/__tests__/generators.test.js.map +1 -0
  15. package/dist/__tests__/interpreter.test.d.ts +2 -0
  16. package/dist/__tests__/interpreter.test.d.ts.map +1 -0
  17. package/dist/__tests__/interpreter.test.js +298 -0
  18. package/dist/__tests__/interpreter.test.js.map +1 -0
  19. package/dist/__tests__/lexer.test.d.ts +2 -0
  20. package/dist/__tests__/lexer.test.d.ts.map +1 -0
  21. package/dist/__tests__/lexer.test.js +89 -0
  22. package/dist/__tests__/lexer.test.js.map +1 -0
  23. package/dist/__tests__/parser.test.d.ts +2 -0
  24. package/dist/__tests__/parser.test.d.ts.map +1 -0
  25. package/dist/__tests__/parser.test.js +116 -0
  26. package/dist/__tests__/parser.test.js.map +1 -0
  27. package/dist/checker/checker.d.ts +37 -0
  28. package/dist/checker/checker.d.ts.map +1 -0
  29. package/dist/checker/checker.js +428 -0
  30. package/dist/checker/checker.js.map +1 -0
  31. package/dist/checker/index.d.ts +3 -0
  32. package/dist/checker/index.d.ts.map +1 -0
  33. package/dist/checker/index.js +2 -0
  34. package/dist/checker/index.js.map +1 -0
  35. package/dist/cli/commands/build.d.ts +2 -0
  36. package/dist/cli/commands/build.d.ts.map +1 -0
  37. package/dist/cli/commands/build.js +173 -0
  38. package/dist/cli/commands/build.js.map +1 -0
  39. package/dist/cli/commands/check.d.ts +2 -0
  40. package/dist/cli/commands/check.d.ts.map +1 -0
  41. package/dist/cli/commands/check.js +79 -0
  42. package/dist/cli/commands/check.js.map +1 -0
  43. package/dist/cli/commands/doctor.d.ts +2 -0
  44. package/dist/cli/commands/doctor.d.ts.map +1 -0
  45. package/dist/cli/commands/doctor.js +187 -0
  46. package/dist/cli/commands/doctor.js.map +1 -0
  47. package/dist/cli/commands/fmt.d.ts +2 -0
  48. package/dist/cli/commands/fmt.d.ts.map +1 -0
  49. package/dist/cli/commands/fmt.js +73 -0
  50. package/dist/cli/commands/fmt.js.map +1 -0
  51. package/dist/cli/commands/import.d.ts +2 -0
  52. package/dist/cli/commands/import.d.ts.map +1 -0
  53. package/dist/cli/commands/import.js +99 -0
  54. package/dist/cli/commands/import.js.map +1 -0
  55. package/dist/cli/commands/init.d.ts +2 -0
  56. package/dist/cli/commands/init.d.ts.map +1 -0
  57. package/dist/cli/commands/init.js +514 -0
  58. package/dist/cli/commands/init.js.map +1 -0
  59. package/dist/cli/commands/play.d.ts +2 -0
  60. package/dist/cli/commands/play.d.ts.map +1 -0
  61. package/dist/cli/commands/play.js +216 -0
  62. package/dist/cli/commands/play.js.map +1 -0
  63. package/dist/cli/commands/record.d.ts +2 -0
  64. package/dist/cli/commands/record.d.ts.map +1 -0
  65. package/dist/cli/commands/record.js +393 -0
  66. package/dist/cli/commands/record.js.map +1 -0
  67. package/dist/cli/commands/render.d.ts +2 -0
  68. package/dist/cli/commands/render.d.ts.map +1 -0
  69. package/dist/cli/commands/render.js +278 -0
  70. package/dist/cli/commands/render.js.map +1 -0
  71. package/dist/cli/index.d.ts +3 -0
  72. package/dist/cli/index.d.ts.map +1 -0
  73. package/dist/cli/index.js +93 -0
  74. package/dist/cli/index.js.map +1 -0
  75. package/dist/compiler/compiler.d.ts +12 -0
  76. package/dist/compiler/compiler.d.ts.map +1 -0
  77. package/dist/compiler/compiler.js +146 -0
  78. package/dist/compiler/compiler.js.map +1 -0
  79. package/dist/compiler/index.d.ts +2 -0
  80. package/dist/compiler/index.d.ts.map +1 -0
  81. package/dist/compiler/index.js +2 -0
  82. package/dist/compiler/index.js.map +1 -0
  83. package/dist/config/config.d.ts +43 -0
  84. package/dist/config/config.d.ts.map +1 -0
  85. package/dist/config/config.js +200 -0
  86. package/dist/config/config.js.map +1 -0
  87. package/dist/config/index.d.ts +2 -0
  88. package/dist/config/index.d.ts.map +1 -0
  89. package/dist/config/index.js +2 -0
  90. package/dist/config/index.js.map +1 -0
  91. package/dist/errors.d.ts +49 -0
  92. package/dist/errors.d.ts.map +1 -0
  93. package/dist/errors.js +168 -0
  94. package/dist/errors.js.map +1 -0
  95. package/dist/formatter/formatter.d.ts +13 -0
  96. package/dist/formatter/formatter.d.ts.map +1 -0
  97. package/dist/formatter/formatter.js +242 -0
  98. package/dist/formatter/formatter.js.map +1 -0
  99. package/dist/formatter/index.d.ts +2 -0
  100. package/dist/formatter/index.d.ts.map +1 -0
  101. package/dist/formatter/index.js +2 -0
  102. package/dist/formatter/index.js.map +1 -0
  103. package/dist/generators/index.d.ts +5 -0
  104. package/dist/generators/index.d.ts.map +1 -0
  105. package/dist/generators/index.js +5 -0
  106. package/dist/generators/index.js.map +1 -0
  107. package/dist/generators/midi.d.ts +3 -0
  108. package/dist/generators/midi.d.ts.map +1 -0
  109. package/dist/generators/midi.js +360 -0
  110. package/dist/generators/midi.js.map +1 -0
  111. package/dist/generators/musicxml.d.ts +3 -0
  112. package/dist/generators/musicxml.d.ts.map +1 -0
  113. package/dist/generators/musicxml.js +332 -0
  114. package/dist/generators/musicxml.js.map +1 -0
  115. package/dist/generators/tempo-midi.d.ts +3 -0
  116. package/dist/generators/tempo-midi.d.ts.map +1 -0
  117. package/dist/generators/tempo-midi.js +123 -0
  118. package/dist/generators/tempo-midi.js.map +1 -0
  119. package/dist/generators/vsqx.d.ts +3 -0
  120. package/dist/generators/vsqx.d.ts.map +1 -0
  121. package/dist/generators/vsqx.js +354 -0
  122. package/dist/generators/vsqx.js.map +1 -0
  123. package/dist/importers/index.d.ts +2 -0
  124. package/dist/importers/index.d.ts.map +1 -0
  125. package/dist/importers/index.js +3 -0
  126. package/dist/importers/index.js.map +1 -0
  127. package/dist/importers/musicxml.d.ts +4 -0
  128. package/dist/importers/musicxml.d.ts.map +1 -0
  129. package/dist/importers/musicxml.js +392 -0
  130. package/dist/importers/musicxml.js.map +1 -0
  131. package/dist/index.d.ts +11 -0
  132. package/dist/index.d.ts.map +1 -0
  133. package/dist/index.js +12 -0
  134. package/dist/index.js.map +1 -0
  135. package/dist/interpreter/builtins/algorithmic.d.ts +12 -0
  136. package/dist/interpreter/builtins/algorithmic.d.ts.map +1 -0
  137. package/dist/interpreter/builtins/algorithmic.js +244 -0
  138. package/dist/interpreter/builtins/algorithmic.js.map +1 -0
  139. package/dist/interpreter/builtins/audio.d.ts +10 -0
  140. package/dist/interpreter/builtins/audio.d.ts.map +1 -0
  141. package/dist/interpreter/builtins/audio.js +169 -0
  142. package/dist/interpreter/builtins/audio.js.map +1 -0
  143. package/dist/interpreter/builtins/core.d.ts +15 -0
  144. package/dist/interpreter/builtins/core.d.ts.map +1 -0
  145. package/dist/interpreter/builtins/core.js +265 -0
  146. package/dist/interpreter/builtins/core.js.map +1 -0
  147. package/dist/interpreter/builtins/dynamics.d.ts +8 -0
  148. package/dist/interpreter/builtins/dynamics.d.ts.map +1 -0
  149. package/dist/interpreter/builtins/dynamics.js +86 -0
  150. package/dist/interpreter/builtins/dynamics.js.map +1 -0
  151. package/dist/interpreter/builtins/effects.d.ts +16 -0
  152. package/dist/interpreter/builtins/effects.d.ts.map +1 -0
  153. package/dist/interpreter/builtins/effects.js +220 -0
  154. package/dist/interpreter/builtins/effects.js.map +1 -0
  155. package/dist/interpreter/builtins/index.d.ts +16 -0
  156. package/dist/interpreter/builtins/index.d.ts.map +1 -0
  157. package/dist/interpreter/builtins/index.js +17 -0
  158. package/dist/interpreter/builtins/index.js.map +1 -0
  159. package/dist/interpreter/builtins/layout.d.ts +12 -0
  160. package/dist/interpreter/builtins/layout.d.ts.map +1 -0
  161. package/dist/interpreter/builtins/layout.js +175 -0
  162. package/dist/interpreter/builtins/layout.js.map +1 -0
  163. package/dist/interpreter/builtins/live.d.ts +11 -0
  164. package/dist/interpreter/builtins/live.d.ts.map +1 -0
  165. package/dist/interpreter/builtins/live.js +125 -0
  166. package/dist/interpreter/builtins/live.js.map +1 -0
  167. package/dist/interpreter/builtins/midi.d.ts +16 -0
  168. package/dist/interpreter/builtins/midi.d.ts.map +1 -0
  169. package/dist/interpreter/builtins/midi.js +320 -0
  170. package/dist/interpreter/builtins/midi.js.map +1 -0
  171. package/dist/interpreter/builtins/mixing.d.ts +6 -0
  172. package/dist/interpreter/builtins/mixing.d.ts.map +1 -0
  173. package/dist/interpreter/builtins/mixing.js +62 -0
  174. package/dist/interpreter/builtins/mixing.js.map +1 -0
  175. package/dist/interpreter/builtins/notation.d.ts +24 -0
  176. package/dist/interpreter/builtins/notation.d.ts.map +1 -0
  177. package/dist/interpreter/builtins/notation.js +251 -0
  178. package/dist/interpreter/builtins/notation.js.map +1 -0
  179. package/dist/interpreter/builtins/ornaments.d.ts +8 -0
  180. package/dist/interpreter/builtins/ornaments.d.ts.map +1 -0
  181. package/dist/interpreter/builtins/ornaments.js +155 -0
  182. package/dist/interpreter/builtins/ornaments.js.map +1 -0
  183. package/dist/interpreter/builtins/techniques.d.ts +11 -0
  184. package/dist/interpreter/builtins/techniques.d.ts.map +1 -0
  185. package/dist/interpreter/builtins/techniques.js +234 -0
  186. package/dist/interpreter/builtins/techniques.js.map +1 -0
  187. package/dist/interpreter/builtins/tuning.d.ts +7 -0
  188. package/dist/interpreter/builtins/tuning.d.ts.map +1 -0
  189. package/dist/interpreter/builtins/tuning.js +52 -0
  190. package/dist/interpreter/builtins/tuning.js.map +1 -0
  191. package/dist/interpreter/builtins/types.d.ts +27 -0
  192. package/dist/interpreter/builtins/types.d.ts.map +1 -0
  193. package/dist/interpreter/builtins/types.js +3 -0
  194. package/dist/interpreter/builtins/types.js.map +1 -0
  195. package/dist/interpreter/builtins/vocaloid.d.ts +10 -0
  196. package/dist/interpreter/builtins/vocaloid.d.ts.map +1 -0
  197. package/dist/interpreter/builtins/vocaloid.js +165 -0
  198. package/dist/interpreter/builtins/vocaloid.js.map +1 -0
  199. package/dist/interpreter/index.d.ts +4 -0
  200. package/dist/interpreter/index.d.ts.map +1 -0
  201. package/dist/interpreter/index.js +4 -0
  202. package/dist/interpreter/index.js.map +1 -0
  203. package/dist/interpreter/interpreter.d.ts +305 -0
  204. package/dist/interpreter/interpreter.d.ts.map +1 -0
  205. package/dist/interpreter/interpreter.js +8463 -0
  206. package/dist/interpreter/interpreter.js.map +1 -0
  207. package/dist/interpreter/runtime.d.ts +67 -0
  208. package/dist/interpreter/runtime.d.ts.map +1 -0
  209. package/dist/interpreter/runtime.js +88 -0
  210. package/dist/interpreter/runtime.js.map +1 -0
  211. package/dist/interpreter/scope.d.ts +21 -0
  212. package/dist/interpreter/scope.d.ts.map +1 -0
  213. package/dist/interpreter/scope.js +60 -0
  214. package/dist/interpreter/scope.js.map +1 -0
  215. package/dist/interpreter/trackState.d.ts +205 -0
  216. package/dist/interpreter/trackState.d.ts.map +1 -0
  217. package/dist/interpreter/trackState.js +12 -0
  218. package/dist/interpreter/trackState.js.map +1 -0
  219. package/dist/lexer/index.d.ts +2 -0
  220. package/dist/lexer/index.d.ts.map +1 -0
  221. package/dist/lexer/index.js +2 -0
  222. package/dist/lexer/index.js.map +1 -0
  223. package/dist/lexer/lexer.d.ts +30 -0
  224. package/dist/lexer/lexer.d.ts.map +1 -0
  225. package/dist/lexer/lexer.js +385 -0
  226. package/dist/lexer/lexer.js.map +1 -0
  227. package/dist/parser/index.d.ts +2 -0
  228. package/dist/parser/index.d.ts.map +1 -0
  229. package/dist/parser/index.js +2 -0
  230. package/dist/parser/index.js.map +1 -0
  231. package/dist/parser/parser.d.ts +55 -0
  232. package/dist/parser/parser.d.ts.map +1 -0
  233. package/dist/parser/parser.js +896 -0
  234. package/dist/parser/parser.js.map +1 -0
  235. package/dist/types/ast.d.ts +220 -0
  236. package/dist/types/ast.d.ts.map +1 -0
  237. package/dist/types/ast.js +3 -0
  238. package/dist/types/ast.js.map +1 -0
  239. package/dist/types/index.d.ts +4 -0
  240. package/dist/types/index.d.ts.map +1 -0
  241. package/dist/types/index.js +4 -0
  242. package/dist/types/index.js.map +1 -0
  243. package/dist/types/ir/advanced.d.ts +491 -0
  244. package/dist/types/ir/advanced.d.ts.map +1 -0
  245. package/dist/types/ir/advanced.js +3 -0
  246. package/dist/types/ir/advanced.js.map +1 -0
  247. package/dist/types/ir/algorithmic.d.ts +48 -0
  248. package/dist/types/ir/algorithmic.d.ts.map +1 -0
  249. package/dist/types/ir/algorithmic.js +3 -0
  250. package/dist/types/ir/algorithmic.js.map +1 -0
  251. package/dist/types/ir/analysis.d.ts +34 -0
  252. package/dist/types/ir/analysis.d.ts.map +1 -0
  253. package/dist/types/ir/analysis.js +3 -0
  254. package/dist/types/ir/analysis.js.map +1 -0
  255. package/dist/types/ir/audio.d.ts +249 -0
  256. package/dist/types/ir/audio.d.ts.map +1 -0
  257. package/dist/types/ir/audio.js +3 -0
  258. package/dist/types/ir/audio.js.map +1 -0
  259. package/dist/types/ir/automation.d.ts +46 -0
  260. package/dist/types/ir/automation.d.ts.map +1 -0
  261. package/dist/types/ir/automation.js +3 -0
  262. package/dist/types/ir/automation.js.map +1 -0
  263. package/dist/types/ir/collaboration.d.ts +20 -0
  264. package/dist/types/ir/collaboration.d.ts.map +1 -0
  265. package/dist/types/ir/collaboration.js +3 -0
  266. package/dist/types/ir/collaboration.js.map +1 -0
  267. package/dist/types/ir/core.d.ts +153 -0
  268. package/dist/types/ir/core.d.ts.map +1 -0
  269. package/dist/types/ir/core.js +3 -0
  270. package/dist/types/ir/core.js.map +1 -0
  271. package/dist/types/ir/effects.d.ts +169 -0
  272. package/dist/types/ir/effects.d.ts.map +1 -0
  273. package/dist/types/ir/effects.js +3 -0
  274. package/dist/types/ir/effects.js.map +1 -0
  275. package/dist/types/ir/extended.d.ts +104 -0
  276. package/dist/types/ir/extended.d.ts.map +1 -0
  277. package/dist/types/ir/extended.js +3 -0
  278. package/dist/types/ir/extended.js.map +1 -0
  279. package/dist/types/ir/index.d.ts +16 -0
  280. package/dist/types/ir/index.d.ts.map +1 -0
  281. package/dist/types/ir/index.js +17 -0
  282. package/dist/types/ir/index.js.map +1 -0
  283. package/dist/types/ir/mastering.d.ts +86 -0
  284. package/dist/types/ir/mastering.d.ts.map +1 -0
  285. package/dist/types/ir/mastering.js +3 -0
  286. package/dist/types/ir/mastering.js.map +1 -0
  287. package/dist/types/ir/midi.d.ts +79 -0
  288. package/dist/types/ir/midi.d.ts.map +1 -0
  289. package/dist/types/ir/midi.js +3 -0
  290. package/dist/types/ir/midi.js.map +1 -0
  291. package/dist/types/ir/notation.d.ts +963 -0
  292. package/dist/types/ir/notation.d.ts.map +1 -0
  293. package/dist/types/ir/notation.js +3 -0
  294. package/dist/types/ir/notation.js.map +1 -0
  295. package/dist/types/ir/recording.d.ts +48 -0
  296. package/dist/types/ir/recording.d.ts.map +1 -0
  297. package/dist/types/ir/recording.js +3 -0
  298. package/dist/types/ir/recording.js.map +1 -0
  299. package/dist/types/ir/sampling.d.ts +59 -0
  300. package/dist/types/ir/sampling.d.ts.map +1 -0
  301. package/dist/types/ir/sampling.js +3 -0
  302. package/dist/types/ir/sampling.js.map +1 -0
  303. package/dist/types/ir/sequencing.d.ts +118 -0
  304. package/dist/types/ir/sequencing.d.ts.map +1 -0
  305. package/dist/types/ir/sequencing.js +3 -0
  306. package/dist/types/ir/sequencing.js.map +1 -0
  307. package/dist/types/ir/sync.d.ts +39 -0
  308. package/dist/types/ir/sync.d.ts.map +1 -0
  309. package/dist/types/ir/sync.js +3 -0
  310. package/dist/types/ir/sync.js.map +1 -0
  311. package/dist/types/ir.d.ts +2 -0
  312. package/dist/types/ir.d.ts.map +1 -0
  313. package/dist/types/ir.js +3 -0
  314. package/dist/types/ir.js.map +1 -0
  315. package/dist/types/token.d.ts +72 -0
  316. package/dist/types/token.d.ts.map +1 -0
  317. package/dist/types/token.js +90 -0
  318. package/dist/types/token.js.map +1 -0
  319. package/dist/utils/stdlib.d.ts +18 -0
  320. package/dist/utils/stdlib.d.ts.map +1 -0
  321. package/dist/utils/stdlib.js +51 -0
  322. package/dist/utils/stdlib.js.map +1 -0
  323. package/lib/articulation.mf +46 -0
  324. package/lib/composition.mf +299 -0
  325. package/lib/curves.mf +183 -0
  326. package/lib/dynamics.mf +141 -0
  327. package/lib/expression.mf +221 -0
  328. package/lib/genres.mf +348 -0
  329. package/lib/notation.mf +224 -0
  330. package/lib/ornaments.mf +98 -0
  331. package/lib/patterns.mf +170 -0
  332. package/lib/rhythm.mf +269 -0
  333. package/lib/theory.mf +257 -0
  334. package/lib/utils.mf +140 -0
  335. package/package.json +49 -0
@@ -0,0 +1,896 @@
1
+ // Parser for MFS language
2
+ import { TokenType } from '../types/token.js';
3
+ import { MFError } from '../errors.js';
4
+ export class Parser {
5
+ tokens;
6
+ current = 0;
7
+ filePath;
8
+ constructor(tokens, filePath) {
9
+ this.tokens = tokens;
10
+ this.filePath = filePath;
11
+ }
12
+ parse() {
13
+ const statements = [];
14
+ const pos = this.peek().position;
15
+ while (!this.isAtEnd()) {
16
+ const stmt = this.parseStatement();
17
+ if (stmt) {
18
+ statements.push(stmt);
19
+ }
20
+ }
21
+ return { kind: 'Program', statements, position: pos };
22
+ }
23
+ parseStatement() {
24
+ if (this.check(TokenType.IMPORT)) {
25
+ return this.parseImport();
26
+ }
27
+ if (this.check(TokenType.EXPORT)) {
28
+ return this.parseExport();
29
+ }
30
+ if (this.check(TokenType.PROC)) {
31
+ return this.parseProc(false);
32
+ }
33
+ if (this.check(TokenType.CONST)) {
34
+ return this.parseConst(false);
35
+ }
36
+ if (this.check(TokenType.LET)) {
37
+ return this.parseLet();
38
+ }
39
+ if (this.check(TokenType.IF)) {
40
+ return this.parseIf();
41
+ }
42
+ if (this.check(TokenType.FOR)) {
43
+ return this.parseFor();
44
+ }
45
+ if (this.check(TokenType.WHILE)) {
46
+ return this.parseWhile();
47
+ }
48
+ if (this.check(TokenType.RETURN)) {
49
+ return this.parseReturn();
50
+ }
51
+ if (this.check(TokenType.BREAK)) {
52
+ return this.parseBreak();
53
+ }
54
+ if (this.check(TokenType.CONTINUE)) {
55
+ return this.parseContinue();
56
+ }
57
+ // track block: track(kind, id, opts?) { ... }
58
+ if (this.checkIdent('track')) {
59
+ return this.parseTrackBlock();
60
+ }
61
+ // Assignment or expression statement
62
+ return this.parseAssignmentOrExpression();
63
+ }
64
+ parseImport() {
65
+ const pos = this.advance().position; // consume 'import'
66
+ this.expect(TokenType.LBRACE, "Expected '{' after 'import'");
67
+ const imports = [];
68
+ if (!this.check(TokenType.RBRACE)) {
69
+ do {
70
+ const ident = this.expect(TokenType.IDENT, 'Expected identifier in import list');
71
+ imports.push(ident.value);
72
+ } while (this.match(TokenType.COMMA));
73
+ }
74
+ this.expect(TokenType.RBRACE, "Expected '}' after import list");
75
+ this.expect(TokenType.IDENT, "Expected 'from'"); // 'from' keyword
76
+ const pathToken = this.expect(TokenType.STRING, 'Expected module path');
77
+ this.expect(TokenType.SEMICOLON, "Expected ';' after import");
78
+ return {
79
+ kind: 'ImportStatement',
80
+ imports,
81
+ path: pathToken.value,
82
+ position: pos,
83
+ };
84
+ }
85
+ parseExport() {
86
+ const pos = this.advance().position; // consume 'export'
87
+ let declaration;
88
+ if (this.check(TokenType.PROC)) {
89
+ declaration = this.parseProc(true);
90
+ }
91
+ else if (this.check(TokenType.CONST)) {
92
+ declaration = this.parseConst(true);
93
+ }
94
+ else {
95
+ throw this.error("Expected 'proc' or 'const' after 'export'");
96
+ }
97
+ return {
98
+ kind: 'ExportStatement',
99
+ declaration,
100
+ position: pos,
101
+ };
102
+ }
103
+ parseProc(exported) {
104
+ const pos = this.advance().position; // consume 'proc'
105
+ const name = this.expect(TokenType.IDENT, 'Expected proc name').value;
106
+ this.expect(TokenType.LPAREN, "Expected '(' after proc name");
107
+ const params = [];
108
+ if (!this.check(TokenType.RPAREN)) {
109
+ do {
110
+ // Check for rest parameter
111
+ if (this.check(TokenType.SPREAD)) {
112
+ this.advance(); // consume '...'
113
+ const param = this.expect(TokenType.IDENT, 'Expected rest parameter name');
114
+ params.push({ name: param.value, rest: true });
115
+ break; // Rest must be last
116
+ }
117
+ const param = this.expect(TokenType.IDENT, 'Expected parameter name');
118
+ params.push({ name: param.value });
119
+ } while (this.match(TokenType.COMMA));
120
+ }
121
+ this.expect(TokenType.RPAREN, "Expected ')' after parameters");
122
+ this.expect(TokenType.LBRACE, "Expected '{' before proc body");
123
+ const body = this.parseBlock();
124
+ return {
125
+ kind: 'ProcDeclaration',
126
+ name,
127
+ params,
128
+ body,
129
+ exported,
130
+ position: pos,
131
+ };
132
+ }
133
+ parseConst(exported) {
134
+ const pos = this.advance().position; // consume 'const'
135
+ const name = this.expect(TokenType.IDENT, 'Expected constant name').value;
136
+ this.expect(TokenType.EQ, "Expected '=' after constant name");
137
+ const value = this.parseExpression();
138
+ this.expect(TokenType.SEMICOLON, "Expected ';' after constant value");
139
+ return {
140
+ kind: 'ConstDeclaration',
141
+ name,
142
+ value,
143
+ exported,
144
+ position: pos,
145
+ };
146
+ }
147
+ parseLet() {
148
+ const pos = this.advance().position; // consume 'let'
149
+ const name = this.expect(TokenType.IDENT, 'Expected variable name').value;
150
+ this.expect(TokenType.EQ, "Expected '=' after variable name");
151
+ const value = this.parseExpression();
152
+ this.expect(TokenType.SEMICOLON, "Expected ';' after variable value");
153
+ return {
154
+ kind: 'LetDeclaration',
155
+ name,
156
+ value,
157
+ position: pos,
158
+ };
159
+ }
160
+ parseIf() {
161
+ const pos = this.advance().position; // consume 'if'
162
+ this.expect(TokenType.LPAREN, "Expected '(' after 'if'");
163
+ const condition = this.parseExpression();
164
+ this.expect(TokenType.RPAREN, "Expected ')' after condition");
165
+ this.expect(TokenType.LBRACE, "Expected '{' before if body");
166
+ const consequent = this.parseBlock();
167
+ let alternate = null;
168
+ if (this.match(TokenType.ELSE)) {
169
+ this.expect(TokenType.LBRACE, "Expected '{' after 'else'");
170
+ alternate = this.parseBlock();
171
+ }
172
+ return {
173
+ kind: 'IfStatement',
174
+ condition,
175
+ consequent,
176
+ alternate,
177
+ position: pos,
178
+ };
179
+ }
180
+ parseFor() {
181
+ const pos = this.advance().position; // consume 'for'
182
+ this.expect(TokenType.LPAREN, "Expected '(' after 'for'");
183
+ const variable = this.expect(TokenType.IDENT, 'Expected loop variable').value;
184
+ this.expect(TokenType.IN, "Expected 'in' after loop variable");
185
+ const firstExpr = this.parseAdditive();
186
+ // Check if this is a range (has ..) or array iteration
187
+ if (this.check(TokenType.DOTDOT) || this.check(TokenType.DOTDOTEQ)) {
188
+ // Range iteration: for (i in 0..10) or for (i in 0..=10)
189
+ let inclusive = false;
190
+ if (this.match(TokenType.DOTDOTEQ)) {
191
+ inclusive = true;
192
+ }
193
+ else {
194
+ this.advance(); // consume '..'
195
+ }
196
+ const rangeEnd = this.parseAdditive();
197
+ const range = {
198
+ kind: 'RangeExpression',
199
+ start: firstExpr,
200
+ end: rangeEnd,
201
+ inclusive,
202
+ position: firstExpr.position,
203
+ };
204
+ this.expect(TokenType.RPAREN, "Expected ')' after range");
205
+ this.expect(TokenType.LBRACE, "Expected '{' before for body");
206
+ const body = this.parseBlock();
207
+ return {
208
+ kind: 'ForStatement',
209
+ variable,
210
+ range,
211
+ body,
212
+ position: pos,
213
+ };
214
+ }
215
+ else {
216
+ // Array iteration: for (x in array)
217
+ this.expect(TokenType.RPAREN, "Expected ')' after iterable");
218
+ this.expect(TokenType.LBRACE, "Expected '{' before for body");
219
+ const body = this.parseBlock();
220
+ return {
221
+ kind: 'ForEachStatement',
222
+ variable,
223
+ iterable: firstExpr,
224
+ body,
225
+ position: pos,
226
+ };
227
+ }
228
+ }
229
+ parseWhile() {
230
+ const pos = this.advance().position; // consume 'while'
231
+ this.expect(TokenType.LPAREN, "Expected '(' after 'while'");
232
+ const condition = this.parseExpression();
233
+ this.expect(TokenType.RPAREN, "Expected ')' after condition");
234
+ this.expect(TokenType.LBRACE, "Expected '{' before while body");
235
+ const body = this.parseBlock();
236
+ return {
237
+ kind: 'WhileStatement',
238
+ condition,
239
+ body,
240
+ position: pos,
241
+ };
242
+ }
243
+ parseReturn() {
244
+ const pos = this.advance().position; // consume 'return'
245
+ let value = null;
246
+ if (!this.check(TokenType.SEMICOLON)) {
247
+ value = this.parseExpression();
248
+ }
249
+ this.expect(TokenType.SEMICOLON, "Expected ';' after return");
250
+ return {
251
+ kind: 'ReturnStatement',
252
+ value,
253
+ position: pos,
254
+ };
255
+ }
256
+ parseBreak() {
257
+ const pos = this.advance().position; // consume 'break'
258
+ this.expect(TokenType.SEMICOLON, "Expected ';' after break");
259
+ return { kind: 'BreakStatement', position: pos };
260
+ }
261
+ parseContinue() {
262
+ const pos = this.advance().position; // consume 'continue'
263
+ this.expect(TokenType.SEMICOLON, "Expected ';' after continue");
264
+ return { kind: 'ContinueStatement', position: pos };
265
+ }
266
+ parseTrackBlock() {
267
+ const pos = this.advance().position; // consume 'track'
268
+ this.expect(TokenType.LPAREN, "Expected '(' after 'track'");
269
+ // Parse track kind (vocal or midi)
270
+ const kindToken = this.advance();
271
+ if (kindToken.type !== TokenType.VOCAL && kindToken.type !== TokenType.MIDI) {
272
+ throw this.error("Expected 'vocal' or 'midi' as track kind");
273
+ }
274
+ const trackKind = kindToken.value;
275
+ this.expect(TokenType.COMMA, "Expected ',' after track kind");
276
+ const id = this.expect(TokenType.IDENT, 'Expected track id').value;
277
+ let options = null;
278
+ if (this.match(TokenType.COMMA)) {
279
+ options = this.parseObjectLiteral();
280
+ }
281
+ this.expect(TokenType.RPAREN, "Expected ')' after track arguments");
282
+ this.expect(TokenType.LBRACE, "Expected '{' before track body");
283
+ const body = this.parseBlock();
284
+ return {
285
+ kind: 'TrackBlock',
286
+ trackKind,
287
+ id,
288
+ options,
289
+ body,
290
+ position: pos,
291
+ };
292
+ }
293
+ parseBlock() {
294
+ const statements = [];
295
+ while (!this.check(TokenType.RBRACE) && !this.isAtEnd()) {
296
+ const stmt = this.parseStatement();
297
+ if (stmt) {
298
+ statements.push(stmt);
299
+ }
300
+ }
301
+ this.expect(TokenType.RBRACE, "Expected '}' after block");
302
+ return statements;
303
+ }
304
+ parseAssignmentOrExpression() {
305
+ const pos = this.peek().position;
306
+ // Parse the left-hand side expression first
307
+ const expr = this.parseExpression();
308
+ // Check for assignment operators
309
+ if (this.check(TokenType.EQ)) {
310
+ this.advance(); // consume '='
311
+ const value = this.parseExpression();
312
+ this.expect(TokenType.SEMICOLON, "Expected ';' after assignment");
313
+ // Simple variable assignment
314
+ if (expr.kind === 'Identifier') {
315
+ return {
316
+ kind: 'AssignmentStatement',
317
+ name: expr.name,
318
+ value,
319
+ position: pos,
320
+ };
321
+ }
322
+ // Index assignment: arr[i] = value
323
+ if (expr.kind === 'IndexExpression') {
324
+ return {
325
+ kind: 'IndexAssignmentStatement',
326
+ object: expr.object,
327
+ index: expr.index,
328
+ value,
329
+ position: pos,
330
+ };
331
+ }
332
+ // Property assignment: obj.prop = value
333
+ if (expr.kind === 'MemberExpression') {
334
+ return {
335
+ kind: 'PropertyAssignmentStatement',
336
+ object: expr.object,
337
+ property: expr.property,
338
+ value,
339
+ position: pos,
340
+ };
341
+ }
342
+ throw this.error('Invalid assignment target');
343
+ }
344
+ // Compound assignment: +=, -=, *=, /=
345
+ if (this.checkCompoundAssign()) {
346
+ const op = this.advance().value; // e.g., '+='
347
+ const binaryOp = op.charAt(0); // e.g., '+'
348
+ const rhs = this.parseExpression();
349
+ this.expect(TokenType.SEMICOLON, "Expected ';' after assignment");
350
+ // Desugar: x += y -> x = x + y
351
+ if (expr.kind === 'Identifier') {
352
+ const binaryExpr = {
353
+ kind: 'BinaryExpression',
354
+ operator: binaryOp,
355
+ left: expr,
356
+ right: rhs,
357
+ position: pos,
358
+ };
359
+ return {
360
+ kind: 'AssignmentStatement',
361
+ name: expr.name,
362
+ value: binaryExpr,
363
+ position: pos,
364
+ };
365
+ }
366
+ // arr[i] += y -> arr[i] = arr[i] + y
367
+ if (expr.kind === 'IndexExpression') {
368
+ const binaryExpr = {
369
+ kind: 'BinaryExpression',
370
+ operator: binaryOp,
371
+ left: expr,
372
+ right: rhs,
373
+ position: pos,
374
+ };
375
+ return {
376
+ kind: 'IndexAssignmentStatement',
377
+ object: expr.object,
378
+ index: expr.index,
379
+ value: binaryExpr,
380
+ position: pos,
381
+ };
382
+ }
383
+ throw this.error('Invalid compound assignment target');
384
+ }
385
+ // Expression statement
386
+ this.expect(TokenType.SEMICOLON, "Expected ';' after expression");
387
+ return {
388
+ kind: 'ExpressionStatement',
389
+ expression: expr,
390
+ position: pos,
391
+ };
392
+ }
393
+ checkCompoundAssign() {
394
+ return this.check(TokenType.PLUSEQ) || this.check(TokenType.MINUSEQ) ||
395
+ this.check(TokenType.STAREQ) || this.check(TokenType.SLASHEQ);
396
+ }
397
+ // Expression parsing with precedence climbing
398
+ parseExpression() {
399
+ return this.parseOr();
400
+ }
401
+ parseOr() {
402
+ let left = this.parseAnd();
403
+ while (this.match(TokenType.OR)) {
404
+ const right = this.parseAnd();
405
+ left = {
406
+ kind: 'BinaryExpression',
407
+ operator: '||',
408
+ left,
409
+ right,
410
+ position: left.position,
411
+ };
412
+ }
413
+ return left;
414
+ }
415
+ parseAnd() {
416
+ let left = this.parseEquality();
417
+ while (this.match(TokenType.AND)) {
418
+ const right = this.parseEquality();
419
+ left = {
420
+ kind: 'BinaryExpression',
421
+ operator: '&&',
422
+ left,
423
+ right,
424
+ position: left.position,
425
+ };
426
+ }
427
+ return left;
428
+ }
429
+ parseEquality() {
430
+ let left = this.parseComparison();
431
+ while (this.check(TokenType.EQEQ) || this.check(TokenType.NEQ)) {
432
+ const op = this.advance().value;
433
+ const right = this.parseComparison();
434
+ left = {
435
+ kind: 'BinaryExpression',
436
+ operator: op,
437
+ left,
438
+ right,
439
+ position: left.position,
440
+ };
441
+ }
442
+ return left;
443
+ }
444
+ parseComparison() {
445
+ let left = this.parseAdditive();
446
+ while (this.check(TokenType.LT) ||
447
+ this.check(TokenType.GT) ||
448
+ this.check(TokenType.LTEQ) ||
449
+ this.check(TokenType.GTEQ)) {
450
+ const op = this.advance().value;
451
+ const right = this.parseAdditive();
452
+ left = {
453
+ kind: 'BinaryExpression',
454
+ operator: op,
455
+ left,
456
+ right,
457
+ position: left.position,
458
+ };
459
+ }
460
+ return left;
461
+ }
462
+ parseAdditive() {
463
+ let left = this.parseMultiplicative();
464
+ while (this.check(TokenType.PLUS) || this.check(TokenType.MINUS)) {
465
+ const op = this.advance().value;
466
+ const right = this.parseMultiplicative();
467
+ left = {
468
+ kind: 'BinaryExpression',
469
+ operator: op,
470
+ left,
471
+ right,
472
+ position: left.position,
473
+ };
474
+ }
475
+ return left;
476
+ }
477
+ parseMultiplicative() {
478
+ let left = this.parseUnary();
479
+ while (this.check(TokenType.STAR) || this.check(TokenType.SLASH) || this.check(TokenType.PERCENT)) {
480
+ const op = this.advance().value;
481
+ const right = this.parseUnary();
482
+ left = {
483
+ kind: 'BinaryExpression',
484
+ operator: op,
485
+ left,
486
+ right,
487
+ position: left.position,
488
+ };
489
+ }
490
+ return left;
491
+ }
492
+ parseUnary() {
493
+ if (this.check(TokenType.NOT) || this.check(TokenType.MINUS)) {
494
+ const op = this.advance().value;
495
+ const operand = this.parseUnary();
496
+ return {
497
+ kind: 'UnaryExpression',
498
+ operator: op,
499
+ operand,
500
+ position: operand.position,
501
+ };
502
+ }
503
+ return this.parseCall();
504
+ }
505
+ parseCall() {
506
+ let expr = this.parsePrimary();
507
+ while (true) {
508
+ if (this.check(TokenType.LPAREN)) {
509
+ // Function call: expr(args) - supports first-class functions
510
+ expr = this.finishCall(expr);
511
+ }
512
+ else if (this.check(TokenType.LBRACKET)) {
513
+ // Index access: expr[index]
514
+ const pos = this.advance().position; // consume '['
515
+ const index = this.parseExpression();
516
+ this.expect(TokenType.RBRACKET, "Expected ']' after index");
517
+ expr = {
518
+ kind: 'IndexExpression',
519
+ object: expr,
520
+ index,
521
+ position: pos,
522
+ };
523
+ }
524
+ else if (this.check(TokenType.DOT)) {
525
+ // Member access: expr.property
526
+ const pos = this.advance().position; // consume '.'
527
+ const prop = this.expect(TokenType.IDENT, 'Expected property name after "."');
528
+ expr = {
529
+ kind: 'MemberExpression',
530
+ object: expr,
531
+ property: prop.value,
532
+ position: pos,
533
+ };
534
+ }
535
+ else {
536
+ break;
537
+ }
538
+ }
539
+ return expr;
540
+ }
541
+ finishCall(callee) {
542
+ const pos = this.advance().position; // consume '('
543
+ const args = [];
544
+ if (!this.check(TokenType.RPAREN)) {
545
+ do {
546
+ // Check for spread argument
547
+ if (this.check(TokenType.SPREAD)) {
548
+ const spreadPos = this.advance().position; // consume '...'
549
+ args.push({
550
+ kind: 'SpreadElement',
551
+ argument: this.parseExpression(),
552
+ position: spreadPos,
553
+ });
554
+ }
555
+ else {
556
+ args.push(this.parseExpression());
557
+ }
558
+ } while (this.match(TokenType.COMMA));
559
+ }
560
+ this.expect(TokenType.RPAREN, "Expected ')' after arguments");
561
+ return {
562
+ kind: 'CallExpression',
563
+ callee,
564
+ arguments: args,
565
+ position: callee.position,
566
+ };
567
+ }
568
+ parsePrimary() {
569
+ const token = this.peek();
570
+ // Int literal
571
+ if (this.check(TokenType.INT)) {
572
+ this.advance();
573
+ return {
574
+ kind: 'IntLiteral',
575
+ value: parseInt(token.value, 10),
576
+ position: token.position,
577
+ };
578
+ }
579
+ // Float literal
580
+ if (this.check(TokenType.FLOAT)) {
581
+ this.advance();
582
+ return {
583
+ kind: 'FloatLiteral',
584
+ value: parseFloat(token.value),
585
+ position: token.position,
586
+ };
587
+ }
588
+ // String literal
589
+ if (this.check(TokenType.STRING)) {
590
+ this.advance();
591
+ return {
592
+ kind: 'StringLiteral',
593
+ value: token.value,
594
+ position: token.position,
595
+ };
596
+ }
597
+ // Bool literal
598
+ if (this.check(TokenType.TRUE)) {
599
+ this.advance();
600
+ return {
601
+ kind: 'BoolLiteral',
602
+ value: true,
603
+ position: token.position,
604
+ };
605
+ }
606
+ if (this.check(TokenType.FALSE)) {
607
+ this.advance();
608
+ return {
609
+ kind: 'BoolLiteral',
610
+ value: false,
611
+ position: token.position,
612
+ };
613
+ }
614
+ // Pitch literal
615
+ if (this.check(TokenType.PITCH)) {
616
+ this.advance();
617
+ return this.parsePitchLiteral(token);
618
+ }
619
+ // Dur literal
620
+ if (this.check(TokenType.DUR)) {
621
+ this.advance();
622
+ return this.parseDurLiteral(token);
623
+ }
624
+ // Time literal
625
+ if (this.check(TokenType.TIME)) {
626
+ this.advance();
627
+ return this.parseTimeLiteral(token);
628
+ }
629
+ // Array literal
630
+ if (this.check(TokenType.LBRACKET)) {
631
+ return this.parseArrayLiteral();
632
+ }
633
+ // Object literal
634
+ if (this.check(TokenType.LBRACE)) {
635
+ return this.parseObjectLiteral();
636
+ }
637
+ // Identifier
638
+ if (this.check(TokenType.IDENT)) {
639
+ this.advance();
640
+ return {
641
+ kind: 'Identifier',
642
+ name: token.value,
643
+ position: token.position,
644
+ };
645
+ }
646
+ // Parenthesized expression or arrow function
647
+ if (this.check(TokenType.LPAREN)) {
648
+ return this.parseParenOrArrow();
649
+ }
650
+ throw this.error(`Unexpected token: ${token.value}`);
651
+ }
652
+ // Parse either a parenthesized expression or an arrow function
653
+ parseParenOrArrow() {
654
+ const startPos = this.peek().position;
655
+ this.advance(); // consume '('
656
+ // Empty params: () => ...
657
+ if (this.check(TokenType.RPAREN)) {
658
+ this.advance(); // consume ')'
659
+ if (this.check(TokenType.ARROW)) {
660
+ return this.finishArrowFunction([], startPos);
661
+ }
662
+ throw this.error('Empty parentheses are not allowed except for arrow functions');
663
+ }
664
+ // Try to parse as parameters for arrow function
665
+ // We need to look ahead to determine if this is arrow function or just parens
666
+ const savedPos = this.current;
667
+ // Check if we can parse as parameters (all identifiers/rest, no complex expressions)
668
+ const params = [];
669
+ let isArrow = true;
670
+ try {
671
+ do {
672
+ if (this.check(TokenType.SPREAD)) {
673
+ this.advance();
674
+ const name = this.expect(TokenType.IDENT, 'Expected parameter name');
675
+ params.push({ name: name.value, rest: true });
676
+ break;
677
+ }
678
+ if (!this.check(TokenType.IDENT)) {
679
+ isArrow = false;
680
+ break;
681
+ }
682
+ const name = this.advance().value;
683
+ params.push({ name });
684
+ } while (this.match(TokenType.COMMA));
685
+ if (isArrow && this.check(TokenType.RPAREN)) {
686
+ this.advance(); // consume ')'
687
+ if (this.check(TokenType.ARROW)) {
688
+ return this.finishArrowFunction(params, startPos);
689
+ }
690
+ }
691
+ }
692
+ catch {
693
+ // Not a valid arrow function parameter list
694
+ }
695
+ // Backtrack and parse as parenthesized expression
696
+ this.current = savedPos;
697
+ const expr = this.parseExpression();
698
+ this.expect(TokenType.RPAREN, "Expected ')' after expression");
699
+ // Check for arrow after single-param parens: (x) => ...
700
+ if (this.check(TokenType.ARROW) && expr.kind === 'Identifier') {
701
+ return this.finishArrowFunction([{ name: expr.name }], startPos);
702
+ }
703
+ return expr;
704
+ }
705
+ finishArrowFunction(params, startPos) {
706
+ this.advance(); // consume '=>'
707
+ let body;
708
+ if (this.check(TokenType.LBRACE)) {
709
+ this.advance(); // consume '{'
710
+ body = this.parseBlock();
711
+ }
712
+ else {
713
+ body = this.parseExpression();
714
+ }
715
+ return {
716
+ kind: 'ArrowFunction',
717
+ params,
718
+ body,
719
+ position: startPos,
720
+ };
721
+ }
722
+ parsePitchLiteral(token) {
723
+ // Parse pitch like C4, C#4, Db4
724
+ const value = token.value;
725
+ let idx = 0;
726
+ const noteChar = value[idx++].toUpperCase();
727
+ let accidental = '';
728
+ if (value[idx] === '#' || value[idx] === 'b') {
729
+ accidental = value[idx++];
730
+ }
731
+ const octaveStr = value.slice(idx);
732
+ const octave = parseInt(octaveStr, 10);
733
+ const note = noteChar + accidental;
734
+ const midi = this.noteToMidi(note, octave);
735
+ return {
736
+ kind: 'PitchLiteral',
737
+ note,
738
+ octave,
739
+ midi,
740
+ position: token.position,
741
+ };
742
+ }
743
+ noteToMidi(note, octave) {
744
+ const noteOffsets = {
745
+ 'C': 0, 'C#': 1, 'Db': 1,
746
+ 'D': 2, 'D#': 3, 'Eb': 3,
747
+ 'E': 4, 'Fb': 4, 'E#': 5,
748
+ 'F': 5, 'F#': 6, 'Gb': 6,
749
+ 'G': 7, 'G#': 8, 'Ab': 8,
750
+ 'A': 9, 'A#': 10, 'Bb': 10,
751
+ 'B': 11, 'Cb': 11, 'B#': 0,
752
+ };
753
+ const offset = noteOffsets[note] ?? 0;
754
+ // MIDI: C4 = 60, so C0 = 12
755
+ return (octave + 1) * 12 + offset;
756
+ }
757
+ parseDurLiteral(token) {
758
+ // Parse duration like 1/4, 3/8
759
+ const [numStr, denStr] = token.value.split('/');
760
+ const numerator = parseInt(numStr, 10);
761
+ const denominator = parseInt(denStr, 10);
762
+ return {
763
+ kind: 'DurLiteral',
764
+ numerator,
765
+ denominator,
766
+ position: token.position,
767
+ };
768
+ }
769
+ parseTimeLiteral(token) {
770
+ // Parse time like 1:1 or 1:1:0
771
+ const parts = token.value.split(':').map((p) => parseInt(p, 10));
772
+ const bar = parts[0];
773
+ const beat = parts[1];
774
+ const sub = parts[2] ?? 0;
775
+ return {
776
+ kind: 'TimeLiteral',
777
+ bar,
778
+ beat,
779
+ sub,
780
+ position: token.position,
781
+ };
782
+ }
783
+ parseArrayLiteral() {
784
+ const pos = this.advance().position; // consume '['
785
+ const elements = [];
786
+ if (!this.check(TokenType.RBRACKET)) {
787
+ do {
788
+ if (this.check(TokenType.SPREAD)) {
789
+ const spreadPos = this.advance().position; // consume '...'
790
+ elements.push({
791
+ kind: 'SpreadElement',
792
+ argument: this.parseExpression(),
793
+ position: spreadPos,
794
+ });
795
+ }
796
+ else {
797
+ elements.push(this.parseExpression());
798
+ }
799
+ } while (this.match(TokenType.COMMA));
800
+ }
801
+ this.expect(TokenType.RBRACKET, "Expected ']' after array elements");
802
+ return {
803
+ kind: 'ArrayLiteral',
804
+ elements,
805
+ position: pos,
806
+ };
807
+ }
808
+ parseObjectLiteral() {
809
+ const pos = this.advance().position; // consume '{'
810
+ const properties = [];
811
+ if (!this.check(TokenType.RBRACE)) {
812
+ do {
813
+ // Spread property: { ...obj }
814
+ if (this.check(TokenType.SPREAD)) {
815
+ this.advance(); // consume '...'
816
+ const argument = this.parseExpression();
817
+ properties.push({ kind: 'spread', argument });
818
+ }
819
+ else {
820
+ const keyToken = this.expect(TokenType.IDENT, 'Expected property name');
821
+ const key = keyToken.value;
822
+ // Shorthand property: { x } means { x: x }
823
+ if (this.check(TokenType.COMMA) || this.check(TokenType.RBRACE)) {
824
+ properties.push({
825
+ kind: 'property',
826
+ key,
827
+ value: { kind: 'Identifier', name: key, position: keyToken.position },
828
+ shorthand: true,
829
+ });
830
+ }
831
+ else {
832
+ // Full property: { key: value }
833
+ this.expect(TokenType.COLON, "Expected ':' after property name");
834
+ const value = this.parseExpression();
835
+ properties.push({ kind: 'property', key, value, shorthand: false });
836
+ }
837
+ }
838
+ } while (this.match(TokenType.COMMA));
839
+ }
840
+ this.expect(TokenType.RBRACE, "Expected '}' after object properties");
841
+ return {
842
+ kind: 'ObjectLiteral',
843
+ properties,
844
+ position: pos,
845
+ };
846
+ }
847
+ // Helper methods
848
+ peek() {
849
+ return this.tokens[this.current];
850
+ }
851
+ peekAhead(offset) {
852
+ const idx = this.current + offset;
853
+ if (idx >= this.tokens.length) {
854
+ return this.tokens[this.tokens.length - 1];
855
+ }
856
+ return this.tokens[idx];
857
+ }
858
+ advance() {
859
+ if (!this.isAtEnd()) {
860
+ this.current++;
861
+ }
862
+ return this.tokens[this.current - 1];
863
+ }
864
+ check(type) {
865
+ if (this.isAtEnd())
866
+ return false;
867
+ return this.peek().type === type;
868
+ }
869
+ checkAhead(offset, type) {
870
+ return this.peekAhead(offset).type === type;
871
+ }
872
+ checkIdent(name) {
873
+ return this.check(TokenType.IDENT) && this.peek().value === name;
874
+ }
875
+ match(type) {
876
+ if (this.check(type)) {
877
+ this.advance();
878
+ return true;
879
+ }
880
+ return false;
881
+ }
882
+ expect(type, message) {
883
+ if (this.check(type)) {
884
+ return this.advance();
885
+ }
886
+ throw this.error(message);
887
+ }
888
+ isAtEnd() {
889
+ return this.peek().type === TokenType.EOF;
890
+ }
891
+ error(message) {
892
+ const token = this.peek();
893
+ return new MFError('SYNTAX', message, token.position, this.filePath);
894
+ }
895
+ }
896
+ //# sourceMappingURL=parser.js.map