@round-core/shared 1.0.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 (2) hide show
  1. package/package.json +7 -0
  2. package/src/index.js +205 -0
package/package.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "@round-core/shared",
3
+ "version": "1.0.0",
4
+ "description": "Shared RoundJS parsing and preprocessing logic",
5
+ "main": "src/index.js",
6
+ "type": "module"
7
+ }
package/src/index.js ADDED
@@ -0,0 +1,205 @@
1
+ // Helper: Source Mapper
2
+ export class SourceMapper {
3
+ constructor() {
4
+ this.mappings = []; // [{ gen, orig }]
5
+ this.code = '';
6
+ }
7
+
8
+ add(generatedText, originalOffset) {
9
+ const genStart = this.code.length;
10
+ this.code += generatedText;
11
+ this.mappings.push({
12
+ gen: [genStart, this.code.length],
13
+ orig: originalOffset
14
+ });
15
+ }
16
+
17
+ remap(genOffset) {
18
+ for (const m of this.mappings) {
19
+ if (genOffset >= m.gen[0] && genOffset < m.gen[1]) {
20
+ const offsetInBlock = genOffset - m.gen[0];
21
+ return m.orig + offsetInBlock;
22
+ }
23
+ }
24
+ // Fallback: finding the closest mapping if exactly on boundaries
25
+ if (this.mappings.length > 0) {
26
+ const last = this.mappings[this.mappings.length-1];
27
+ if (genOffset >= last.gen[1]) return last.orig + (last.gen[1] - last.gen[0]);
28
+ }
29
+ return genOffset;
30
+ }
31
+ }
32
+
33
+ export function findBlockEnd(text, startIndex) {
34
+ let depth = 0;
35
+ let i = startIndex;
36
+ if (text[i] !== '{') return -1;
37
+
38
+ let inString = false;
39
+ let quote = '';
40
+ let inLineComment = false;
41
+ let inBlockComment = false;
42
+
43
+ while (i < text.length) {
44
+ const c = text[i];
45
+ const next = text[i+1] || '';
46
+
47
+ if (inLineComment) {
48
+ if (c === '\n') inLineComment = false;
49
+ } else if (inBlockComment) {
50
+ if (c === '*' && next === '/') { inBlockComment = false; i++; }
51
+ } else if (inString) {
52
+ if (c === quote && text[i-1] !== '\\') inString = false;
53
+ else if ((quote === '"' || quote === "'") && c === '\n') inString = false;
54
+ } else {
55
+ if (c === '/' && next === '/') inLineComment = true;
56
+ else if (c === '/' && next === '*') inBlockComment = true;
57
+ else if (c === '"' || c === "'" || c === '`') { inString = true; quote = c; }
58
+ else if (c === '{') depth++;
59
+ else if (c === '}') {
60
+ depth--;
61
+ if (depth === 0) return i + 1;
62
+ }
63
+ }
64
+ i++;
65
+ }
66
+ return -1;
67
+ }
68
+
69
+ // Preprocess: Updates mapper and returns processed code (via mapper.code)
70
+ export function runPreprocess(text, mapper, globalOffset) {
71
+ let i = 0;
72
+
73
+ let inString = false;
74
+ let quote = '';
75
+ let inLineComment = false;
76
+ let inBlockComment = false;
77
+
78
+ while (i < text.length) {
79
+ const c = text[i];
80
+ const next = text[i+1] || '';
81
+
82
+ if (inLineComment) {
83
+ if (c === '\n') inLineComment = false;
84
+ } else if (inBlockComment) {
85
+ if (c === '*' && next === '/') { inBlockComment = false; mapper.add('*/', globalOffset + i); i+=2; continue; }
86
+ } else if (inString) {
87
+ if (c === quote && text[i-1] !== '\\') inString = false;
88
+ else if ((quote === '"' || quote === "'") && c === '\n') inString = false;
89
+ } else {
90
+ if (c === '/' && next === '/') { inLineComment = true; }
91
+ else if (c === '/' && next === '*') { inBlockComment = true; }
92
+ else if (c === '"' || c === "'" || c === '`') { inString = true; quote = c; }
93
+ else if (c === '{') {
94
+ const match = text.slice(i).match(/^\{\s*(if|else\s+if|else-if|else|for|switch|try|catch|finally)\b/);
95
+ if (match) {
96
+ const end = findBlockEnd(text, i);
97
+ if (end !== -1) {
98
+ const content = text.slice(i + 1, end - 1);
99
+
100
+ let p = 0;
101
+ let validChain = true;
102
+
103
+ const chainSegments = [];
104
+
105
+ // Parse Chain Loop
106
+ while (p < content.length) {
107
+ while (p < content.length && /\s/.test(content[p])) p++;
108
+ if (p >= content.length) break;
109
+
110
+ const sub = content.slice(p);
111
+ const keyMatch = sub.match(/^(if|else\s+if|else-if|else|for|switch|try|catch|finally)\b/);
112
+
113
+ if (!keyMatch) { validChain = false; break; }
114
+
115
+ const keyword = keyMatch[1].replace('-', ' ');
116
+ const partStart = p;
117
+ p += keyMatch[0].length;
118
+
119
+ let head = '';
120
+ let attrs = '';
121
+ let bodyContent = '';
122
+ let bodyStartI = -1;
123
+ let bodyEndI = -1;
124
+
125
+ // Head
126
+ while (p < content.length && /\s/.test(content[p])) p++;
127
+ if (p < content.length && content[p] === '(') {
128
+ const headStart = p;
129
+ let pDepth = 1, h = p + 1;
130
+ let inS=false, qt='';
131
+ while(h < content.length && pDepth > 0) {
132
+ if (inS) { if(content[h]===qt && content[h-1]!=='\\') inS=false; }
133
+ else if ('"\''.includes(content[h])) { inS=true; qt=content[h]; }
134
+ else if (content[h] === '(') pDepth++;
135
+ else if (content[h] === ')') pDepth--;
136
+ h++;
137
+ }
138
+ if (pDepth === 0) { head = content.slice(headStart, h); p = h; }
139
+ }
140
+
141
+ // Attrs
142
+ const attrStart = p;
143
+ while (p < content.length) {
144
+ if (content[p] === '{') {
145
+ let back = p - 1;
146
+ while (back >= attrStart && /\s/.test(content[back])) back--;
147
+ if (back >= attrStart && content[back] === '=') {
148
+ const bEnd = findBlockEnd(content, p);
149
+ if (bEnd !== -1) { p = bEnd; continue; }
150
+ } else { break; }
151
+ }
152
+ p++;
153
+ }
154
+ attrs = content.slice(attrStart, p).trim();
155
+
156
+ const guard = /\b(return|throw|function|const|let|var|if|for|while|class|import|export|raise)\b/;
157
+ if (guard.test(attrs) || attrs.includes(';')) { validChain=false; break; }
158
+
159
+ // Body
160
+ if (p < content.length && content[p] === '{') {
161
+ const bEnd = findBlockEnd(content, p);
162
+ if (bEnd !== -1) {
163
+ bodyContent = content.slice(p + 1, bEnd - 1);
164
+ bodyStartI = p + 1;
165
+ bodyEndI = bEnd - 1;
166
+ p = bEnd;
167
+ } else { validChain=false; break; }
168
+ } else { validChain=false; break; }
169
+
170
+ chainSegments.push({ keyword, head, attrs, bodyContent, origStart: partStart,
171
+ bodyRef: { start: bodyStartI, end: bodyEndI } });
172
+ }
173
+
174
+ if (validChain) {
175
+ chainSegments.forEach((seg) => {
176
+ // For head, we use curly braces {} so ESLint can see variable usages
177
+ // We strip outer parens as we'll add them back in the printer to be stable
178
+ const cleanHead = seg.head.trim().replace(/^\(|\)$/g, '');
179
+ const headPart = cleanHead ? `head={${cleanHead}}` : '';
180
+ const safeAttrs = seg.attrs.replace(/"/g, '&quot;');
181
+ const normKind = seg.keyword;
182
+
183
+ const openTag = `<RoundControlFlow kind="${normKind}" ${headPart} _attrs="${safeAttrs}">`;
184
+ const segOrigStart = globalOffset + i + 1 + (seg.origStart || 0);
185
+
186
+ mapper.add(openTag, segOrigStart);
187
+
188
+ const bodyOrigOffset = globalOffset + i + 1 + seg.bodyRef.start;
189
+ runPreprocess(seg.bodyContent, mapper, bodyOrigOffset);
190
+
191
+ mapper.add('</RoundControlFlow>', bodyOrigOffset + seg.bodyContent.length);
192
+ });
193
+
194
+ i = end;
195
+ continue;
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+
202
+ mapper.add(text[i], globalOffset + i);
203
+ i++;
204
+ }
205
+ }