bonescript-compiler 0.5.3 → 0.5.4

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 (194) hide show
  1. package/LICENSE +21 -21
  2. package/dist/algorithm_catalog.js +166 -166
  3. package/dist/cli.d.ts +1 -2
  4. package/dist/cli.js +543 -75
  5. package/dist/cli.js.map +1 -1
  6. package/dist/emit_capability.d.ts +0 -13
  7. package/dist/emit_capability.js +134 -296
  8. package/dist/emit_capability.js.map +1 -1
  9. package/dist/emit_composition.js +3 -37
  10. package/dist/emit_composition.js.map +1 -1
  11. package/dist/emit_deploy.js +167 -165
  12. package/dist/emit_deploy.js.map +1 -1
  13. package/dist/emit_events.d.ts +0 -1
  14. package/dist/emit_events.js +275 -325
  15. package/dist/emit_events.js.map +1 -1
  16. package/dist/emit_extras.js +5 -3
  17. package/dist/emit_extras.js.map +1 -1
  18. package/dist/emit_full.js +112 -272
  19. package/dist/emit_full.js.map +1 -1
  20. package/dist/emit_maintenance.js +249 -249
  21. package/dist/emit_runtime.d.ts +11 -17
  22. package/dist/emit_runtime.js +688 -29
  23. package/dist/emit_runtime.js.map +1 -1
  24. package/dist/emit_sourcemap.js +66 -66
  25. package/dist/emit_tests.js +12 -47
  26. package/dist/emit_tests.js.map +1 -1
  27. package/dist/emit_websocket.js +3 -0
  28. package/dist/emit_websocket.js.map +1 -1
  29. package/dist/emitter.js +49 -94
  30. package/dist/emitter.js.map +1 -1
  31. package/dist/extension_manager.d.ts +2 -2
  32. package/dist/extension_manager.js +20 -9
  33. package/dist/extension_manager.js.map +1 -1
  34. package/dist/ir.d.ts +0 -4
  35. package/dist/lowering.d.ts +14 -5
  36. package/dist/lowering.js +417 -66
  37. package/dist/lowering.js.map +1 -1
  38. package/dist/module_loader.d.ts +2 -2
  39. package/dist/module_loader.js +23 -20
  40. package/dist/module_loader.js.map +1 -1
  41. package/dist/optimizer.js +3 -6
  42. package/dist/optimizer.js.map +1 -1
  43. package/dist/scaffold.d.ts +2 -2
  44. package/dist/scaffold.js +319 -315
  45. package/dist/scaffold.js.map +1 -1
  46. package/dist/solver.js +1 -1
  47. package/dist/solver.js.map +1 -1
  48. package/dist/source_map.js.map +1 -0
  49. package/dist/test.js.map +1 -0
  50. package/dist/test_typechecker.d.ts +5 -0
  51. package/dist/test_typechecker.js +126 -0
  52. package/dist/test_typechecker.js.map +1 -0
  53. package/dist/typechecker.d.ts +0 -7
  54. package/dist/typechecker.js +16 -103
  55. package/dist/typechecker.js.map +1 -1
  56. package/dist/verifier.d.ts +1 -5
  57. package/dist/verifier.js +38 -142
  58. package/dist/verifier.js.map +1 -1
  59. package/package.json +52 -62
  60. package/src/algorithm_catalog.ts +345 -345
  61. package/src/ast.d.ts +244 -0
  62. package/src/ast.ts +334 -334
  63. package/src/cli.ts +624 -98
  64. package/src/emit_batch.ts +140 -140
  65. package/src/emit_capability.ts +436 -613
  66. package/src/emit_composition.ts +196 -229
  67. package/src/emit_deploy.ts +190 -187
  68. package/src/emit_events.ts +307 -362
  69. package/src/emit_extras.ts +240 -237
  70. package/src/emit_full.ts +309 -472
  71. package/src/emit_maintenance.ts +459 -459
  72. package/src/emit_runtime.ts +730 -17
  73. package/src/emit_sourcemap.ts +140 -140
  74. package/src/emit_tests.ts +205 -243
  75. package/src/emit_websocket.ts +229 -226
  76. package/src/emitter.ts +578 -626
  77. package/src/extension_manager.ts +187 -177
  78. package/src/formatter.ts +297 -297
  79. package/src/index.ts +88 -88
  80. package/src/ir.ts +215 -216
  81. package/src/lexer.d.ts +195 -0
  82. package/src/lexer.ts +630 -630
  83. package/src/lowering.ts +556 -168
  84. package/src/module_loader.ts +114 -112
  85. package/src/optimizer.ts +196 -199
  86. package/src/parse_decls.d.ts +13 -0
  87. package/src/parse_decls.ts +409 -409
  88. package/src/parse_decls2.d.ts +13 -0
  89. package/src/parse_decls2.ts +244 -244
  90. package/src/parse_expr.d.ts +7 -0
  91. package/src/parse_expr.ts +197 -197
  92. package/src/parse_types.d.ts +6 -0
  93. package/src/parse_types.ts +54 -54
  94. package/src/parser.d.ts +10 -0
  95. package/src/parser.ts +1 -1
  96. package/src/parser_base.d.ts +19 -0
  97. package/src/parser_base.ts +57 -57
  98. package/src/parser_recovery.ts +153 -153
  99. package/src/scaffold.ts +375 -371
  100. package/src/solver.ts +330 -330
  101. package/src/typechecker.d.ts +52 -0
  102. package/src/typechecker.ts +591 -700
  103. package/src/types.d.ts +38 -0
  104. package/src/types.ts +122 -122
  105. package/src/verifier.ts +49 -154
  106. package/README.md +0 -382
  107. package/dist/commands/check.d.ts +0 -5
  108. package/dist/commands/check.js +0 -34
  109. package/dist/commands/check.js.map +0 -1
  110. package/dist/commands/compile.d.ts +0 -5
  111. package/dist/commands/compile.js +0 -215
  112. package/dist/commands/compile.js.map +0 -1
  113. package/dist/commands/debug.d.ts +0 -5
  114. package/dist/commands/debug.js +0 -59
  115. package/dist/commands/debug.js.map +0 -1
  116. package/dist/commands/diff.d.ts +0 -5
  117. package/dist/commands/diff.js +0 -123
  118. package/dist/commands/diff.js.map +0 -1
  119. package/dist/commands/fmt.d.ts +0 -5
  120. package/dist/commands/fmt.js +0 -49
  121. package/dist/commands/fmt.js.map +0 -1
  122. package/dist/commands/init.d.ts +0 -5
  123. package/dist/commands/init.js +0 -96
  124. package/dist/commands/init.js.map +0 -1
  125. package/dist/commands/ir.d.ts +0 -5
  126. package/dist/commands/ir.js +0 -27
  127. package/dist/commands/ir.js.map +0 -1
  128. package/dist/commands/lex.d.ts +0 -5
  129. package/dist/commands/lex.js +0 -21
  130. package/dist/commands/lex.js.map +0 -1
  131. package/dist/commands/parse.d.ts +0 -5
  132. package/dist/commands/parse.js +0 -30
  133. package/dist/commands/parse.js.map +0 -1
  134. package/dist/commands/test.d.ts +0 -5
  135. package/dist/commands/test.js +0 -61
  136. package/dist/commands/test.js.map +0 -1
  137. package/dist/commands/verify_determinism.d.ts +0 -5
  138. package/dist/commands/verify_determinism.js +0 -64
  139. package/dist/commands/verify_determinism.js.map +0 -1
  140. package/dist/commands/watch.d.ts +0 -5
  141. package/dist/commands/watch.js +0 -50
  142. package/dist/commands/watch.js.map +0 -1
  143. package/dist/emit_auth.d.ts +0 -18
  144. package/dist/emit_auth.js +0 -507
  145. package/dist/emit_auth.js.map +0 -1
  146. package/dist/emit_database.d.ts +0 -7
  147. package/dist/emit_database.js +0 -72
  148. package/dist/emit_database.js.map +0 -1
  149. package/dist/emit_index.d.ts +0 -6
  150. package/dist/emit_index.js +0 -202
  151. package/dist/emit_index.js.map +0 -1
  152. package/dist/emit_models.d.ts +0 -12
  153. package/dist/emit_models.js +0 -171
  154. package/dist/emit_models.js.map +0 -1
  155. package/dist/emit_openapi.d.ts +0 -9
  156. package/dist/emit_openapi.js +0 -306
  157. package/dist/emit_openapi.js.map +0 -1
  158. package/dist/emit_package.d.ts +0 -7
  159. package/dist/emit_package.js +0 -68
  160. package/dist/emit_package.js.map +0 -1
  161. package/dist/emit_router.d.ts +0 -12
  162. package/dist/emit_router.js +0 -389
  163. package/dist/emit_router.js.map +0 -1
  164. package/dist/lowering_channels.d.ts +0 -11
  165. package/dist/lowering_channels.js +0 -103
  166. package/dist/lowering_channels.js.map +0 -1
  167. package/dist/lowering_entities.d.ts +0 -11
  168. package/dist/lowering_entities.js +0 -232
  169. package/dist/lowering_entities.js.map +0 -1
  170. package/dist/lowering_helpers.d.ts +0 -13
  171. package/dist/lowering_helpers.js +0 -76
  172. package/dist/lowering_helpers.js.map +0 -1
  173. package/src/commands/check.ts +0 -33
  174. package/src/commands/compile.ts +0 -191
  175. package/src/commands/debug.ts +0 -33
  176. package/src/commands/diff.ts +0 -105
  177. package/src/commands/fmt.ts +0 -22
  178. package/src/commands/init.ts +0 -72
  179. package/src/commands/ir.ts +0 -23
  180. package/src/commands/lex.ts +0 -17
  181. package/src/commands/parse.ts +0 -24
  182. package/src/commands/test.ts +0 -36
  183. package/src/commands/verify_determinism.ts +0 -66
  184. package/src/commands/watch.ts +0 -25
  185. package/src/emit_auth.ts +0 -513
  186. package/src/emit_database.ts +0 -72
  187. package/src/emit_index.ts +0 -210
  188. package/src/emit_models.ts +0 -176
  189. package/src/emit_openapi.ts +0 -315
  190. package/src/emit_package.ts +0 -66
  191. package/src/emit_router.ts +0 -408
  192. package/src/lowering_channels.ts +0 -108
  193. package/src/lowering_entities.ts +0 -258
  194. package/src/lowering_helpers.ts +0 -75
package/src/lexer.ts CHANGED
@@ -1,630 +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
- }
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
+ }