wyreframe 0.1.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 (117) hide show
  1. package/README.md +123 -0
  2. package/dist/index.d.ts +267 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +195 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +63 -0
  7. package/src/parser/Core/Bounds.mjs +61 -0
  8. package/src/parser/Core/Bounds.res +65 -0
  9. package/src/parser/Core/Grid.mjs +268 -0
  10. package/src/parser/Core/Grid.res +265 -0
  11. package/src/parser/Core/Position.mjs +83 -0
  12. package/src/parser/Core/Position.res +54 -0
  13. package/src/parser/Core/Types.mjs +435 -0
  14. package/src/parser/Core/Types.res +331 -0
  15. package/src/parser/Core/__tests__/Bounds_test.mjs +326 -0
  16. package/src/parser/Core/__tests__/Bounds_test.res +412 -0
  17. package/src/parser/Core/__tests__/Grid_test.mjs +322 -0
  18. package/src/parser/Core/__tests__/Grid_test.res +319 -0
  19. package/src/parser/Core/__tests__/Types_test.mjs +614 -0
  20. package/src/parser/Core/__tests__/Types_test.res +650 -0
  21. package/src/parser/Detector/BoxTracer.mjs +302 -0
  22. package/src/parser/Detector/BoxTracer.res +374 -0
  23. package/src/parser/Detector/HierarchyBuilder.mjs +158 -0
  24. package/src/parser/Detector/HierarchyBuilder.res +315 -0
  25. package/src/parser/Detector/ShapeDetector.mjs +134 -0
  26. package/src/parser/Detector/ShapeDetector.res +236 -0
  27. package/src/parser/Detector/__tests__/BoxTracer_test.mjs +70 -0
  28. package/src/parser/Detector/__tests__/BoxTracer_test.res +92 -0
  29. package/src/parser/Detector/__tests__/HierarchyBuilder_test.mjs +489 -0
  30. package/src/parser/Detector/__tests__/HierarchyBuilder_test.res +849 -0
  31. package/src/parser/Detector/__tests__/ShapeDetector_test.mjs +377 -0
  32. package/src/parser/Detector/__tests__/ShapeDetector_test.res +563 -0
  33. package/src/parser/Errors/ErrorContext.mjs +106 -0
  34. package/src/parser/Errors/ErrorContext.res +191 -0
  35. package/src/parser/Errors/ErrorMessages.mjs +289 -0
  36. package/src/parser/Errors/ErrorMessages.res +303 -0
  37. package/src/parser/Errors/ErrorTypes.mjs +105 -0
  38. package/src/parser/Errors/ErrorTypes.res +169 -0
  39. package/src/parser/Interactions/InteractionMerger.mjs +266 -0
  40. package/src/parser/Interactions/InteractionMerger.res +450 -0
  41. package/src/parser/Interactions/InteractionParser.mjs +88 -0
  42. package/src/parser/Interactions/InteractionParser.res +127 -0
  43. package/src/parser/Interactions/SimpleInteractionParser.mjs +278 -0
  44. package/src/parser/Interactions/SimpleInteractionParser.res +262 -0
  45. package/src/parser/Interactions/__tests__/InteractionMerger_test.mjs +576 -0
  46. package/src/parser/Interactions/__tests__/InteractionMerger_test.res +646 -0
  47. package/src/parser/Parser.gen.tsx +96 -0
  48. package/src/parser/Parser.mjs +212 -0
  49. package/src/parser/Parser.res +481 -0
  50. package/src/parser/Scanner/__tests__/Grid_manual.mjs +214 -0
  51. package/src/parser/Scanner/__tests__/Grid_manual.res +141 -0
  52. package/src/parser/Semantic/ASTBuilder.mjs +197 -0
  53. package/src/parser/Semantic/ASTBuilder.res +288 -0
  54. package/src/parser/Semantic/AlignmentCalc.mjs +41 -0
  55. package/src/parser/Semantic/AlignmentCalc.res +104 -0
  56. package/src/parser/Semantic/Elements/ButtonParser.mjs +58 -0
  57. package/src/parser/Semantic/Elements/ButtonParser.res +131 -0
  58. package/src/parser/Semantic/Elements/CheckboxParser.mjs +58 -0
  59. package/src/parser/Semantic/Elements/CheckboxParser.res +79 -0
  60. package/src/parser/Semantic/Elements/CodeTextParser.mjs +50 -0
  61. package/src/parser/Semantic/Elements/CodeTextParser.res +111 -0
  62. package/src/parser/Semantic/Elements/ElementParser.mjs +15 -0
  63. package/src/parser/Semantic/Elements/ElementParser.res +83 -0
  64. package/src/parser/Semantic/Elements/EmphasisParser.mjs +46 -0
  65. package/src/parser/Semantic/Elements/EmphasisParser.res +67 -0
  66. package/src/parser/Semantic/Elements/InputParser.mjs +41 -0
  67. package/src/parser/Semantic/Elements/InputParser.res +97 -0
  68. package/src/parser/Semantic/Elements/LinkParser.mjs +60 -0
  69. package/src/parser/Semantic/Elements/LinkParser.res +156 -0
  70. package/src/parser/Semantic/Elements/TextParser.mjs +19 -0
  71. package/src/parser/Semantic/Elements/TextParser.res +42 -0
  72. package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.mjs +189 -0
  73. package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.res +257 -0
  74. package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.mjs +202 -0
  75. package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.res +250 -0
  76. package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.mjs +293 -0
  77. package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.res +134 -0
  78. package/src/parser/Semantic/Elements/__tests__/InputParser_test.mjs +253 -0
  79. package/src/parser/Semantic/Elements/__tests__/InputParser_test.res +304 -0
  80. package/src/parser/Semantic/Elements/__tests__/LinkParser_test.mjs +289 -0
  81. package/src/parser/Semantic/Elements/__tests__/LinkParser_test.res +402 -0
  82. package/src/parser/Semantic/Elements/__tests__/TextParser_test.mjs +149 -0
  83. package/src/parser/Semantic/Elements/__tests__/TextParser_test.res +167 -0
  84. package/src/parser/Semantic/ParserRegistry.mjs +82 -0
  85. package/src/parser/Semantic/ParserRegistry.res +145 -0
  86. package/src/parser/Semantic/SemanticParser.mjs +850 -0
  87. package/src/parser/Semantic/SemanticParser.res +1368 -0
  88. package/src/parser/Semantic/__tests__/ASTBuilder_test.mjs +187 -0
  89. package/src/parser/Semantic/__tests__/ASTBuilder_test.res +192 -0
  90. package/src/parser/Semantic/__tests__/ParserRegistry_test.mjs +154 -0
  91. package/src/parser/Semantic/__tests__/ParserRegistry_test.res +191 -0
  92. package/src/parser/Semantic/__tests__/SemanticParser_integration_test.mjs +768 -0
  93. package/src/parser/Semantic/__tests__/SemanticParser_integration_test.res +1069 -0
  94. package/src/parser/Semantic/__tests__/SemanticParser_manual.mjs +1329 -0
  95. package/src/parser/Semantic/__tests__/SemanticParser_manual.res +544 -0
  96. package/src/parser/TestMain.mjs +21 -0
  97. package/src/parser/TestMain.res +14 -0
  98. package/src/parser/TextExtractor.mjs +179 -0
  99. package/src/parser/TextExtractor.res +264 -0
  100. package/src/parser/__tests__/GridScanner_integration.test.mjs +632 -0
  101. package/src/parser/__tests__/GridScanner_integration.test.res +816 -0
  102. package/src/parser/__tests__/Performance.test.mjs +244 -0
  103. package/src/parser/__tests__/Performance.test.res +371 -0
  104. package/src/parser/__tests__/PerformanceFixtures.mjs +200 -0
  105. package/src/parser/__tests__/PerformanceFixtures.res +284 -0
  106. package/src/parser/__tests__/WyreframeParser_integration.test.mjs +770 -0
  107. package/src/parser/__tests__/WyreframeParser_integration.test.res +1008 -0
  108. package/src/parser/__tests__/fixtures/alignment-test.txt +9 -0
  109. package/src/parser/__tests__/fixtures/all-elements.txt +16 -0
  110. package/src/parser/__tests__/fixtures/login-scene.txt +17 -0
  111. package/src/parser/__tests__/fixtures/multi-scene.txt +25 -0
  112. package/src/parser/__tests__/fixtures/nested-boxes.txt +15 -0
  113. package/src/parser/__tests__/fixtures/simple-box.txt +5 -0
  114. package/src/parser/__tests__/fixtures/with-dividers.txt +14 -0
  115. package/src/renderer/Renderer.gen.tsx +32 -0
  116. package/src/renderer/Renderer.mjs +391 -0
  117. package/src/renderer/Renderer.res +558 -0
