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,179 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ function isWireframeLine(line) {
5
+ let trimmed = line.trim();
6
+ if (trimmed === "") {
7
+ return false;
8
+ }
9
+ let boxBorder = /^\+[-=]+\+/;
10
+ let boxSide = /^\|.*\|$/;
11
+ let namedBorder = /^\+--[^-].*--\+/;
12
+ let startsWithPlus = trimmed.startsWith("+");
13
+ let startsWithPipe = trimmed.startsWith("|");
14
+ if (boxBorder.test(trimmed) || boxSide.test(trimmed) || namedBorder.test(trimmed) || startsWithPlus && trimmed.includes("-")) {
15
+ return true;
16
+ } else if (startsWithPipe) {
17
+ return trimmed.endsWith("|");
18
+ } else {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ function isDirectiveLine(line) {
24
+ let trimmed = line.trim();
25
+ if (trimmed.startsWith("@scene:") || trimmed.startsWith("@title:") || trimmed.startsWith("@device:")) {
26
+ return true;
27
+ } else {
28
+ return trimmed.startsWith("@transition:");
29
+ }
30
+ }
31
+
32
+ function isSceneSeparator(line) {
33
+ return line.trim() === "---";
34
+ }
35
+
36
+ function isInteractionSelector(line) {
37
+ let trimmed = line.trim();
38
+ let inputSelector = /^#[\w-]+:$/;
39
+ let buttonSelector = /^\[.+\]:$/;
40
+ let linkSelector = /^\"[^\"]+\":$/;
41
+ if (inputSelector.test(trimmed) || buttonSelector.test(trimmed)) {
42
+ return true;
43
+ } else {
44
+ return linkSelector.test(trimmed);
45
+ }
46
+ }
47
+
48
+ function isInteractionProperty(line) {
49
+ let hasIndent = line.length > 0 && (line.startsWith(" ") || line.startsWith("\t"));
50
+ if (!hasIndent) {
51
+ return false;
52
+ }
53
+ let trimmed = line.trim();
54
+ let propertyPattern = /^[\w@-]+:?\s*(->|\S)/;
55
+ if (trimmed !== "") {
56
+ return propertyPattern.test(trimmed);
57
+ } else {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ function classifyLine(line) {
63
+ let trimmed = line.trim();
64
+ if (trimmed === "") {
65
+ return "EmptyLine";
66
+ } else if (line.trim() === "---") {
67
+ return "SceneSeparator";
68
+ } else if (isDirectiveLine(line)) {
69
+ return "Directive";
70
+ } else if (isInteractionSelector(line)) {
71
+ return "InteractionSelector";
72
+ } else if (isInteractionProperty(line)) {
73
+ return "InteractionProperty";
74
+ } else if (isWireframeLine(line)) {
75
+ return "Wireframe";
76
+ } else {
77
+ return "Noise";
78
+ }
79
+ }
80
+
81
+ function extract(text) {
82
+ let lines = text.split("\n");
83
+ let wireframeLines = [];
84
+ let interactionLines = [];
85
+ let context = {
86
+ contents: "Initial"
87
+ };
88
+ let currentSceneId = {
89
+ contents: undefined
90
+ };
91
+ let interactionHasSceneHeader = {
92
+ contents: false
93
+ };
94
+ lines.forEach(line => {
95
+ let lineType = classifyLine(line);
96
+ switch (lineType) {
97
+ case "Wireframe" :
98
+ wireframeLines.push(line);
99
+ context.contents = "InWireframe";
100
+ return;
101
+ case "Directive" :
102
+ wireframeLines.push(line);
103
+ let trimmed = line.trim();
104
+ if (trimmed.startsWith("@scene:")) {
105
+ let sceneId = trimmed.slice(7).trim();
106
+ currentSceneId.contents = sceneId;
107
+ interactionHasSceneHeader.contents = false;
108
+ }
109
+ context.contents = "InWireframe";
110
+ return;
111
+ case "SceneSeparator" :
112
+ wireframeLines.push(line);
113
+ if (interactionLines.length > 0) {
114
+ interactionLines.push(line);
115
+ }
116
+ context.contents = "Initial";
117
+ interactionHasSceneHeader.contents = false;
118
+ return;
119
+ case "InteractionSelector" :
120
+ let sceneId$1 = currentSceneId.contents;
121
+ if (sceneId$1 !== undefined && !interactionHasSceneHeader.contents) {
122
+ interactionLines.push("@scene: " + sceneId$1);
123
+ interactionLines.push("");
124
+ interactionHasSceneHeader.contents = true;
125
+ }
126
+ interactionLines.push(line);
127
+ context.contents = "InInteraction";
128
+ return;
129
+ case "InteractionProperty" :
130
+ if (context.contents === "InInteraction") {
131
+ interactionLines.push(line);
132
+ return;
133
+ } else {
134
+ return;
135
+ }
136
+ case "EmptyLine" :
137
+ let match = context.contents;
138
+ switch (match) {
139
+ case "Initial" :
140
+ return;
141
+ case "InWireframe" :
142
+ wireframeLines.push(line);
143
+ return;
144
+ case "InInteraction" :
145
+ interactionLines.push(line);
146
+ return;
147
+ }
148
+ case "Noise" :
149
+ return;
150
+ }
151
+ });
152
+ return {
153
+ wireframe: wireframeLines.join("\n"),
154
+ interactions: interactionLines.join("\n").trim()
155
+ };
156
+ }
157
+
158
+ function hasWireframe(text) {
159
+ let lines = text.split("\n");
160
+ return lines.some(isWireframeLine);
161
+ }
162
+
163
+ function hasInteractions(text) {
164
+ let lines = text.split("\n");
165
+ return lines.some(isInteractionSelector);
166
+ }
167
+
168
+ export {
169
+ isWireframeLine,
170
+ isDirectiveLine,
171
+ isSceneSeparator,
172
+ isInteractionSelector,
173
+ isInteractionProperty,
174
+ classifyLine,
175
+ extract,
176
+ hasWireframe,
177
+ hasInteractions,
178
+ }
179
+ /* No side effect */
@@ -0,0 +1,264 @@
1
+ // TextExtractor.res
2
+ // Intelligent extraction of wireframe and interaction content from mixed text
3
+ // Supports markdown, comments, and other noise in the input
4
+
5
+ // ============================================================================
6
+ // Pattern Detection
7
+ // ============================================================================
8
+
9
+ /**
10
+ * Check if a line is part of an ASCII wireframe.
11
+ * Matches: +---+, |...|, +===+, box borders
12
+ */
13
+ let isWireframeLine = (line: string): bool => {
14
+ let trimmed = line->String.trim
15
+
16
+ // Empty lines within wireframe context are preserved
17
+ if trimmed === "" {
18
+ false // Will be handled by context
19
+ } else {
20
+ // Box top/bottom border: +---+ or +===+
21
+ let boxBorder = %re("/^\+[-=]+\+/")
22
+ // Box side with content: | ... |
23
+ let boxSide = %re("/^\|.*\|$/")
24
+ // Named box border: +--Name--+
25
+ let namedBorder = %re("/^\+--[^-].*--\+/")
26
+ // Partial box patterns (for multi-line detection)
27
+ let startsWithPlus = trimmed->String.startsWith("+")
28
+ let startsWithPipe = trimmed->String.startsWith("|")
29
+
30
+ Js.Re.test_(boxBorder, trimmed) ||
31
+ Js.Re.test_(boxSide, trimmed) ||
32
+ Js.Re.test_(namedBorder, trimmed) ||
33
+ (startsWithPlus && trimmed->String.includes("-")) ||
34
+ (startsWithPipe && trimmed->String.endsWith("|"))
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Check if a line is a scene/title/device directive.
40
+ * Matches: @scene:, @title:, @device:, @transition:
41
+ */
42
+ let isDirectiveLine = (line: string): bool => {
43
+ let trimmed = line->String.trim
44
+ trimmed->String.startsWith("@scene:") ||
45
+ trimmed->String.startsWith("@title:") ||
46
+ trimmed->String.startsWith("@device:") ||
47
+ trimmed->String.startsWith("@transition:")
48
+ }
49
+
50
+ /**
51
+ * Check if a line is a scene separator.
52
+ */
53
+ let isSceneSeparator = (line: string): bool => {
54
+ line->String.trim === "---"
55
+ }
56
+
57
+ /**
58
+ * Check if a line starts an interaction block.
59
+ * Matches: #id:, [Button]:, "Link":
60
+ */
61
+ let isInteractionSelector = (line: string): bool => {
62
+ let trimmed = line->String.trim
63
+
64
+ // Input selector: #email:
65
+ let inputSelector = %re("/^#[\w-]+:$/")
66
+ // Button selector: [Button Text]:
67
+ let buttonSelector = %re("/^\[.+\]:$/")
68
+ // Link selector: "Link Text":
69
+ let linkSelector = %re("/^\"[^\"]+\":$/")
70
+
71
+ Js.Re.test_(inputSelector, trimmed) ||
72
+ Js.Re.test_(buttonSelector, trimmed) ||
73
+ Js.Re.test_(linkSelector, trimmed)
74
+ }
75
+
76
+ /**
77
+ * Check if a line is an indented interaction property.
78
+ * Matches: lines starting with 2+ spaces followed by property
79
+ */
80
+ let isInteractionProperty = (line: string): bool => {
81
+ // Must start with whitespace (indentation)
82
+ let hasIndent = line->String.length > 0 &&
83
+ (line->String.startsWith(" ") || line->String.startsWith("\t"))
84
+
85
+ if !hasIndent {
86
+ false
87
+ } else {
88
+ let trimmed = line->String.trim
89
+ // Property patterns: key: value, @click -> action
90
+ let propertyPattern = %re("/^[\w@-]+:?\s*(->|\S)/")
91
+ trimmed !== "" && Js.Re.test_(propertyPattern, trimmed)
92
+ }
93
+ }
94
+
95
+ // ============================================================================
96
+ // Content Classification
97
+ // ============================================================================
98
+
99
+ type lineType =
100
+ | Wireframe
101
+ | Directive
102
+ | SceneSeparator
103
+ | InteractionSelector
104
+ | InteractionProperty
105
+ | EmptyLine
106
+ | Noise
107
+
108
+ /**
109
+ * Classify a single line.
110
+ */
111
+ let classifyLine = (line: string): lineType => {
112
+ let trimmed = line->String.trim
113
+
114
+ if trimmed === "" {
115
+ EmptyLine
116
+ } else if isSceneSeparator(line) {
117
+ SceneSeparator
118
+ } else if isDirectiveLine(line) {
119
+ Directive
120
+ } else if isInteractionSelector(line) {
121
+ InteractionSelector
122
+ } else if isInteractionProperty(line) {
123
+ InteractionProperty
124
+ } else if isWireframeLine(line) {
125
+ Wireframe
126
+ } else {
127
+ Noise
128
+ }
129
+ }
130
+
131
+ // ============================================================================
132
+ // Extraction State Machine
133
+ // ============================================================================
134
+
135
+ type extractionContext =
136
+ | Initial
137
+ | InWireframe
138
+ | InInteraction
139
+
140
+ type extractedContent = {
141
+ wireframe: string,
142
+ interactions: string,
143
+ }
144
+
145
+ /**
146
+ * Extract wireframe and interaction content from mixed text.
147
+ *
148
+ * Algorithm:
149
+ * 1. Process line by line with context awareness
150
+ * 2. Directives and scene separators go to wireframe
151
+ * 3. ASCII box patterns go to wireframe
152
+ * 4. Selector + indented properties go to interactions
153
+ * 5. Empty lines are context-sensitive
154
+ * 6. Everything else is ignored (noise)
155
+ */
156
+ let extract = (text: string): extractedContent => {
157
+ let lines = text->String.split("\n")
158
+
159
+ let wireframeLines: array<string> = []
160
+ let interactionLines: array<string> = []
161
+ let context = ref(Initial)
162
+ let currentSceneId = ref(None)
163
+ let interactionHasSceneHeader = ref(false)
164
+
165
+ lines->Array.forEach(line => {
166
+ let lineType = classifyLine(line)
167
+
168
+ switch lineType {
169
+ | SceneSeparator => {
170
+ // Scene separator goes to both
171
+ wireframeLines->Array.push(line)->ignore
172
+ if interactionLines->Array.length > 0 {
173
+ interactionLines->Array.push(line)->ignore
174
+ }
175
+ context := Initial
176
+ interactionHasSceneHeader := false
177
+ }
178
+
179
+ | Directive => {
180
+ // Directives go to wireframe
181
+ wireframeLines->Array.push(line)->ignore
182
+
183
+ // Track current scene for interactions
184
+ let trimmed = line->String.trim
185
+ if trimmed->String.startsWith("@scene:") {
186
+ let sceneId = trimmed
187
+ ->String.sliceToEnd(~start=7)
188
+ ->String.trim
189
+ currentSceneId := Some(sceneId)
190
+ interactionHasSceneHeader := false
191
+ }
192
+
193
+ context := InWireframe
194
+ }
195
+
196
+ | Wireframe => {
197
+ wireframeLines->Array.push(line)->ignore
198
+ context := InWireframe
199
+ }
200
+
201
+ | InteractionSelector => {
202
+ // Add scene header to interactions if needed
203
+ switch currentSceneId.contents {
204
+ | Some(sceneId) if !interactionHasSceneHeader.contents => {
205
+ interactionLines->Array.push("@scene: " ++ sceneId)->ignore
206
+ interactionLines->Array.push("")->ignore
207
+ interactionHasSceneHeader := true
208
+ }
209
+ | _ => ()
210
+ }
211
+
212
+ interactionLines->Array.push(line)->ignore
213
+ context := InInteraction
214
+ }
215
+
216
+ | InteractionProperty => {
217
+ // Only add if we're in an interaction context
218
+ if context.contents === InInteraction {
219
+ interactionLines->Array.push(line)->ignore
220
+ }
221
+ }
222
+
223
+ | EmptyLine => {
224
+ // Empty lines are context-sensitive
225
+ switch context.contents {
226
+ | InWireframe => wireframeLines->Array.push(line)->ignore
227
+ | InInteraction => interactionLines->Array.push(line)->ignore
228
+ | Initial => () // Ignore leading empty lines
229
+ }
230
+ }
231
+
232
+ | Noise => {
233
+ // Ignore noise (markdown, comments, etc.)
234
+ // But preserve empty-like structure in wireframe context
235
+ if context.contents === InWireframe {
236
+ // Check if this might be text content inside a box
237
+ // (text that doesn't match wireframe patterns but is between | |)
238
+ ()
239
+ }
240
+ }
241
+ }
242
+ })
243
+
244
+ {
245
+ wireframe: wireframeLines->Array.join("\n"),
246
+ interactions: interactionLines->Array.join("\n")->String.trim,
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Check if text contains any wireframe content.
252
+ */
253
+ let hasWireframe = (text: string): bool => {
254
+ let lines = text->String.split("\n")
255
+ lines->Array.some(line => isWireframeLine(line))
256
+ }
257
+
258
+ /**
259
+ * Check if text contains any interaction content.
260
+ */
261
+ let hasInteractions = (text: string): bool => {
262
+ let lines = text->String.split("\n")
263
+ lines->Array.some(line => isInteractionSelector(line))
264
+ }