wyreframe 0.1.0 → 0.1.5

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 (58) hide show
  1. package/LICENSE +692 -0
  2. package/README.md +65 -5
  3. package/package.json +8 -7
  4. package/src/index.ts +425 -0
  5. package/src/renderer/Renderer.gen.tsx +49 -0
  6. package/src/renderer/Renderer.mjs +41 -1
  7. package/src/renderer/Renderer.res +78 -0
  8. package/src/parser/Core/__tests__/Bounds_test.mjs +0 -326
  9. package/src/parser/Core/__tests__/Bounds_test.res +0 -412
  10. package/src/parser/Core/__tests__/Grid_test.mjs +0 -322
  11. package/src/parser/Core/__tests__/Grid_test.res +0 -319
  12. package/src/parser/Core/__tests__/Types_test.mjs +0 -614
  13. package/src/parser/Core/__tests__/Types_test.res +0 -650
  14. package/src/parser/Detector/__tests__/BoxTracer_test.mjs +0 -70
  15. package/src/parser/Detector/__tests__/BoxTracer_test.res +0 -92
  16. package/src/parser/Detector/__tests__/HierarchyBuilder_test.mjs +0 -489
  17. package/src/parser/Detector/__tests__/HierarchyBuilder_test.res +0 -849
  18. package/src/parser/Detector/__tests__/ShapeDetector_test.mjs +0 -377
  19. package/src/parser/Detector/__tests__/ShapeDetector_test.res +0 -563
  20. package/src/parser/Interactions/__tests__/InteractionMerger_test.mjs +0 -576
  21. package/src/parser/Interactions/__tests__/InteractionMerger_test.res +0 -646
  22. package/src/parser/Scanner/__tests__/Grid_manual.mjs +0 -214
  23. package/src/parser/Scanner/__tests__/Grid_manual.res +0 -141
  24. package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.mjs +0 -189
  25. package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.res +0 -257
  26. package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.mjs +0 -202
  27. package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.res +0 -250
  28. package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.mjs +0 -293
  29. package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.res +0 -134
  30. package/src/parser/Semantic/Elements/__tests__/InputParser_test.mjs +0 -253
  31. package/src/parser/Semantic/Elements/__tests__/InputParser_test.res +0 -304
  32. package/src/parser/Semantic/Elements/__tests__/LinkParser_test.mjs +0 -289
  33. package/src/parser/Semantic/Elements/__tests__/LinkParser_test.res +0 -402
  34. package/src/parser/Semantic/Elements/__tests__/TextParser_test.mjs +0 -149
  35. package/src/parser/Semantic/Elements/__tests__/TextParser_test.res +0 -167
  36. package/src/parser/Semantic/__tests__/ASTBuilder_test.mjs +0 -187
  37. package/src/parser/Semantic/__tests__/ASTBuilder_test.res +0 -192
  38. package/src/parser/Semantic/__tests__/ParserRegistry_test.mjs +0 -154
  39. package/src/parser/Semantic/__tests__/ParserRegistry_test.res +0 -191
  40. package/src/parser/Semantic/__tests__/SemanticParser_integration_test.mjs +0 -768
  41. package/src/parser/Semantic/__tests__/SemanticParser_integration_test.res +0 -1069
  42. package/src/parser/Semantic/__tests__/SemanticParser_manual.mjs +0 -1329
  43. package/src/parser/Semantic/__tests__/SemanticParser_manual.res +0 -544
  44. package/src/parser/__tests__/GridScanner_integration.test.mjs +0 -632
  45. package/src/parser/__tests__/GridScanner_integration.test.res +0 -816
  46. package/src/parser/__tests__/Performance.test.mjs +0 -244
  47. package/src/parser/__tests__/Performance.test.res +0 -371
  48. package/src/parser/__tests__/PerformanceFixtures.mjs +0 -200
  49. package/src/parser/__tests__/PerformanceFixtures.res +0 -284
  50. package/src/parser/__tests__/WyreframeParser_integration.test.mjs +0 -770
  51. package/src/parser/__tests__/WyreframeParser_integration.test.res +0 -1008
  52. package/src/parser/__tests__/fixtures/alignment-test.txt +0 -9
  53. package/src/parser/__tests__/fixtures/all-elements.txt +0 -16
  54. package/src/parser/__tests__/fixtures/login-scene.txt +0 -17
  55. package/src/parser/__tests__/fixtures/multi-scene.txt +0 -25
  56. package/src/parser/__tests__/fixtures/nested-boxes.txt +0 -15
  57. package/src/parser/__tests__/fixtures/simple-box.txt +0 -5
  58. package/src/parser/__tests__/fixtures/with-dividers.txt +0 -14
