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,158 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Types from "../Core/Types.mjs";
4
+ import * as ErrorTypes from "../Errors/ErrorTypes.mjs";
5
+ import * as Belt_Option from "@rescript/runtime/lib/es6/Belt_Option.js";
6
+
7
+ function contains(outer, inner) {
8
+ return Types.Bounds.contains(outer, inner);
9
+ }
10
+
11
+ function findParent(target, candidates) {
12
+ let containers = candidates.filter(candidate => {
13
+ if (candidate !== target) {
14
+ return Types.Bounds.contains(candidate.bounds, target.bounds);
15
+ } else {
16
+ return false;
17
+ }
18
+ });
19
+ if (containers.length === 0) {
20
+ return;
21
+ }
22
+ let sorted = containers.toSorted((a, b) => {
23
+ let areaA = Types.Bounds.area(a.bounds);
24
+ let areaB = Types.Bounds.area(b.bounds);
25
+ return areaA - areaB | 0;
26
+ });
27
+ return sorted[0];
28
+ }
29
+
30
+ function buildHierarchy(boxes) {
31
+ let overlappingPair = boxes.find(box1 => boxes.some(box2 => {
32
+ if (box1 !== box2 && !Types.Bounds.contains(box1.bounds, box2.bounds) && !Types.Bounds.contains(box2.bounds, box1.bounds)) {
33
+ return Types.Bounds.overlaps(box1.bounds, box2.bounds);
34
+ } else {
35
+ return false;
36
+ }
37
+ }));
38
+ if (overlappingPair !== undefined) {
39
+ let box2 = Belt_Option.getExn(boxes.find(box2 => {
40
+ if (overlappingPair !== box2 && !Types.Bounds.contains(overlappingPair.bounds, box2.bounds) && !Types.Bounds.contains(box2.bounds, overlappingPair.bounds)) {
41
+ return Types.Bounds.overlaps(overlappingPair.bounds, box2.bounds);
42
+ } else {
43
+ return false;
44
+ }
45
+ }));
46
+ return {
47
+ TAG: "Error",
48
+ _0: {
49
+ TAG: "OverlappingBoxes",
50
+ box1: overlappingPair,
51
+ box2: box2
52
+ }
53
+ };
54
+ }
55
+ let sorted = boxes.toSorted((a, b) => {
56
+ let areaA = Types.Bounds.area(a.bounds);
57
+ let areaB = Types.Bounds.area(b.bounds);
58
+ return areaB - areaA | 0;
59
+ });
60
+ sorted.forEach(box => {
61
+ let parent = findParent(box, sorted);
62
+ if (parent !== undefined) {
63
+ parent.children.push(box);
64
+ return;
65
+ }
66
+ });
67
+ let roots = sorted.filter(box => !sorted.some(candidate => {
68
+ if (candidate !== box) {
69
+ return candidate.children.includes(box);
70
+ } else {
71
+ return false;
72
+ }
73
+ }));
74
+ return {
75
+ TAG: "Ok",
76
+ _0: roots
77
+ };
78
+ }
79
+
80
+ function getDepth(box, allBoxes) {
81
+ let parent = findParent(box, allBoxes);
82
+ if (parent !== undefined) {
83
+ return 1 + getDepth(parent, allBoxes) | 0;
84
+ } else {
85
+ return 0;
86
+ }
87
+ }
88
+
89
+ function collectDeepNestingWarnings(box, currentDepth, thresholdOpt) {
90
+ let threshold = thresholdOpt !== undefined ? thresholdOpt : 4;
91
+ let warnings = [];
92
+ if (currentDepth > threshold) {
93
+ let warningCode_1 = {
94
+ row: box.bounds.top,
95
+ col: box.bounds.left
96
+ };
97
+ let warningCode = {
98
+ TAG: "DeepNesting",
99
+ depth: currentDepth,
100
+ position: warningCode_1
101
+ };
102
+ let warning = ErrorTypes.make(warningCode, undefined);
103
+ warnings.push(warning);
104
+ }
105
+ box.children.forEach(child => {
106
+ let childWarnings = collectDeepNestingWarnings(child, currentDepth + 1 | 0, threshold);
107
+ childWarnings.forEach(w => {
108
+ warnings.push(w);
109
+ });
110
+ });
111
+ return warnings;
112
+ }
113
+
114
+ function detectDeepNesting(roots, thresholdOpt) {
115
+ let threshold = thresholdOpt !== undefined ? thresholdOpt : 4;
116
+ let allWarnings = [];
117
+ roots.forEach(root => {
118
+ let warnings = collectDeepNestingWarnings(root, 0, threshold);
119
+ warnings.forEach(w => {
120
+ allWarnings.push(w);
121
+ });
122
+ });
123
+ return allWarnings;
124
+ }
125
+
126
+ function getMaxDepth(roots) {
127
+ let maxDepth = {
128
+ contents: 0
129
+ };
130
+ let traverse = (box, depth) => {
131
+ if (depth > maxDepth.contents) {
132
+ maxDepth.contents = depth;
133
+ }
134
+ box.children.forEach(child => traverse(child, depth + 1 | 0));
135
+ };
136
+ roots.forEach(root => traverse(root, 0));
137
+ return maxDepth.contents;
138
+ }
139
+
140
+ function makeBox(name, bounds) {
141
+ return {
142
+ name: name,
143
+ bounds: bounds,
144
+ children: []
145
+ };
146
+ }
147
+
148
+ export {
149
+ contains,
150
+ findParent,
151
+ buildHierarchy,
152
+ getDepth,
153
+ collectDeepNestingWarnings,
154
+ detectDeepNesting,
155
+ getMaxDepth,
156
+ makeBox,
157
+ }
158
+ /* Types Not a pure module */
@@ -0,0 +1,315 @@
1
+ // HierarchyBuilder.res
2
+ // Build hierarchical parent-child relationships between boxes based on spatial containment
3
+ // Requirements: REQ-6 (Shape Detector - Nesting Hierarchy Construction)
4
+
5
+ open Types
6
+
7
+ // Re-use the box type from BoxTracer
8
+ type box = BoxTracer.box
9
+
10
+ // Hierarchy building errors
11
+ type hierarchyError =
12
+ | OverlappingBoxes({
13
+ box1: box,
14
+ box2: box,
15
+ })
16
+ | CircularNesting
17
+
18
+ // ============================================================================
19
+ // Containment Detection
20
+ // ============================================================================
21
+
22
+ /**
23
+ * Check if outer box completely contains inner box.
24
+ * Uses the Bounds.contains function for the actual containment check.
25
+ *
26
+ * A box is considered to contain another if its bounds completely enclose
27
+ * the other box's bounds (strict containment, not touching edges).
28
+ *
29
+ * @param outer - The potentially containing box bounds
30
+ * @param inner - The potentially contained box bounds
31
+ * @return bool - true if outer completely contains inner
32
+ *
33
+ * Requirements: REQ-6 (Nesting Hierarchy Construction)
34
+ */
35
+ let contains = (outer: Bounds.t, inner: Bounds.t): bool => {
36
+ Bounds.contains(outer, inner)
37
+ }
38
+
39
+ // ============================================================================
40
+ // Parent-Child Relationships
41
+ // ============================================================================
42
+
43
+ /**
44
+ * Find the smallest box that completely contains the given box.
45
+ * This identifies the immediate parent in the hierarchy.
46
+ *
47
+ * Algorithm:
48
+ * 1. Filter candidates to only boxes that contain the target
49
+ * 2. Among containing boxes, find the one with the smallest area
50
+ * 3. Return None if no containing box exists (box is a root)
51
+ *
52
+ * @param target - The box to find a parent for
53
+ * @param candidates - Array of potential parent boxes
54
+ * @return option<box> - Some(parent) if found, None if box is a root
55
+ *
56
+ * Requirements: REQ-6 (Nesting Hierarchy Construction)
57
+ */
58
+ let findParent = (target: box, candidates: array<box>): option<box> => {
59
+ // Find all boxes that contain this box
60
+ let containers =
61
+ candidates->Array.filter(candidate => {
62
+ // Don't compare with self
63
+ candidate !== target && contains(candidate.bounds, target.bounds)
64
+ })
65
+
66
+ // If no containers, this is a root box
67
+ if Array.length(containers) === 0 {
68
+ None
69
+ } else {
70
+ // Find the smallest container (immediate parent)
71
+ // Sort by area ascending and take the first
72
+ let sorted =
73
+ containers->Array.toSorted((a, b) => {
74
+ let areaA = Bounds.area(a.bounds)
75
+ let areaB = Bounds.area(b.bounds)
76
+ Int.toFloat(areaA - areaB) // Ascending - smallest first
77
+ })
78
+
79
+ Array.get(sorted, 0)
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Build parent-child hierarchy from a flat array of boxes.
85
+ *
86
+ * Algorithm:
87
+ * 1. Sort boxes by area (descending) - larger boxes processed first
88
+ * 2. For each box, find its immediate parent (smallest containing box)
89
+ * 3. Populate children arrays by adding child to parent's children
90
+ * 4. Validate no invalid overlaps exist (boxes must be nested or disjoint)
91
+ * 5. Return only root-level boxes (boxes with no parent)
92
+ *
93
+ * This function mutates the children arrays of the boxes to build the hierarchy.
94
+ *
95
+ * @param boxes - Flat array of all boxes detected
96
+ * @return result<array<box>, hierarchyError> - Ok(roots) or Error(overlap)
97
+ *
98
+ * Example hierarchy:
99
+ * Root1
100
+ * ├── Child1-1
101
+ * │ └── Child1-1-1
102
+ * └── Child1-2
103
+ * Root2
104
+ * └── Child2-1
105
+ *
106
+ * Requirements: REQ-6 (Nesting Hierarchy Construction)
107
+ */
108
+ let buildHierarchy = (boxes: array<box>): result<array<box>, hierarchyError> => {
109
+ // Step 1: Check for invalid overlapping boxes first (before building hierarchy)
110
+ // Boxes are invalid if they overlap but neither contains the other
111
+ let overlappingPair = boxes->Array.find(box1 => {
112
+ boxes->Array.some(box2 => {
113
+ // Check if boxes are different
114
+ box1 !== box2 &&
115
+ // Not nested (neither contains the other)
116
+ !contains(box1.bounds, box2.bounds) &&
117
+ !contains(box2.bounds, box1.bounds) &&
118
+ // But they overlap
119
+ Bounds.overlaps(box1.bounds, box2.bounds)
120
+ })
121
+ })
122
+
123
+ switch overlappingPair {
124
+ | Some(box1) => {
125
+ // Find the specific box2 that overlaps with box1
126
+ let box2 =
127
+ boxes
128
+ ->Array.find(box2 => {
129
+ box1 !== box2 &&
130
+ !contains(box1.bounds, box2.bounds) &&
131
+ !contains(box2.bounds, box1.bounds) &&
132
+ Bounds.overlaps(box1.bounds, box2.bounds)
133
+ })
134
+ ->Belt.Option.getExn // Safe because we just found an overlapping pair
135
+
136
+ Error(OverlappingBoxes({box1, box2}))
137
+ }
138
+ | None => {
139
+ // Step 2: Sort by area (descending) for efficient parent finding
140
+ // Larger boxes are processed first as they are potential parents
141
+ let sorted =
142
+ boxes->Array.toSorted((a, b) => {
143
+ let areaA = Bounds.area(a.bounds)
144
+ let areaB = Bounds.area(b.bounds)
145
+ Int.toFloat(areaB - areaA) // Descending - largest first
146
+ })
147
+
148
+ // Step 3: Build parent-child relationships
149
+ sorted->Array.forEach(box => {
150
+ // Find immediate parent for this box
151
+ switch findParent(box, sorted) {
152
+ | Some(parent) => {
153
+ // Add this box to parent's children array (mutation)
154
+ parent.children->Array.push(box)->ignore
155
+ }
156
+ | None => () // No parent - this is a root box
157
+ }
158
+ })
159
+
160
+ // Step 4: Return only root boxes (boxes with no parent)
161
+ // A root box is one that is not in any other box's children array
162
+ let roots = sorted->Array.filter(box => {
163
+ // Check if this box is in any other box's children
164
+ let isChild = sorted->Array.some(candidate => {
165
+ candidate !== box && candidate.children->Array.includes(box)
166
+ })
167
+ !isChild
168
+ })
169
+
170
+ Ok(roots)
171
+ }
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Get the depth of nesting for a box (0 for root, 1 for first level child, etc.)
177
+ * Useful for validation and testing.
178
+ *
179
+ * @param box - The box to calculate depth for
180
+ * @param allBoxes - All boxes in the hierarchy (to find parent)
181
+ * @return int - Nesting depth
182
+ */
183
+ let rec getDepth = (box: box, allBoxes: array<box>): int => {
184
+ // Find this box's parent
185
+ switch findParent(box, allBoxes) {
186
+ | None => 0 // Root box
187
+ | Some(parent) => 1 + getDepth(parent, allBoxes)
188
+ }
189
+ }
190
+
191
+ // ============================================================================
192
+ // Deep Nesting Warning Detection
193
+ // ============================================================================
194
+
195
+ /**
196
+ * Recursively collect deep nesting warnings for boxes exceeding depth threshold.
197
+ *
198
+ * This function traverses the box hierarchy and generates warnings for any box
199
+ * that is nested deeper than the specified threshold (default: 4 levels).
200
+ *
201
+ * Algorithm:
202
+ * 1. Check if current box depth exceeds threshold
203
+ * 2. If yes, create a DeepNesting warning with depth and position
204
+ * 3. Recursively check all children with incremented depth
205
+ * 4. Collect and return all warnings found
206
+ *
207
+ * @param box - The box to check (and its children)
208
+ * @param currentDepth - Current nesting depth (0 for root)
209
+ * @param threshold - Maximum allowed depth before warning (default: 4)
210
+ * @return array<ErrorTypes.t> - Array of deep nesting warnings
211
+ *
212
+ * Example:
213
+ * Root (depth 0) - OK
214
+ * Child1 (depth 1) - OK
215
+ * Child1-1 (depth 2) - OK
216
+ * Child1-1-1 (depth 3) - OK
217
+ * Child1-1-1-1 (depth 4) - OK
218
+ * Child1-1-1-1-1 (depth 5) - WARNING!
219
+ *
220
+ * Requirements: REQ-19 (Deep Nesting Warning)
221
+ */
222
+ let rec collectDeepNestingWarnings = (
223
+ box: box,
224
+ currentDepth: int,
225
+ ~threshold: int=4,
226
+ ): array<ErrorTypes.t> => {
227
+ let warnings = []
228
+
229
+ // Check if current box exceeds threshold
230
+ if currentDepth > threshold {
231
+ // Create warning with depth and position information
232
+ let warningCode = ErrorTypes.DeepNesting({
233
+ depth: currentDepth,
234
+ position: {
235
+ row: box.bounds.top,
236
+ col: box.bounds.left,
237
+ },
238
+ })
239
+
240
+ // Create ParseError from warning code (no context needed for warnings)
241
+ let warning = ErrorTypes.make(warningCode, None)
242
+ warnings->Array.push(warning)->ignore
243
+ }
244
+
245
+ // Recursively check all children with incremented depth
246
+ box.children->Array.forEach(child => {
247
+ let childWarnings = collectDeepNestingWarnings(child, currentDepth + 1, ~threshold)
248
+ childWarnings->Array.forEach(w => warnings->Array.push(w)->ignore)
249
+ })
250
+
251
+ warnings
252
+ }
253
+
254
+ /**
255
+ * Scan entire hierarchy and collect all deep nesting warnings.
256
+ *
257
+ * This is the main entry point for deep nesting detection. It processes
258
+ * all root boxes and their entire subtrees.
259
+ *
260
+ * @param roots - Array of root boxes (from buildHierarchy)
261
+ * @param threshold - Maximum allowed depth before warning (default: 4)
262
+ * @return array<ErrorTypes.t> - All deep nesting warnings found
263
+ *
264
+ * Requirements: REQ-19 (Deep Nesting Warning)
265
+ */
266
+ let detectDeepNesting = (roots: array<box>, ~threshold: int=4): array<ErrorTypes.t> => {
267
+ let allWarnings = []
268
+
269
+ // Process each root tree
270
+ roots->Array.forEach(root => {
271
+ // Root boxes start at depth 0
272
+ let warnings = collectDeepNestingWarnings(root, 0, ~threshold)
273
+ warnings->Array.forEach(w => allWarnings->Array.push(w)->ignore)
274
+ })
275
+
276
+ allWarnings
277
+ }
278
+
279
+ /**
280
+ * Get maximum nesting depth in the entire hierarchy.
281
+ * Useful for metrics, validation, and testing.
282
+ *
283
+ * @param roots - Array of root boxes
284
+ * @return int - Maximum depth found (0 if no boxes)
285
+ */
286
+ let getMaxDepth = (roots: array<box>): int => {
287
+ let maxDepth = ref(0)
288
+
289
+ let rec traverse = (box: box, depth: int) => {
290
+ if depth > maxDepth.contents {
291
+ maxDepth := depth
292
+ }
293
+
294
+ box.children->Array.forEach(child => traverse(child, depth + 1))
295
+ }
296
+
297
+ roots->Array.forEach(root => traverse(root, 0))
298
+
299
+ maxDepth.contents
300
+ }
301
+
302
+ /**
303
+ * Create a box for testing purposes.
304
+ *
305
+ * @param name - Optional box name
306
+ * @param bounds - Bounding box coordinates
307
+ * @return box - New box instance with empty children array
308
+ */
309
+ let makeBox = (~name: option<string>=?, bounds: Bounds.t): box => {
310
+ {
311
+ name: name,
312
+ bounds: bounds,
313
+ children: [],
314
+ }
315
+ }
@@ -0,0 +1,134 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Types from "../Core/Types.mjs";
4
+ import * as BoxTracer from "./BoxTracer.mjs";
5
+ import * as ErrorTypes from "../Errors/ErrorTypes.mjs";
6
+ import * as Core__Array from "@rescript/core/src/Core__Array.mjs";
7
+ import * as HierarchyBuilder from "./HierarchyBuilder.mjs";
8
+
9
+ function hierarchyErrorToParseError(error) {
10
+ if (typeof error !== "object") {
11
+ return ErrorTypes.make({
12
+ TAG: "InvalidElement",
13
+ content: "Circular nesting detected in box hierarchy",
14
+ position: {
15
+ row: 0,
16
+ col: 0
17
+ }
18
+ }, undefined);
19
+ }
20
+ let box2 = error.box2;
21
+ let box1 = error.box1;
22
+ return ErrorTypes.make({
23
+ TAG: "InvalidElement",
24
+ content: `Overlapping boxes detected at (` + box1.bounds.top.toString() + `,` + box1.bounds.left.toString() + `) and (` + box2.bounds.top.toString() + `,` + box2.bounds.left.toString() + `)`,
25
+ position: {
26
+ row: box1.bounds.top,
27
+ col: box1.bounds.left
28
+ }
29
+ }, undefined);
30
+ }
31
+
32
+ function deduplicateBoxes(boxes) {
33
+ let seen = [];
34
+ let unique = [];
35
+ boxes.forEach(box => {
36
+ let isDuplicate = seen.some(seenBounds => Types.Bounds.equals(seenBounds, box.bounds));
37
+ if (!isDuplicate) {
38
+ unique.push(box);
39
+ seen.push(box.bounds);
40
+ return;
41
+ }
42
+ });
43
+ return unique;
44
+ }
45
+
46
+ function detect(grid) {
47
+ let corners = grid.cornerIndex;
48
+ let boxes = [];
49
+ let traceFailures = [];
50
+ corners.forEach(corner => {
51
+ let box = BoxTracer.traceBox(grid, corner);
52
+ if (box.TAG === "Ok") {
53
+ boxes.push(box._0);
54
+ return;
55
+ }
56
+ traceFailures.push(box._0);
57
+ });
58
+ if (boxes.length === 0) {
59
+ if (corners.length === 0) {
60
+ return {
61
+ TAG: "Ok",
62
+ _0: []
63
+ };
64
+ } else if (traceFailures.length > 0) {
65
+ return {
66
+ TAG: "Error",
67
+ _0: traceFailures
68
+ };
69
+ } else {
70
+ return {
71
+ TAG: "Ok",
72
+ _0: []
73
+ };
74
+ }
75
+ }
76
+ let uniqueBoxes = deduplicateBoxes(boxes);
77
+ let rootBoxes = HierarchyBuilder.buildHierarchy(uniqueBoxes);
78
+ if (rootBoxes.TAG === "Ok") {
79
+ return {
80
+ TAG: "Ok",
81
+ _0: rootBoxes._0
82
+ };
83
+ }
84
+ let parseError = hierarchyErrorToParseError(rootBoxes._0);
85
+ return {
86
+ TAG: "Error",
87
+ _0: [parseError]
88
+ };
89
+ }
90
+
91
+ function countBoxes(boxes) {
92
+ return Core__Array.reduce(boxes, 0, (acc, box) => (1 + acc | 0) + countBoxes(box.children) | 0);
93
+ }
94
+
95
+ function flattenBoxes(boxes) {
96
+ return Core__Array.reduce(boxes, [], (acc, box) => {
97
+ let childBoxes = flattenBoxes(box.children);
98
+ return acc.concat([box]).concat(childBoxes);
99
+ });
100
+ }
101
+
102
+ function getStats(result) {
103
+ if (result.TAG === "Ok") {
104
+ let boxes = result._0;
105
+ let rootCount = boxes.length;
106
+ let totalCount = countBoxes(boxes);
107
+ return `Shape Detection Success:
108
+ Root boxes: ` + rootCount.toString() + `
109
+ Total boxes (including nested): ` + totalCount.toString();
110
+ }
111
+ let errors = result._0;
112
+ let errorCount = errors.length;
113
+ let warningCount = Core__Array.reduce(errors, 0, (acc, err) => {
114
+ if (ErrorTypes.isWarning(err)) {
115
+ return acc + 1 | 0;
116
+ } else {
117
+ return acc;
118
+ }
119
+ });
120
+ let realErrorCount = errorCount - warningCount | 0;
121
+ return `Shape Detection Failed:
122
+ Errors: ` + realErrorCount.toString() + `
123
+ Warnings: ` + warningCount.toString();
124
+ }
125
+
126
+ export {
127
+ hierarchyErrorToParseError,
128
+ deduplicateBoxes,
129
+ detect,
130
+ countBoxes,
131
+ flattenBoxes,
132
+ getStats,
133
+ }
134
+ /* Types Not a pure module */