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,244 +0,0 @@
1
- // Generated by ReScript, PLEASE EDIT WITH CARE
2
-
3
- import * as Parser from "../Parser.mjs";
4
- import * as Vitest from "rescript-vitest/src/Vitest.mjs";
5
- import * as Core__Array from "@rescript/core/src/Core__Array.mjs";
6
- import * as PerformanceFixtures from "./PerformanceFixtures.mjs";
7
-
8
- function measureTime(fn) {
9
- let start = Date.now();
10
- let result = fn();
11
- let end = Date.now();
12
- let duration = end - start;
13
- return [
14
- duration,
15
- result
16
- ];
17
- }
18
-
19
- function getMemoryUsageMB() {
20
- return (typeof process !== 'undefined' && process.memoryUsage ? process.memoryUsage().heapUsed / 1024 / 1024 : 0);
21
- }
22
-
23
- function forceGC() {
24
- ((typeof global !== 'undefined' && global.gc ? global.gc() : undefined));
25
- }
26
-
27
- Vitest.describe("Performance Benchmarks", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
28
- Vitest.test("parses 100-line wireframe in ≤50ms", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
29
- let wireframe = PerformanceFixtures.generateWireframe(100);
30
- let match = measureTime(() => Parser.parse(wireframe));
31
- let result = match[1];
32
- let duration = match[0];
33
- if (result.TAG === "Ok") {
34
- t.expect(result._0.scenes.length).toBeGreaterThan(0);
35
- } else {
36
- console.error("Parse errors:");
37
- console.error(result._0);
38
- t.expect(true).toBe(false);
39
- }
40
- console.log(`100-line wireframe parsed in ` + duration.toString() + `ms`);
41
- t.expect(duration).toBeLessThanOrEqual(50.0);
42
- });
43
- Vitest.test("parses 500-line wireframe in ≤200ms", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
44
- let wireframe = PerformanceFixtures.generateWireframe(500);
45
- let match = measureTime(() => Parser.parse(wireframe));
46
- let result = match[1];
47
- let duration = match[0];
48
- if (result.TAG === "Ok") {
49
- t.expect(result._0.scenes.length).toBeGreaterThan(0);
50
- } else {
51
- console.error("Parse errors:");
52
- console.error(result._0);
53
- t.expect(true).toBe(false);
54
- }
55
- console.log(`500-line wireframe parsed in ` + duration.toString() + `ms`);
56
- t.expect(duration).toBeLessThanOrEqual(200.0);
57
- });
58
- Vitest.test("parses 2000-line wireframe in ≤1000ms", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
59
- let wireframe = PerformanceFixtures.generateWireframe(2000);
60
- let match = measureTime(() => Parser.parse(wireframe));
61
- let result = match[1];
62
- let duration = match[0];
63
- if (result.TAG === "Ok") {
64
- t.expect(result._0.scenes.length).toBeGreaterThan(0);
65
- } else {
66
- console.error("Parse errors:");
67
- console.error(result._0);
68
- t.expect(true).toBe(false);
69
- }
70
- console.log(`2000-line wireframe parsed in ` + duration.toString() + `ms`);
71
- t.expect(duration).toBeLessThanOrEqual(1000.0);
72
- });
73
- Vitest.test("memory usage <50MB for 2000-line wireframe", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
74
- let wireframe = PerformanceFixtures.generateWireframe(2000);
75
- forceGC();
76
- let initialMemory = getMemoryUsageMB();
77
- let result = Parser.parse(wireframe);
78
- let finalMemory = getMemoryUsageMB();
79
- let memoryDelta = finalMemory - initialMemory;
80
- if (result.TAG === "Ok") {
81
- t.expect(result._0.scenes.length).toBeGreaterThan(0);
82
- } else {
83
- console.error("Parse errors:");
84
- console.error(result._0);
85
- t.expect(true).toBe(false);
86
- }
87
- console.log(`Initial memory: ` + initialMemory.toString() + `MB`);
88
- console.log(`Final memory: ` + finalMemory.toString() + `MB`);
89
- console.log(`Memory delta: ` + memoryDelta.toString() + `MB`);
90
- if (memoryDelta > 0.0) {
91
- return t.expect(memoryDelta).toBeLessThan(50.0);
92
- } else {
93
- console.warn("Memory test skipped: gc not exposed or memory delta is zero");
94
- return;
95
- }
96
- });
97
- Vitest.describe("Fixture Generation Validation", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
98
- let testSizes = [
99
- 50,
100
- 100,
101
- 200,
102
- 500,
103
- 1000,
104
- 2000
105
- ];
106
- testSizes.forEach(targetSize => Vitest.test(`generates valid ` + targetSize.toString() + `-line wireframe`, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
107
- let wireframe = PerformanceFixtures.generateWireframe(targetSize);
108
- let actualLines = PerformanceFixtures.getLineCount(wireframe);
109
- let minLines = targetSize * (1.0 - 0.30) | 0;
110
- let maxLines = targetSize * (1.0 + 0.30) | 0;
111
- console.log(`Target: ` + targetSize.toString() + ` lines, Actual: ` + actualLines.toString() + ` lines`);
112
- t.expect(actualLines).toBeGreaterThanOrEqual(minLines);
113
- t.expect(actualLines).toBeLessThanOrEqual(maxLines);
114
- t.expect(PerformanceFixtures.validateWireframe(wireframe)).toBe(true);
115
- let result = Parser.parse(wireframe);
116
- if (result.TAG === "Ok") {
117
- return t.expect(result._0.scenes.length).toBeGreaterThan(0);
118
- }
119
- console.error(`Parse errors for ` + targetSize.toString() + `-line wireframe:`);
120
- console.error(result._0);
121
- t.expect(true).toBe(false);
122
- }));
123
- });
124
- Vitest.test("parsing is consistent across multiple iterations", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
125
- let wireframe = PerformanceFixtures.generateWireframe(500);
126
- let durations = [];
127
- Parser.parse(wireframe);
128
- for (let _for = 0; _for <= 9; ++_for) {
129
- let match = measureTime(() => Parser.parse(wireframe));
130
- let result = match[1];
131
- durations.push(match[0]);
132
- if (result.TAG === "Ok") {
133
- t.expect(result._0.scenes.length).toBeGreaterThan(0);
134
- } else {
135
- t.expect(true).toBe(false);
136
- }
137
- }
138
- let sum = Core__Array.reduce(durations, 0.0, (acc, d) => acc + d);
139
- let average = sum / 10;
140
- let variance = Core__Array.reduce(durations, 0.0, (acc, d) => {
141
- let diff = d - average;
142
- return acc + diff * diff;
143
- }) / 10;
144
- let stdDev = Math.sqrt(variance);
145
- let min = Core__Array.reduce(durations, Number.POSITIVE_INFINITY, (acc, d) => Math.min(acc, d));
146
- let max = Core__Array.reduce(durations, Number.NEGATIVE_INFINITY, (acc, d) => Math.max(acc, d));
147
- console.log(`\nPerformance Statistics (` + (10).toString() + ` iterations):`);
148
- console.log(` Average: ` + average.toString() + `ms`);
149
- console.log(` Std Dev: ` + stdDev.toString() + `ms`);
150
- console.log(` Min: ` + min.toString() + `ms`);
151
- console.log(` Max: ` + max.toString() + `ms`);
152
- console.log(` Coefficient of Variation: ` + (stdDev / average * 100.0).toString() + `%`);
153
- t.expect(max).toBeLessThanOrEqual(200.0);
154
- if (average >= 10.0) {
155
- let coefficientOfVariation = stdDev / average;
156
- return t.expect(coefficientOfVariation).toBeLessThan(0.5);
157
- }
158
- console.log(" CV check skipped: average too small for meaningful variance measurement");
159
- });
160
- Vitest.test("performance scales linearly with wireframe size", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
161
- let sizes = [
162
- 100,
163
- 200,
164
- 400,
165
- 800
166
- ];
167
- let measurements = [];
168
- sizes.forEach(size => {
169
- let wireframe = PerformanceFixtures.generateWireframe(size);
170
- let match = measureTime(() => Parser.parse(wireframe));
171
- if (match[1].TAG !== "Ok") {
172
- return t.expect(true).toBe(false);
173
- }
174
- measurements.push([
175
- size,
176
- match[0]
177
- ]);
178
- });
179
- console.log("\nLinear Scaling Analysis:");
180
- measurements.forEach(param => {
181
- console.log(` ` + param[0].toString() + ` lines: ` + param[1].toString() + `ms`);
182
- });
183
- for (let i = 0, i_finish = measurements.length - 2 | 0; i <= i_finish; ++i) {
184
- let match = measurements[i];
185
- let duration1 = match[1];
186
- let match$1 = measurements[i + 1 | 0];
187
- let duration2 = match$1[1];
188
- let sizeRatio = match$1[0] / match[0];
189
- if (duration1 >= 10.0 && duration2 >= 10.0) {
190
- let durationRatio = duration2 / duration1;
191
- console.log(` Size ratio: ` + sizeRatio.toString() + `x, Duration ratio: ` + durationRatio.toString() + `x`);
192
- t.expect(durationRatio).toBeLessThan(sizeRatio * 2.5);
193
- } else {
194
- console.log(` Size ratio: ` + sizeRatio.toString() + `x, Duration ratio: N/A (durations too small)`);
195
- }
196
- }
197
- });
198
- });
199
-
200
- Vitest.describe("Nested Boxes Performance", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => Vitest.test("handles deep nesting efficiently", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
201
- let depths = [
202
- 1,
203
- 2,
204
- 3,
205
- 4
206
- ];
207
- depths.forEach(depth => {
208
- let wireframe = PerformanceFixtures.generateNestedBoxes(depth);
209
- let match = measureTime(() => Parser.parse(wireframe));
210
- let result = match[1];
211
- let duration = match[0];
212
- console.log(`Nesting depth ` + depth.toString() + `: ` + duration.toString() + `ms`);
213
- if (result.TAG !== "Ok") {
214
- console.error(`Parse errors at depth ` + depth.toString() + `:`);
215
- console.error(result._0);
216
- t.expect(true).toBe(false);
217
- }
218
- t.expect(duration).toBeLessThan(100.0);
219
- });
220
- }));
221
-
222
- Vitest.describe("Simple Box Generation", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => Vitest.test("generates and parses simple boxes", undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, t => {
223
- let box1 = PerformanceFixtures.generateSimpleBox(undefined, undefined, undefined);
224
- let box2 = PerformanceFixtures.generateSimpleBox(30, 5, "TestBox");
225
- t.expect(PerformanceFixtures.validateWireframe(box1)).toBe(true);
226
- t.expect(PerformanceFixtures.validateWireframe(box2)).toBe(true);
227
- let result1 = Parser.parse(box1);
228
- let result2 = Parser.parse(box2);
229
- if (result1.TAG === "Ok" && result2.TAG === "Ok") {
230
- return;
231
- } else {
232
- return t.expect(true).toBe(false);
233
- }
234
- }));
235
-
236
- let pass;
237
-
238
- export {
239
- pass,
240
- measureTime,
241
- getMemoryUsageMB,
242
- forceGC,
243
- }
244
- /* Not a pure module */
@@ -1,371 +0,0 @@
1
- // Performance.test.res
2
- // Performance benchmarks for WyreframeParser
3
- // Requirements: REQ-22 (Parsing Speed), REQ-23 (Memory Efficiency)
4
-
5
- open Vitest
6
-
7
- let pass = ()
8
-
9
- // Helper to measure execution time
10
- let measureTime = (fn: unit => 'a): (float, 'a) => {
11
- let start = Date.now()
12
- let result = fn()
13
- let end = Date.now()
14
- let duration = end -. start
15
- (duration, result)
16
- }
17
-
18
- // Helper to get memory usage in MB
19
- let getMemoryUsageMB = (): float => {
20
- // Node.js specific - process.memoryUsage().heapUsed
21
- %raw(`typeof process !== 'undefined' && process.memoryUsage ? process.memoryUsage().heapUsed / 1024 / 1024 : 0`)
22
- }
23
-
24
- // Helper to force garbage collection if available
25
- let forceGC = (): unit => {
26
- %raw(`typeof global !== 'undefined' && global.gc ? global.gc() : undefined`)
27
- }
28
-
29
- describe("Performance Benchmarks", t => {
30
- // PERF-01: Small wireframe parsing (100 lines) - ≤50ms
31
- test("parses 100-line wireframe in ≤50ms", t => {
32
- let wireframe = PerformanceFixtures.generateWireframe(100)
33
-
34
- let (duration, result) = measureTime(() => {
35
- Parser.parse(wireframe)
36
- })
37
-
38
- // Verify parse succeeded
39
- switch result {
40
- | Ok(ast) => {
41
- t->expect(Array.length(ast.scenes))->Expect.Int.toBeGreaterThan(0)
42
- }
43
- | Error(errors) => {
44
- // Log errors for debugging
45
- Console.error("Parse errors:")
46
- Console.error(errors)
47
- t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
48
- }
49
- }
50
-
51
- // Verify performance requirement
52
- Console.log(`100-line wireframe parsed in ${Float.toString(duration)}ms`)
53
- t->expect(duration)->Expect.Float.toBeLessThanOrEqual(50.0)
54
- })
55
-
56
- // PERF-02: Medium wireframe parsing (500 lines) - ≤200ms
57
- test("parses 500-line wireframe in ≤200ms", t => {
58
- let wireframe = PerformanceFixtures.generateWireframe(500)
59
-
60
- let (duration, result) = measureTime(() => {
61
- Parser.parse(wireframe)
62
- })
63
-
64
- // Verify parse succeeded
65
- switch result {
66
- | Ok(ast) => {
67
- t->expect(Array.length(ast.scenes))->Expect.Int.toBeGreaterThan(0)
68
- }
69
- | Error(errors) => {
70
- Console.error("Parse errors:")
71
- Console.error(errors)
72
- t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
73
- }
74
- }
75
-
76
- // Verify performance requirement
77
- Console.log(`500-line wireframe parsed in ${Float.toString(duration)}ms`)
78
- t->expect(duration)->Expect.Float.toBeLessThanOrEqual(200.0)
79
- })
80
-
81
- // PERF-03: Large wireframe parsing (2000 lines) - ≤1000ms
82
- test("parses 2000-line wireframe in ≤1000ms", t => {
83
- let wireframe = PerformanceFixtures.generateWireframe(2000)
84
-
85
- let (duration, result) = measureTime(() => {
86
- Parser.parse(wireframe)
87
- })
88
-
89
- // Verify parse succeeded
90
- switch result {
91
- | Ok(ast) => {
92
- t->expect(Array.length(ast.scenes))->Expect.Int.toBeGreaterThan(0)
93
- }
94
- | Error(errors) => {
95
- Console.error("Parse errors:")
96
- Console.error(errors)
97
- t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
98
- }
99
- }
100
-
101
- // Verify performance requirement
102
- Console.log(`2000-line wireframe parsed in ${Float.toString(duration)}ms`)
103
- t->expect(duration)->Expect.Float.toBeLessThanOrEqual(1000.0)
104
- })
105
-
106
- // PERF-04: Memory usage test - <50MB for 2000 lines
107
- test("memory usage <50MB for 2000-line wireframe", t => {
108
- let wireframe = PerformanceFixtures.generateWireframe(2000)
109
-
110
- // Force garbage collection to get clean baseline
111
- forceGC()
112
-
113
- // Record initial memory
114
- let initialMemory = getMemoryUsageMB()
115
-
116
- // Parse wireframe
117
- let result = Parser.parse(wireframe)
118
-
119
- // Record final memory
120
- let finalMemory = getMemoryUsageMB()
121
-
122
- // Calculate delta
123
- let memoryDelta = finalMemory -. initialMemory
124
-
125
- // Verify parse succeeded
126
- switch result {
127
- | Ok(ast) => {
128
- t->expect(Array.length(ast.scenes))->Expect.Int.toBeGreaterThan(0)
129
- }
130
- | Error(errors) => {
131
- Console.error("Parse errors:")
132
- Console.error(errors)
133
- t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
134
- }
135
- }
136
-
137
- // Log memory usage
138
- Console.log(`Initial memory: ${Float.toString(initialMemory)}MB`)
139
- Console.log(`Final memory: ${Float.toString(finalMemory)}MB`)
140
- Console.log(`Memory delta: ${Float.toString(memoryDelta)}MB`)
141
-
142
- // Verify memory requirement
143
- // Note: This test may be skipped if gc is not exposed
144
- if memoryDelta > 0.0 {
145
- t->expect(memoryDelta)->Expect.Float.toBeLessThan(50.0)
146
- } else {
147
- Console.warn("Memory test skipped: gc not exposed or memory delta is zero")
148
- pass
149
- }
150
- })
151
-
152
- // PERF-05: Fixture generation validation
153
- describe("Fixture Generation Validation", t => {
154
- let testSizes = [50, 100, 200, 500, 1000, 2000]
155
-
156
- testSizes->Array.forEach(targetSize => {
157
- test(`generates valid ${Int.toString(targetSize)}-line wireframe`, t => {
158
- let wireframe = PerformanceFixtures.generateWireframe(targetSize)
159
-
160
- // Check line count is approximately correct (±30% tolerance for all sizes)
161
- let actualLines = PerformanceFixtures.getLineCount(wireframe)
162
- let tolerance = 0.30 // 30% tolerance - fixture generation is approximate
163
- let minLines = Float.toInt(Float.fromInt(targetSize) *. (1.0 -. tolerance))
164
- let maxLines = Float.toInt(Float.fromInt(targetSize) *. (1.0 +. tolerance))
165
-
166
- Console.log(
167
- `Target: ${Int.toString(targetSize)} lines, Actual: ${Int.toString(actualLines)} lines`,
168
- )
169
-
170
- t->expect(actualLines)->Expect.Int.toBeGreaterThanOrEqual(minLines)
171
- t->expect(actualLines)->Expect.Int.toBeLessThanOrEqual(maxLines)
172
-
173
- // Verify basic syntactic correctness
174
- t->expect(PerformanceFixtures.validateWireframe(wireframe))->Expect.toBe(true)
175
-
176
- // Verify it parses without errors
177
- let result = Parser.parse(wireframe)
178
- switch result {
179
- | Ok(ast) => {
180
- t->expect(Array.length(ast.scenes))->Expect.Int.toBeGreaterThan(0)
181
- }
182
- | Error(errors) => {
183
- Console.error(`Parse errors for ${Int.toString(targetSize)}-line wireframe:`)
184
- Console.error(errors)
185
- t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
186
- }
187
- }
188
- })
189
- })
190
- })
191
-
192
- // PERF-06: Multiple parse iterations consistency
193
- test("parsing is consistent across multiple iterations", t => {
194
- let wireframe = PerformanceFixtures.generateWireframe(500)
195
- let iterations = 10
196
- let durations = []
197
-
198
- // Warm-up iteration (JIT compilation)
199
- let _ = Parser.parse(wireframe)
200
-
201
- // Measured iterations
202
- for _ in 0 to iterations - 1 {
203
- let (duration, result) = measureTime(() => {
204
- Parser.parse(wireframe)
205
- })
206
-
207
- durations->Array.push(duration)->ignore
208
-
209
- // Verify each parse succeeds
210
- switch result {
211
- | Ok(ast) => {
212
- t->expect(Array.length(ast.scenes))->Expect.Int.toBeGreaterThan(0)
213
- }
214
- | Error(_) => {
215
- t->expect(true)->Expect.toBe(false) // fail: Expected successful parse on all iterations
216
- }
217
- }
218
- }
219
-
220
- // Calculate statistics
221
- let sum = durations->Array.reduce(0.0, (acc, d) => acc +. d)
222
- let average = sum /. Float.fromInt(iterations)
223
-
224
- // Calculate standard deviation
225
- let variance =
226
- durations->Array.reduce(0.0, (acc, d) => {
227
- let diff = d -. average
228
- acc +. diff *. diff
229
- }) /. Float.fromInt(iterations)
230
- let stdDev = Math.sqrt(variance)
231
-
232
- let min = durations->Array.reduce(Float.Constants.positiveInfinity, (acc, d) => Math.min(acc, d))
233
- let max = durations->Array.reduce(Float.Constants.negativeInfinity, (acc, d) => Math.max(acc, d))
234
-
235
- // Log statistics
236
- Console.log(`\nPerformance Statistics (${Int.toString(iterations)} iterations):`)
237
- Console.log(` Average: ${Float.toString(average)}ms`)
238
- Console.log(` Std Dev: ${Float.toString(stdDev)}ms`)
239
- Console.log(` Min: ${Float.toString(min)}ms`)
240
- Console.log(` Max: ${Float.toString(max)}ms`)
241
- Console.log(` Coefficient of Variation: ${Float.toString(stdDev /. average *. 100.0)}%`)
242
-
243
- // Verify all iterations meet performance target
244
- t->expect(max)->Expect.Float.toBeLessThanOrEqual(200.0)
245
-
246
- // Verify consistency (standard deviation < 50% of average)
247
- // Note: Skip CV check for very fast operations (< 10ms average)
248
- // because sub-millisecond timing has inherently high variance
249
- if average >= 10.0 {
250
- let coefficientOfVariation = stdDev /. average
251
- t->expect(coefficientOfVariation)->Expect.Float.toBeLessThan(0.5)
252
- } else {
253
- // For fast operations, just verify max is reasonable
254
- Console.log(" CV check skipped: average too small for meaningful variance measurement")
255
- pass
256
- }
257
- })
258
-
259
- // Additional test: Verify linear scaling
260
- test("performance scales linearly with wireframe size", t => {
261
- let sizes = [100, 200, 400, 800]
262
- let measurements = []
263
-
264
- sizes->Array.forEach(size => {
265
- let wireframe = PerformanceFixtures.generateWireframe(size)
266
-
267
- let (duration, result) = measureTime(() => {
268
- Parser.parse(wireframe)
269
- })
270
-
271
- // Verify parse succeeded
272
- switch result {
273
- | Ok(_) => {
274
- measurements->Array.push((size, duration))->ignore
275
- }
276
- | Error(_) => {
277
- t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
278
- }
279
- }
280
- })
281
-
282
- // Log measurements
283
- Console.log("\nLinear Scaling Analysis:")
284
- measurements->Array.forEach(((size, duration)) => {
285
- Console.log(` ${Int.toString(size)} lines: ${Float.toString(duration)}ms`)
286
- })
287
-
288
- // Verify approximate linear relationship
289
- // For each doubling of size, duration should roughly double (±50% tolerance)
290
- for i in 0 to Array.length(measurements) - 2 {
291
- let (size1, duration1) = measurements->Array.getUnsafe(i)
292
- let (size2, duration2) = measurements->Array.getUnsafe(i + 1)
293
-
294
- let sizeRatio = Float.fromInt(size2) /. Float.fromInt(size1)
295
-
296
- // Skip ratio comparison if durations are too small (< 10ms)
297
- // Small durations have too much variance for meaningful ratio comparison
298
- if duration1 >= 10.0 && duration2 >= 10.0 {
299
- let durationRatio = duration2 /. duration1
300
-
301
- Console.log(
302
- ` Size ratio: ${Float.toString(sizeRatio)}x, Duration ratio: ${Float.toString(
303
- durationRatio,
304
- )}x`,
305
- )
306
-
307
- // Duration ratio should be within 0.5x to 2.5x of size ratio
308
- // (allowing for variance due to fixed overhead and JIT optimization)
309
- t->expect(durationRatio)->Expect.Float.toBeLessThan(sizeRatio *. 2.5)
310
- } else {
311
- Console.log(
312
- ` Size ratio: ${Float.toString(sizeRatio)}x, Duration ratio: N/A (durations too small)`,
313
- )
314
- // If durations are small, parsing is extremely fast which is good
315
- pass
316
- }
317
- }
318
- })
319
- })
320
-
321
- // Nested boxes performance test
322
- describe("Nested Boxes Performance", t => {
323
- test("handles deep nesting efficiently", t => {
324
- // Test nesting depths 1-4
325
- let depths = [1, 2, 3, 4]
326
-
327
- depths->Array.forEach(depth => {
328
- let wireframe = PerformanceFixtures.generateNestedBoxes(depth)
329
-
330
- let (duration, result) = measureTime(() => {
331
- Parser.parse(wireframe)
332
- })
333
-
334
- Console.log(`Nesting depth ${Int.toString(depth)}: ${Float.toString(duration)}ms`)
335
-
336
- // Verify parse succeeded
337
- switch result {
338
- | Ok(_) => pass
339
- | Error(errors) => {
340
- Console.error(`Parse errors at depth ${Int.toString(depth)}:`)
341
- Console.error(errors)
342
- t->expect(true)->Expect.toBe(false) // fail: Expected successful parse
343
- }
344
- }
345
-
346
- // Even deep nesting should be fast (<100ms for simple structures)
347
- t->expect(duration)->Expect.Float.toBeLessThan(100.0)
348
- })
349
- })
350
- })
351
-
352
- // Simple box generation test
353
- describe("Simple Box Generation", t => {
354
- test("generates and parses simple boxes", t => {
355
- let box1 = PerformanceFixtures.generateSimpleBox()
356
- let box2 = PerformanceFixtures.generateSimpleBox(~width=30, ~height=5, ~name=Some("TestBox"))
357
-
358
- // Verify both boxes are valid
359
- t->expect(PerformanceFixtures.validateWireframe(box1))->Expect.toBe(true)
360
- t->expect(PerformanceFixtures.validateWireframe(box2))->Expect.toBe(true)
361
-
362
- // Parse both
363
- let result1 = Parser.parse(box1)
364
- let result2 = Parser.parse(box2)
365
-
366
- switch (result1, result2) {
367
- | (Ok(_), Ok(_)) => pass
368
- | _ => t->expect(true)->Expect.toBe(false) // fail: Expected successful parse for simple boxes
369
- }
370
- })
371
- })