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,104 @@
1
+ // AlignmentCalc.res
2
+ // Alignment calculation module for determining element positioning within boxes
3
+
4
+ open Types
5
+
6
+ /**
7
+ * Calculates the horizontal alignment of an element based on its position
8
+ * within a box's boundaries.
9
+ *
10
+ * Algorithm:
11
+ * 1. Calculate the space to the left and right of the content
12
+ * 2. Convert these to ratios relative to the box interior width
13
+ * 3. Apply threshold rules to determine alignment:
14
+ * - Left: Element is close to left edge (leftRatio < 0.2 && rightRatio > 0.3)
15
+ * - Right: Element is close to right edge (rightRatio < 0.2 && leftRatio > 0.3)
16
+ * - Center: Element is roughly centered (abs(leftRatio - rightRatio) < 0.15)
17
+ * - Default to Left if no conditions match
18
+ *
19
+ * @param content The text content of the element
20
+ * @param position The grid position where the content starts
21
+ * @param boxBounds The bounding box containing the element
22
+ * @return alignment The calculated alignment (Left, Center, or Right)
23
+ */
24
+ let calculate = (
25
+ content: string,
26
+ position: Position.t,
27
+ boxBounds: Bounds.t,
28
+ ): Types.alignment => {
29
+ // Trim content to get actual content length
30
+ let trimmed = content->String.trim
31
+ let contentStart = position.col
32
+ let contentEnd = contentStart + String.length(trimmed)
33
+
34
+ // Box interior bounds (excluding border characters '|' on both sides)
35
+ // boxBounds.left is the column of the left '|', so interior starts at left + 1
36
+ // boxBounds.right is the column of the right '|', so interior ends at right - 1
37
+ let boxLeft = boxBounds.left + 1
38
+ let boxRight = boxBounds.right - 1
39
+ let boxWidth = boxRight - boxLeft
40
+
41
+ // Handle edge case: box is too narrow (width <= 0)
42
+ if boxWidth <= 0 {
43
+ Left
44
+ } else {
45
+ // Calculate space on each side
46
+ let leftSpace = contentStart - boxLeft
47
+ let rightSpace = boxRight - contentEnd
48
+
49
+ // Convert to ratios (0.0 to 1.0) relative to box width
50
+ let leftRatio = Int.toFloat(leftSpace) /. Int.toFloat(boxWidth)
51
+ let rightRatio = Int.toFloat(rightSpace) /. Int.toFloat(boxWidth)
52
+
53
+ // Thresholds for alignment detection
54
+ let leftThreshold = 0.2
55
+ let rightThreshold = 0.2
56
+ let centerTolerance = 0.15
57
+
58
+ // Apply alignment rules
59
+ if leftRatio < leftThreshold && rightRatio > 0.3 {
60
+ // Element is close to left edge with significant space on right
61
+ Left
62
+ } else if rightRatio < rightThreshold && leftRatio > 0.3 {
63
+ // Element is close to right edge with significant space on left
64
+ Right
65
+ } else if Math.abs(leftRatio -. rightRatio) < centerTolerance {
66
+ // Element is roughly centered (ratios are similar)
67
+ Center
68
+ } else {
69
+ // Default to left alignment
70
+ Left
71
+ }
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Alignment strategy determines how alignment should be calculated.
77
+ *
78
+ * - RespectPosition: Use position-based alignment calculation (buttons, links, emphasis)
79
+ * - AlwaysLeft: Always return Left alignment regardless of position (text, checkboxes, inputs)
80
+ */
81
+ type alignmentStrategy =
82
+ | RespectPosition // Buttons, links, emphasis text
83
+ | AlwaysLeft // Regular text, checkboxes, inputs
84
+
85
+ /**
86
+ * Calculates alignment with a specific strategy.
87
+ *
88
+ * @param content The text content of the element
89
+ * @param position The grid position where the content starts
90
+ * @param boxBounds The bounding box containing the element
91
+ * @param strategy The alignment strategy to use
92
+ * @return alignment The calculated alignment
93
+ */
94
+ let calculateWithStrategy = (
95
+ content: string,
96
+ position: Position.t,
97
+ boxBounds: Bounds.t,
98
+ strategy: alignmentStrategy,
99
+ ): Types.alignment => {
100
+ switch strategy {
101
+ | RespectPosition => calculate(content, position, boxBounds)
102
+ | AlwaysLeft => Left
103
+ }
104
+ }
@@ -0,0 +1,58 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Types from "../../Core/Types.mjs";
4
+ import * as AlignmentCalc from "../AlignmentCalc.mjs";
5
+ import * as ElementParser from "./ElementParser.mjs";
6
+
7
+ function slugify(text) {
8
+ return text.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
9
+ }
10
+
11
+ let buttonPattern = /^\s*\[\s*([^\]]+?)\s*\]\s*$/;
12
+
13
+ let quickTestPattern = /\[.*\]/;
14
+
15
+ function canParse(content) {
16
+ return quickTestPattern.test(content);
17
+ }
18
+
19
+ function parse(content, position, bounds) {
20
+ let result = buttonPattern.exec(content);
21
+ if (result == null) {
22
+ return;
23
+ }
24
+ let matches = result.slice(1);
25
+ let buttonText = matches[0];
26
+ if (buttonText === undefined) {
27
+ return;
28
+ }
29
+ let buttonText$1 = buttonText.trim();
30
+ let lowerText = buttonText$1.toLowerCase();
31
+ if (buttonText$1 === "" || lowerText === "x") {
32
+ return;
33
+ }
34
+ let buttonId = slugify(buttonText$1);
35
+ let align = AlignmentCalc.calculateWithStrategy(content, position, bounds, "RespectPosition");
36
+ return {
37
+ TAG: "Button",
38
+ id: buttonId,
39
+ text: buttonText$1,
40
+ position: Types.Position.make(position.row, position.col),
41
+ align: align,
42
+ actions: []
43
+ };
44
+ }
45
+
46
+ function make() {
47
+ return ElementParser.make(100, canParse, parse);
48
+ }
49
+
50
+ export {
51
+ slugify,
52
+ buttonPattern,
53
+ quickTestPattern,
54
+ canParse,
55
+ parse,
56
+ make,
57
+ }
58
+ /* Types Not a pure module */
@@ -0,0 +1,131 @@
1
+ // ButtonParser.res
2
+
3
+ open Types
4
+ // Parser for button syntax: [ Text ]
5
+ //
6
+ // Recognizes button elements in wireframes and extracts their text content.
7
+ // Buttons are defined with square brackets containing text: [ Submit ], [ Cancel ], etc.
8
+
9
+ /**
10
+ * Slugify a string to create a valid identifier.
11
+ * Converts text to lowercase, replaces spaces and special chars with hyphens.
12
+ *
13
+ * Examples:
14
+ * - "Submit Form" -> "submit-form"
15
+ * - "Log In" -> "log-in"
16
+ * - "Create Account!" -> "create-account"
17
+ *
18
+ * @param text - The text to slugify
19
+ * @return A slugified identifier string
20
+ */
21
+ let slugify = (text: string): string => {
22
+ text
23
+ ->String.trim
24
+ ->String.toLowerCase
25
+ // Replace spaces with hyphens
26
+ ->Js.String2.replaceByRe(%re("/\s+/g"), "-")
27
+ // Remove non-alphanumeric characters (except hyphens)
28
+ ->Js.String2.replaceByRe(%re("/[^a-z0-9-]/g"), "")
29
+ // Remove consecutive hyphens
30
+ ->Js.String2.replaceByRe(%re("/-+/g"), "-")
31
+ // Remove leading/trailing hyphens
32
+ ->Js.String2.replaceByRe(%re("/^-+|-+$/g"), "")
33
+ }
34
+
35
+ /**
36
+ * Button pattern regex: \[\s*[^\]]+\s*\]
37
+ * Matches: [ Text ] with optional whitespace
38
+ * Examples:
39
+ * - [ Submit ]
40
+ * - [Login]
41
+ * - [ Create Account ]
42
+ */
43
+ let buttonPattern = %re("/^\s*\[\s*([^\]]+?)\s*\]\s*$/")
44
+
45
+ /**
46
+ * Quick test pattern for canParse (optimized for speed).
47
+ * Just checks if content contains [ and ]
48
+ */
49
+ let quickTestPattern = %re("/\[.*\]/")
50
+
51
+ /**
52
+ * Check if content matches button syntax pattern.
53
+ *
54
+ * @param content - The text content to test
55
+ * @return true if content looks like a button
56
+ */
57
+ let canParse = (content: string): bool => {
58
+ quickTestPattern->RegExp.test(content)
59
+ }
60
+
61
+ /**
62
+ * Parse button element from content.
63
+ *
64
+ * Extracts button text from [ Text ] syntax and creates a Button element.
65
+ * Returns None if:
66
+ * - Pattern doesn't match
67
+ * - Button text is empty after trimming
68
+ *
69
+ * @param content - The text content containing button syntax
70
+ * @param position - Grid position where button appears
71
+ * @param bounds - Bounding box of containing box (for alignment calculation)
72
+ * @return Some(Button element) or None
73
+ */
74
+ let parse = (
75
+ content: string,
76
+ position: Position.t,
77
+ bounds: Bounds.t,
78
+ ): option<Types.element> => {
79
+ switch buttonPattern->RegExp.exec(content) {
80
+ | None => None
81
+ | Some(result) => {
82
+ let matches = result->RegExp.Result.matches
83
+ // RegExp.Result.matches slices off the full match, so matches[0] is the first captured group
84
+ switch matches[0] {
85
+ | None => None
86
+ | Some(buttonText) => {
87
+ let buttonText = buttonText->String.trim
88
+
89
+ // Return None for empty button text or single character 'x' (checkbox pattern)
90
+ // This allows [x] to be handled by CheckboxParser instead
91
+ let lowerText = buttonText->String.toLowerCase
92
+ if buttonText === "" || lowerText === "x" {
93
+ None
94
+ } else {
95
+ let buttonId = slugify(buttonText)
96
+
97
+ // Calculate alignment based on position within bounds
98
+ let align = AlignmentCalc.calculateWithStrategy(
99
+ content,
100
+ position,
101
+ bounds,
102
+ AlignmentCalc.RespectPosition,
103
+ )
104
+
105
+ // Create Button element
106
+ Some(
107
+ Types.Button({
108
+ id: buttonId,
109
+ text: buttonText,
110
+ position: Types.Position.make(position.row, position.col),
111
+ align: align,
112
+ actions: [],
113
+ }),
114
+ )
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Create a ButtonParser instance.
124
+ *
125
+ * Priority: 100 (high priority, checked early in the registry)
126
+ *
127
+ * @return An ElementParser configured for button recognition
128
+ */
129
+ let make = (): ElementParser.elementParser => {
130
+ ElementParser.make(~priority=100, ~canParse, ~parse)
131
+ }
@@ -0,0 +1,58 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Types from "../../Core/Types.mjs";
4
+ import * as ElementParser from "./ElementParser.mjs";
5
+
6
+ let checkedTestPattern = /\[x\]/i;
7
+
8
+ let uncheckedTestPattern = /\[\s*\]/;
9
+
10
+ let checkedParsePattern = /^\[x\]\s*(.*)/i;
11
+
12
+ let uncheckedParsePattern = /^\[\s*\]\s*(.*)/;
13
+
14
+ function make() {
15
+ return ElementParser.make(85, content => {
16
+ if (checkedTestPattern.test(content)) {
17
+ return true;
18
+ } else {
19
+ return uncheckedTestPattern.test(content);
20
+ }
21
+ }, (content, position, _bounds) => {
22
+ let trimmed = content.trim();
23
+ let result = checkedParsePattern.exec(trimmed);
24
+ if (result == null) {
25
+ let result$1 = uncheckedParsePattern.exec(trimmed);
26
+ if (result$1 == null) {
27
+ return;
28
+ }
29
+ let matches = result$1.slice(1);
30
+ let labelStr = matches[0];
31
+ let label = labelStr !== undefined ? labelStr.trim() : "";
32
+ return {
33
+ TAG: "Checkbox",
34
+ checked: false,
35
+ label: label,
36
+ position: Types.Position.make(position.row, position.col)
37
+ };
38
+ }
39
+ let matches$1 = result.slice(1);
40
+ let labelStr$1 = matches$1[0];
41
+ let label$1 = labelStr$1 !== undefined ? labelStr$1.trim() : "";
42
+ return {
43
+ TAG: "Checkbox",
44
+ checked: true,
45
+ label: label$1,
46
+ position: Types.Position.make(position.row, position.col)
47
+ };
48
+ });
49
+ }
50
+
51
+ export {
52
+ checkedTestPattern,
53
+ uncheckedTestPattern,
54
+ checkedParsePattern,
55
+ uncheckedParsePattern,
56
+ make,
57
+ }
58
+ /* Types Not a pure module */
@@ -0,0 +1,79 @@
1
+ // CheckboxParser.res
2
+ // Parser for checkbox syntax "[x]" (checked) and "[ ]" (unchecked)
3
+
4
+ /**
5
+ * Creates a checkbox element parser.
6
+ *
7
+ * Recognizes:
8
+ * - "[x]" - Checked checkbox
9
+ * - "[ ]" - Unchecked checkbox
10
+ *
11
+ * Optionally followed by label text on the same line.
12
+ *
13
+ * Priority: 85 (high - should be checked before text parser but after buttons)
14
+ *
15
+ * Examples:
16
+ * - "[x] Accept terms" -> Checkbox(checked=true, label="Accept terms")
17
+ * - "[ ] Subscribe" -> Checkbox(checked=false, label="Subscribe")
18
+ * - "[x]" -> Checkbox(checked=true, label="")
19
+ */
20
+ // Pattern for detecting checkboxes (used in canParse)
21
+ let checkedTestPattern = %re("/\[x\]/i")
22
+ let uncheckedTestPattern = %re("/\[\s*\]/")
23
+
24
+ // Patterns for parsing (capturing label)
25
+ let checkedParsePattern = %re("/^\[x\]\s*(.*)/i")
26
+ let uncheckedParsePattern = %re("/^\[\s*\]\s*(.*)/")
27
+
28
+ let make = (): ElementParser.elementParser => {
29
+ ElementParser.make(
30
+ ~priority=85,
31
+ ~canParse=content => {
32
+ // Match either [x] or [ ] patterns
33
+ checkedTestPattern->RegExp.test(content) || uncheckedTestPattern->RegExp.test(content)
34
+ },
35
+ ~parse=(content, position, _bounds) => {
36
+ let trimmed = content->String.trim
37
+
38
+ // Try to match checked checkbox [x]
39
+ switch checkedParsePattern->RegExp.exec(trimmed) {
40
+ | Some(result) => {
41
+ let matches = result->RegExp.Result.matches
42
+ let label = switch matches[0] {
43
+ | Some(labelStr) => labelStr->String.trim
44
+ | None => ""
45
+ }
46
+
47
+ Some(
48
+ Types.Checkbox({
49
+ checked: true,
50
+ label: label,
51
+ position: Types.Position.make(position.row, position.col),
52
+ })
53
+ )
54
+ }
55
+ | None => {
56
+ // Try to match unchecked checkbox [ ]
57
+ switch uncheckedParsePattern->RegExp.exec(trimmed) {
58
+ | Some(result) => {
59
+ let matches = result->RegExp.Result.matches
60
+ let label = switch matches[0] {
61
+ | Some(labelStr) => labelStr->String.trim
62
+ | None => ""
63
+ }
64
+
65
+ Some(
66
+ Types.Checkbox({
67
+ checked: false,
68
+ label: label,
69
+ position: Types.Position.make(position.row, position.col),
70
+ })
71
+ )
72
+ }
73
+ | None => None
74
+ }
75
+ }
76
+ }
77
+ }
78
+ )
79
+ }
@@ -0,0 +1,50 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Types from "../../Core/Types.mjs";
4
+ import * as AlignmentCalc from "../AlignmentCalc.mjs";
5
+ import * as ElementParser from "./ElementParser.mjs";
6
+
7
+ let quotedPattern = /^\s*'([^']+)'\s*$/;
8
+
9
+ let quickTestPattern = /'.+'/;
10
+
11
+ function canParse(content) {
12
+ return quickTestPattern.test(content);
13
+ }
14
+
15
+ function parse(content, position, bounds) {
16
+ let result = quotedPattern.exec(content);
17
+ if (result == null) {
18
+ return;
19
+ }
20
+ let matches = result.slice(1);
21
+ let quotedText = matches[0];
22
+ if (quotedText === undefined) {
23
+ return;
24
+ }
25
+ let trimmedText = quotedText.trim();
26
+ if (trimmedText === "") {
27
+ return;
28
+ }
29
+ let align = AlignmentCalc.calculateWithStrategy(content, position, bounds, "RespectPosition");
30
+ return {
31
+ TAG: "Text",
32
+ content: trimmedText,
33
+ emphasis: true,
34
+ position: Types.Position.make(position.row, position.col),
35
+ align: align
36
+ };
37
+ }
38
+
39
+ function make() {
40
+ return ElementParser.make(75, canParse, parse);
41
+ }
42
+
43
+ export {
44
+ quotedPattern,
45
+ quickTestPattern,
46
+ canParse,
47
+ parse,
48
+ make,
49
+ }
50
+ /* Types Not a pure module */
@@ -0,0 +1,111 @@
1
+ // CodeTextParser.res
2
+ // Parser for aligned text syntax: 'text'
3
+ //
4
+ // Recognizes single-quote-wrapped text and applies position-based alignment.
5
+ // Unlike regular text (which defaults to left), quoted text respects its
6
+ // position within the parent container for alignment calculation.
7
+ //
8
+ // Examples:
9
+ // - 'Submit' at center position -> Center aligned
10
+ // - 'Cancel' at left position -> Left aligned
11
+ // - 'Next' at right position -> Right aligned
12
+
13
+ open Types
14
+
15
+ /**
16
+ * Quoted text pattern regex: 'text'
17
+ * Matches text wrapped in single quotes
18
+ * Examples:
19
+ * - 'Submit'
20
+ * - 'Centered Text'
21
+ * - ' spaced '
22
+ */
23
+ let quotedPattern = %re("/^\s*'([^']+)'\s*$/")
24
+
25
+ /**
26
+ * Quick test pattern for canParse (optimized for speed).
27
+ * Just checks if content contains single quotes wrapping text
28
+ */
29
+ let quickTestPattern = %re("/'.+'/")
30
+
31
+ /**
32
+ * Check if content matches quoted text syntax pattern.
33
+ *
34
+ * @param content - The text content to test
35
+ * @return true if content looks like quoted text
36
+ */
37
+ let canParse = (content: string): bool => {
38
+ Js.Re.test_(quickTestPattern, content)
39
+ }
40
+
41
+ /**
42
+ * Parse quoted text element from content.
43
+ *
44
+ * Extracts text from 'text' syntax and creates a Text element with:
45
+ * - Position-based alignment (RespectPosition strategy)
46
+ * - emphasis: true (indicates special formatting)
47
+ *
48
+ * This allows single-quote-wrapped text to be centered or right-aligned
49
+ * based on its position within the parent container, unlike regular
50
+ * text which always defaults to left alignment.
51
+ *
52
+ * @param content - The text content containing quote syntax
53
+ * @param position - Grid position where quoted text appears
54
+ * @param bounds - Bounding box of containing box (for alignment calculation)
55
+ * @return Some(Text element) or None
56
+ */
57
+ let parse = (
58
+ content: string,
59
+ position: Position.t,
60
+ bounds: Bounds.t,
61
+ ): option<Types.element> => {
62
+ switch quotedPattern->RegExp.exec(content) {
63
+ | None => None
64
+ | Some(result) => {
65
+ let matches = result->RegExp.Result.matches
66
+ // RegExp.Result.matches slices off the full match, so matches[0] is the first captured group
67
+ switch matches[0] {
68
+ | None => None
69
+ | Some(quotedText) => {
70
+ let trimmedText = quotedText->String.trim
71
+
72
+ // Return None for empty quoted text
73
+ if trimmedText === "" {
74
+ None
75
+ } else {
76
+ // Calculate alignment based on position within bounds
77
+ // Quoted text uses RespectPosition strategy for proper alignment
78
+ let align = AlignmentCalc.calculateWithStrategy(
79
+ content,
80
+ position,
81
+ bounds,
82
+ AlignmentCalc.RespectPosition,
83
+ )
84
+
85
+ // Create Text element with emphasis=true to indicate special formatting
86
+ Some(
87
+ Types.Text({
88
+ content: trimmedText,
89
+ emphasis: true,
90
+ position: Types.Position.make(position.row, position.col),
91
+ align: align,
92
+ }),
93
+ )
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Create a CodeTextParser instance.
103
+ *
104
+ * Priority: 75 (between EmphasisParser and LinkParser)
105
+ * Higher than emphasis but lower than links/buttons
106
+ *
107
+ * @return An ElementParser configured for quoted text recognition
108
+ */
109
+ let make = (): ElementParser.elementParser => {
110
+ ElementParser.make(~priority=75, ~canParse, ~parse)
111
+ }
@@ -0,0 +1,15 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ function make(priority, canParse, parse) {
5
+ return {
6
+ priority: priority,
7
+ canParse: canParse,
8
+ parse: parse
9
+ };
10
+ }
11
+
12
+ export {
13
+ make,
14
+ }
15
+ /* No side effect */
@@ -0,0 +1,83 @@
1
+ // ElementParser.res
2
+ // Interface for all element parsers in the semantic parsing stage
3
+ //
4
+ // This module defines the contract that all element parsers must implement
5
+ // to be registered in the ParserRegistry and participate in element recognition.
6
+
7
+ open Types
8
+
9
+ /**
10
+ * Result type for parsing operations.
11
+ * Returns Some(element) if parsing succeeds, None if the pattern doesn't match.
12
+ */
13
+ type parseResult<'element> = option<'element>
14
+
15
+ /**
16
+ * ElementParser interface type.
17
+ *
18
+ * All element parsers (ButtonParser, InputParser, LinkParser, etc.) must
19
+ * implement this interface to be compatible with the ParserRegistry.
20
+ *
21
+ * Fields:
22
+ * - priority: Determines the order in which parsers are tried (higher = earlier)
23
+ * Recommended ranges: 100 (buttons), 90 (inputs), 80 (links), 70 (emphasis), 1 (text fallback)
24
+ * - canParse: Quick check function to determine if this parser can handle the content
25
+ * - parse: Full parsing function that extracts element data from content
26
+ */
27
+ type t<'position, 'bounds, 'element> = {
28
+ /**
29
+ * Priority value for parser ordering.
30
+ * Higher priority parsers are checked first.
31
+ * Use 1 for fallback parsers, 100+ for specific element types.
32
+ */
33
+ priority: int,
34
+
35
+ /**
36
+ * Quick pattern matching function.
37
+ * Returns true if this parser can handle the given content string.
38
+ * Should be fast and use simple pattern matching (regex test, string contains, etc.)
39
+ *
40
+ * @param content - The text content to check
41
+ * @return true if this parser should attempt to parse the content
42
+ */
43
+ canParse: string => bool,
44
+
45
+ /**
46
+ * Full parsing function.
47
+ * Extracts element data from content and returns a structured element.
48
+ * Only called if canParse() returns true.
49
+ *
50
+ * @param content - The text content to parse
51
+ * @param position - Position in the grid where this content was found
52
+ * @param bounds - Bounding box of the containing box (for alignment calculation)
53
+ * @return Some(element) if parsing succeeds, None if it fails
54
+ */
55
+ parse: (string, 'position, 'bounds) => parseResult<'element>,
56
+ }
57
+
58
+ /**
59
+ * Concrete type alias for element parsers using the actual types.
60
+ * This will be used by the ParserRegistry and concrete parser implementations.
61
+ *
62
+ * Note: This assumes Position.t, Bounds.t, and element types are defined in Core/Types.res
63
+ * The generic version above allows for testing without those dependencies.
64
+ */
65
+ type elementParser = t<Position.t, Bounds.t, Types.element>
66
+
67
+ /**
68
+ * Helper function to create an element parser.
69
+ *
70
+ * @param priority - Priority value for ordering
71
+ * @param canParse - Pattern matching function
72
+ * @param parse - Full parsing function
73
+ * @return An ElementParser.t instance
74
+ */
75
+ let make = (
76
+ ~priority: int,
77
+ ~canParse: string => bool,
78
+ ~parse: (string, Position.t, Bounds.t) => parseResult<Types.element>,
79
+ ): elementParser => {
80
+ priority,
81
+ canParse,
82
+ parse,
83
+ }