sugar-high 0.0.1 → 0.0.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.
- package/README.md +61 -0
- package/lib/index.mjs +315 -0
- package/package.json +8 -3
- package/docs/index.html +0 -72
- package/docs/main.js +0 -50
- package/lib/index.js +0 -203
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Sugar High
|
|
2
|
+
### Introduction
|
|
3
|
+
|
|
4
|
+
Super lightweight JSX syntax highlighter, around 1KB after minified and gzipped
|
|
5
|
+
|
|
6
|
+
> ⚠️ Still in experiment! Use it in production with caution!
|
|
7
|
+
|
|
8
|
+
### Usage
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install --save sugar-high
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
import { highlight } from 'sugar-high'
|
|
16
|
+
|
|
17
|
+
const codeHTML = highlight(code)
|
|
18
|
+
|
|
19
|
+
document.querySelector('pre > code').innerHTML = codeHTML
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Highlight with CSS
|
|
23
|
+
|
|
24
|
+
Then make your own theme with customized colors and put in global CSS
|
|
25
|
+
|
|
26
|
+
```css
|
|
27
|
+
/*
|
|
28
|
+
* 0 - comment
|
|
29
|
+
* 1 - keyword
|
|
30
|
+
* 2 - break
|
|
31
|
+
* 3 - string
|
|
32
|
+
* 4 - space
|
|
33
|
+
* 5 - sign
|
|
34
|
+
* 6 - identifier
|
|
35
|
+
* 7 - Class, number and null
|
|
36
|
+
*/
|
|
37
|
+
.sh__7 {
|
|
38
|
+
color: #3476cb;
|
|
39
|
+
}
|
|
40
|
+
.sh__6 {
|
|
41
|
+
color: #2d333b;
|
|
42
|
+
}
|
|
43
|
+
.sh__5 {
|
|
44
|
+
color: #818c9b;
|
|
45
|
+
font-weight: bold;
|
|
46
|
+
}
|
|
47
|
+
.sh__3 {
|
|
48
|
+
color: #00a99a;
|
|
49
|
+
}
|
|
50
|
+
.sh__1 {
|
|
51
|
+
color: #f47067;
|
|
52
|
+
}
|
|
53
|
+
.sh__0 {
|
|
54
|
+
color: #818c9b;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### LICENSE
|
|
59
|
+
|
|
60
|
+
MIT
|
|
61
|
+
|
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
const jsxBrackets = new Set(['<', '>', '{', '}', '[', ']'])
|
|
4
|
+
const keywords = new Set([
|
|
5
|
+
'for',
|
|
6
|
+
'while',
|
|
7
|
+
'if',
|
|
8
|
+
'else',
|
|
9
|
+
'return',
|
|
10
|
+
'function',
|
|
11
|
+
'var',
|
|
12
|
+
'let',
|
|
13
|
+
'const',
|
|
14
|
+
'true',
|
|
15
|
+
'false',
|
|
16
|
+
'undefined',
|
|
17
|
+
'this',
|
|
18
|
+
'new',
|
|
19
|
+
'delete',
|
|
20
|
+
'typeof',
|
|
21
|
+
'in',
|
|
22
|
+
'instanceof',
|
|
23
|
+
'void',
|
|
24
|
+
'break',
|
|
25
|
+
'continue',
|
|
26
|
+
'switch',
|
|
27
|
+
'case',
|
|
28
|
+
'default',
|
|
29
|
+
'throw',
|
|
30
|
+
'try',
|
|
31
|
+
'catch',
|
|
32
|
+
'finally',
|
|
33
|
+
'debugger',
|
|
34
|
+
'with',
|
|
35
|
+
'yield',
|
|
36
|
+
'async',
|
|
37
|
+
'await',
|
|
38
|
+
'class',
|
|
39
|
+
'extends',
|
|
40
|
+
'super',
|
|
41
|
+
'import',
|
|
42
|
+
'export',
|
|
43
|
+
'from',
|
|
44
|
+
'static',
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
const signs = new Set([
|
|
48
|
+
'+',
|
|
49
|
+
'-',
|
|
50
|
+
'*',
|
|
51
|
+
'/',
|
|
52
|
+
'%',
|
|
53
|
+
'=',
|
|
54
|
+
'!',
|
|
55
|
+
...jsxBrackets,
|
|
56
|
+
'&',
|
|
57
|
+
'|',
|
|
58
|
+
'^',
|
|
59
|
+
'~',
|
|
60
|
+
'!',
|
|
61
|
+
'?',
|
|
62
|
+
':',
|
|
63
|
+
'.',
|
|
64
|
+
',',
|
|
65
|
+
`'`,
|
|
66
|
+
'"',
|
|
67
|
+
'.',
|
|
68
|
+
'(',
|
|
69
|
+
')',
|
|
70
|
+
'[',
|
|
71
|
+
']',
|
|
72
|
+
'#',
|
|
73
|
+
'\\',
|
|
74
|
+
])
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
*
|
|
78
|
+
* 0 - comment
|
|
79
|
+
* 1 - keyword
|
|
80
|
+
* 2 - break
|
|
81
|
+
* 3 - string
|
|
82
|
+
* 4 - space
|
|
83
|
+
* 5 - sign
|
|
84
|
+
* 6 - identifier
|
|
85
|
+
* 7 - Class, number and null
|
|
86
|
+
*
|
|
87
|
+
*/
|
|
88
|
+
const [
|
|
89
|
+
T_COMMENT,
|
|
90
|
+
T_KEYWORD,
|
|
91
|
+
T_BREAK,
|
|
92
|
+
T_STRING,
|
|
93
|
+
T_SPACE,
|
|
94
|
+
T_SIGN,
|
|
95
|
+
T_IDENTIFIER,
|
|
96
|
+
T_CLS_NUMBER,
|
|
97
|
+
] = Array(8).fill().map((_, i) => i)
|
|
98
|
+
|
|
99
|
+
function isSpaces(str) {
|
|
100
|
+
return /^[^\S\r\n]+$/g.test(str)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function encode(str) {
|
|
104
|
+
return str
|
|
105
|
+
.replace(/&/g, '&')
|
|
106
|
+
.replace(/</g, '<')
|
|
107
|
+
.replace(/>/g, '>')
|
|
108
|
+
.replace(/"/g, '"')
|
|
109
|
+
.replace(/'/g, ''')
|
|
110
|
+
// matches space but not new line
|
|
111
|
+
.replace(/[^\S\r\n]/g, ' ')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isIdentifierChar(chr) {
|
|
115
|
+
return /[a-zA-Z0-9_$]/.test(chr)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isStringQuotation(chr) {
|
|
119
|
+
return chr === '"' || chr === "'" || chr === '`'
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isCommentStart(str) {
|
|
123
|
+
str = str.slice(0, 2)
|
|
124
|
+
return str === '//' || str === '/*'
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isRegexStart(str) {
|
|
128
|
+
return str[0] === '/' && !isCommentStart(str[0] + str[1])
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {string} code
|
|
133
|
+
* @return {Array<[number, string]>}
|
|
134
|
+
*/
|
|
135
|
+
export function tokenize(code) {
|
|
136
|
+
let current = ''
|
|
137
|
+
let type = -1
|
|
138
|
+
/** @type {Array<[number, string]>} */
|
|
139
|
+
const tokens = []
|
|
140
|
+
// string.type = 0 for string or string template
|
|
141
|
+
// string.type = 1 for regex
|
|
142
|
+
const string = { entered: false, type: 0 }
|
|
143
|
+
|
|
144
|
+
// comment.type = 0 for single line comments
|
|
145
|
+
// comment.type = 1 for multi-line comments
|
|
146
|
+
const comment = { entered: false, type: 0 }
|
|
147
|
+
|
|
148
|
+
// jsx.tag for entering open or closed tag
|
|
149
|
+
// jsx.child for entering children
|
|
150
|
+
// jsx.expr for entering {expression}
|
|
151
|
+
/** @type {{ tag: boolean; child: boolean; expr: boolean }} */
|
|
152
|
+
const jsx = { tag: false, child: false, expr: false }
|
|
153
|
+
|
|
154
|
+
function classify(token) {
|
|
155
|
+
if (isCommentStart(token[0] + token[1])) {
|
|
156
|
+
return T_COMMENT
|
|
157
|
+
} else if (keywords.has(token)) {
|
|
158
|
+
return T_KEYWORD
|
|
159
|
+
} else if (token === '\n') {
|
|
160
|
+
return T_BREAK
|
|
161
|
+
} else if (
|
|
162
|
+
(
|
|
163
|
+
// is quoted string
|
|
164
|
+
(isStringQuotation(token[0]) && !isStringQuotation(token[1])) ||
|
|
165
|
+
// is regex
|
|
166
|
+
(!jsx.tag && isRegexStart(token[0] + token[1]) && token[token.length - 1] === '/')
|
|
167
|
+
)
|
|
168
|
+
) {
|
|
169
|
+
return T_STRING
|
|
170
|
+
} else if (token === ' ') {
|
|
171
|
+
return T_SPACE
|
|
172
|
+
} else if (signs.has(token[0])) {
|
|
173
|
+
return T_SIGN
|
|
174
|
+
} else if (
|
|
175
|
+
token[0] === token[0].toUpperCase() ||
|
|
176
|
+
token === 'null'
|
|
177
|
+
) {
|
|
178
|
+
return T_CLS_NUMBER
|
|
179
|
+
} else {
|
|
180
|
+
return T_IDENTIFIER
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const append = () => {
|
|
185
|
+
if (current) {
|
|
186
|
+
type = classify(current)
|
|
187
|
+
tokens.push([type, current])
|
|
188
|
+
}
|
|
189
|
+
current = ''
|
|
190
|
+
}
|
|
191
|
+
for (let i = 0; i < code.length; i++) {
|
|
192
|
+
const curr = code[i]
|
|
193
|
+
const prev = code[i - 1]
|
|
194
|
+
const next = code[i + 1]
|
|
195
|
+
const c_n = curr + next // current and next
|
|
196
|
+
const p_c = prev + curr // previous and current
|
|
197
|
+
const isJsxLiterals = jsx.child && !jsx.expr
|
|
198
|
+
|
|
199
|
+
if (jsx.tag) {
|
|
200
|
+
const isOpenElementEnd = curr === '>'
|
|
201
|
+
const isCloseElementEnd = p_c === '/>'
|
|
202
|
+
jsx.child = !isCloseElementEnd && !isCloseElementEnd
|
|
203
|
+
jsx.tag = !(isOpenElementEnd || isCloseElementEnd)
|
|
204
|
+
}
|
|
205
|
+
// if it's not in a jsx tag declaration or a string, close child if next is jsx close tag
|
|
206
|
+
if (!jsx.tag && !string.entered && (curr === '<' && isIdentifierChar(next) || c_n === '</')) {
|
|
207
|
+
jsx.tag = true
|
|
208
|
+
jsx.child = false
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (jsx.child && curr === '{') {
|
|
212
|
+
jsx.expr = true
|
|
213
|
+
}
|
|
214
|
+
if (jsx.child && jsx.expr && curr === '}') {
|
|
215
|
+
jsx.expr = false
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (
|
|
219
|
+
!string.entered &&
|
|
220
|
+
(isStringQuotation(curr) || !jsx.tag && isRegexStart(c_n))
|
|
221
|
+
) {
|
|
222
|
+
string.entered = true
|
|
223
|
+
string.type = isStringQuotation(curr) ? 0 : 1
|
|
224
|
+
append()
|
|
225
|
+
current = curr
|
|
226
|
+
} else if (string.entered) {
|
|
227
|
+
current += curr
|
|
228
|
+
if (string.type === 0 && isStringQuotation(curr)) {
|
|
229
|
+
string.entered = false
|
|
230
|
+
append()
|
|
231
|
+
} else if (string.type === 1 && prev !== '\\' && curr === '/') {
|
|
232
|
+
string.entered = false
|
|
233
|
+
append()
|
|
234
|
+
}
|
|
235
|
+
} else if (
|
|
236
|
+
!comment.entered &&
|
|
237
|
+
isCommentStart(c_n)
|
|
238
|
+
) {
|
|
239
|
+
comment.type = next === '/' ? 0 : 1
|
|
240
|
+
comment.entered = true
|
|
241
|
+
append()
|
|
242
|
+
current = c_n
|
|
243
|
+
i++
|
|
244
|
+
} else if (comment.entered) {
|
|
245
|
+
current += curr
|
|
246
|
+
if (comment.type === 0 && next === '\n') {
|
|
247
|
+
comment.entered = false
|
|
248
|
+
append()
|
|
249
|
+
} else if (comment.type === 1 && (c_n === '*/')) {
|
|
250
|
+
comment.entered = false
|
|
251
|
+
current += '/'
|
|
252
|
+
append()
|
|
253
|
+
i++
|
|
254
|
+
}
|
|
255
|
+
} else if (curr === ' ' || curr === '\n') {
|
|
256
|
+
if (
|
|
257
|
+
curr === ' ' &&
|
|
258
|
+
(
|
|
259
|
+
(isSpaces(current) || !current) ||
|
|
260
|
+
isJsxLiterals
|
|
261
|
+
)
|
|
262
|
+
) {
|
|
263
|
+
current += curr
|
|
264
|
+
} else {
|
|
265
|
+
append()
|
|
266
|
+
current = curr
|
|
267
|
+
append()
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
if (
|
|
271
|
+
(isJsxLiterals && !jsxBrackets.has(curr)) ||
|
|
272
|
+
isIdentifierChar(curr) === isIdentifierChar(current[current.length - 1]) &&
|
|
273
|
+
!signs.has(curr)
|
|
274
|
+
) {
|
|
275
|
+
current += curr
|
|
276
|
+
} else {
|
|
277
|
+
append()
|
|
278
|
+
current = curr
|
|
279
|
+
if (c_n === '</') {
|
|
280
|
+
current = c_n
|
|
281
|
+
append()
|
|
282
|
+
i++
|
|
283
|
+
}
|
|
284
|
+
else if (jsxBrackets.has(curr)) append()
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
append()
|
|
290
|
+
|
|
291
|
+
return tokens
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @param {Array<[number, string]>} tokens
|
|
296
|
+
* @return {Array<string>}
|
|
297
|
+
*/
|
|
298
|
+
function generate(tokens) {
|
|
299
|
+
const output = []
|
|
300
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
301
|
+
const [type, token] = tokens[i]
|
|
302
|
+
output.push(
|
|
303
|
+
type === T_BREAK
|
|
304
|
+
? '<br>'
|
|
305
|
+
: `<span class="sh__${type}">${encode(token)}</span>`
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
return output
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function highlight(code) {
|
|
312
|
+
const tokens = tokenize(code)
|
|
313
|
+
const output = generate(tokens).join('')
|
|
314
|
+
return output
|
|
315
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sugar-high",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": "./lib/index.mjs",
|
|
6
|
+
"description": "Super lightweight JSX syntax highlighter",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib",
|
|
9
|
+
"*.md"
|
|
10
|
+
],
|
|
6
11
|
"license": "MIT",
|
|
7
12
|
"scripts": {
|
|
8
13
|
"dev": "vite docs",
|
package/docs/index.html
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>sugar high</title>
|
|
8
|
-
<style>
|
|
9
|
-
html {
|
|
10
|
-
font-family: "Inter",-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif
|
|
11
|
-
}
|
|
12
|
-
body {
|
|
13
|
-
max-width: 1120px;
|
|
14
|
-
margin: auto;
|
|
15
|
-
padding: 0 10px;
|
|
16
|
-
}
|
|
17
|
-
#code {
|
|
18
|
-
font-family: Consolas, Monaco, monospace;
|
|
19
|
-
font-size: 16px;
|
|
20
|
-
display: block;
|
|
21
|
-
width: 100%;
|
|
22
|
-
min-height: 100px;
|
|
23
|
-
background-color: #fff;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
#output {
|
|
27
|
-
font-family: Consolas, Monaco, monospace;
|
|
28
|
-
font-size: 16px;
|
|
29
|
-
display: block;
|
|
30
|
-
min-height: 100px;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.identifier {
|
|
34
|
-
color: #2d333b;
|
|
35
|
-
}
|
|
36
|
-
.sign {
|
|
37
|
-
color: #818c9b;
|
|
38
|
-
font-weight: bold;
|
|
39
|
-
}
|
|
40
|
-
.string {
|
|
41
|
-
color: #00a99a;
|
|
42
|
-
}
|
|
43
|
-
.keyword {
|
|
44
|
-
color: #f47067;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.flex {
|
|
48
|
-
display: flex;
|
|
49
|
-
}
|
|
50
|
-
.flex-1 {
|
|
51
|
-
flex: 1 0;
|
|
52
|
-
margin: 8px;
|
|
53
|
-
padding: 12px 10px 12px 10px;
|
|
54
|
-
background-color: #f9f9f9;
|
|
55
|
-
border: 1px solid #d0d0d0;
|
|
56
|
-
border-radius: 4px;
|
|
57
|
-
}
|
|
58
|
-
.comment {
|
|
59
|
-
color: #818c9b;
|
|
60
|
-
}
|
|
61
|
-
</style>
|
|
62
|
-
</head>
|
|
63
|
-
<body>
|
|
64
|
-
<h1>Sugar High</h1>
|
|
65
|
-
<div class="flex">
|
|
66
|
-
<textarea class="flex-1" id="code"></textarea>
|
|
67
|
-
|
|
68
|
-
<pre class="flex-1"><code id="output"></code></pre>
|
|
69
|
-
</div>
|
|
70
|
-
<script type="module" src="./main.js"></script>
|
|
71
|
-
</body>
|
|
72
|
-
</html>
|
package/docs/main.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { tokenize, generate } from '../lib'
|
|
2
|
-
|
|
3
|
-
// console interactive API
|
|
4
|
-
window.tokenize = tokenize
|
|
5
|
-
window.generate = generate
|
|
6
|
-
|
|
7
|
-
const codeInput = document.getElementById('code')
|
|
8
|
-
const codeOutput = document.getElementById('output')
|
|
9
|
-
|
|
10
|
-
codeInput.addEventListener('input', () => {
|
|
11
|
-
update()
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
codeInput.value = `
|
|
15
|
-
// hello-world.js
|
|
16
|
-
import { planet } from '../space'
|
|
17
|
-
|
|
18
|
-
class SuperArray extends Array {
|
|
19
|
-
static core = planet
|
|
20
|
-
|
|
21
|
-
constructor(...args) {
|
|
22
|
-
super(...args)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
bump() {
|
|
26
|
-
return this.map(x => x + 1)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @param {string} name
|
|
32
|
-
* @return {void}
|
|
33
|
-
*/
|
|
34
|
-
function hello(name) {
|
|
35
|
-
console.log('hello', name)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
`.trim()
|
|
39
|
-
|
|
40
|
-
function update() {
|
|
41
|
-
const code = codeInput.value?.trim() || ''
|
|
42
|
-
const tokens = tokenize(code)
|
|
43
|
-
const output = generate(tokens).join('')
|
|
44
|
-
|
|
45
|
-
console.log(tokens)
|
|
46
|
-
|
|
47
|
-
codeOutput.innerHTML = output
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
update()
|
package/lib/index.js
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
const keywords = new Set([
|
|
4
|
-
'for',
|
|
5
|
-
'while',
|
|
6
|
-
'if',
|
|
7
|
-
'else',
|
|
8
|
-
'return',
|
|
9
|
-
'function',
|
|
10
|
-
'var',
|
|
11
|
-
'let',
|
|
12
|
-
'const',
|
|
13
|
-
'true',
|
|
14
|
-
'false',
|
|
15
|
-
'null',
|
|
16
|
-
'undefined',
|
|
17
|
-
'NaN',
|
|
18
|
-
'Infinity',
|
|
19
|
-
'this',
|
|
20
|
-
'new',
|
|
21
|
-
'delete',
|
|
22
|
-
'typeof',
|
|
23
|
-
'in',
|
|
24
|
-
'instanceof',
|
|
25
|
-
'void',
|
|
26
|
-
'break',
|
|
27
|
-
'continue',
|
|
28
|
-
'switch',
|
|
29
|
-
'case',
|
|
30
|
-
'default',
|
|
31
|
-
'throw',
|
|
32
|
-
'try',
|
|
33
|
-
'catch',
|
|
34
|
-
'finally',
|
|
35
|
-
'debugger',
|
|
36
|
-
'with',
|
|
37
|
-
'yield',
|
|
38
|
-
'async',
|
|
39
|
-
'await',
|
|
40
|
-
'class',
|
|
41
|
-
'extends',
|
|
42
|
-
'super',
|
|
43
|
-
'import',
|
|
44
|
-
'export',
|
|
45
|
-
'from',
|
|
46
|
-
'static',
|
|
47
|
-
])
|
|
48
|
-
|
|
49
|
-
const signs = new Set([
|
|
50
|
-
'+',
|
|
51
|
-
'-',
|
|
52
|
-
'*',
|
|
53
|
-
'/',
|
|
54
|
-
'%',
|
|
55
|
-
'=',
|
|
56
|
-
'!',
|
|
57
|
-
'>',
|
|
58
|
-
'<',
|
|
59
|
-
'&',
|
|
60
|
-
'|',
|
|
61
|
-
'^',
|
|
62
|
-
'~',
|
|
63
|
-
'!',
|
|
64
|
-
'?',
|
|
65
|
-
':',
|
|
66
|
-
'.',
|
|
67
|
-
',',
|
|
68
|
-
`'`,
|
|
69
|
-
'"',
|
|
70
|
-
'.',
|
|
71
|
-
'{',
|
|
72
|
-
'}',
|
|
73
|
-
'(',
|
|
74
|
-
')',
|
|
75
|
-
'[',
|
|
76
|
-
']',
|
|
77
|
-
'#',
|
|
78
|
-
'\\',
|
|
79
|
-
])
|
|
80
|
-
|
|
81
|
-
function encode(str) {
|
|
82
|
-
return str
|
|
83
|
-
.replace(/&/g, "&")
|
|
84
|
-
.replace(/</g, "<")
|
|
85
|
-
.replace(/>/g, ">")
|
|
86
|
-
.replace(/"/g, """)
|
|
87
|
-
.replace(/'/g, "'")
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function isIdentifierChar(chr) {
|
|
91
|
-
return /[a-zA-Z0-9_$]/.test(chr)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function isQuotationMark(chr) {
|
|
95
|
-
return chr === '"' || chr === "'" || chr === '`'
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* @param {string} code
|
|
100
|
-
* @return {Array<string>}
|
|
101
|
-
*/
|
|
102
|
-
export function tokenize(code) {
|
|
103
|
-
let current = ''
|
|
104
|
-
const tokens = []
|
|
105
|
-
const string = { entered: false }
|
|
106
|
-
const comment = { entered: false, type: 0 }
|
|
107
|
-
for (let i = 0; i < code.length; i++) {
|
|
108
|
-
if (!string.entered && isQuotationMark(code[i])) {
|
|
109
|
-
string.entered = true
|
|
110
|
-
if (current) tokens.push(current)
|
|
111
|
-
current = code[i]
|
|
112
|
-
} else if (string.entered) {
|
|
113
|
-
current += code[i]
|
|
114
|
-
if (isQuotationMark(code[i])) {
|
|
115
|
-
string.entered = false
|
|
116
|
-
tokens.push(current)
|
|
117
|
-
current = ''
|
|
118
|
-
}
|
|
119
|
-
} else if (
|
|
120
|
-
!comment.entered &&
|
|
121
|
-
code[i] === '/' &&
|
|
122
|
-
(code[i + 1] === '/' || code[i + 1] === '*')
|
|
123
|
-
) {
|
|
124
|
-
comment.type = code[i + 1] === '/' ? 0 : 1
|
|
125
|
-
comment.entered = true
|
|
126
|
-
if (current) tokens.push(current)
|
|
127
|
-
current = code[i]
|
|
128
|
-
} else if (comment.entered) {
|
|
129
|
-
current += code[i]
|
|
130
|
-
if (comment.type === 0 && code[i + 1] === '\n') {
|
|
131
|
-
comment.entered = false
|
|
132
|
-
tokens.push(current)
|
|
133
|
-
current = ''
|
|
134
|
-
} else if (comment.type === 1 && code[i] === '*' && code[i + 1] === '/') {
|
|
135
|
-
comment.entered = false
|
|
136
|
-
tokens.push(current)
|
|
137
|
-
current = ''
|
|
138
|
-
tokens.push(code[i])
|
|
139
|
-
tokens.push(code[i + 1])
|
|
140
|
-
i++
|
|
141
|
-
}
|
|
142
|
-
} else if (code[i] === ' ' || code[i] === '\n') {
|
|
143
|
-
if (current.length > 0) {
|
|
144
|
-
tokens.push(current)
|
|
145
|
-
current = ''
|
|
146
|
-
}
|
|
147
|
-
tokens.push(code[i])
|
|
148
|
-
} else {
|
|
149
|
-
if (isIdentifierChar(code[i]) !== isIdentifierChar(current[current.length - 1])) {
|
|
150
|
-
if (current) tokens.push(current)
|
|
151
|
-
current = code[i]
|
|
152
|
-
} else {
|
|
153
|
-
current += code[i]
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (current) {
|
|
158
|
-
tokens.push(current)
|
|
159
|
-
current = ''
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return tokens
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
function classify(token) {
|
|
167
|
-
if (token[0] === '/' && (token[1] === '*' || token[1] === '/')) {
|
|
168
|
-
return 'comment'
|
|
169
|
-
} else if (keywords.has(token)) {
|
|
170
|
-
return 'keyword'
|
|
171
|
-
} else if (token === '\n') {
|
|
172
|
-
return 'linebreak'
|
|
173
|
-
} else if (isQuotationMark(token[0]) && !isQuotationMark(token[1])) {
|
|
174
|
-
return 'string'
|
|
175
|
-
} else if (token === ' ') {
|
|
176
|
-
return 'space'
|
|
177
|
-
} else if (signs.has(token[0])) {
|
|
178
|
-
return 'sign'
|
|
179
|
-
} else {
|
|
180
|
-
return 'identifier'
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* @param {Array<string>} tokens
|
|
186
|
-
* @return {Array<string>}
|
|
187
|
-
*/
|
|
188
|
-
export function generate(tokens) {
|
|
189
|
-
let output = []
|
|
190
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
191
|
-
const token = tokens[i]
|
|
192
|
-
const type = classify(token)
|
|
193
|
-
if (type === 'linebreak') {
|
|
194
|
-
output.push('<br>')
|
|
195
|
-
} else if (type === 'space') {
|
|
196
|
-
output.push(`<span class="space"> </span>`)
|
|
197
|
-
} else {
|
|
198
|
-
output.push(`<span class="${type}">${encode(token)}</span>`)
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return output
|
|
202
|
-
}
|
|
203
|
-
|