@@ -1,200 +0,0 @@
1
- // Generated by ReScript, PLEASE EDIT WITH CARE
2
-
3
- import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
4
- import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
5
-
6
- function generateWireframe(targetLines) {
7
- let match = targetLines <= 50 ? [
8
- 1,
9
- 3,
10
- 6
11
- ] : (
12
- targetLines <= 100 ? [
13
- 2,
14
- 3,
15
- 6
16
- ] : (
17
- targetLines <= 200 ? [
18
- 2,
19
- 5,
20
- 8
21
- ] : (
22
- targetLines <= 500 ? [
23
- 3,
24
- 7,
25
- 10
26
- ] : (
27
- targetLines <= 1000 ? [
28
- 4,
29
- 10,
30
- 12
31
- ] : [
32
- 6,
33
- 12,
34
- 14
35
- ]
36
- )
37
- )
38
- )
39
- );
40
- let elementsPerBox = match[2];
41
- let boxesPerScene = match[1];
42
- let numScenes = match[0];
43
- let scenes = [];
44
- for (let sceneIdx = 0; sceneIdx < numScenes; ++sceneIdx) {
45
- let sceneName = `scene` + (sceneIdx + 1 | 0).toString();
46
- let sceneTitle = `Scene ` + (sceneIdx + 1 | 0).toString();
47
- scenes.push(`@scene: ` + sceneName);
48
- scenes.push(`@title: ` + sceneTitle);
49
- scenes.push("");
50
- for (let boxIdx = 0; boxIdx < boxesPerScene; ++boxIdx) {
51
- let boxName = `Box` + (boxIdx + 1 | 0).toString();
52
- let topBorder = `+--` + boxName + "-".repeat((40 - boxName.length | 0) - 2 | 0) + `+`;
53
- scenes.push(topBorder);
54
- scenes.push(`|` + " ".repeat(40) + `|`);
55
- for (let elemIdx = 0; elemIdx < elementsPerBox; ++elemIdx) {
56
- let elementType = elemIdx % 5;
57
- switch (elementType) {
58
- case 0 :
59
- let buttonText = `Button ` + (elemIdx + 1 | 0).toString();
60
- let button = `[ ` + buttonText + ` ]`;
61
- let padding = (40 - button.length | 0) / 2 | 0;
62
- let line = `|` + " ".repeat(padding) + button + " ".repeat((40 - padding | 0) - button.length | 0) + `|`;
63
- scenes.push(line);
64
- break;
65
- case 1 :
66
- let fieldName = `field` + (elemIdx + 1 | 0).toString();
67
- let label = `Label ` + (elemIdx + 1 | 0).toString();
68
- scenes.push(`| ` + label + " ".repeat((40 - label.length | 0) - 2 | 0) + `|`);
69
- scenes.push(`| #` + fieldName + " ".repeat((40 - fieldName.length | 0) - 3 | 0) + `|`);
70
- break;
71
- case 2 :
72
- let linkText = `Link ` + (elemIdx + 1 | 0).toString();
73
- let link = `"` + linkText + `"`;
74
- scenes.push(`| ` + link + " ".repeat((40 - link.length | 0) - 2 | 0) + `|`);
75
- break;
76
- case 3 :
77
- let checked = elemIdx % 2 === 0;
78
- let checkboxLabel = `Option ` + (elemIdx + 1 | 0).toString();
79
- let checkbox = checked ? "[x]" : "[ ]";
80
- scenes.push(`| ` + checkbox + ` ` + checkboxLabel + " ".repeat(((40 - checkbox.length | 0) - checkboxLabel.length | 0) - 3 | 0) + `|`);
81
- break;
82
- case 4 :
83
- let emphasisText = `Important ` + (elemIdx + 1 | 0).toString();
84
- scenes.push(`| * ` + emphasisText + " ".repeat((40 - emphasisText.length | 0) - 4 | 0) + `|`);
85
- break;
86
- }
87
- if (elemIdx < (elementsPerBox - 1 | 0)) {
88
- scenes.push(`|` + " ".repeat(40) + `|`);
89
- }
90
- }
91
- if (boxIdx < (boxesPerScene - 1 | 0) && boxIdx === (boxesPerScene / 2 | 0)) {
92
- scenes.push(`|` + "=".repeat(40) + `|`);
93
- }
94
- scenes.push(`|` + " ".repeat(40) + `|`);
95
- let bottomBorder = `+` + "-".repeat(40) + `+`;
96
- scenes.push(bottomBorder);
97
- if (boxIdx < (boxesPerScene - 1 | 0)) {
98
- scenes.push("");
99
- }
100
- }
101
- if (sceneIdx < (numScenes - 1 | 0)) {
102
- scenes.push("");
103
- scenes.push("---");
104
- scenes.push("");
105
- }
106
- }
107
- return scenes.join("\n");
108
- }
109
-
110
- function generateSimpleBox(widthOpt, heightOpt, nameOpt) {
111
- let width = widthOpt !== undefined ? widthOpt : 20;
112
- let height = heightOpt !== undefined ? heightOpt : 3;
113
- let name = nameOpt !== undefined ? Primitive_option.valFromOption(nameOpt) : undefined;
114
- let lines = [];
115
- let totalWidth = width + 4 | 0;
116
- let topBorder;
117
- if (name !== undefined) {
118
- let nameStr = `--` + name + `--`;
119
- let padding = (totalWidth - 2 | 0) - nameStr.length | 0;
120
- if (padding < 0) {
121
- topBorder = `+` + "-".repeat(totalWidth - 2 | 0) + `+`;
122
- } else {
123
- let leftPad = padding / 2 | 0;
124
- let rightPad = padding - leftPad | 0;
125
- topBorder = `+` + "-".repeat(leftPad) + nameStr + "-".repeat(rightPad) + `+`;
126
- }
127
- } else {
128
- topBorder = `+` + "-".repeat(totalWidth - 2 | 0) + `+`;
129
- }
130
- lines.push(topBorder);
131
- for (let _for = 0; _for < height; ++_for) {
132
- lines.push(`| ` + " ".repeat(width) + ` |`);
133
- }
134
- lines.push(`+` + "-".repeat(totalWidth - 2 | 0) + `+`);
135
- return lines.join("\n");
136
- }
137
-
138
- function generateNestedBoxes(depth) {
139
- let buildNested = (currentDepth, maxDepth) => {
140
- if (currentDepth > maxDepth) {
141
- return [];
142
- }
143
- let lines = [];
144
- let boxName = `Level` + currentDepth.toString();
145
- let boxWidth = 40 - ((currentDepth - 1 | 0) << 2) | 0;
146
- let topBorder = `+--` + boxName + "-".repeat((boxWidth - boxName.length | 0) - 2 | 0) + `+`;
147
- lines.push(topBorder);
148
- lines.push(`|` + " ".repeat(boxWidth) + `|`);
149
- if (currentDepth < maxDepth) {
150
- let nested = buildNested(currentDepth + 1 | 0, maxDepth);
151
- nested.forEach(nestedLine => {
152
- let paddedLine = `|` + " ".repeat(2) + nestedLine + " ".repeat((boxWidth - 2 | 0) - nestedLine.length | 0) + `|`;
153
- lines.push(paddedLine);
154
- });
155
- } else {
156
- let buttonText = "[ OK ]";
157
- let padding = (boxWidth - buttonText.length | 0) / 2 | 0;
158
- let contentLine = `|` + " ".repeat(padding) + buttonText + " ".repeat((boxWidth - padding | 0) - buttonText.length | 0) + `|`;
159
- lines.push(contentLine);
160
- }
161
- lines.push(`|` + " ".repeat(boxWidth) + `|`);
162
- lines.push(`+` + "-".repeat(boxWidth) + `+`);
163
- return lines;
164
- };
165
- let lines = buildNested(1, depth);
166
- return lines.join("\n");
167
- }
168
-
169
- function getLineCount(wireframe) {
170
- return wireframe.split("\n").length;
171
- }
172
-
173
- function validateWireframe(wireframe) {
174
- let lines = wireframe.split("\n");
175
- let openCorners = {
176
- contents: 0
177
- };
178
- lines.forEach(line => {
179
- let plusCount = 0;
180
- for (let i = 0, i_finish = line.length; i < i_finish; ++i) {
181
- if (Primitive_object.equal(line[i], "+")) {
182
- plusCount = plusCount + 1 | 0;
183
- }
184
- }
185
- if (plusCount >= 2) {
186
- openCorners.contents = openCorners.contents + 1 | 0;
187
- return;
188
- }
189
- });
190
- return openCorners.contents > 0;
191
- }
192
-
193
- export {
194
- generateWireframe,
195
- generateSimpleBox,
196
- generateNestedBoxes,
197
- getLineCount,
198
- validateWireframe,
199
- }
200
- /* No side effect */
@@ -1,284 +0,0 @@
1
- // PerformanceFixtures.res
2
- // Programmatic wireframe generation for performance testing
3
-
4
- /**
5
- * Generate a wireframe with approximately the target number of lines.
6
- * Creates realistic nested structures with various element types.
7
- *
8
- * @param targetLines - Approximate number of lines to generate
9
- * @returns ASCII wireframe string
10
- */
11
- let generateWireframe = (targetLines: int): string => {
12
- // Calculate structure to approximate target line count
13
- // Each box produces roughly: 3 (borders) + 2 (empty) + elementsPerBox * 1.5 (elements + spacing) lines
14
- // Each scene adds: 3 (header) + scene separators
15
- // Formula: targetLines ≈ numScenes * (3 + boxesPerScene * (5 + elementsPerBox * 1.5))
16
-
17
- // Use more granular scaling
18
- let (numScenes, boxesPerScene, elementsPerBox) = if targetLines <= 50 {
19
- (1, 3, 6) // ~45 lines
20
- } else if targetLines <= 100 {
21
- (2, 3, 6) // ~95 lines
22
- } else if targetLines <= 200 {
23
- (2, 5, 8) // ~185 lines
24
- } else if targetLines <= 500 {
25
- (3, 7, 10) // ~480 lines
26
- } else if targetLines <= 1000 {
27
- (4, 10, 12) // ~950 lines
28
- } else {
29
- (6, 12, 14) // ~2000 lines
30
- }
31
-
32
- let scenes = []
33
-
34
- // Generate each scene
35
- for sceneIdx in 0 to numScenes - 1 {
36
- let sceneName = `scene${Int.toString(sceneIdx + 1)}`
37
- let sceneTitle = `Scene ${Int.toString(sceneIdx + 1)}`
38
-
39
- // Scene header
40
- scenes->Array.push(`@scene: ${sceneName}`)->ignore
41
- scenes->Array.push(`@title: ${sceneTitle}`)->ignore
42
- scenes->Array.push("")->ignore
43
-
44
- // Generate boxes for this scene
45
- for boxIdx in 0 to boxesPerScene - 1 {
46
- let boxName = `Box${Int.toString(boxIdx + 1)}`
47
- let boxWidth = 40
48
-
49
- // Box top border with name
50
- // Total width = boxWidth + 2 (for the two | characters on content lines)
51
- // Top border: + -- Name dashes +
52
- // Content: | spaces |
53
- let topBorder = `+--${boxName}${"-"->String.repeat(boxWidth - String.length(boxName) - 2)}+`
54
- scenes->Array.push(topBorder)->ignore
55
-
56
- // Empty line
57
- scenes->Array.push(`|${" "->String.repeat(boxWidth)}|`)->ignore
58
-
59
- // Generate elements
60
- for elemIdx in 0 to elementsPerBox - 1 {
61
- let elementType = mod(elemIdx, 5)
62
-
63
- switch elementType {
64
- | 0 => {
65
- // Button
66
- let buttonText = `Button ${Int.toString(elemIdx + 1)}`
67
- let button = `[ ${buttonText} ]`
68
- let padding = (boxWidth - String.length(button)) / 2
69
- let line = `|${" "->String.repeat(padding)}${button}${" "->String.repeat(
70
- boxWidth - padding - String.length(button),
71
- )}|`
72
- scenes->Array.push(line)->ignore
73
- }
74
- | 1 => {
75
- // Input field
76
- let fieldName = `field${Int.toString(elemIdx + 1)}`
77
- let label = `Label ${Int.toString(elemIdx + 1)}`
78
- scenes->Array.push(`| ${label}${" "->String.repeat(boxWidth - String.length(label) - 2)}|`)->ignore
79
- scenes->Array.push(`| #${fieldName}${" "->String.repeat(
80
- boxWidth - String.length(fieldName) - 3,
81
- )}|`)->ignore
82
- }
83
- | 2 => {
84
- // Link
85
- let linkText = `Link ${Int.toString(elemIdx + 1)}`
86
- let link = `"${linkText}"`
87
- scenes->Array.push(`| ${link}${" "->String.repeat(boxWidth - String.length(link) - 2)}|`)->ignore
88
- }
89
- | 3 => {
90
- // Checkbox
91
- let checked = mod(elemIdx, 2) == 0
92
- let checkboxLabel = `Option ${Int.toString(elemIdx + 1)}`
93
- let checkbox = checked ? "[x]" : "[ ]"
94
- scenes->Array.push(`| ${checkbox} ${checkboxLabel}${" "->String.repeat(
95
- boxWidth - String.length(checkbox) - String.length(checkboxLabel) - 3,
96
- )}|`)->ignore
97
- }
98
- | 4 => {
99
- // Emphasis text
100
- let emphasisText = `Important ${Int.toString(elemIdx + 1)}`
101
- scenes->Array.push(`| * ${emphasisText}${" "->String.repeat(
102
- boxWidth - String.length(emphasisText) - 4,
103
- )}|`)->ignore
104
- }
105
- | _ => ()
106
- }
107
-
108
- // Add spacing between elements
109
- if elemIdx < elementsPerBox - 1 {
110
- scenes->Array.push(`|${" "->String.repeat(boxWidth)}|`)->ignore
111
- }
112
- }
113
-
114
- // Add divider if not last box in scene
115
- if boxIdx < boxesPerScene - 1 && boxIdx == boxesPerScene / 2 {
116
- scenes->Array.push(`|${"="->String.repeat(boxWidth)}|`)->ignore
117
- }
118
-
119
- // Empty line
120
- scenes->Array.push(`|${" "->String.repeat(boxWidth)}|`)->ignore
121
-
122
- // Box bottom border
123
- let bottomBorder = `+${"-"->String.repeat(boxWidth)}+`
124
- scenes->Array.push(bottomBorder)->ignore
125
-
126
- // Space between boxes
127
- if boxIdx < boxesPerScene - 1 {
128
- scenes->Array.push("")->ignore
129
- }
130
- }
131
-
132
- // Space between scenes
133
- if sceneIdx < numScenes - 1 {
134
- scenes->Array.push("")->ignore
135
- scenes->Array.push("---")->ignore
136
- scenes->Array.push("")->ignore
137
- }
138
- }
139
-
140
- scenes->Array.joinWith("\n")
141
- }
142
-
143
- /**
144
- * Generate a simple box for basic testing
145
- *
146
- * @param width - Box width (interior)
147
- * @param height - Box height (interior lines)
148
- * @param name - Optional box name
149
- * @returns ASCII box string
150
- */
151
- let generateSimpleBox = (~width: int=20, ~height: int=3, ~name: option<string>=None): string => {
152
- let lines = []
153
-
154
- // Total width = width + 4 (for "| " and " |" on content lines)
155
- let totalWidth = width + 4
156
-
157
- // Top border
158
- let topBorder = switch name {
159
- | Some(n) => {
160
- let nameStr = `--${n}--`
161
- let padding = totalWidth - 2 - String.length(nameStr) // -2 for the two +
162
- if padding < 0 {
163
- `+${"-"->String.repeat(totalWidth - 2)}+` // Name too long, use plain border
164
- } else {
165
- let leftPad = padding / 2
166
- let rightPad = padding - leftPad
167
- `+${"-"->String.repeat(leftPad)}${nameStr}${"-"->String.repeat(rightPad)}+`
168
- }
169
- }
170
- | None => `+${"-"->String.repeat(totalWidth - 2)}+`
171
- }
172
- lines->Array.push(topBorder)->ignore
173
-
174
- // Interior lines
175
- for _ in 0 to height - 1 {
176
- lines->Array.push(`| ${" "->String.repeat(width)} |`)->ignore
177
- }
178
-
179
- // Bottom border
180
- lines->Array.push(`+${"-"->String.repeat(totalWidth - 2)}+`)->ignore
181
-
182
- lines->Array.joinWith("\n")
183
- }
184
-
185
- /**
186
- * Generate nested boxes for hierarchy testing
187
- *
188
- * @param depth - Nesting depth (1 = no nesting, 2 = one level, etc.)
189
- * @returns ASCII wireframe with nested boxes
190
- */
191
- let generateNestedBoxes = (depth: int): string => {
192
- // Generate properly nested boxes where inner boxes are contained within outer boxes
193
- // Each level reduces width by 4 (2 for indent + 2 for padding)
194
- let baseWidth = 40
195
-
196
- let rec buildNested = (currentDepth: int, maxDepth: int): array<string> => {
197
- if currentDepth > maxDepth {
198
- []
199
- } else {
200
- let lines = []
201
- let boxName = `Level${Int.toString(currentDepth)}`
202
- let boxWidth = baseWidth - (currentDepth - 1) * 4
203
- let innerPadding = 2 // Padding inside box for nested content
204
-
205
- // Top border
206
- let topBorder = `+--${boxName}${"-"->String.repeat(boxWidth - String.length(boxName) - 2)}+`
207
- lines->Array.push(topBorder)->ignore
208
-
209
- // Empty line
210
- lines->Array.push(`|${" "->String.repeat(boxWidth)}|`)->ignore
211
-
212
- // Nested content
213
- if currentDepth < maxDepth {
214
- let nested = buildNested(currentDepth + 1, maxDepth)
215
- // Wrap each nested line with outer box borders
216
- nested->Array.forEach(nestedLine => {
217
- let paddedLine = `|${" "->String.repeat(innerPadding)}${nestedLine}${" "->String.repeat(boxWidth - innerPadding - String.length(nestedLine))}|`
218
- lines->Array.push(paddedLine)->ignore
219
- })
220
- } else {
221
- // Leaf content - add a button
222
- let buttonText = "[ OK ]"
223
- let padding = (boxWidth - String.length(buttonText)) / 2
224
- let contentLine = `|${" "->String.repeat(padding)}${buttonText}${" "->String.repeat(boxWidth - padding - String.length(buttonText))}|`
225
- lines->Array.push(contentLine)->ignore
226
- }
227
-
228
- // Empty line
229
- lines->Array.push(`|${" "->String.repeat(boxWidth)}|`)->ignore
230
-
231
- // Bottom border
232
- lines->Array.push(`+${"-"->String.repeat(boxWidth)}+`)->ignore
233
-
234
- lines
235
- }
236
- }
237
-
238
- let lines = buildNested(1, depth)
239
- lines->Array.joinWith("\n")
240
- }
241
-
242
- /**
243
- * Get the approximate line count of a generated wireframe
244
- * Useful for validation in tests
245
- *
246
- * @param wireframe - ASCII wireframe string
247
- * @returns Number of lines
248
- */
249
- let getLineCount = (wireframe: string): int => {
250
- wireframe->String.split("\n")->Array.length
251
- }
252
-
253
- /**
254
- * Validate that a wireframe has basic syntactic correctness
255
- * Checks for balanced borders, no empty lines in boxes, etc.
256
- *
257
- * @param wireframe - ASCII wireframe string
258
- * @returns true if valid, false otherwise
259
- */
260
- let validateWireframe = (wireframe: string): bool => {
261
- let lines = wireframe->String.split("\n")
262
-
263
- // Check that corner characters are balanced
264
- let openCorners = ref(0)
265
- let closeCorners = ref(0)
266
-
267
- lines->Array.forEach(line => {
268
- // Count '+' characters
269
- let plusCount = ref(0)
270
- for i in 0 to String.length(line) - 1 {
271
- if String.get(line, i) == Some("+") {
272
- plusCount := plusCount.contents + 1
273
- }
274
- }
275
-
276
- // Assume pairs of '+' (start and end of border)
277
- if plusCount.contents >= 2 {
278
- openCorners := openCorners.contents + 1
279
- }
280
- })
281
-
282
- // Basic validation: should have at least some box structure
283
- openCorners.contents > 0
284
- }