juxscript 1.1.56 → 1.1.58
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.
- package/lib/utils/codeparser.d.ts +1 -1
- package/lib/utils/codeparser.d.ts.map +1 -1
- package/lib/utils/codeparser.js +229 -27
- package/lib/utils/codeparser.ts +245 -27
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ export interface ParsedLine {
|
|
|
8
8
|
*/
|
|
9
9
|
declare function escapeHtml(text: string): string;
|
|
10
10
|
/**
|
|
11
|
-
* Parse code into lines
|
|
11
|
+
* Parse code into lines - CHARACTER-BY-CHARACTER TOKENIZATION
|
|
12
12
|
*/
|
|
13
13
|
export declare function parseCode(code: string, language?: string): ParsedLine[];
|
|
14
14
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codeparser.d.ts","sourceRoot":"","sources":["codeparser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"codeparser.d.ts","sourceRoot":"","sources":["codeparser.ts"],"names":[],"mappings":"AA2DA,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOxC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAqB,GAAG,UAAU,EAAE,CAQrF;AAiLD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAEnE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAkG9C;;;;;;;AAED,wBAKE"}
|
package/lib/utils/codeparser.js
CHANGED
|
@@ -12,6 +12,43 @@ const KEYWORDS = new Set([
|
|
|
12
12
|
'!=', '!==', '+', '-', '*', '/', '%', '++', '--', '&&', '||', '!', '<', '>',
|
|
13
13
|
'<=', '>=', '=>', '...', '.', ',', '(', ')', '{', '}', '[', ']', ':', '?'
|
|
14
14
|
]);
|
|
15
|
+
/**
|
|
16
|
+
* Categorized token sets for semantic highlighting
|
|
17
|
+
*/
|
|
18
|
+
// Control flow keywords (loops, conditionals, etc.)
|
|
19
|
+
const CONTROL_FLOW = new Set([
|
|
20
|
+
'if', 'else', 'switch', 'case', 'default',
|
|
21
|
+
'for', 'while', 'do', 'break', 'continue',
|
|
22
|
+
'return', 'throw', 'try', 'catch', 'finally'
|
|
23
|
+
]);
|
|
24
|
+
// Declaration keywords
|
|
25
|
+
const DECLARATIONS = new Set([
|
|
26
|
+
'const', 'let', 'var', 'function', 'class',
|
|
27
|
+
'import', 'export', 'from', 'as',
|
|
28
|
+
'interface', 'type', 'enum', 'namespace',
|
|
29
|
+
'static', 'get', 'set', 'extends'
|
|
30
|
+
]);
|
|
31
|
+
// Operator keywords
|
|
32
|
+
const OPERATORS = new Set([
|
|
33
|
+
'new', 'delete', 'typeof', 'instanceof',
|
|
34
|
+
'void', 'await', 'yield', 'in', 'of'
|
|
35
|
+
]);
|
|
36
|
+
// Special keywords
|
|
37
|
+
const SPECIAL = new Set([
|
|
38
|
+
'this', 'super', 'async', 'debugger', 'with'
|
|
39
|
+
]);
|
|
40
|
+
// JUX-specific keywords
|
|
41
|
+
const JUX_KEYWORDS = new Set([
|
|
42
|
+
'jux', 'state', 'registry', 'render', 'bind', 'sync'
|
|
43
|
+
]);
|
|
44
|
+
// Combine all keywords for quick lookup
|
|
45
|
+
const ALL_KEYWORDS = new Set([
|
|
46
|
+
...CONTROL_FLOW,
|
|
47
|
+
...DECLARATIONS,
|
|
48
|
+
...OPERATORS,
|
|
49
|
+
...SPECIAL,
|
|
50
|
+
...JUX_KEYWORDS
|
|
51
|
+
]);
|
|
15
52
|
/**
|
|
16
53
|
* Escape HTML entities
|
|
17
54
|
*/
|
|
@@ -24,47 +61,176 @@ function escapeHtml(text) {
|
|
|
24
61
|
.replace(/'/g, ''');
|
|
25
62
|
}
|
|
26
63
|
/**
|
|
27
|
-
* Parse code into lines
|
|
64
|
+
* Parse code into lines - CHARACTER-BY-CHARACTER TOKENIZATION
|
|
28
65
|
*/
|
|
29
66
|
export function parseCode(code, language = 'javascript') {
|
|
30
67
|
const lines = code.split('\n');
|
|
31
68
|
return lines.map((line, index) => ({
|
|
32
69
|
lineNumber: index + 1,
|
|
33
|
-
html:
|
|
70
|
+
html: tokenizeLine(line),
|
|
34
71
|
raw: line
|
|
35
72
|
}));
|
|
36
73
|
}
|
|
37
74
|
/**
|
|
38
|
-
*
|
|
75
|
+
* Get the appropriate CSS class for a keyword
|
|
76
|
+
*/
|
|
77
|
+
function getKeywordClass(word) {
|
|
78
|
+
if (CONTROL_FLOW.has(word))
|
|
79
|
+
return 'token-control';
|
|
80
|
+
if (DECLARATIONS.has(word))
|
|
81
|
+
return 'token-declaration';
|
|
82
|
+
if (OPERATORS.has(word))
|
|
83
|
+
return 'token-operator-keyword';
|
|
84
|
+
if (SPECIAL.has(word))
|
|
85
|
+
return 'token-special';
|
|
86
|
+
if (JUX_KEYWORDS.has(word))
|
|
87
|
+
return 'token-jux';
|
|
88
|
+
return 'token-keyword'; // fallback
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if character is a structural punctuation (braces, brackets, parens)
|
|
39
92
|
*/
|
|
40
|
-
function
|
|
93
|
+
function isStructural(char) {
|
|
94
|
+
return '(){}[]'.includes(char);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if character is a delimiter (semicolon, comma, colon)
|
|
98
|
+
*/
|
|
99
|
+
function isDelimiter(char) {
|
|
100
|
+
return ';,:'.includes(char);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if character is an operator
|
|
104
|
+
*/
|
|
105
|
+
function isOperator(char) {
|
|
106
|
+
return '+-*/%=<>!&|^~?'.includes(char);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Tokenize a single line character-by-character with semantic categories
|
|
110
|
+
*/
|
|
111
|
+
function tokenizeLine(line) {
|
|
41
112
|
if (!line.trim())
|
|
42
113
|
return ' ';
|
|
43
|
-
// ✅ Split line into tokens (words, operators, whitespace)
|
|
44
|
-
const tokens = line.match(/\w+|[^\w\s]|\s+/g) || [];
|
|
45
114
|
let result = '';
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
115
|
+
let i = 0;
|
|
116
|
+
const len = line.length;
|
|
117
|
+
// Check for comment at start
|
|
118
|
+
if (line.trim().startsWith('//')) {
|
|
119
|
+
return `<span class="token-comment">${escapeHtml(line)}</span>`;
|
|
120
|
+
}
|
|
121
|
+
while (i < len) {
|
|
122
|
+
const char = line[i];
|
|
123
|
+
// Whitespace - preserve
|
|
124
|
+
if (/\s/.test(char)) {
|
|
125
|
+
result += char;
|
|
126
|
+
i++;
|
|
127
|
+
continue;
|
|
54
128
|
}
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
129
|
+
// Comment (// or /* */)
|
|
130
|
+
if (char === '/' && i + 1 < len) {
|
|
131
|
+
if (line[i + 1] === '/') {
|
|
132
|
+
// Line comment - consume rest of line
|
|
133
|
+
const comment = line.slice(i);
|
|
134
|
+
result += `<span class="token-comment">${escapeHtml(comment)}</span>`;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
else if (line[i + 1] === '*') {
|
|
138
|
+
// Block comment start
|
|
139
|
+
const commentEnd = line.indexOf('*/', i + 2);
|
|
140
|
+
if (commentEnd !== -1) {
|
|
141
|
+
const comment = line.slice(i, commentEnd + 2);
|
|
142
|
+
result += `<span class="token-comment">${escapeHtml(comment)}</span>`;
|
|
143
|
+
i = commentEnd + 2;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
60
147
|
}
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
148
|
+
// String literals (single, double, backtick)
|
|
149
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
150
|
+
const stringEnd = line.indexOf(char, i + 1);
|
|
151
|
+
if (stringEnd !== -1) {
|
|
152
|
+
const str = line.slice(i, stringEnd + 1);
|
|
153
|
+
result += `<span class="token-string">${escapeHtml(str)}</span>`;
|
|
154
|
+
i = stringEnd + 1;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Word (identifier or keyword)
|
|
159
|
+
if (/[a-zA-Z_$]/.test(char)) {
|
|
160
|
+
const word = consumeWord(line, i);
|
|
161
|
+
const escaped = escapeHtml(word);
|
|
162
|
+
if (ALL_KEYWORDS.has(word)) {
|
|
163
|
+
const tokenClass = getKeywordClass(word);
|
|
164
|
+
result += `<span class="${tokenClass}">${escaped}</span>`;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
result += escaped;
|
|
168
|
+
}
|
|
169
|
+
i += word.length;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// Number
|
|
173
|
+
if (/\d/.test(char)) {
|
|
174
|
+
const num = consumeNumber(line, i);
|
|
175
|
+
result += `<span class="token-number">${escapeHtml(num)}</span>`;
|
|
176
|
+
i += num.length;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
// Structural punctuation (braces, brackets, parens)
|
|
180
|
+
if (isStructural(char)) {
|
|
181
|
+
result += `<span class="token-structural">${escapeHtml(char)}</span>`;
|
|
182
|
+
i++;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
// Delimiters (semicolon, comma, colon)
|
|
186
|
+
if (isDelimiter(char)) {
|
|
187
|
+
result += `<span class="token-delimiter">${escapeHtml(char)}</span>`;
|
|
188
|
+
i++;
|
|
189
|
+
continue;
|
|
64
190
|
}
|
|
65
|
-
|
|
191
|
+
// Operators
|
|
192
|
+
if (isOperator(char)) {
|
|
193
|
+
result += `<span class="token-operator">${escapeHtml(char)}</span>`;
|
|
194
|
+
i++;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
// Everything else - just escape and append
|
|
198
|
+
result += escapeHtml(char);
|
|
199
|
+
i++;
|
|
200
|
+
}
|
|
66
201
|
return result || ' ';
|
|
67
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Consume a complete word (identifier)
|
|
205
|
+
*/
|
|
206
|
+
function consumeWord(line, start) {
|
|
207
|
+
let i = start;
|
|
208
|
+
while (i < line.length && /[a-zA-Z0-9_$]/.test(line[i])) {
|
|
209
|
+
i++;
|
|
210
|
+
}
|
|
211
|
+
return line.substring(start, i);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Consume a complete number
|
|
215
|
+
*/
|
|
216
|
+
function consumeNumber(line, start) {
|
|
217
|
+
let i = start;
|
|
218
|
+
let hasDot = false;
|
|
219
|
+
while (i < line.length) {
|
|
220
|
+
const char = line[i];
|
|
221
|
+
if (/\d/.test(char)) {
|
|
222
|
+
i++;
|
|
223
|
+
}
|
|
224
|
+
else if (char === '.' && !hasDot) {
|
|
225
|
+
hasDot = true;
|
|
226
|
+
i++;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return line.substring(start, i);
|
|
233
|
+
}
|
|
68
234
|
/**
|
|
69
235
|
* Render a parsed line
|
|
70
236
|
*/
|
|
@@ -76,7 +242,7 @@ export function renderLineWithTokens(parsedLine) {
|
|
|
76
242
|
*/
|
|
77
243
|
export function getSyntaxHighlightCSS() {
|
|
78
244
|
return `
|
|
79
|
-
/*
|
|
245
|
+
/* Semantic Code Highlighting */
|
|
80
246
|
.jux-code {
|
|
81
247
|
font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;
|
|
82
248
|
font-size: 14px;
|
|
@@ -130,10 +296,46 @@ export function getSyntaxHighlightCSS() {
|
|
|
130
296
|
display: none;
|
|
131
297
|
}
|
|
132
298
|
|
|
133
|
-
/* Token colors -
|
|
134
|
-
.token-
|
|
135
|
-
color: #c678dd;
|
|
299
|
+
/* Token colors - Semantic categories */
|
|
300
|
+
.token-control {
|
|
301
|
+
color: #c678dd; /* Purple - control flow (if, for, return) */
|
|
302
|
+
font-weight: 600;
|
|
303
|
+
}
|
|
304
|
+
.token-declaration {
|
|
305
|
+
color: #61afef; /* Blue - declarations (const, let, function) */
|
|
306
|
+
font-weight: 600;
|
|
307
|
+
}
|
|
308
|
+
.token-operator-keyword {
|
|
309
|
+
color: #e06c75; /* Red - operator keywords (typeof, instanceof) */
|
|
136
310
|
font-weight: 600;
|
|
311
|
+
}
|
|
312
|
+
.token-special {
|
|
313
|
+
color: #e5c07b; /* Yellow - special keywords (this, super, async) */
|
|
314
|
+
font-weight: 600;
|
|
315
|
+
}
|
|
316
|
+
.token-jux {
|
|
317
|
+
color: #56b6c2; /* Cyan - JUX-specific (jux, state, render) */
|
|
318
|
+
font-weight: 700;
|
|
319
|
+
}
|
|
320
|
+
.token-number {
|
|
321
|
+
color: #d19a66; /* Orange - numbers */
|
|
322
|
+
}
|
|
323
|
+
.token-string {
|
|
324
|
+
color: #98c379; /* Green - strings */
|
|
325
|
+
}
|
|
326
|
+
.token-comment {
|
|
327
|
+
color: #5c6370; /* Gray - comments */
|
|
328
|
+
font-style: italic;
|
|
329
|
+
}
|
|
330
|
+
.token-structural {
|
|
331
|
+
color: #abb2bf; /* Light gray - braces, brackets, parens */
|
|
332
|
+
font-weight: 600;
|
|
333
|
+
}
|
|
334
|
+
.token-delimiter {
|
|
335
|
+
color: #777; /* Dark gray - semicolons, commas, colons */
|
|
336
|
+
}
|
|
337
|
+
.token-operator {
|
|
338
|
+
color: #56b6c2; /* Cyan - operators (+, -, *, etc.) */
|
|
137
339
|
}
|
|
138
340
|
`;
|
|
139
341
|
}
|
package/lib/utils/codeparser.ts
CHANGED
|
@@ -13,6 +13,50 @@ const KEYWORDS = new Set([
|
|
|
13
13
|
'<=', '>=', '=>', '...', '.', ',', '(', ')', '{', '}', '[', ']', ':', '?'
|
|
14
14
|
]);
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Categorized token sets for semantic highlighting
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// Control flow keywords (loops, conditionals, etc.)
|
|
21
|
+
const CONTROL_FLOW = new Set([
|
|
22
|
+
'if', 'else', 'switch', 'case', 'default',
|
|
23
|
+
'for', 'while', 'do', 'break', 'continue',
|
|
24
|
+
'return', 'throw', 'try', 'catch', 'finally'
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
// Declaration keywords
|
|
28
|
+
const DECLARATIONS = new Set([
|
|
29
|
+
'const', 'let', 'var', 'function', 'class',
|
|
30
|
+
'import', 'export', 'from', 'as',
|
|
31
|
+
'interface', 'type', 'enum', 'namespace',
|
|
32
|
+
'static', 'get', 'set', 'extends'
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
// Operator keywords
|
|
36
|
+
const OPERATORS = new Set([
|
|
37
|
+
'new', 'delete', 'typeof', 'instanceof',
|
|
38
|
+
'void', 'await', 'yield', 'in', 'of'
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
// Special keywords
|
|
42
|
+
const SPECIAL = new Set([
|
|
43
|
+
'this', 'super', 'async', 'debugger', 'with'
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
// JUX-specific keywords
|
|
47
|
+
const JUX_KEYWORDS = new Set([
|
|
48
|
+
'jux', 'state', 'registry', 'render', 'bind', 'sync'
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
// Combine all keywords for quick lookup
|
|
52
|
+
const ALL_KEYWORDS = new Set([
|
|
53
|
+
...CONTROL_FLOW,
|
|
54
|
+
...DECLARATIONS,
|
|
55
|
+
...OPERATORS,
|
|
56
|
+
...SPECIAL,
|
|
57
|
+
...JUX_KEYWORDS
|
|
58
|
+
]);
|
|
59
|
+
|
|
16
60
|
export interface ParsedLine {
|
|
17
61
|
lineNumber: number;
|
|
18
62
|
html: string;
|
|
@@ -32,55 +76,193 @@ function escapeHtml(text: string): string {
|
|
|
32
76
|
}
|
|
33
77
|
|
|
34
78
|
/**
|
|
35
|
-
* Parse code into lines
|
|
79
|
+
* Parse code into lines - CHARACTER-BY-CHARACTER TOKENIZATION
|
|
36
80
|
*/
|
|
37
81
|
export function parseCode(code: string, language: string = 'javascript'): ParsedLine[] {
|
|
38
82
|
const lines = code.split('\n');
|
|
39
83
|
|
|
40
84
|
return lines.map((line, index) => ({
|
|
41
85
|
lineNumber: index + 1,
|
|
42
|
-
html:
|
|
86
|
+
html: tokenizeLine(line),
|
|
43
87
|
raw: line
|
|
44
88
|
}));
|
|
45
89
|
}
|
|
46
90
|
|
|
47
91
|
/**
|
|
48
|
-
*
|
|
92
|
+
* Get the appropriate CSS class for a keyword
|
|
49
93
|
*/
|
|
50
|
-
function
|
|
51
|
-
if (
|
|
94
|
+
function getKeywordClass(word: string): string {
|
|
95
|
+
if (CONTROL_FLOW.has(word)) return 'token-control';
|
|
96
|
+
if (DECLARATIONS.has(word)) return 'token-declaration';
|
|
97
|
+
if (OPERATORS.has(word)) return 'token-operator-keyword';
|
|
98
|
+
if (SPECIAL.has(word)) return 'token-special';
|
|
99
|
+
if (JUX_KEYWORDS.has(word)) return 'token-jux';
|
|
100
|
+
return 'token-keyword'; // fallback
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check if character is a structural punctuation (braces, brackets, parens)
|
|
105
|
+
*/
|
|
106
|
+
function isStructural(char: string): boolean {
|
|
107
|
+
return '(){}[]'.includes(char);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if character is a delimiter (semicolon, comma, colon)
|
|
112
|
+
*/
|
|
113
|
+
function isDelimiter(char: string): boolean {
|
|
114
|
+
return ';,:'.includes(char);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if character is an operator
|
|
119
|
+
*/
|
|
120
|
+
function isOperator(char: string): boolean {
|
|
121
|
+
return '+-*/%=<>!&|^~?'.includes(char);
|
|
122
|
+
}
|
|
52
123
|
|
|
53
|
-
|
|
54
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Tokenize a single line character-by-character with semantic categories
|
|
126
|
+
*/
|
|
127
|
+
function tokenizeLine(line: string): string {
|
|
128
|
+
if (!line.trim()) return ' ';
|
|
55
129
|
|
|
56
130
|
let result = '';
|
|
131
|
+
let i = 0;
|
|
132
|
+
const len = line.length;
|
|
57
133
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
134
|
+
// Check for comment at start
|
|
135
|
+
if (line.trim().startsWith('//')) {
|
|
136
|
+
return `<span class="token-comment">${escapeHtml(line)}</span>`;
|
|
137
|
+
}
|
|
61
138
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
139
|
+
while (i < len) {
|
|
140
|
+
const char = line[i];
|
|
141
|
+
|
|
142
|
+
// Whitespace - preserve
|
|
143
|
+
if (/\s/.test(char)) {
|
|
144
|
+
result += char;
|
|
145
|
+
i++;
|
|
146
|
+
continue;
|
|
66
147
|
}
|
|
67
148
|
|
|
68
|
-
//
|
|
69
|
-
|
|
149
|
+
// Comment (// or /* */)
|
|
150
|
+
if (char === '/' && i + 1 < len) {
|
|
151
|
+
if (line[i + 1] === '/') {
|
|
152
|
+
// Line comment - consume rest of line
|
|
153
|
+
const comment = line.slice(i);
|
|
154
|
+
result += `<span class="token-comment">${escapeHtml(comment)}</span>`;
|
|
155
|
+
break;
|
|
156
|
+
} else if (line[i + 1] === '*') {
|
|
157
|
+
// Block comment start
|
|
158
|
+
const commentEnd = line.indexOf('*/', i + 2);
|
|
159
|
+
if (commentEnd !== -1) {
|
|
160
|
+
const comment = line.slice(i, commentEnd + 2);
|
|
161
|
+
result += `<span class="token-comment">${escapeHtml(comment)}</span>`;
|
|
162
|
+
i = commentEnd + 2;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
70
167
|
|
|
71
|
-
//
|
|
72
|
-
if (
|
|
73
|
-
|
|
168
|
+
// String literals (single, double, backtick)
|
|
169
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
170
|
+
const stringEnd = line.indexOf(char, i + 1);
|
|
171
|
+
if (stringEnd !== -1) {
|
|
172
|
+
const str = line.slice(i, stringEnd + 1);
|
|
173
|
+
result += `<span class="token-string">${escapeHtml(str)}</span>`;
|
|
174
|
+
i = stringEnd + 1;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
74
177
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
178
|
+
|
|
179
|
+
// Word (identifier or keyword)
|
|
180
|
+
if (/[a-zA-Z_$]/.test(char)) {
|
|
181
|
+
const word = consumeWord(line, i);
|
|
182
|
+
const escaped = escapeHtml(word);
|
|
183
|
+
|
|
184
|
+
if (ALL_KEYWORDS.has(word)) {
|
|
185
|
+
const tokenClass = getKeywordClass(word);
|
|
186
|
+
result += `<span class="${tokenClass}">${escaped}</span>`;
|
|
187
|
+
} else {
|
|
188
|
+
result += escaped;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
i += word.length;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Number
|
|
196
|
+
if (/\d/.test(char)) {
|
|
197
|
+
const num = consumeNumber(line, i);
|
|
198
|
+
result += `<span class="token-number">${escapeHtml(num)}</span>`;
|
|
199
|
+
i += num.length;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Structural punctuation (braces, brackets, parens)
|
|
204
|
+
if (isStructural(char)) {
|
|
205
|
+
result += `<span class="token-structural">${escapeHtml(char)}</span>`;
|
|
206
|
+
i++;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Delimiters (semicolon, comma, colon)
|
|
211
|
+
if (isDelimiter(char)) {
|
|
212
|
+
result += `<span class="token-delimiter">${escapeHtml(char)}</span>`;
|
|
213
|
+
i++;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Operators
|
|
218
|
+
if (isOperator(char)) {
|
|
219
|
+
result += `<span class="token-operator">${escapeHtml(char)}</span>`;
|
|
220
|
+
i++;
|
|
221
|
+
continue;
|
|
78
222
|
}
|
|
79
|
-
|
|
223
|
+
|
|
224
|
+
// Everything else - just escape and append
|
|
225
|
+
result += escapeHtml(char);
|
|
226
|
+
i++;
|
|
227
|
+
}
|
|
80
228
|
|
|
81
229
|
return result || ' ';
|
|
82
230
|
}
|
|
83
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Consume a complete word (identifier)
|
|
234
|
+
*/
|
|
235
|
+
function consumeWord(line: string, start: number): string {
|
|
236
|
+
let i = start;
|
|
237
|
+
while (i < line.length && /[a-zA-Z0-9_$]/.test(line[i])) {
|
|
238
|
+
i++;
|
|
239
|
+
}
|
|
240
|
+
return line.substring(start, i);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Consume a complete number
|
|
245
|
+
*/
|
|
246
|
+
function consumeNumber(line: string, start: number): string {
|
|
247
|
+
let i = start;
|
|
248
|
+
let hasDot = false;
|
|
249
|
+
|
|
250
|
+
while (i < line.length) {
|
|
251
|
+
const char = line[i];
|
|
252
|
+
|
|
253
|
+
if (/\d/.test(char)) {
|
|
254
|
+
i++;
|
|
255
|
+
} else if (char === '.' && !hasDot) {
|
|
256
|
+
hasDot = true;
|
|
257
|
+
i++;
|
|
258
|
+
} else {
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return line.substring(start, i);
|
|
264
|
+
}
|
|
265
|
+
|
|
84
266
|
/**
|
|
85
267
|
* Render a parsed line
|
|
86
268
|
*/
|
|
@@ -93,7 +275,7 @@ export function renderLineWithTokens(parsedLine: ParsedLine): string {
|
|
|
93
275
|
*/
|
|
94
276
|
export function getSyntaxHighlightCSS(): string {
|
|
95
277
|
return `
|
|
96
|
-
/*
|
|
278
|
+
/* Semantic Code Highlighting */
|
|
97
279
|
.jux-code {
|
|
98
280
|
font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;
|
|
99
281
|
font-size: 14px;
|
|
@@ -147,10 +329,46 @@ export function getSyntaxHighlightCSS(): string {
|
|
|
147
329
|
display: none;
|
|
148
330
|
}
|
|
149
331
|
|
|
150
|
-
/* Token colors -
|
|
151
|
-
.token-
|
|
152
|
-
color: #c678dd;
|
|
332
|
+
/* Token colors - Semantic categories */
|
|
333
|
+
.token-control {
|
|
334
|
+
color: #c678dd; /* Purple - control flow (if, for, return) */
|
|
153
335
|
font-weight: 600;
|
|
336
|
+
}
|
|
337
|
+
.token-declaration {
|
|
338
|
+
color: #61afef; /* Blue - declarations (const, let, function) */
|
|
339
|
+
font-weight: 600;
|
|
340
|
+
}
|
|
341
|
+
.token-operator-keyword {
|
|
342
|
+
color: #e06c75; /* Red - operator keywords (typeof, instanceof) */
|
|
343
|
+
font-weight: 600;
|
|
344
|
+
}
|
|
345
|
+
.token-special {
|
|
346
|
+
color: #e5c07b; /* Yellow - special keywords (this, super, async) */
|
|
347
|
+
font-weight: 600;
|
|
348
|
+
}
|
|
349
|
+
.token-jux {
|
|
350
|
+
color: #56b6c2; /* Cyan - JUX-specific (jux, state, render) */
|
|
351
|
+
font-weight: 700;
|
|
352
|
+
}
|
|
353
|
+
.token-number {
|
|
354
|
+
color: #d19a66; /* Orange - numbers */
|
|
355
|
+
}
|
|
356
|
+
.token-string {
|
|
357
|
+
color: #98c379; /* Green - strings */
|
|
358
|
+
}
|
|
359
|
+
.token-comment {
|
|
360
|
+
color: #5c6370; /* Gray - comments */
|
|
361
|
+
font-style: italic;
|
|
362
|
+
}
|
|
363
|
+
.token-structural {
|
|
364
|
+
color: #abb2bf; /* Light gray - braces, brackets, parens */
|
|
365
|
+
font-weight: 600;
|
|
366
|
+
}
|
|
367
|
+
.token-delimiter {
|
|
368
|
+
color: #777; /* Dark gray - semicolons, commas, colons */
|
|
369
|
+
}
|
|
370
|
+
.token-operator {
|
|
371
|
+
color: #56b6c2; /* Cyan - operators (+, -, *, etc.) */
|
|
154
372
|
}
|
|
155
373
|
`;
|
|
156
374
|
}
|