@katabatic/compiler 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.
- package/README.md +3 -0
- package/package.json +32 -0
- package/src/analyse/context.js +31 -0
- package/src/analyse/index.js +47 -0
- package/src/analyse/visitors/AssignmentExpression.js +14 -0
- package/src/analyse/visitors/Attribute.js +11 -0
- package/src/analyse/visitors/CallExpression.js +36 -0
- package/src/analyse/visitors/ClassBody.js +20 -0
- package/src/analyse/visitors/ClassDeclaration.js +5 -0
- package/src/analyse/visitors/CustomElement.js +10 -0
- package/src/analyse/visitors/EachBlock.js +10 -0
- package/src/analyse/visitors/Element.js +16 -0
- package/src/analyse/visitors/ExpressionTag.js +9 -0
- package/src/analyse/visitors/IfBlock.js +16 -0
- package/src/analyse/visitors/ImportDeclaration.js +35 -0
- package/src/analyse/visitors/MethodDefinition.js +17 -0
- package/src/analyse/visitors/Program.js +24 -0
- package/src/analyse/visitors/PropertyDefinition.js +12 -0
- package/src/analyse/visitors/Selector.js +23 -0
- package/src/analyse/visitors/Template.js +14 -0
- package/src/builders.js +1663 -0
- package/src/checkers.js +120 -0
- package/src/css-matcher.js +100 -0
- package/src/css-transform.js +21 -0
- package/src/css.js +65 -0
- package/src/exp-matcher.js +65 -0
- package/src/id-matcher.js +17 -0
- package/src/index.js +19 -0
- package/src/module-matcher.js +17 -0
- package/src/parser/attributes.js +66 -0
- package/src/parser/each-block.js +73 -0
- package/src/parser/element.js +115 -0
- package/src/parser/expression.js +44 -0
- package/src/parser/if-block.js +71 -0
- package/src/parser/index.js +10 -0
- package/src/parser/parser.js +259 -0
- package/src/parser/root.js +33 -0
- package/src/parser/script.js +59 -0
- package/src/parser/style.js +57 -0
- package/src/parser/template.js +30 -0
- package/src/parser/tokentype.js +75 -0
- package/src/router/html.js +18 -0
- package/src/router/index.js +130 -0
- package/src/transform/context.js +74 -0
- package/src/transform/index.js +52 -0
- package/src/transform/static/index.js +40 -0
- package/src/transform/static/visitors/Attribute.js +27 -0
- package/src/transform/static/visitors/EachBlock.js +23 -0
- package/src/transform/static/visitors/Element.js +17 -0
- package/src/transform/static/visitors/ExpressionTag.js +6 -0
- package/src/transform/static/visitors/IfBlock.js +28 -0
- package/src/transform/static/visitors/Program.js +10 -0
- package/src/transform/static/visitors/Script.js +9 -0
- package/src/transform/static/visitors/SlotElement.js +8 -0
- package/src/transform/static/visitors/Style.js +9 -0
- package/src/transform/static/visitors/Template.js +12 -0
- package/src/transform/static/visitors/Text.js +5 -0
- package/src/transform/visitors/AssignmentExpression.js +7 -0
- package/src/transform/visitors/Attribute.js +79 -0
- package/src/transform/visitors/CallExpression.js +17 -0
- package/src/transform/visitors/ClassBody.js +36 -0
- package/src/transform/visitors/CustomElement.js +43 -0
- package/src/transform/visitors/EachBlock.js +39 -0
- package/src/transform/visitors/Element.js +49 -0
- package/src/transform/visitors/ExpressionTag.js +6 -0
- package/src/transform/visitors/Fragment.js +42 -0
- package/src/transform/visitors/Identifier.js +10 -0
- package/src/transform/visitors/IfBlock.js +55 -0
- package/src/transform/visitors/ImportDeclaration.js +21 -0
- package/src/transform/visitors/MethodDefinition.js +115 -0
- package/src/transform/visitors/Program.js +65 -0
- package/src/transform/visitors/PropertyDefinition.js +7 -0
- package/src/transform/visitors/Selector.js +41 -0
- package/src/transform/visitors/Style.js +11 -0
- package/src/transform/visitors/Template.js +44 -0
- package/src/transform/visitors/Text.js +5 -0
- package/src/utils/misc.js +9 -0
- package/src/utils/template.js +15 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { parseExpressionAt } from 'acorn'
|
|
2
|
+
import { Parser } from './parser.js'
|
|
3
|
+
import { TokenTypes } from './tokentype.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {Parser} p
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
export function parseExpressionTag(p) {
|
|
11
|
+
p.expectToken([TokenTypes.braceL])
|
|
12
|
+
p.skipWhitespaces()
|
|
13
|
+
const expression = parseExpression(p)
|
|
14
|
+
p.skipWhitespaces()
|
|
15
|
+
p.expectToken([TokenTypes.braceR])
|
|
16
|
+
|
|
17
|
+
return { type: 'ExpressionTag', expression }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @param {Parser} p
|
|
23
|
+
* @returns
|
|
24
|
+
*/
|
|
25
|
+
export function parseAttributeExpressionTag(p) {
|
|
26
|
+
p.expectToken([TokenTypes.quoteBraceL, TokenTypes.doubleQuoteBraceL])
|
|
27
|
+
p.skipWhitespaces()
|
|
28
|
+
const expression = parseExpression(p)
|
|
29
|
+
p.skipWhitespaces()
|
|
30
|
+
p.expectToken([TokenTypes.braceRQuote, TokenTypes.braceRDoubleQuote])
|
|
31
|
+
|
|
32
|
+
return { type: 'ExpressionTag', expression }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
*
|
|
37
|
+
* @param {Parser} p
|
|
38
|
+
* @returns
|
|
39
|
+
*/
|
|
40
|
+
function parseExpression(p) {
|
|
41
|
+
const node = parseExpressionAt(p.input, p.pos, { ecmaVersion: 'latest' })
|
|
42
|
+
p.pos = node.end
|
|
43
|
+
return node
|
|
44
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { parseExpressionAt } from 'acorn'
|
|
2
|
+
import { parseFragment } from './element.js'
|
|
3
|
+
import { Parser } from './parser.js'
|
|
4
|
+
import { TokenTypes } from './tokentype.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param {Parser} p
|
|
9
|
+
* @returns
|
|
10
|
+
*/
|
|
11
|
+
export function parseIfBlock(p, elseif = false) {
|
|
12
|
+
const start = p.pos
|
|
13
|
+
p.expectToken([TokenTypes.braceLHash, TokenTypes.braceLColumn])
|
|
14
|
+
p.expectToken([TokenTypes.name])
|
|
15
|
+
const name = p.value
|
|
16
|
+
|
|
17
|
+
if (elseif) {
|
|
18
|
+
p.skipWhitespaces()
|
|
19
|
+
p.expectToken([TokenTypes.name])
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const test = parseExpression(p)
|
|
23
|
+
p.expectToken([TokenTypes.braceR])
|
|
24
|
+
const consequent = parseFragment(p)
|
|
25
|
+
|
|
26
|
+
const punctToken = p.peakToken([TokenTypes.braceLColumn])
|
|
27
|
+
const nameToken1 = p.peakToken([TokenTypes.name], punctToken)
|
|
28
|
+
const whitespaceToken = p.peakWhitespaces(nameToken1)
|
|
29
|
+
const nameToken2 = p.peakToken([TokenTypes.name], whitespaceToken)
|
|
30
|
+
|
|
31
|
+
let alternate
|
|
32
|
+
if (nameToken1?.value === 'else' && nameToken2?.value === 'if') {
|
|
33
|
+
alternate = fragment([parseIfBlock(p, true)])
|
|
34
|
+
} else if (nameToken1?.value === 'else') {
|
|
35
|
+
p.expectToken([TokenTypes.braceLColumn])
|
|
36
|
+
p.expectToken([TokenTypes.name])
|
|
37
|
+
p.skipWhitespaces()
|
|
38
|
+
p.expectToken([TokenTypes.braceR])
|
|
39
|
+
|
|
40
|
+
alternate = parseFragment(p)
|
|
41
|
+
} else if (punctToken) {
|
|
42
|
+
throw new Error(`unknown block ${nameToken1?.value}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!elseif) {
|
|
46
|
+
p.expectToken([TokenTypes.braceLSlash])
|
|
47
|
+
p.expectToken([TokenTypes.name])
|
|
48
|
+
const blockNameClose = p.value
|
|
49
|
+
p.skipWhitespaces()
|
|
50
|
+
p.expectToken([TokenTypes.braceR])
|
|
51
|
+
|
|
52
|
+
if (name !== blockNameClose) throw new Error('wrong closing tag')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { type: 'IfBlock', elseif, test, consequent, alternate, start, end: p.pos }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
*
|
|
60
|
+
* @param {Parser} p
|
|
61
|
+
* @returns
|
|
62
|
+
*/
|
|
63
|
+
function parseExpression(p) {
|
|
64
|
+
const node = parseExpressionAt(p.input, p.pos, { ecmaVersion: 'latest' })
|
|
65
|
+
p.pos = node.end
|
|
66
|
+
return node
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function fragment(nodes) {
|
|
70
|
+
return { type: 'Fragment', nodes }
|
|
71
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { parseRoot } from './root.js'
|
|
2
|
+
|
|
3
|
+
/** @typedef {import('./tokentype.js').TokenType} TokenType */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} Token
|
|
7
|
+
* @property {TokenType} type
|
|
8
|
+
* @property {number} start
|
|
9
|
+
* @property {number} end
|
|
10
|
+
* @property {string} [value]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* * @member {string} input
|
|
15
|
+
*/
|
|
16
|
+
export class Parser {
|
|
17
|
+
constructor(input) {
|
|
18
|
+
this.input = input
|
|
19
|
+
|
|
20
|
+
this.start = 0
|
|
21
|
+
this.end = 0
|
|
22
|
+
this.pos = 0
|
|
23
|
+
this.type = undefined
|
|
24
|
+
this.value = undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
parse() {
|
|
28
|
+
return parseRoot(this)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
*
|
|
33
|
+
* @param {Array<TokenType>} types
|
|
34
|
+
* @param {Token} [after]
|
|
35
|
+
* @returns {Token}
|
|
36
|
+
*/
|
|
37
|
+
peakToken(types, after) {
|
|
38
|
+
for (const type of types) {
|
|
39
|
+
let token
|
|
40
|
+
|
|
41
|
+
if (type.charCodes) {
|
|
42
|
+
token = this.peakCharToken(type, after)
|
|
43
|
+
} else {
|
|
44
|
+
token = this.peakStringToken(type, after)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (token) return token
|
|
48
|
+
}
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
*
|
|
54
|
+
* @param {Array<TokenType>} types
|
|
55
|
+
*/
|
|
56
|
+
expectToken(types) {
|
|
57
|
+
for (const type of types) {
|
|
58
|
+
if (type.charCodes) {
|
|
59
|
+
if (this.readCharToken(type)) return
|
|
60
|
+
} else {
|
|
61
|
+
if (this.readStringToken(type)) return
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw new Error(`unexpected token ${this.input[this.pos]} at position ${this.pos}`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
*
|
|
69
|
+
* @param {Array<TokenType>} types
|
|
70
|
+
* @returns boolean
|
|
71
|
+
*/
|
|
72
|
+
readToken(types) {
|
|
73
|
+
for (const type of types) {
|
|
74
|
+
if (type.charCodes) {
|
|
75
|
+
if (this.readCharToken(type)) return true
|
|
76
|
+
} else {
|
|
77
|
+
if (this.readStringToken(type)) return true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
*
|
|
85
|
+
* @param {TokenType} type
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
readStringToken(type) {
|
|
89
|
+
const start = this.pos
|
|
90
|
+
while (this.pos < this.input.length) {
|
|
91
|
+
if (!type.test(this.input.charCodeAt(this.pos))) break
|
|
92
|
+
this.pos++
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (this.pos !== start) {
|
|
96
|
+
this.type = type
|
|
97
|
+
this.start = start
|
|
98
|
+
this.end = this.pos
|
|
99
|
+
this.value = this.input.substring(this.start, this.end)
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
*
|
|
107
|
+
* @param {TokenType} type
|
|
108
|
+
* @param {Token} after
|
|
109
|
+
* @returns {Token}
|
|
110
|
+
*/
|
|
111
|
+
peakStringToken(type, after) {
|
|
112
|
+
if (after === null) return null
|
|
113
|
+
|
|
114
|
+
const start = after?.end ?? this.pos
|
|
115
|
+
let pos = start
|
|
116
|
+
while (pos < this.input.length) {
|
|
117
|
+
if (!type.test(this.input.charCodeAt(pos))) break
|
|
118
|
+
pos++
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (pos !== start) {
|
|
122
|
+
return {
|
|
123
|
+
type,
|
|
124
|
+
start,
|
|
125
|
+
end: pos,
|
|
126
|
+
value: this.input.substring(start, pos)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
*
|
|
134
|
+
* @param {TokenType} type
|
|
135
|
+
* @returns {boolean}
|
|
136
|
+
*/
|
|
137
|
+
readCharToken(type) {
|
|
138
|
+
if (this.isCharToken(type)) {
|
|
139
|
+
this.type = type
|
|
140
|
+
this.start = this.pos
|
|
141
|
+
this.pos += type.charCodes.length
|
|
142
|
+
this.end = this.pos
|
|
143
|
+
return true
|
|
144
|
+
}
|
|
145
|
+
return false
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
*
|
|
150
|
+
* @param {TokenType} type
|
|
151
|
+
* @param {Token} [after]
|
|
152
|
+
* @returns {Token}
|
|
153
|
+
*/
|
|
154
|
+
peakCharToken(type, after) {
|
|
155
|
+
if (after === null) return null
|
|
156
|
+
|
|
157
|
+
const pos = after?.end ?? this.pos
|
|
158
|
+
if (this.isCharToken(type, pos)) {
|
|
159
|
+
return {
|
|
160
|
+
type,
|
|
161
|
+
start: pos,
|
|
162
|
+
end: pos + type.charCodes.length
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return null
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
*
|
|
170
|
+
* @param {TokenType} type
|
|
171
|
+
* @param {number} [pos]
|
|
172
|
+
* @returns {boolean}
|
|
173
|
+
*/
|
|
174
|
+
isCharToken(type, pos) {
|
|
175
|
+
assert(!!type.charCodes)
|
|
176
|
+
|
|
177
|
+
pos ??= this.pos
|
|
178
|
+
return (
|
|
179
|
+
this.input.charCodeAt(pos) === type.charCodes[0] &&
|
|
180
|
+
(type.charCodes[1] ? this.input.charCodeAt(pos + 1) === type.charCodes[1] : true) &&
|
|
181
|
+
(type.charCodes[2] ? this.input.charCodeAt(pos + 2) === type.charCodes[2] : true)
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
token() {
|
|
186
|
+
return {
|
|
187
|
+
type: this.type,
|
|
188
|
+
start: this.start,
|
|
189
|
+
end: this.end,
|
|
190
|
+
value: this.value
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
peakWhitespaces(after) {
|
|
195
|
+
if (after === null) return null
|
|
196
|
+
|
|
197
|
+
let pos = after?.end ?? this.pos
|
|
198
|
+
const start = pos
|
|
199
|
+
|
|
200
|
+
while (this.pos < this.input.length) {
|
|
201
|
+
const code = this.input.charCodeAt(pos)
|
|
202
|
+
if (code === 10 || code === 32) {
|
|
203
|
+
pos++
|
|
204
|
+
continue
|
|
205
|
+
}
|
|
206
|
+
break
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (pos !== start) {
|
|
210
|
+
return {
|
|
211
|
+
type: { label: 'whitespace' },
|
|
212
|
+
start,
|
|
213
|
+
end: pos
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return null
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
skipWhitespaces() {
|
|
220
|
+
while (this.pos < this.input.length) {
|
|
221
|
+
const code = this.input.charCodeAt(this.pos)
|
|
222
|
+
if (code === 10 || code === 32) {
|
|
223
|
+
this.pos++
|
|
224
|
+
continue
|
|
225
|
+
}
|
|
226
|
+
break
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
raiseUnexpectedToken() {
|
|
231
|
+
throw new Error(`unexpected token ${this.input[this.pos]} at position ${this.pos}`)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
isEOF() {
|
|
235
|
+
return this.pos === this.input.length
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
*
|
|
241
|
+
* @param {number} code
|
|
242
|
+
* @returns {boolean}
|
|
243
|
+
*/
|
|
244
|
+
function isIdentifierChar(code) {
|
|
245
|
+
if (code < 48) return false
|
|
246
|
+
if (code < 58) return true
|
|
247
|
+
if (code < 65) return false
|
|
248
|
+
if (code < 91) return true
|
|
249
|
+
if (code < 123) return true
|
|
250
|
+
return false
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
*
|
|
255
|
+
* @param {boolean} test
|
|
256
|
+
*/
|
|
257
|
+
function assert(test) {
|
|
258
|
+
if (!test) throw new Error('assert error')
|
|
259
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Parser } from './parser.js'
|
|
2
|
+
import { parseScript } from './script.js'
|
|
3
|
+
import { parseTemplate } from './template.js'
|
|
4
|
+
import { TokenTypes } from './tokentype.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {Parser} p
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
export function parseRoot(p) {
|
|
11
|
+
let script
|
|
12
|
+
let template
|
|
13
|
+
|
|
14
|
+
p.skipWhitespaces()
|
|
15
|
+
while (!p.isEOF()) {
|
|
16
|
+
const lteToken = p.peakToken([TokenTypes.lte])
|
|
17
|
+
const nameToken = p.peakToken([TokenTypes.name], lteToken)
|
|
18
|
+
|
|
19
|
+
switch (nameToken?.value) {
|
|
20
|
+
case 'script':
|
|
21
|
+
script = parseScript(p)
|
|
22
|
+
break
|
|
23
|
+
case 'template':
|
|
24
|
+
template = parseTemplate(p)
|
|
25
|
+
break
|
|
26
|
+
default:
|
|
27
|
+
throw new Error('unexpected html tag at position ' + p.pos)
|
|
28
|
+
}
|
|
29
|
+
p.skipWhitespaces()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { type: 'Root', script, template }
|
|
33
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { parse as parseJS } from 'acorn'
|
|
2
|
+
import { TokenTypes } from './tokentype.js'
|
|
3
|
+
import { Parser } from './parser.js'
|
|
4
|
+
import { parseAttributes } from './attributes.js'
|
|
5
|
+
/** @import {Program} from 'acorn' */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param {Parser} p
|
|
10
|
+
* @returns
|
|
11
|
+
*/
|
|
12
|
+
export function parseScript(p) {
|
|
13
|
+
const start = p.pos
|
|
14
|
+
p.expectToken([TokenTypes.lte])
|
|
15
|
+
p.expectToken([TokenTypes.name])
|
|
16
|
+
const name = p.value
|
|
17
|
+
parseAttributes(p)
|
|
18
|
+
p.expectToken([TokenTypes.gte])
|
|
19
|
+
|
|
20
|
+
const content = parseProgram(p)
|
|
21
|
+
|
|
22
|
+
p.expectToken([TokenTypes.lteSlash])
|
|
23
|
+
p.expectToken([TokenTypes.name])
|
|
24
|
+
const tagNameClose = p.value
|
|
25
|
+
p.skipWhitespaces()
|
|
26
|
+
p.expectToken([TokenTypes.gte])
|
|
27
|
+
|
|
28
|
+
if (name !== tagNameClose) throw new Error('wrong closing tag')
|
|
29
|
+
|
|
30
|
+
return { type: 'Script', content, start, end: p.pos }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
*
|
|
35
|
+
* @param {Parser} p
|
|
36
|
+
* @returns {Program}
|
|
37
|
+
*/
|
|
38
|
+
function parseProgram(p) {
|
|
39
|
+
const start = p.pos
|
|
40
|
+
skipCode(p)
|
|
41
|
+
return parseJS(p.input.slice(start, p.pos), { ecmaVersion: 'latest', sourceType: 'module' })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
*
|
|
46
|
+
* @param {Parser} p
|
|
47
|
+
*/
|
|
48
|
+
function skipCode(p) {
|
|
49
|
+
while (p.pos < p.input.length) {
|
|
50
|
+
if (
|
|
51
|
+
p.input.charCodeAt(p.pos) === 60 &&
|
|
52
|
+
p.input.charCodeAt(p.pos + 1) === 47 &&
|
|
53
|
+
p.input.substring(p.pos + 2, p.pos + 8) === 'script'
|
|
54
|
+
) {
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
p.pos++
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { parse } from 'css-tree'
|
|
2
|
+
import { TokenTypes } from './tokentype.js'
|
|
3
|
+
import { Parser } from './parser.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {Parser} p
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
export function parseStyle(p) {
|
|
11
|
+
const start = p.pos
|
|
12
|
+
p.expectToken([TokenTypes.lte])
|
|
13
|
+
p.expectToken([TokenTypes.name])
|
|
14
|
+
const name = p.value
|
|
15
|
+
|
|
16
|
+
p.skipWhitespaces()
|
|
17
|
+
p.expectToken([TokenTypes.gte])
|
|
18
|
+
|
|
19
|
+
const content = parseCSS(p)
|
|
20
|
+
|
|
21
|
+
p.expectToken([TokenTypes.lteSlash])
|
|
22
|
+
p.expectToken([TokenTypes.name])
|
|
23
|
+
const tagNameClose = p.value
|
|
24
|
+
p.skipWhitespaces()
|
|
25
|
+
p.expectToken([TokenTypes.gte])
|
|
26
|
+
|
|
27
|
+
if (name !== tagNameClose) throw new Error('wrong closing tag')
|
|
28
|
+
|
|
29
|
+
return { type: 'Style', content, start, end: p.pos }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @param {Parser} p
|
|
35
|
+
*/
|
|
36
|
+
function parseCSS(p) {
|
|
37
|
+
const start = p.pos
|
|
38
|
+
skipCSS(p)
|
|
39
|
+
return parse(p.input.slice(start, p.pos))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
*
|
|
44
|
+
* @param {Parser} p
|
|
45
|
+
*/
|
|
46
|
+
function skipCSS(p) {
|
|
47
|
+
while (p.pos < p.input.length) {
|
|
48
|
+
if (
|
|
49
|
+
p.input.charCodeAt(p.pos) === 60 &&
|
|
50
|
+
p.input.charCodeAt(p.pos + 1) === 47 &&
|
|
51
|
+
p.input.substring(p.pos + 2, p.pos + 7) === 'style'
|
|
52
|
+
) {
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
p.pos++
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { parseAttributes } from './attributes.js'
|
|
2
|
+
import { parseFragment } from './element.js'
|
|
3
|
+
import { Parser } from './parser.js'
|
|
4
|
+
import { TokenTypes } from './tokentype.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param {Parser} p
|
|
9
|
+
*/
|
|
10
|
+
export function parseTemplate(p) {
|
|
11
|
+
const start = p.pos
|
|
12
|
+
|
|
13
|
+
p.expectToken([TokenTypes.lte])
|
|
14
|
+
p.expectToken([TokenTypes.name])
|
|
15
|
+
const name = p.value
|
|
16
|
+
const attributes = parseAttributes(p)
|
|
17
|
+
p.expectToken([TokenTypes.gte])
|
|
18
|
+
|
|
19
|
+
const fragment = parseFragment(p, true, true)
|
|
20
|
+
|
|
21
|
+
p.expectToken([TokenTypes.lteSlash])
|
|
22
|
+
p.expectToken([TokenTypes.name])
|
|
23
|
+
const tagNameClose = p.value
|
|
24
|
+
p.skipWhitespaces()
|
|
25
|
+
p.expectToken([TokenTypes.gte])
|
|
26
|
+
|
|
27
|
+
if (name !== tagNameClose) throw new Error('wrong closing tag')
|
|
28
|
+
|
|
29
|
+
return { type: 'Template', attributes, fragment, start, end: p.pos }
|
|
30
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} TokenType
|
|
3
|
+
* @property {string} label
|
|
4
|
+
* @property {Array<number>} [charCodes]
|
|
5
|
+
* @property {(char: number) => boolean} [test]
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @param {string} label
|
|
11
|
+
* @param {Array<number>} charCodes
|
|
12
|
+
* @returns {TokenType}
|
|
13
|
+
*/
|
|
14
|
+
function charTT(label, charCodes) {
|
|
15
|
+
return { label, charCodes }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
* @param {string} label
|
|
21
|
+
* @param {(char: number) => boolean} test
|
|
22
|
+
* @returns {TokenType}
|
|
23
|
+
*/
|
|
24
|
+
function stringTT(label, test) {
|
|
25
|
+
return { label, test }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const TokenTypes = {
|
|
29
|
+
name: stringTT('name', name),
|
|
30
|
+
text: stringTT('text', text),
|
|
31
|
+
|
|
32
|
+
// html punctuation
|
|
33
|
+
lte: charTT('<', [60]),
|
|
34
|
+
gte: charTT('>', [62]),
|
|
35
|
+
slashGte: charTT('/>', [47, 62]),
|
|
36
|
+
lteSlash: charTT('</', [60, 47]),
|
|
37
|
+
eq: charTT('=', [61]),
|
|
38
|
+
quote: charTT('\'', [39]),
|
|
39
|
+
doubleQuote: charTT('"', [34]),
|
|
40
|
+
|
|
41
|
+
// block punctuation
|
|
42
|
+
quoteBraceL: charTT('\'{', [39, 123]),
|
|
43
|
+
doubleQuoteBraceL: charTT('"{', [34, 123]),
|
|
44
|
+
braceRQuote: charTT('}\'', [125, 39]),
|
|
45
|
+
braceRDoubleQuote: charTT('}"', [125, 34]),
|
|
46
|
+
braceL: charTT('{', [123]),
|
|
47
|
+
braceR: charTT('}', [125]),
|
|
48
|
+
braceLHash: charTT('{#', [123, 35]),
|
|
49
|
+
braceLColumn: charTT('{:', [123, 58]),
|
|
50
|
+
braceLSlash: charTT('{/', [123, 47]),
|
|
51
|
+
parenthesesL: charTT('(', [40]),
|
|
52
|
+
parenthesesR: charTT(')', [41]),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function name(code) {
|
|
56
|
+
if (code === 45) return true
|
|
57
|
+
if (code < 48) return false
|
|
58
|
+
if (code < 58) return true
|
|
59
|
+
if (code < 65) return false
|
|
60
|
+
if (code < 91) return true
|
|
61
|
+
if (code < 123) return true
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function text(code) {
|
|
66
|
+
if (code === 10) return true
|
|
67
|
+
if (code === 32) return true
|
|
68
|
+
if (code === 47) return true
|
|
69
|
+
if (code < 48) return false
|
|
70
|
+
if (code < 58) return true
|
|
71
|
+
if (code < 65) return false
|
|
72
|
+
if (code < 91) return true
|
|
73
|
+
if (code < 123) return true
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function compileHtmlIndex(source, context) {
|
|
2
|
+
const template = '`' + source.replace('%katabatic.body%', '${slot.body}') + '`'
|
|
3
|
+
|
|
4
|
+
return {
|
|
5
|
+
...context,
|
|
6
|
+
...printIndex({ template })
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function printIndex({ template }) {
|
|
11
|
+
return {
|
|
12
|
+
code: `
|
|
13
|
+
export function renderIndex(slot) {
|
|
14
|
+
return ${template}
|
|
15
|
+
}
|
|
16
|
+
`
|
|
17
|
+
}
|
|
18
|
+
}
|