bonescript-compiler 0.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 (146) hide show
  1. package/LICENSE +21 -0
  2. package/dist/algorithm_catalog.d.ts +32 -0
  3. package/dist/algorithm_catalog.js +323 -0
  4. package/dist/algorithm_catalog.js.map +1 -0
  5. package/dist/ast.d.ts +244 -0
  6. package/dist/ast.js +8 -0
  7. package/dist/ast.js.map +1 -0
  8. package/dist/cli.d.ts +4 -0
  9. package/dist/cli.js +605 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/emit_batch.d.ts +7 -0
  12. package/dist/emit_batch.js +133 -0
  13. package/dist/emit_batch.js.map +1 -0
  14. package/dist/emit_capability.d.ts +7 -0
  15. package/dist/emit_capability.js +376 -0
  16. package/dist/emit_capability.js.map +1 -0
  17. package/dist/emit_composition.d.ts +22 -0
  18. package/dist/emit_composition.js +184 -0
  19. package/dist/emit_composition.js.map +1 -0
  20. package/dist/emit_deploy.d.ts +9 -0
  21. package/dist/emit_deploy.js +191 -0
  22. package/dist/emit_deploy.js.map +1 -0
  23. package/dist/emit_events.d.ts +14 -0
  24. package/dist/emit_events.js +305 -0
  25. package/dist/emit_events.js.map +1 -0
  26. package/dist/emit_extras.d.ts +12 -0
  27. package/dist/emit_extras.js +234 -0
  28. package/dist/emit_extras.js.map +1 -0
  29. package/dist/emit_full.d.ts +13 -0
  30. package/dist/emit_full.js +273 -0
  31. package/dist/emit_full.js.map +1 -0
  32. package/dist/emit_maintenance.d.ts +16 -0
  33. package/dist/emit_maintenance.js +442 -0
  34. package/dist/emit_maintenance.js.map +1 -0
  35. package/dist/emit_runtime.d.ts +13 -0
  36. package/dist/emit_runtime.js +691 -0
  37. package/dist/emit_runtime.js.map +1 -0
  38. package/dist/emit_sourcemap.d.ts +29 -0
  39. package/dist/emit_sourcemap.js +123 -0
  40. package/dist/emit_sourcemap.js.map +1 -0
  41. package/dist/emit_tests.d.ts +15 -0
  42. package/dist/emit_tests.js +185 -0
  43. package/dist/emit_tests.js.map +1 -0
  44. package/dist/emit_websocket.d.ts +6 -0
  45. package/dist/emit_websocket.js +223 -0
  46. package/dist/emit_websocket.js.map +1 -0
  47. package/dist/emitter.d.ts +25 -0
  48. package/dist/emitter.js +511 -0
  49. package/dist/emitter.js.map +1 -0
  50. package/dist/extension_manager.d.ts +38 -0
  51. package/dist/extension_manager.js +170 -0
  52. package/dist/extension_manager.js.map +1 -0
  53. package/dist/formatter.d.ts +34 -0
  54. package/dist/formatter.js +317 -0
  55. package/dist/formatter.js.map +1 -0
  56. package/dist/index.d.ts +42 -0
  57. package/dist/index.js +113 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/ir.d.ts +168 -0
  60. package/dist/ir.js +10 -0
  61. package/dist/ir.js.map +1 -0
  62. package/dist/lexer.d.ts +195 -0
  63. package/dist/lexer.js +619 -0
  64. package/dist/lexer.js.map +1 -0
  65. package/dist/lowering.d.ts +25 -0
  66. package/dist/lowering.js +500 -0
  67. package/dist/lowering.js.map +1 -0
  68. package/dist/module_loader.d.ts +25 -0
  69. package/dist/module_loader.js +126 -0
  70. package/dist/module_loader.js.map +1 -0
  71. package/dist/optimizer.d.ts +26 -0
  72. package/dist/optimizer.js +158 -0
  73. package/dist/optimizer.js.map +1 -0
  74. package/dist/parse_decls.d.ts +13 -0
  75. package/dist/parse_decls.js +442 -0
  76. package/dist/parse_decls.js.map +1 -0
  77. package/dist/parse_decls2.d.ts +13 -0
  78. package/dist/parse_decls2.js +295 -0
  79. package/dist/parse_decls2.js.map +1 -0
  80. package/dist/parse_expr.d.ts +7 -0
  81. package/dist/parse_expr.js +197 -0
  82. package/dist/parse_expr.js.map +1 -0
  83. package/dist/parse_types.d.ts +6 -0
  84. package/dist/parse_types.js +51 -0
  85. package/dist/parse_types.js.map +1 -0
  86. package/dist/parser.d.ts +10 -0
  87. package/dist/parser.js +62 -0
  88. package/dist/parser.js.map +1 -0
  89. package/dist/parser_base.d.ts +19 -0
  90. package/dist/parser_base.js +50 -0
  91. package/dist/parser_base.js.map +1 -0
  92. package/dist/parser_recovery.d.ts +26 -0
  93. package/dist/parser_recovery.js +140 -0
  94. package/dist/parser_recovery.js.map +1 -0
  95. package/dist/scaffold.d.ts +13 -0
  96. package/dist/scaffold.js +376 -0
  97. package/dist/scaffold.js.map +1 -0
  98. package/dist/solver.d.ts +26 -0
  99. package/dist/solver.js +281 -0
  100. package/dist/solver.js.map +1 -0
  101. package/dist/typechecker.d.ts +52 -0
  102. package/dist/typechecker.js +534 -0
  103. package/dist/typechecker.js.map +1 -0
  104. package/dist/types.d.ts +38 -0
  105. package/dist/types.js +85 -0
  106. package/dist/types.js.map +1 -0
  107. package/dist/verifier.d.ts +46 -0
  108. package/dist/verifier.js +307 -0
  109. package/dist/verifier.js.map +1 -0
  110. package/package.json +52 -0
  111. package/src/algorithm_catalog.ts +345 -0
  112. package/src/ast.ts +334 -0
  113. package/src/cli.ts +624 -0
  114. package/src/emit_batch.ts +140 -0
  115. package/src/emit_capability.ts +436 -0
  116. package/src/emit_composition.ts +196 -0
  117. package/src/emit_deploy.ts +190 -0
  118. package/src/emit_events.ts +307 -0
  119. package/src/emit_extras.ts +240 -0
  120. package/src/emit_full.ts +309 -0
  121. package/src/emit_maintenance.ts +459 -0
  122. package/src/emit_runtime.ts +731 -0
  123. package/src/emit_sourcemap.ts +140 -0
  124. package/src/emit_tests.ts +205 -0
  125. package/src/emit_websocket.ts +229 -0
  126. package/src/emitter.ts +566 -0
  127. package/src/extension_manager.ts +187 -0
  128. package/src/formatter.ts +297 -0
  129. package/src/index.ts +88 -0
  130. package/src/ir.ts +215 -0
  131. package/src/lexer.ts +630 -0
  132. package/src/lowering.ts +556 -0
  133. package/src/module_loader.ts +114 -0
  134. package/src/optimizer.ts +196 -0
  135. package/src/parse_decls.ts +409 -0
  136. package/src/parse_decls2.ts +244 -0
  137. package/src/parse_expr.ts +197 -0
  138. package/src/parse_types.ts +54 -0
  139. package/src/parser.ts +64 -0
  140. package/src/parser_base.ts +57 -0
  141. package/src/parser_recovery.ts +153 -0
  142. package/src/scaffold.ts +375 -0
  143. package/src/solver.ts +330 -0
  144. package/src/typechecker.ts +591 -0
  145. package/src/types.ts +122 -0
  146. package/src/verifier.ts +348 -0