@@ -0,0 +1,481 @@
1
+ // WyreframeParser.res
2
+ // Public API for Wyreframe Parser - ReScript Implementation
3
+ // This module provides the main entry points for parsing wireframes and interactions.
4
+ // All functions are exported to TypeScript via GenType annotations.
5
+
6
+ // ============================================================================
7
+ // Type Aliases for Public API
8
+ // ============================================================================
9
+
10
+ /**
11
+ * Parse result type - either a successful AST or an array of parse errors.
12
+ * This Result type is compatible with TypeScript through GenType.
13
+ */
14
+ type parseResult = result<Types.ast, array<ErrorTypes.t>>
15
+
16
+ /**
17
+ * Interaction parse result type.
18
+ */
19
+ type interactionResult = result<array<Types.sceneInteractions>, array<ErrorTypes.t>>
20
+
21
+ // ============================================================================
22
+ // Type Aliases for Internal Use
23
+ // ============================================================================
24
+
25
+ // Box type from BoxTracer
26
+ type box = BoxTracer.box
27
+
28
+ // ============================================================================
29
+ // Stage 1: Grid Scanner
30
+ // ============================================================================
31
+
32
+ /**
33
+ * Scan ASCII wireframe input into a 2D grid structure.
34
+ * Normalizes line endings and creates indexed grid.
35
+ *
36
+ * @param wireframe Raw ASCII wireframe string
37
+ * @returns Result containing Grid or errors
38
+ *
39
+ * Requirements: REQ-1, REQ-2 (Grid Scanner)
40
+ */
41
+ let scanGrid = (wireframe: string): result<Grid.t, array<ErrorTypes.t>> => {
42
+ // Normalize line endings (CRLF -> LF, CR -> LF)
43
+ let normalized = wireframe->String.replaceAll("\r\n", "\n")->String.replaceAll("\r", "\n")
44
+
45
+ // Split into lines
46
+ let lines = normalized->String.split("\n")
47
+
48
+ // TODO: Add validation for unusual spacing (tabs vs spaces)
49
+ // This would generate UnusualSpacing warnings (REQ-19)
50
+
51
+ // Create grid structure
52
+ let grid = Grid.fromLines(lines)
53
+
54
+ Ok(grid)
55
+ }
56
+
57
+ // ============================================================================
58
+ // Stage 2: Shape Detector
59
+ // ============================================================================
60
+
61
+ /**
62
+ * Detect all boxes and dividers in the grid.
63
+ *
64
+ * This function identifies boxes, traces their boundaries, detects dividers,
65
+ * and builds parent-child hierarchy.
66
+ *
67
+ * @param grid The 2D grid from Stage 1
68
+ * @returns Result containing root boxes or errors
69
+ *
70
+ * Requirements: REQ-3, REQ-4, REQ-5, REQ-6, REQ-7 (Shape Detection)
71
+ */
72
+ let detectShapes = (grid: Grid.t): result<array<box>, array<ErrorTypes.t>> => {
73
+ // Use ShapeDetector to detect all shapes in the grid
74
+ ShapeDetector.detect(grid)
75
+ }
76
+
77
+ // ============================================================================
78
+ // Stage 3: Semantic Parser
79
+ // ============================================================================
80
+
81
+ /**
82
+ * Convert BoxTracer.box to SemanticParser.box
83
+ * The types are structurally identical except BoxTracer has mutable children
84
+ */
85
+ let rec convertBox = (tracerBox: box): SemanticParser.box => {
86
+ {
87
+ name: tracerBox.name,
88
+ bounds: tracerBox.bounds,
89
+ children: tracerBox.children->Array.map(convertBox),
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Parse box contents into semantic elements and build AST.
95
+ *
96
+ * This function extracts content, recognizes elements, calculates alignment,
97
+ * parses scene directives, and builds the complete AST.
98
+ *
99
+ * @param grid The 2D grid from Stage 1
100
+ * @param shapes Root boxes from Stage 2
101
+ * @returns Result containing AST or errors
102
+ *
103
+ * Requirements: REQ-8 through REQ-15 (Semantic Parser)
104
+ */
105
+ let parseSemantics = (
106
+ grid: Grid.t,
107
+ shapes: array<box>,
108
+ ): result<Types.ast, array<ErrorTypes.t>> => {
109
+ // Create parser registry with all element parsers
110
+ let registry = ParserRegistry.makeDefault()
111
+
112
+ // Convert BoxTracer boxes to SemanticParser boxes
113
+ let semanticBoxes = shapes->Array.map(convertBox)
114
+
115
+ // Build parse context
116
+ let context: SemanticParser.parseContext = {
117
+ gridCells: grid.cells,
118
+ shapes: semanticBoxes,
119
+ registry: registry,
120
+ }
121
+
122
+ // Parse semantics using SemanticParser
123
+ SemanticParser.parse(context)
124
+ }
125
+
126
+ // ============================================================================
127
+ // Interaction DSL Parser (Optional)
128
+ // ============================================================================
129
+
130
+ /**
131
+ * Parse interaction DSL and return interaction definitions.
132
+ *
133
+ * @param dsl Interaction DSL string
134
+ * @returns Result containing interactions or errors
135
+ *
136
+ * Requirements: Interaction DSL Parser
137
+ */
138
+ let parseInteractionsDSL = (
139
+ dsl: string,
140
+ ): result<array<Types.sceneInteractions>, array<ErrorTypes.t>> => {
141
+ // Parse interactions using InteractionParser
142
+ switch InteractionParser.parse(dsl) {
143
+ | Ok(interactions) => Ok(interactions)
144
+ | Error({message, position}) => {
145
+ // Convert InteractionParser error to ErrorTypes.t
146
+ let error = ErrorTypes.make(
147
+ InvalidInteractionDSL({
148
+ message: message,
149
+ position: position,
150
+ }),
151
+ None,
152
+ )
153
+ Error([error])
154
+ }
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Merge interaction definitions into the AST.
160
+ *
161
+ * Matches interactions to elements by ID and attaches properties and actions.
162
+ *
163
+ * @param ast Base AST from semantic parsing
164
+ * @param sceneInteractions Interaction definitions grouped by scene
165
+ * @returns AST with interactions merged in
166
+ *
167
+ * Requirements: Integration
168
+ */
169
+ let mergeInteractionsIntoAST = (
170
+ ast: Types.ast,
171
+ sceneInteractions: array<Types.sceneInteractions>,
172
+ ): Types.ast => {
173
+ // Merge interactions using InteractionMerger
174
+ switch InteractionMerger.mergeInteractions(ast, sceneInteractions) {
175
+ | Ok(mergedAst) => mergedAst
176
+ | Error(_errors) => {
177
+ // If merging fails, return AST without interactions
178
+ // Errors are silent - this maintains backward compatibility
179
+ ast
180
+ }
181
+ }
182
+ }
183
+
184
+ // ============================================================================
185
+ // Main Public API Functions
186
+ // ============================================================================
187
+
188
+ /**
189
+ * Parse a single scene block through the 3-stage pipeline.
190
+ *
191
+ * @param sceneContent ASCII wireframe content for one scene (without directives)
192
+ * @param sceneMetadata Scene metadata from directives
193
+ * @param errors Accumulator for errors
194
+ * @returns Parsed scene or None if parsing failed
195
+ */
196
+ let parseSingleScene = (
197
+ sceneContent: string,
198
+ sceneMetadata: SemanticParser.sceneMetadata,
199
+ errors: array<ErrorTypes.t>,
200
+ ): option<Types.scene> => {
201
+ // Stage 1: Grid Scanner
202
+ let gridResult = scanGrid(sceneContent)
203
+
204
+ switch gridResult {
205
+ | Error(gridErrors) => {
206
+ gridErrors->Array.forEach(err => errors->Array.push(err)->ignore)
207
+ None
208
+ }
209
+ | Ok(grid) => {
210
+ // Stage 2: Shape Detector
211
+ let shapesResult = detectShapes(grid)
212
+
213
+ let shapes = switch shapesResult {
214
+ | Error(shapeErrors) => {
215
+ shapeErrors->Array.forEach(err => errors->Array.push(err)->ignore)
216
+ []
217
+ }
218
+ | Ok(shapes) => shapes
219
+ }
220
+
221
+ // Stage 3: Parse box content into elements
222
+ let registry = ParserRegistry.makeDefault()
223
+ let semanticBoxes = shapes->Array.map(convertBox)
224
+
225
+ // Parse each box recursively
226
+ // parseBoxRecursive returns a Box element with children inside
227
+ // Strategy:
228
+ // - Named boxes (e.g., "Login", "Level1") are semantically meaningful → keep them
229
+ // - Unnamed boxes are just visual containers → flatten to children only
230
+ // This prevents duplication: children are only inside the Box, NOT duplicated at scene level
231
+ let elements = semanticBoxes->Array.flatMap(box => {
232
+ let boxElement = SemanticParser.parseBoxRecursive(box, grid.cells, registry)
233
+ switch boxElement {
234
+ | Box({name: Some(_), _}) as namedBox => [namedBox] // Keep named boxes
235
+ | Box({name: None, children, _}) => children // Flatten unnamed boxes
236
+ | elem => [elem]
237
+ }
238
+ })
239
+
240
+ // Build scene from metadata and elements
241
+ Some({
242
+ id: sceneMetadata.id,
243
+ title: sceneMetadata.title,
244
+ transition: sceneMetadata.transition,
245
+ device: sceneMetadata.device,
246
+ elements: elements,
247
+ })
248
+ }
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Internal parsing function - parses wireframe and optional interactions separately.
254
+ *
255
+ * This executes the complete 3-stage pipeline for each scene:
256
+ * 1. Split wireframe by scene separators ("---")
257
+ * 2. For each scene:
258
+ * a. Parse scene directives (@scene, @title, @transition)
259
+ * b. Grid Scanner - converts ASCII to 2D grid
260
+ * c. Shape Detector - identifies boxes and hierarchy
261
+ * d. Semantic Parser - recognizes elements and builds scene
262
+ * 3. Combine all scenes into AST
263
+ *
264
+ * Optionally parses and merges interaction DSL if provided.
265
+ *
266
+ * Error Handling:
267
+ * - Collects errors from all stages
268
+ * - Returns all errors together (no early stopping)
269
+ * - Continues parsing even after non-fatal errors
270
+ *
271
+ * @param wireframe ASCII wireframe string (may contain multiple scenes)
272
+ * @param interactions Optional interaction DSL string
273
+ * @returns Result containing AST or array of parse errors
274
+ */
275
+ let parseInternal = (wireframe: string, interactions: option<string>): parseResult => {
276
+ // Accumulator for all errors across stages
277
+ let allErrors = []
278
+
279
+ // Split wireframe into scene blocks
280
+ let sceneBlocks = SemanticParser.splitSceneBlocks(wireframe)
281
+
282
+ // Check if wireframe is empty
283
+ let trimmed = wireframe->String.trim
284
+ if sceneBlocks->Array.length === 0 && trimmed === "" {
285
+ // Empty wireframe - return empty AST
286
+ Ok({scenes: []})
287
+ } else {
288
+ // Parse each scene block
289
+ let scenes = []
290
+
291
+ sceneBlocks->Array.forEach(block => {
292
+ // Parse scene directives
293
+ let lines = block->String.split("\n")
294
+ let (metadata, contentLines) = SemanticParser.parseSceneDirectives(lines)
295
+
296
+ // Rejoin content lines (without directives)
297
+ let sceneContent = contentLines->Array.join("\n")
298
+
299
+ // Parse this scene through 3-stage pipeline
300
+ switch parseSingleScene(sceneContent, metadata, allErrors) {
301
+ | Some(scene) => scenes->Array.push(scene)->ignore
302
+ | None => () // Scene parsing failed, errors already collected
303
+ }
304
+ })
305
+
306
+ // Build base AST from scenes
307
+ let baseAst: Types.ast = {scenes: scenes}
308
+
309
+ // Optional: Parse and merge interactions
310
+ let finalAst = switch interactions {
311
+ | None => baseAst
312
+ | Some(dsl) => {
313
+ let interactionsResult = parseInteractionsDSL(dsl)
314
+
315
+ switch interactionsResult {
316
+ | Error(errors) => {
317
+ errors->Array.forEach(err => allErrors->Array.push(err)->ignore)
318
+ baseAst // Return AST without interactions on error
319
+ }
320
+ | Ok(sceneInteractions) => {
321
+ // Merge interactions into AST
322
+ mergeInteractionsIntoAST(baseAst, sceneInteractions)
323
+ }
324
+ }
325
+ }
326
+ }
327
+
328
+ // Return final result
329
+ // Return Error if all boxes failed to parse and we have errors
330
+ // Return Ok if at least some elements were parsed successfully
331
+ let totalElements = finalAst.scenes->Array.reduce(0, (acc, scene) => {
332
+ acc + Array.length(scene.elements)
333
+ })
334
+
335
+ if Array.length(allErrors) > 0 && totalElements === 0 {
336
+ // No elements parsed and we have errors - return error
337
+ Error(allErrors)
338
+ } else {
339
+ // Either no errors, or some elements were parsed - return Ok
340
+ Ok(finalAst)
341
+ }
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Main parsing function - parses mixed text containing wireframe and interactions.
347
+ *
348
+ * This function accepts a single text input that can contain:
349
+ * - ASCII wireframe (boxes with +---+, | |, etc.)
350
+ * - Interaction DSL (#id:, [Button]:, "Link": with properties)
351
+ * - Markdown, comments, or other noise (automatically ignored)
352
+ *
353
+ * The parser intelligently extracts wireframe and interaction content,
354
+ * allowing you to write everything in one place.
355
+ *
356
+ * Example:
357
+ * ```
358
+ * @scene: login
359
+ *
360
+ * +---------------------------+
361
+ * | 'WYREFRAME' |
362
+ * | +---------------------+ |
363
+ * | | #email | |
364
+ * | +---------------------+ |
365
+ * | [ Login ] |
366
+ * +---------------------------+
367
+ *
368
+ * #email:
369
+ * placeholder: "Enter your email"
370
+ *
371
+ * [Login]:
372
+ * @click -> goto(dashboard)
373
+ * ```
374
+ *
375
+ * @param text Mixed text containing wireframe and/or interactions
376
+ * @returns Result containing AST or array of parse errors
377
+ *
378
+ * REQ-20: Backward Compatibility
379
+ * REQ-21: Public API Stability
380
+ * REQ-28: Error Recovery (collect all errors)
381
+ */
382
+ @genType
383
+ let parse = (text: string): parseResult => {
384
+ // Extract wireframe and interactions from mixed content
385
+ let extracted = TextExtractor.extract(text)
386
+
387
+ // Parse with extracted content
388
+ let interactions = if extracted.interactions->String.trim === "" {
389
+ None
390
+ } else {
391
+ Some(extracted.interactions)
392
+ }
393
+
394
+ parseInternal(extracted.wireframe, interactions)
395
+ }
396
+
397
+ /**
398
+ * Parse only the wireframe structure (no interactions).
399
+ * Convenience function that calls parseInternal(wireframe, None).
400
+ *
401
+ * @param wireframe ASCII wireframe string
402
+ * @returns Result containing AST or array of parse errors
403
+ *
404
+ * REQ-21: Public API Stability
405
+ */
406
+ @genType
407
+ let parseWireframe = (wireframe: string): parseResult => {
408
+ parseInternal(wireframe, None)
409
+ }
410
+
411
+ /**
412
+ * Parse only the interaction DSL.
413
+ * Useful when interactions are defined separately from the wireframe structure.
414
+ *
415
+ * @param dsl Interaction DSL string
416
+ * @returns Result containing scene interactions or array of parse errors
417
+ *
418
+ * REQ-21: Public API Stability
419
+ */
420
+ @genType
421
+ let parseInteractions = (dsl: string): interactionResult => {
422
+ parseInteractionsDSL(dsl)
423
+ }
424
+
425
+ /**
426
+ * Merge interaction definitions into an existing AST.
427
+ * Attaches properties and actions to elements based on element IDs.
428
+ *
429
+ * @param ast Base AST from wireframe parsing
430
+ * @param sceneInteractions Array of scene interactions to merge
431
+ * @returns AST with interactions merged into elements
432
+ *
433
+ * Integration requirement
434
+ */
435
+ @genType
436
+ let mergeInteractions = (
437
+ ast: Types.ast,
438
+ _sceneInteractions: array<Types.sceneInteractions>,
439
+ ): Types.ast => {
440
+ // TODO: Implement AST merger
441
+ // Match interactions to elements by ID
442
+ // Validate all element IDs exist
443
+ // Attach properties and actions
444
+
445
+ // Placeholder - return unchanged AST
446
+ ast
447
+ }
448
+
449
+ // ============================================================================
450
+ // Helper Functions (Not exported to TypeScript)
451
+ // ============================================================================
452
+
453
+ /**
454
+ * Collect all errors from multiple parsing stages.
455
+ * Ensures all errors are reported, not just the first one (REQ-28).
456
+ */
457
+ let collectErrors = (
458
+ gridErrors: array<ErrorTypes.t>,
459
+ shapeErrors: array<ErrorTypes.t>,
460
+ semanticErrors: array<ErrorTypes.t>,
461
+ ): array<ErrorTypes.t> => {
462
+ [gridErrors, shapeErrors, semanticErrors]->Array.flatMap(x => x)
463
+ }
464
+
465
+ // ============================================================================
466
+ // Version Information
467
+ // ============================================================================
468
+
469
+ /**
470
+ * Parser version string.
471
+ * Exported to TypeScript for version checking and compatibility validation.
472
+ */
473
+ @genType
474
+ let version = "0.1.0"
475
+
476
+ /**
477
+ * Parser implementation identifier.
478
+ * Useful for distinguishing between legacy JavaScript and new ReScript parser.
479
+ */
480
+ @genType
481
+ let implementation = "rescript"
@@ -0,0 +1,214 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Grid from "../../Core/Grid.mjs";
4
+ import * as Types from "../../Core/Types.mjs";
5
+ import * as Core__Array from "@rescript/core/src/Core__Array.mjs";
6
+ import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
7
+
8
+ function check(condition, message) {
9
+ if (condition) {
10
+ console.log("✓ PASS: " + message);
11
+ } else {
12
+ console.error("✗ FAIL: " + message);
13
+ }
14
+ }
15
+
16
+ function assertEqual(actual, expected, message) {
17
+ if (Primitive_object.equal(actual, expected)) {
18
+ console.log("✓ PASS: " + message);
19
+ } else {
20
+ console.error("✗ FAIL: " + message);
21
+ }
22
+ }
23
+
24
+ console.log("\n=== Grid Construction Tests ===\n");
25
+
26
+ let lines1 = [
27
+ "abc",
28
+ "def",
29
+ "ghi"
30
+ ];
31
+
32
+ let grid1 = Grid.fromLines(lines1);
33
+
34
+ assertEqual(grid1.width, 3, "Grid width for uniform lines");
35
+
36
+ assertEqual(grid1.height, 3, "Grid height for uniform lines");
37
+
38
+ let lines2 = [
39
+ "abc",
40
+ "de",
41
+ "f"
42
+ ];
43
+
44
+ let grid2 = Grid.fromLines(lines2);
45
+
46
+ assertEqual(grid2.width, 3, "Grid width after normalization");
47
+
48
+ assertEqual(grid2.height, 3, "Grid height after normalization");
49
+
50
+ let lines3 = [
51
+ "+--+",
52
+ "| |",
53
+ "+--+"
54
+ ];
55
+
56
+ let grid3 = Grid.fromLines(lines3);
57
+
58
+ assertEqual(grid3.cornerIndex.length, 4, "Corner index count");
59
+
60
+ assertEqual(grid3.hLineIndex.length, 4, "HLine index count");
61
+
62
+ assertEqual(grid3.vLineIndex.length, 2, "VLine index count");
63
+
64
+ console.log("\n=== Character Access Tests ===\n");
65
+
66
+ let match = Grid.get(grid3, Types.Position.make(0, 0));
67
+
68
+ if (match === "Corner" && typeof match !== "object") {
69
+ console.log("✓ PASS: Get character at (0,0) returns Corner");
70
+ } else {
71
+ console.error("✗ FAIL: Get character at (0,0) should return Corner");
72
+ }
73
+
74
+ let match$1 = Grid.get(grid3, Types.Position.make(10, 10));
75
+
76
+ if (match$1 !== undefined) {
77
+ console.error("✗ FAIL: Get character out of bounds should return None");
78
+ } else {
79
+ console.log("✓ PASS: Get character out of bounds returns None");
80
+ }
81
+
82
+ let line = Grid.getLine(grid3, 0);
83
+
84
+ if (line !== undefined) {
85
+ assertEqual(line.length, 4, "Get line returns correct length");
86
+ } else {
87
+ console.error("✗ FAIL: Get line should return Some");
88
+ }
89
+
90
+ console.log("\n=== Directional Scanning Tests ===\n");
91
+
92
+ let scanResults = Grid.scanRight(grid3, Types.Position.make(0, 0), cell => {
93
+ if (typeof cell === "object") {
94
+ return false;
95
+ }
96
+ switch (cell) {
97
+ case "Corner" :
98
+ case "HLine" :
99
+ return true;
100
+ default:
101
+ return false;
102
+ }
103
+ });
104
+
105
+ assertEqual(scanResults.length, 4, "Scan right collects correct number of characters");
106
+
107
+ let scanDownResults = Grid.scanDown(grid3, Types.Position.make(0, 0), cell => {
108
+ if (typeof cell === "object") {
109
+ return false;
110
+ }
111
+ switch (cell) {
112
+ case "Corner" :
113
+ case "VLine" :
114
+ return true;
115
+ default:
116
+ return false;
117
+ }
118
+ });
119
+
120
+ assertEqual(scanDownResults.length, 3, "Scan down collects correct number of characters");
121
+
122
+ console.log("\n=== Search Operations Tests ===\n");
123
+
124
+ let allCorners = Grid.findAll(grid3, "Corner");
125
+
126
+ assertEqual(allCorners.length, 4, "Find all corners returns 4 positions");
127
+
128
+ let bounds = Types.Bounds.make(0, 0, 1, 2);
129
+
130
+ let cornersInRange = Grid.findInRange(grid3, "Corner", bounds);
131
+
132
+ assertEqual(cornersInRange.length, 1, "Find in range returns corners within bounds");
133
+
134
+ console.log("\n=== Performance Tests ===\n");
135
+
136
+ let largeLines = Core__Array.make(1000, "+".repeat(100));
137
+
138
+ let startTime = Date.now();
139
+
140
+ let largeGrid = Grid.fromLines(largeLines);
141
+
142
+ let endTime = Date.now();
143
+
144
+ let duration = endTime - startTime;
145
+
146
+ assertEqual(largeGrid.height, 1000, "Large grid has correct height");
147
+
148
+ assertEqual(largeGrid.width, 100, "Large grid has correct width");
149
+
150
+ check(duration < 10.0, `Grid construction <10ms (actual: ` + duration.toString() + `ms)`);
151
+
152
+ let findStartTime = Date.now();
153
+
154
+ let _corners = Grid.findAll(largeGrid, "Corner");
155
+
156
+ let findEndTime = Date.now();
157
+
158
+ let findDuration = findEndTime - findStartTime;
159
+
160
+ check(findDuration < 5.0, `Finding all corners using index <5ms (actual: ` + findDuration.toString() + `ms)`);
161
+
162
+ console.log("\n=== Edge Cases Tests ===\n");
163
+
164
+ let singleCharLines = ["+"];
165
+
166
+ let singleCharGrid = Grid.fromLines(singleCharLines);
167
+
168
+ assertEqual(singleCharGrid.width, 1, "Single character grid width");
169
+
170
+ assertEqual(singleCharGrid.height, 1, "Single character grid height");
171
+
172
+ let emptyLines = [
173
+ "",
174
+ "",
175
+ ""
176
+ ];
177
+
178
+ let emptyGrid = Grid.fromLines(emptyLines);
179
+
180
+ assertEqual(emptyGrid.width, 0, "Empty lines grid width is 0");
181
+
182
+ assertEqual(emptyGrid.height, 3, "Empty lines grid height is 3");
183
+
184
+ console.log("\n=== All Tests Complete ===\n");
185
+
186
+ export {
187
+ check,
188
+ assertEqual,
189
+ lines1,
190
+ grid1,
191
+ lines2,
192
+ grid2,
193
+ lines3,
194
+ grid3,
195
+ scanResults,
196
+ scanDownResults,
197
+ allCorners,
198
+ bounds,
199
+ cornersInRange,
200
+ largeLines,
201
+ startTime,
202
+ largeGrid,
203
+ endTime,
204
+ duration,
205
+ findStartTime,
206
+ _corners,
207
+ findEndTime,
208
+ findDuration,
209
+ singleCharLines,
210
+ singleCharGrid,
211
+ emptyLines,
212
+ emptyGrid,
213
+ }
214
+ /* Not a pure module */