package/src/lexer.ts ADDED
@@ -0,0 +1,630 @@
1
+ /**
2
+ * bone lexer
3
+ * Converts source text into a token stream.
4
+ *
5
+ * This is a hand-written lexer (not regex-based) for precise error reporting
6
+ * and deterministic behavior. It implements the lexical rules from spec/02_GRAMMAR.peg.
7
+ */
8
+
9
+ // ─── Token Types ─────────────────────────────────────────────────────────────
10
+
11
+ export enum TokenKind {
12
+ // Structural
13
+ LBrace = "LBrace",
14
+ RBrace = "RBrace",
15
+ LBracket = "LBracket",
16
+ RBracket = "RBracket",
17
+ LParen = "LParen",
18
+ RParen = "RParen",
19
+ LAngle = "LAngle",
20
+ RAngle = "RAngle",
21
+ Colon = "Colon",
22
+ Comma = "Comma",
23
+ Dot = "Dot",
24
+ DotDot = "DotDot",
25
+ Arrow = "Arrow",
26
+ Pipe = "Pipe",
27
+ Semicolon = "Semicolon",
28
+
29
+ // Operators
30
+ Equals = "Equals",
31
+ EqEq = "EqEq",
32
+ NotEq = "NotEq",
33
+ LtEq = "LtEq",
34
+ GtEq = "GtEq",
35
+ Plus = "Plus",
36
+ Minus = "Minus",
37
+ Star = "Star",
38
+ Slash = "Slash",
39
+ Percent = "Percent",
40
+ PlusEq = "PlusEq",
41
+ MinusEq = "MinusEq",
42
+ AppendEq = "AppendEq",
43
+ Question = "Question",
44
+ Bang = "Bang",
45
+
46
+ // Literals
47
+ StringLiteral = "StringLiteral",
48
+ IntLiteral = "IntLiteral",
49
+ FloatLiteral = "FloatLiteral",
50
+
51
+ // Keywords (each keyword is its own token kind for unambiguous parsing)
52
+ KwSystem = "KwSystem",
53
+ KwEntity = "KwEntity",
54
+ KwCapability = "KwCapability",
55
+ KwChannel = "KwChannel",
56
+ KwStore = "KwStore",
57
+ KwEvent = "KwEvent",
58
+ KwConstraint = "KwConstraint",
59
+ KwPolicy = "KwPolicy",
60
+ KwFlow = "KwFlow",
61
+ KwImport = "KwImport",
62
+ KwFrom = "KwFrom",
63
+ KwDomain = "KwDomain",
64
+ KwOwns = "KwOwns",
65
+ KwConstraints = "KwConstraints",
66
+ KwStates = "KwStates",
67
+ KwAuth = "KwAuth",
68
+ KwRelation = "KwRelation",
69
+ KwIndex = "KwIndex",
70
+ KwDerived = "KwDerived",
71
+ KwRequires = "KwRequires",
72
+ KwEffects = "KwEffects",
73
+ KwEmits = "KwEmits",
74
+ KwSync = "KwSync",
75
+ KwTimeout = "KwTimeout",
76
+ KwRetry = "KwRetry",
77
+ KwIdempotent = "KwIdempotent",
78
+ KwTransport = "KwTransport",
79
+ KwOrdering = "KwOrdering",
80
+ KwParticipants = "KwParticipants",
81
+ KwPersistence = "KwPersistence",
82
+ KwFilter = "KwFilter",
83
+ KwMaxSize = "KwMaxSize",
84
+ KwEngine = "KwEngine",
85
+ KwSchema = "KwSchema",
86
+ KwRetention = "KwRetention",
87
+ KwPartition = "KwPartition",
88
+ KwReplicas = "KwReplicas",
89
+ KwPayload = "KwPayload",
90
+ KwDelivery = "KwDelivery",
91
+ KwTtl = "KwTtl",
92
+ KwRateLimit = "KwRateLimit",
93
+ KwAccess = "KwAccess",
94
+ KwAudit = "KwAudit",
95
+ KwEncryption = "KwEncryption",
96
+ KwStep = "KwStep",
97
+ KwCompensate = "KwCompensate",
98
+ KwPer = "KwPer",
99
+ KwHasOne = "KwHasOne",
100
+ KwHasMany = "KwHasMany",
101
+ KwBelongsTo = "KwBelongsTo",
102
+ KwManyToMany = "KwManyToMany",
103
+
104
+ // Auth methods
105
+ KwJwt = "KwJwt",
106
+ KwOauth2 = "KwOauth2",
107
+ KwApikey = "KwApikey",
108
+ KwSession = "KwSession",
109
+
110
+ // Transport types
111
+ KwWebsocket = "KwWebsocket",
112
+ KwSse = "KwSse",
113
+ KwPolling = "KwPolling",
114
+ KwGrpcStream = "KwGrpcStream",
115
+
116
+ // Ordering types
117
+ KwCausal = "KwCausal",
118
+ KwFifo = "KwFifo",
119
+ KwTotal = "KwTotal",
120
+ KwUnordered = "KwUnordered",
121
+
122
+ // Engine types
123
+ KwPostgresql = "KwPostgresql",
124
+ KwRedis = "KwRedis",
125
+ KwMongodb = "KwMongodb",
126
+ KwSqlite = "KwSqlite",
127
+ KwS3 = "KwS3",
128
+ KwDynamodb = "KwDynamodb",
129
+
130
+ // Delivery modes
131
+ KwAtLeastOnce = "KwAtLeastOnce",
132
+ KwAtMostOnce = "KwAtMostOnce",
133
+ KwExactlyOnce = "KwExactlyOnce",
134
+
135
+ // Encryption modes
136
+ KwAtRest = "KwAtRest",
137
+ KwInTransit = "KwInTransit",
138
+ KwBoth = "KwBoth",
139
+
140
+ // Sync modes
141
+ KwRealtime = "KwRealtime",
142
+ KwEventual = "KwEventual",
143
+ KwBatch = "KwBatch",
144
+ KwTransactional = "KwTransactional",
145
+
146
+ // Retry fields
147
+ KwMaxAttempts = "KwMaxAttempts",
148
+ KwBackoff = "KwBackoff",
149
+ KwInterval = "KwInterval",
150
+
151
+ // Primitive types
152
+ KwString = "KwString",
153
+ KwUint = "KwUint",
154
+ KwInt = "KwInt",
155
+ KwFloat = "KwFloat",
156
+ KwBool = "KwBool",
157
+ KwTimestamp = "KwTimestamp",
158
+ KwUuid = "KwUuid",
159
+ KwBytes = "KwBytes",
160
+ KwMap = "KwMap",
161
+ KwJson = "KwJson",
162
+
163
+ // Generic type constructors
164
+ KwSet = "KwSet",
165
+ KwList = "KwList",
166
+ KwOptional = "KwOptional",
167
+ KwResult = "KwResult",
168
+
169
+ // Boolean literals
170
+ KwTrue = "KwTrue",
171
+ KwFalse = "KwFalse",
172
+ KwNone = "KwNone",
173
+
174
+ // Logical operators
175
+ KwAnd = "KwAnd",
176
+ KwOr = "KwOr",
177
+ KwNot = "KwNot",
178
+ KwIn = "KwIn",
179
+ KwContains = "KwContains",
180
+ KwUnique = "KwUnique",
181
+
182
+ // Persistence modes
183
+ KwFull = "KwFull",
184
+
185
+ // Composition (Leap 1)
186
+ KwPipeline = "KwPipeline",
187
+ KwParallel = "KwParallel",
188
+ KwOnError = "KwOnError",
189
+ KwReturns = "KwReturns",
190
+
191
+ // Algorithm catalog (Leap 2)
192
+ KwAlgorithm = "KwAlgorithm",
193
+ KwUsing = "KwUsing",
194
+
195
+ // Extension points
196
+ KwExtensionPoint = "KwExtensionPoint",
197
+ KwStable = "KwStable",
198
+ KwLanguage = "KwLanguage",
199
+
200
+ // Special
201
+ KwNow = "KwNow",
202
+
203
+ // Identifier (anything not a keyword)
204
+ Identifier = "Identifier",
205
+
206
+ // End of file
207
+ EOF = "EOF",
208
+ }
209
+
210
+ // ─── Token ───────────────────────────────────────────────────────────────────
211
+
212
+ export interface SourceLocation {
213
+ line: number;
214
+ column: number;
215
+ offset: number;
216
+ }
217
+
218
+ export interface Token {
219
+ kind: TokenKind;
220
+ value: string;
221
+ loc: SourceLocation;
222
+ }
223
+
224
+ // ─── Keyword Table ───────────────────────────────────────────────────────────
225
+
226
+ const KEYWORDS: Record<string, TokenKind> = {
227
+ system: TokenKind.KwSystem,
228
+ entity: TokenKind.KwEntity,
229
+ capability: TokenKind.KwCapability,
230
+ channel: TokenKind.KwChannel,
231
+ store: TokenKind.KwStore,
232
+ event: TokenKind.KwEvent,
233
+ constraint: TokenKind.KwConstraint,
234
+ policy: TokenKind.KwPolicy,
235
+ flow: TokenKind.KwFlow,
236
+ import: TokenKind.KwImport,
237
+ from: TokenKind.KwFrom,
238
+ domain: TokenKind.KwDomain,
239
+ owns: TokenKind.KwOwns,
240
+ constraints: TokenKind.KwConstraints,
241
+ states: TokenKind.KwStates,
242
+ auth: TokenKind.KwAuth,
243
+ relation: TokenKind.KwRelation,
244
+ index: TokenKind.KwIndex,
245
+ derived: TokenKind.KwDerived,
246
+ requires: TokenKind.KwRequires,
247
+ effects: TokenKind.KwEffects,
248
+ emits: TokenKind.KwEmits,
249
+ sync: TokenKind.KwSync,
250
+ timeout: TokenKind.KwTimeout,
251
+ retry: TokenKind.KwRetry,
252
+ idempotent: TokenKind.KwIdempotent,
253
+ transport: TokenKind.KwTransport,
254
+ ordering: TokenKind.KwOrdering,
255
+ participants: TokenKind.KwParticipants,
256
+ persistence: TokenKind.KwPersistence,
257
+ filter: TokenKind.KwFilter,
258
+ max_size: TokenKind.KwMaxSize,
259
+ engine: TokenKind.KwEngine,
260
+ schema: TokenKind.KwSchema,
261
+ retention: TokenKind.KwRetention,
262
+ partition: TokenKind.KwPartition,
263
+ replicas: TokenKind.KwReplicas,
264
+ payload: TokenKind.KwPayload,
265
+ delivery: TokenKind.KwDelivery,
266
+ ttl: TokenKind.KwTtl,
267
+ rate_limit: TokenKind.KwRateLimit,
268
+ access: TokenKind.KwAccess,
269
+ audit: TokenKind.KwAudit,
270
+ encryption: TokenKind.KwEncryption,
271
+ step: TokenKind.KwStep,
272
+ compensate: TokenKind.KwCompensate,
273
+ per: TokenKind.KwPer,
274
+ has_one: TokenKind.KwHasOne,
275
+ has_many: TokenKind.KwHasMany,
276
+ belongs_to: TokenKind.KwBelongsTo,
277
+ many_to_many: TokenKind.KwManyToMany,
278
+ jwt: TokenKind.KwJwt,
279
+ oauth2: TokenKind.KwOauth2,
280
+ apikey: TokenKind.KwApikey,
281
+ session: TokenKind.KwSession,
282
+ websocket: TokenKind.KwWebsocket,
283
+ sse: TokenKind.KwSse,
284
+ polling: TokenKind.KwPolling,
285
+ grpc_stream: TokenKind.KwGrpcStream,
286
+ causal: TokenKind.KwCausal,
287
+ fifo: TokenKind.KwFifo,
288
+ total: TokenKind.KwTotal,
289
+ unordered: TokenKind.KwUnordered,
290
+ postgresql: TokenKind.KwPostgresql,
291
+ redis: TokenKind.KwRedis,
292
+ mongodb: TokenKind.KwMongodb,
293
+ sqlite: TokenKind.KwSqlite,
294
+ s3: TokenKind.KwS3,
295
+ dynamodb: TokenKind.KwDynamodb,
296
+ at_least_once: TokenKind.KwAtLeastOnce,
297
+ at_most_once: TokenKind.KwAtMostOnce,
298
+ exactly_once: TokenKind.KwExactlyOnce,
299
+ at_rest: TokenKind.KwAtRest,
300
+ in_transit: TokenKind.KwInTransit,
301
+ both: TokenKind.KwBoth,
302
+ realtime: TokenKind.KwRealtime,
303
+ eventual: TokenKind.KwEventual,
304
+ batch: TokenKind.KwBatch,
305
+ transactional: TokenKind.KwTransactional,
306
+ max_attempts: TokenKind.KwMaxAttempts,
307
+ backoff: TokenKind.KwBackoff,
308
+ interval: TokenKind.KwInterval,
309
+ string: TokenKind.KwString,
310
+ uint: TokenKind.KwUint,
311
+ int: TokenKind.KwInt,
312
+ float: TokenKind.KwFloat,
313
+ bool: TokenKind.KwBool,
314
+ timestamp: TokenKind.KwTimestamp,
315
+ uuid: TokenKind.KwUuid,
316
+ bytes: TokenKind.KwBytes,
317
+ map: TokenKind.KwMap,
318
+ json: TokenKind.KwJson,
319
+ set: TokenKind.KwSet,
320
+ list: TokenKind.KwList,
321
+ optional: TokenKind.KwOptional,
322
+ result: TokenKind.KwResult,
323
+ true: TokenKind.KwTrue,
324
+ false: TokenKind.KwFalse,
325
+ none: TokenKind.KwNone,
326
+ and: TokenKind.KwAnd,
327
+ or: TokenKind.KwOr,
328
+ not: TokenKind.KwNot,
329
+ in: TokenKind.KwIn,
330
+ contains: TokenKind.KwContains,
331
+ unique: TokenKind.KwUnique,
332
+ full: TokenKind.KwFull,
333
+ now: TokenKind.KwNow,
334
+ pipeline: TokenKind.KwPipeline,
335
+ parallel: TokenKind.KwParallel,
336
+ on_error: TokenKind.KwOnError,
337
+ returns: TokenKind.KwReturns,
338
+ algorithm: TokenKind.KwAlgorithm,
339
+ using: TokenKind.KwUsing,
340
+ extension_point: TokenKind.KwExtensionPoint,
341
+ stable: TokenKind.KwStable,
342
+ language: TokenKind.KwLanguage,
343
+ };
344
+
345
+ // ─── Lexer Error ─────────────────────────────────────────────────────────────
346
+
347
+ export class LexerError extends Error {
348
+ constructor(
349
+ message: string,
350
+ public loc: SourceLocation
351
+ ) {
352
+ super(`Lexer error at ${loc.line}:${loc.column}: ${message}`);
353
+ this.name = "LexerError";
354
+ }
355
+ }
356
+
357
+ // ─── Lexer ───────────────────────────────────────────────────────────────────
358
+
359
+ export class Lexer {
360
+ private source: string;
361
+ private pos: number = 0;
362
+ private line: number = 1;
363
+ private column: number = 1;
364
+ private tokens: Token[] = [];
365
+
366
+ constructor(source: string) {
367
+ // Strip BOM if present
368
+ if (source.charCodeAt(0) === 0xFEFF) {
369
+ source = source.slice(1);
370
+ }
371
+ this.source = source;
372
+ }
373
+
374
+ tokenize(): Token[] {
375
+ while (this.pos < this.source.length) {
376
+ this.skipWhitespaceAndComments();
377
+ if (this.pos >= this.source.length) break;
378
+
379
+ const token = this.nextToken();
380
+ if (token) {
381
+ this.tokens.push(token);
382
+ }
383
+ }
384
+
385
+ this.tokens.push({
386
+ kind: TokenKind.EOF,
387
+ value: "",
388
+ loc: this.currentLoc(),
389
+ });
390
+
391
+ return this.tokens;
392
+ }
393
+
394
+ private currentLoc(): SourceLocation {
395
+ return { line: this.line, column: this.column, offset: this.pos };
396
+ }
397
+
398
+ private peek(): string {
399
+ return this.source[this.pos] ?? "";
400
+ }
401
+
402
+ private peekAt(offset: number): string {
403
+ return this.source[this.pos + offset] ?? "";
404
+ }
405
+
406
+ private advance(): string {
407
+ const ch = this.source[this.pos];
408
+ this.pos++;
409
+ if (ch === "\n") {
410
+ this.line++;
411
+ this.column = 1;
412
+ } else {
413
+ this.column++;
414
+ }
415
+ return ch;
416
+ }
417
+
418
+ private skipWhitespaceAndComments(): void {
419
+ while (this.pos < this.source.length) {
420
+ const ch = this.peek();
421
+
422
+ // Whitespace
423
+ if (ch === " " || ch === "\t" || ch === "\r" || ch === "\n") {
424
+ this.advance();
425
+ continue;
426
+ }
427
+
428
+ // Line comment
429
+ if (ch === "/" && this.peekAt(1) === "/") {
430
+ this.advance(); // /
431
+ this.advance(); // /
432
+ while (this.pos < this.source.length && this.peek() !== "\n") {
433
+ this.advance();
434
+ }
435
+ continue;
436
+ }
437
+
438
+ // Block comment
439
+ if (ch === "/" && this.peekAt(1) === "*") {
440
+ this.advance(); // /
441
+ this.advance(); // *
442
+ while (this.pos < this.source.length) {
443
+ if (this.peek() === "*" && this.peekAt(1) === "/") {
444
+ this.advance(); // *
445
+ this.advance(); // /
446
+ break;
447
+ }
448
+ this.advance();
449
+ }
450
+ continue;
451
+ }
452
+
453
+ break;
454
+ }
455
+ }
456
+
457
+ private nextToken(): Token | null {
458
+ const loc = this.currentLoc();
459
+ const ch = this.peek();
460
+
461
+ // Multi-character operators (check longest first)
462
+ if (ch === "<" && this.peekAt(1) === "<" && this.peekAt(2) === "=") {
463
+ this.advance(); this.advance(); this.advance();
464
+ return { kind: TokenKind.AppendEq, value: "<<=", loc };
465
+ }
466
+ if (ch === "-" && this.peekAt(1) === ">") {
467
+ this.advance(); this.advance();
468
+ return { kind: TokenKind.Arrow, value: "->", loc };
469
+ }
470
+ if (ch === "=" && this.peekAt(1) === "=") {
471
+ this.advance(); this.advance();
472
+ return { kind: TokenKind.EqEq, value: "==", loc };
473
+ }
474
+ if (ch === "!" && this.peekAt(1) === "=") {
475
+ this.advance(); this.advance();
476
+ return { kind: TokenKind.NotEq, value: "!=", loc };
477
+ }
478
+ if (ch === "<" && this.peekAt(1) === "=") {
479
+ this.advance(); this.advance();
480
+ return { kind: TokenKind.LtEq, value: "<=", loc };
481
+ }
482
+ if (ch === ">" && this.peekAt(1) === "=") {
483
+ this.advance(); this.advance();
484
+ return { kind: TokenKind.GtEq, value: ">=", loc };
485
+ }
486
+ if (ch === "+" && this.peekAt(1) === "=") {
487
+ this.advance(); this.advance();
488
+ return { kind: TokenKind.PlusEq, value: "+=", loc };
489
+ }
490
+ if (ch === "-" && this.peekAt(1) === "=") {
491
+ this.advance(); this.advance();
492
+ return { kind: TokenKind.MinusEq, value: "-=", loc };
493
+ }
494
+ if (ch === "." && this.peekAt(1) === ".") {
495
+ this.advance(); this.advance();
496
+ return { kind: TokenKind.DotDot, value: "..", loc };
497
+ }
498
+
499
+ // Single-character operators
500
+ switch (ch) {
501
+ case "{": this.advance(); return { kind: TokenKind.LBrace, value: "{", loc };
502
+ case "}": this.advance(); return { kind: TokenKind.RBrace, value: "}", loc };
503
+ case "[": this.advance(); return { kind: TokenKind.LBracket, value: "[", loc };
504
+ case "]": this.advance(); return { kind: TokenKind.RBracket, value: "]", loc };
505
+ case "(": this.advance(); return { kind: TokenKind.LParen, value: "(", loc };
506
+ case ")": this.advance(); return { kind: TokenKind.RParen, value: ")", loc };
507
+ case "<": this.advance(); return { kind: TokenKind.LAngle, value: "<", loc };
508
+ case ">": this.advance(); return { kind: TokenKind.RAngle, value: ">", loc };
509
+ case ":": this.advance(); return { kind: TokenKind.Colon, value: ":", loc };
510
+ case ",": this.advance(); return { kind: TokenKind.Comma, value: ",", loc };
511
+ case ".": this.advance(); return { kind: TokenKind.Dot, value: ".", loc };
512
+ case "|": this.advance(); return { kind: TokenKind.Pipe, value: "|", loc };
513
+ case "=": this.advance(); return { kind: TokenKind.Equals, value: "=", loc };
514
+ case "+": this.advance(); return { kind: TokenKind.Plus, value: "+", loc };
515
+ case "-": this.advance(); return { kind: TokenKind.Minus, value: "-", loc };
516
+ case "*": this.advance(); return { kind: TokenKind.Star, value: "*", loc };
517
+ case "/": this.advance(); return { kind: TokenKind.Slash, value: "/", loc };
518
+ case "%": this.advance(); return { kind: TokenKind.Percent, value: "%", loc };
519
+ case "?": this.advance(); return { kind: TokenKind.Question, value: "?", loc };
520
+ case "!": this.advance(); return { kind: TokenKind.Bang, value: "!", loc };
521
+ case ";": this.advance(); return { kind: TokenKind.Semicolon, value: ";", loc };
522
+ }
523
+
524
+ // String literal
525
+ if (ch === '"') {
526
+ return this.readString(loc);
527
+ }
528
+
529
+ // Number literal
530
+ if (this.isDigit(ch)) {
531
+ return this.readNumber(loc);
532
+ }
533
+
534
+ // Identifier or keyword
535
+ if (this.isIdentStart(ch)) {
536
+ return this.readIdentifierOrKeyword(loc);
537
+ }
538
+
539
+ throw new LexerError(`Unexpected character: '${ch}'`, loc);
540
+ }
541
+
542
+ private readString(loc: SourceLocation): Token {
543
+ this.advance(); // opening "
544
+ let value = "";
545
+
546
+ while (this.pos < this.source.length && this.peek() !== '"') {
547
+ if (this.peek() === "\\") {
548
+ this.advance(); // backslash
549
+ const escaped = this.advance();
550
+ switch (escaped) {
551
+ case "n": value += "\n"; break;
552
+ case "r": value += "\r"; break;
553
+ case "t": value += "\t"; break;
554
+ case '"': value += '"'; break;
555
+ case "\\": value += "\\"; break;
556
+ default:
557
+ throw new LexerError(`Invalid escape sequence: \\${escaped}`, loc);
558
+ }
559
+ } else {
560
+ value += this.advance();
561
+ }
562
+ }
563
+
564
+ if (this.pos >= this.source.length) {
565
+ throw new LexerError("Unterminated string literal", loc);
566
+ }
567
+
568
+ this.advance(); // closing "
569
+ return { kind: TokenKind.StringLiteral, value, loc };
570
+ }
571
+
572
+ private readNumber(loc: SourceLocation): Token {
573
+ let value = "";
574
+ while (this.pos < this.source.length && this.isDigit(this.peek())) {
575
+ value += this.advance();
576
+ }
577
+
578
+ // Check for float
579
+ if (this.peek() === "." && this.isDigit(this.peekAt(1))) {
580
+ value += this.advance(); // .
581
+ while (this.pos < this.source.length && this.isDigit(this.peek())) {
582
+ value += this.advance();
583
+ }
584
+ return { kind: TokenKind.FloatLiteral, value, loc };
585
+ }
586
+
587
+ // Check for duration suffix (not a separate token — part of the literal)
588
+ // Duration suffixes: ms, s, m, h, d
589
+ if (this.peek() === "m" && this.peekAt(1) === "s") {
590
+ value += this.advance(); value += this.advance();
591
+ return { kind: TokenKind.IntLiteral, value, loc };
592
+ }
593
+ if (
594
+ (this.peek() === "s" || this.peek() === "m" || this.peek() === "h" || this.peek() === "d") &&
595
+ !this.isIdentChar(this.peekAt(1))
596
+ ) {
597
+ value += this.advance();
598
+ return { kind: TokenKind.IntLiteral, value, loc };
599
+ }
600
+
601
+ return { kind: TokenKind.IntLiteral, value, loc };
602
+ }
603
+
604
+ private readIdentifierOrKeyword(loc: SourceLocation): Token {
605
+ let value = "";
606
+ while (this.pos < this.source.length && this.isIdentChar(this.peek())) {
607
+ value += this.advance();
608
+ }
609
+
610
+ // Check keyword table
611
+ const kwKind = KEYWORDS[value];
612
+ if (kwKind !== undefined) {
613
+ return { kind: kwKind, value, loc };
614
+ }
615
+
616
+ return { kind: TokenKind.Identifier, value, loc };
617
+ }
618
+
619
+ private isDigit(ch: string): boolean {
620
+ return ch >= "0" && ch <= "9";
621
+ }
622
+
623
+ private isIdentStart(ch: string): boolean {
624
+ return (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z") || ch === "_";
625
+ }
626
+
627
+ private isIdentChar(ch: string): boolean {
628
+ return this.isIdentStart(ch) || this.isDigit(ch);
629
+ }
630
+ }