shaderkit 0.7.0 → 0.8.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/LICENSE +21 -21
- package/README.md +781 -781
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/package.json +46 -46
- package/src/ast.ts +425 -425
- package/src/constants.ts +1005 -1005
- package/src/generator.ts +128 -128
- package/src/hoister.ts +171 -0
- package/src/index.ts +7 -7
- package/src/minifier.ts +445 -445
- package/src/parser.ts +878 -802
- package/src/tokenizer.ts +105 -105
- package/src/visitor.ts +131 -131
package/src/generator.ts
CHANGED
|
@@ -1,128 +1,128 @@
|
|
|
1
|
-
import { type AST, type Program } from './ast.js'
|
|
2
|
-
|
|
3
|
-
function formatLayout(layout: Record<string, string | boolean> | null): string {
|
|
4
|
-
if (!layout) return ''
|
|
5
|
-
|
|
6
|
-
return `layout(${Object.entries(layout)
|
|
7
|
-
.map(([k, v]) => (v === true ? k : `${k}=${v}`))
|
|
8
|
-
.join(',')})`
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// TODO: restore comments/whitespace with sourcemaps, WGSL support
|
|
12
|
-
function format(node: AST | null): string {
|
|
13
|
-
if (!node) return ''
|
|
14
|
-
|
|
15
|
-
switch (node.type) {
|
|
16
|
-
case 'Identifier':
|
|
17
|
-
return node.name
|
|
18
|
-
case 'Literal':
|
|
19
|
-
return node.value
|
|
20
|
-
case 'ArraySpecifier':
|
|
21
|
-
return `${node.typeSpecifier.name}${node.dimensions.map((d) => `[${format(d)}]`).join('')}`
|
|
22
|
-
case 'ExpressionStatement':
|
|
23
|
-
return `${format(node.expression)};`
|
|
24
|
-
case 'BlockStatement':
|
|
25
|
-
return `{${node.body.map(format).join('')}}`
|
|
26
|
-
case 'DiscardStatement':
|
|
27
|
-
return 'discard;'
|
|
28
|
-
case 'PreprocessorStatement': {
|
|
29
|
-
let value = ''
|
|
30
|
-
if (node.value) {
|
|
31
|
-
if (node.name === 'include') value = ` <${format(node.value[0])}>` // three is whitespace sensitive
|
|
32
|
-
else if (node.name === 'extension') value = ` ${node.value.map(format).join(':')}`
|
|
33
|
-
else if (node.value.length) value = ` ${node.value.map(format).join(' ')}`
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return `\n#${node.name}${value}\n`
|
|
37
|
-
}
|
|
38
|
-
case 'PrecisionQualifierStatement':
|
|
39
|
-
return `precision ${node.precision} ${node.typeSpecifier.name};`
|
|
40
|
-
case 'InvariantQualifierStatement':
|
|
41
|
-
return `invariant ${format(node.typeSpecifier)};`
|
|
42
|
-
case 'LayoutQualifierStatement':
|
|
43
|
-
return `${formatLayout(node.layout)}${node.qualifier};`
|
|
44
|
-
case 'ReturnStatement':
|
|
45
|
-
return node.argument ? `return ${format(node.argument)};` : 'return;'
|
|
46
|
-
case 'BreakStatement':
|
|
47
|
-
return 'break;'
|
|
48
|
-
case 'ContinueStatement':
|
|
49
|
-
return 'continue;'
|
|
50
|
-
case 'IfStatement': {
|
|
51
|
-
const alternate = node.alternate ? ` else${format(node.consequent)}` : ''
|
|
52
|
-
return `if(${format(node.test)})${format(node.consequent)}${alternate}`
|
|
53
|
-
}
|
|
54
|
-
case 'SwitchStatement':
|
|
55
|
-
return `switch(${format(node.discriminant)}){${node.cases.map(format).join('')}}`
|
|
56
|
-
case 'SwitchCase':
|
|
57
|
-
return `case ${node.test ? format(node.test) : 'default'}:{${node.consequent.map(format).join(';')}}`
|
|
58
|
-
case 'WhileStatement':
|
|
59
|
-
return `while (${format(node.test)}) ${format(node.body)}`
|
|
60
|
-
case 'DoWhileStatement':
|
|
61
|
-
return `do ${format(node.body)}while(${format(node.test)})`
|
|
62
|
-
case 'ForStatement':
|
|
63
|
-
return `for(${format(node.init)};${format(node.test)};${format(node.update)})${format(node.body)}`
|
|
64
|
-
case 'FunctionDeclaration': {
|
|
65
|
-
const qualifiers = node.qualifiers.length ? `${node.qualifiers.join(' ')} ` : '' // precision
|
|
66
|
-
const body = node.body ? format(node.body) : ';'
|
|
67
|
-
return `${qualifiers}${format(node.typeSpecifier)} ${format(node.id)}(${node.params
|
|
68
|
-
.map(format)
|
|
69
|
-
.join(',')})${body}`
|
|
70
|
-
}
|
|
71
|
-
case 'FunctionParameter': {
|
|
72
|
-
const qualifiers = node.qualifiers.length ? `${node.qualifiers.join(' ')} ` : ''
|
|
73
|
-
const id = node.id ? ` ${format(node.id)}` : ''
|
|
74
|
-
return `${qualifiers}${format(node.typeSpecifier)}${id}`
|
|
75
|
-
}
|
|
76
|
-
case 'VariableDeclaration': {
|
|
77
|
-
const head = node.declarations[0]
|
|
78
|
-
const layout = formatLayout(head.layout)
|
|
79
|
-
const qualifiers = head.qualifiers.length ? `${head.qualifiers.join(' ')} ` : ''
|
|
80
|
-
return `${layout}${qualifiers}${format(head.typeSpecifier)} ${node.declarations.map(format).join(',')};`
|
|
81
|
-
}
|
|
82
|
-
case 'VariableDeclarator': {
|
|
83
|
-
const init = node.init ? `=${format(node.init)}` : ''
|
|
84
|
-
return `${format(node.id)}${init}`
|
|
85
|
-
}
|
|
86
|
-
case 'StructuredBufferDeclaration': {
|
|
87
|
-
const layout = formatLayout(node.layout)
|
|
88
|
-
const scope = node.id ? `${format(node.id)}` : ''
|
|
89
|
-
return `${layout}${node.qualifiers.join(' ')} ${format(node.typeSpecifier)}{${node.members
|
|
90
|
-
.map(format)
|
|
91
|
-
.join('')}}${scope};`
|
|
92
|
-
}
|
|
93
|
-
case 'StructDeclaration':
|
|
94
|
-
return `struct ${format(node.id)}{${node.members.map(format).join('')}};`
|
|
95
|
-
case 'ArrayExpression':
|
|
96
|
-
return `${format(node.typeSpecifier)}(${node.elements.map(format).join(',')})`
|
|
97
|
-
case 'UnaryExpression':
|
|
98
|
-
case 'UpdateExpression':
|
|
99
|
-
return node.prefix ? `${node.operator}${format(node.argument)}` : `${format(node.argument)}${node.operator}`
|
|
100
|
-
case 'BinaryExpression':
|
|
101
|
-
case 'AssignmentExpression':
|
|
102
|
-
case 'LogicalExpression':
|
|
103
|
-
return `${format(node.left)}${node.operator}${format(node.right)}`
|
|
104
|
-
case 'MemberExpression':
|
|
105
|
-
return node.computed
|
|
106
|
-
? `${format(node.object)}[${format(node.property)}]`
|
|
107
|
-
: `${format(node.object)}.${format(node.property)}`
|
|
108
|
-
case 'ConditionalExpression':
|
|
109
|
-
return `${format(node.test)}?${format(node.alternate)}:${format(node.consequent)}`
|
|
110
|
-
case 'CallExpression':
|
|
111
|
-
return `${format(node.callee)}(${node.arguments.map(format).join(',')})`
|
|
112
|
-
case 'Program':
|
|
113
|
-
return `${node.body.map(format).join('')}`
|
|
114
|
-
default:
|
|
115
|
-
return node satisfies never
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export interface GenerateOptions {
|
|
120
|
-
target: 'GLSL' // | 'WGSL'
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Generates a string of GLSL (WGSL WIP) code from an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree).
|
|
125
|
-
*/
|
|
126
|
-
export function generate(program: Program, options: GenerateOptions): string {
|
|
127
|
-
return format(program).replaceAll('\n\n', '\n').replaceAll('] ', ']').trim()
|
|
128
|
-
}
|
|
1
|
+
import { type AST, type Program } from './ast.js'
|
|
2
|
+
|
|
3
|
+
function formatLayout(layout: Record<string, string | boolean> | null): string {
|
|
4
|
+
if (!layout) return ''
|
|
5
|
+
|
|
6
|
+
return `layout(${Object.entries(layout)
|
|
7
|
+
.map(([k, v]) => (v === true ? k : `${k}=${v}`))
|
|
8
|
+
.join(',')})`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// TODO: restore comments/whitespace with sourcemaps, WGSL support
|
|
12
|
+
function format(node: AST | null): string {
|
|
13
|
+
if (!node) return ''
|
|
14
|
+
|
|
15
|
+
switch (node.type) {
|
|
16
|
+
case 'Identifier':
|
|
17
|
+
return node.name
|
|
18
|
+
case 'Literal':
|
|
19
|
+
return node.value
|
|
20
|
+
case 'ArraySpecifier':
|
|
21
|
+
return `${node.typeSpecifier.name}${node.dimensions.map((d) => `[${format(d)}]`).join('')}`
|
|
22
|
+
case 'ExpressionStatement':
|
|
23
|
+
return `${format(node.expression)};`
|
|
24
|
+
case 'BlockStatement':
|
|
25
|
+
return `{${node.body.map(format).join('')}}`
|
|
26
|
+
case 'DiscardStatement':
|
|
27
|
+
return 'discard;'
|
|
28
|
+
case 'PreprocessorStatement': {
|
|
29
|
+
let value = ''
|
|
30
|
+
if (node.value) {
|
|
31
|
+
if (node.name === 'include') value = ` <${format(node.value[0])}>` // three is whitespace sensitive
|
|
32
|
+
else if (node.name === 'extension') value = ` ${node.value.map(format).join(':')}`
|
|
33
|
+
else if (node.value.length) value = ` ${node.value.map(format).join(' ')}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return `\n#${node.name}${value}\n`
|
|
37
|
+
}
|
|
38
|
+
case 'PrecisionQualifierStatement':
|
|
39
|
+
return `precision ${node.precision} ${node.typeSpecifier.name};`
|
|
40
|
+
case 'InvariantQualifierStatement':
|
|
41
|
+
return `invariant ${format(node.typeSpecifier)};`
|
|
42
|
+
case 'LayoutQualifierStatement':
|
|
43
|
+
return `${formatLayout(node.layout)}${node.qualifier};`
|
|
44
|
+
case 'ReturnStatement':
|
|
45
|
+
return node.argument ? `return ${format(node.argument)};` : 'return;'
|
|
46
|
+
case 'BreakStatement':
|
|
47
|
+
return 'break;'
|
|
48
|
+
case 'ContinueStatement':
|
|
49
|
+
return 'continue;'
|
|
50
|
+
case 'IfStatement': {
|
|
51
|
+
const alternate = node.alternate ? ` else${format(node.consequent)}` : ''
|
|
52
|
+
return `if(${format(node.test)})${format(node.consequent)}${alternate}`
|
|
53
|
+
}
|
|
54
|
+
case 'SwitchStatement':
|
|
55
|
+
return `switch(${format(node.discriminant)}){${node.cases.map(format).join('')}}`
|
|
56
|
+
case 'SwitchCase':
|
|
57
|
+
return `case ${node.test ? format(node.test) : 'default'}:{${node.consequent.map(format).join(';')}}`
|
|
58
|
+
case 'WhileStatement':
|
|
59
|
+
return `while (${format(node.test)}) ${format(node.body)}`
|
|
60
|
+
case 'DoWhileStatement':
|
|
61
|
+
return `do ${format(node.body)}while(${format(node.test)})`
|
|
62
|
+
case 'ForStatement':
|
|
63
|
+
return `for(${format(node.init)};${format(node.test)};${format(node.update)})${format(node.body)}`
|
|
64
|
+
case 'FunctionDeclaration': {
|
|
65
|
+
const qualifiers = node.qualifiers.length ? `${node.qualifiers.join(' ')} ` : '' // precision
|
|
66
|
+
const body = node.body ? format(node.body) : ';'
|
|
67
|
+
return `${qualifiers}${format(node.typeSpecifier)} ${format(node.id)}(${node.params
|
|
68
|
+
.map(format)
|
|
69
|
+
.join(',')})${body}`
|
|
70
|
+
}
|
|
71
|
+
case 'FunctionParameter': {
|
|
72
|
+
const qualifiers = node.qualifiers.length ? `${node.qualifiers.join(' ')} ` : ''
|
|
73
|
+
const id = node.id ? ` ${format(node.id)}` : ''
|
|
74
|
+
return `${qualifiers}${format(node.typeSpecifier)}${id}`
|
|
75
|
+
}
|
|
76
|
+
case 'VariableDeclaration': {
|
|
77
|
+
const head = node.declarations[0]
|
|
78
|
+
const layout = formatLayout(head.layout)
|
|
79
|
+
const qualifiers = head.qualifiers.length ? `${head.qualifiers.join(' ')} ` : ''
|
|
80
|
+
return `${layout}${qualifiers}${format(head.typeSpecifier)} ${node.declarations.map(format).join(',')};`
|
|
81
|
+
}
|
|
82
|
+
case 'VariableDeclarator': {
|
|
83
|
+
const init = node.init ? `=${format(node.init)}` : ''
|
|
84
|
+
return `${format(node.id)}${init}`
|
|
85
|
+
}
|
|
86
|
+
case 'StructuredBufferDeclaration': {
|
|
87
|
+
const layout = formatLayout(node.layout)
|
|
88
|
+
const scope = node.id ? `${format(node.id)}` : ''
|
|
89
|
+
return `${layout}${node.qualifiers.join(' ')} ${format(node.typeSpecifier)}{${node.members
|
|
90
|
+
.map(format)
|
|
91
|
+
.join('')}}${scope};`
|
|
92
|
+
}
|
|
93
|
+
case 'StructDeclaration':
|
|
94
|
+
return `struct ${format(node.id)}{${node.members.map(format).join('')}};`
|
|
95
|
+
case 'ArrayExpression':
|
|
96
|
+
return `${format(node.typeSpecifier)}(${node.elements.map(format).join(',')})`
|
|
97
|
+
case 'UnaryExpression':
|
|
98
|
+
case 'UpdateExpression':
|
|
99
|
+
return node.prefix ? `${node.operator}${format(node.argument)}` : `${format(node.argument)}${node.operator}`
|
|
100
|
+
case 'BinaryExpression':
|
|
101
|
+
case 'AssignmentExpression':
|
|
102
|
+
case 'LogicalExpression':
|
|
103
|
+
return `${format(node.left)}${node.operator}${format(node.right)}`
|
|
104
|
+
case 'MemberExpression':
|
|
105
|
+
return node.computed
|
|
106
|
+
? `${format(node.object)}[${format(node.property)}]`
|
|
107
|
+
: `${format(node.object)}.${format(node.property)}`
|
|
108
|
+
case 'ConditionalExpression':
|
|
109
|
+
return `${format(node.test)}?${format(node.alternate)}:${format(node.consequent)}`
|
|
110
|
+
case 'CallExpression':
|
|
111
|
+
return `${format(node.callee)}(${node.arguments.map(format).join(',')})`
|
|
112
|
+
case 'Program':
|
|
113
|
+
return `${node.body.map(format).join('')}`
|
|
114
|
+
default:
|
|
115
|
+
return node satisfies never
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface GenerateOptions {
|
|
120
|
+
target: 'GLSL' // | 'WGSL'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Generates a string of GLSL (WGSL WIP) code from an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree).
|
|
125
|
+
*/
|
|
126
|
+
export function generate(program: Program, options: GenerateOptions): string {
|
|
127
|
+
return format(program).replaceAll('\n\n', '\n').replaceAll('] ', ']').trim()
|
|
128
|
+
}
|
package/src/hoister.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { Token } from './tokenizer.js'
|
|
2
|
+
|
|
3
|
+
export interface HoistNode {
|
|
4
|
+
prefix: Token[]
|
|
5
|
+
cases: { cond: Token[]; node: HoistNode }[]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A set of tokens optionally starting with a directive
|
|
10
|
+
*/
|
|
11
|
+
type PreprocessorSegment = {
|
|
12
|
+
directive?: Token[] | undefined
|
|
13
|
+
suffix: Token[]
|
|
14
|
+
scope: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const preScopeDelta = {
|
|
18
|
+
if: 1,
|
|
19
|
+
ifdef: 1,
|
|
20
|
+
ifndef: 1,
|
|
21
|
+
} as Record<string, number>
|
|
22
|
+
|
|
23
|
+
const postScopeDelta = {
|
|
24
|
+
endif: -1,
|
|
25
|
+
} as Record<string, number>
|
|
26
|
+
|
|
27
|
+
export function segmentDirectives(tokens: Token[]): PreprocessorSegment[] {
|
|
28
|
+
const segments: PreprocessorSegment[] = []
|
|
29
|
+
|
|
30
|
+
// Gathering the first non-directive segment, if it exists
|
|
31
|
+
let cursor = 0
|
|
32
|
+
let scope = 0
|
|
33
|
+
const prefix: Token[] = []
|
|
34
|
+
while (cursor < tokens.length && tokens[cursor].value !== '#') {
|
|
35
|
+
prefix.push(tokens[cursor++])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (prefix.length > 0) {
|
|
39
|
+
segments.push({ suffix: trimWhitespace(prefix), scope })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
while (cursor < tokens.length) {
|
|
43
|
+
const directive: Token[] = []
|
|
44
|
+
while (cursor < tokens.length && tokens[cursor].value !== '\\') {
|
|
45
|
+
directive.push(tokens[cursor++])
|
|
46
|
+
}
|
|
47
|
+
directive.push(tokens[cursor++]) // push the '\\'
|
|
48
|
+
|
|
49
|
+
const suffix: Token[] = []
|
|
50
|
+
while (cursor < tokens.length && tokens[cursor].value !== '#') {
|
|
51
|
+
suffix.push(tokens[cursor++])
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const name = directive[1]?.value ?? ''
|
|
55
|
+
scope += preScopeDelta[name] || 0
|
|
56
|
+
segments.push({ directive, suffix: trimWhitespace(suffix), scope })
|
|
57
|
+
scope += postScopeDelta[name] || 0
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return segments
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getDirectiveName(segment: PreprocessorSegment): string {
|
|
64
|
+
return segment.directive?.[1]?.value ?? ''
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function trimWhitespace(tokens: Token[]): Token[] {
|
|
68
|
+
let start = 0
|
|
69
|
+
let end = tokens.length - 1
|
|
70
|
+
|
|
71
|
+
if (end === 0 && tokens[0].type === 'whitespace') {
|
|
72
|
+
return []
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
while (start < end && tokens[start].type === 'whitespace') {
|
|
76
|
+
start++
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
while (end > start && tokens[end].type === 'whitespace') {
|
|
80
|
+
end--
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return tokens.slice(start, end + 1)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function constructHoistTree(
|
|
87
|
+
segments: PreprocessorSegment[],
|
|
88
|
+
scope: number = 0,
|
|
89
|
+
remainder: HoistNode | undefined = undefined,
|
|
90
|
+
): HoistNode {
|
|
91
|
+
let leafNode: HoistNode = {
|
|
92
|
+
cases: remainder?.cases ?? [],
|
|
93
|
+
prefix: remainder?.prefix ?? [],
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let currentNode: HoistNode | undefined
|
|
97
|
+
// The segment index that marks the end of the currently
|
|
98
|
+
// explored case (exclusive)
|
|
99
|
+
let caseEnd = segments.length - 1
|
|
100
|
+
|
|
101
|
+
for (let seg = segments.length - 1; seg >= 0; seg--) {
|
|
102
|
+
const segment = segments[seg]
|
|
103
|
+
if (segment.scope === scope) {
|
|
104
|
+
// A segment that belongs to the outer scope, meaning we're at the top of the nested node
|
|
105
|
+
leafNode.prefix = [...segment.suffix, ...leafNode.prefix]
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (segment.scope > scope + 1) {
|
|
110
|
+
continue // A nested segment, skip it
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const name = getDirectiveName(segment)
|
|
114
|
+
if (name === '') {
|
|
115
|
+
// The first segment, no directive
|
|
116
|
+
if (leafNode) {
|
|
117
|
+
leafNode.prefix = [...segment.suffix, ...leafNode.prefix]
|
|
118
|
+
}
|
|
119
|
+
} else if (name === 'endif') {
|
|
120
|
+
// Prepending the suffix of the endif segment before the leaf node
|
|
121
|
+
leafNode.prefix = [...segment.suffix, ...leafNode.prefix]
|
|
122
|
+
caseEnd = seg
|
|
123
|
+
currentNode = {
|
|
124
|
+
cases: [],
|
|
125
|
+
prefix: [],
|
|
126
|
+
}
|
|
127
|
+
} else if (name === 'else' || name === 'elif' || name === 'if' || name === 'ifdef' || name === 'ifndef') {
|
|
128
|
+
const caseNode = constructHoistTree(segments.slice(seg, caseEnd), scope + 1, leafNode)
|
|
129
|
+
caseEnd = seg // the next ends where the previous started
|
|
130
|
+
currentNode?.cases.unshift({ cond: segment.directive ?? [], node: caseNode })
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (name === 'if' || name === 'ifdef' || name === 'ifndef') {
|
|
134
|
+
// We're finishing up a whole node
|
|
135
|
+
leafNode = currentNode!
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// No nested conditions in this node
|
|
140
|
+
return leafNode
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function flattenHoistNode(node: HoistNode, prefix: Token[]): Token[] {
|
|
144
|
+
if (node.cases.length === 0) {
|
|
145
|
+
return [...prefix, ...node.prefix]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return [
|
|
149
|
+
...node.cases.flatMap((case_) => {
|
|
150
|
+
return [
|
|
151
|
+
...case_.cond,
|
|
152
|
+
{ type: 'whitespace' as const, value: '\n' },
|
|
153
|
+
...flattenHoistNode(case_.node, [...prefix, ...node.prefix]),
|
|
154
|
+
{ type: 'whitespace' as const, value: '\n' },
|
|
155
|
+
]
|
|
156
|
+
}),
|
|
157
|
+
{ type: 'symbol', value: '#' },
|
|
158
|
+
{ type: 'keyword', value: 'endif' },
|
|
159
|
+
{ type: 'symbol' as const, value: '\\' },
|
|
160
|
+
{ type: 'whitespace' as const, value: '\n' },
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Hoists preprocessor directives on the token level
|
|
166
|
+
*/
|
|
167
|
+
export function hoistPreprocessorDirectives(tokens: Token[]): Token[] {
|
|
168
|
+
const tree = constructHoistTree(segmentDirectives(tokens))
|
|
169
|
+
|
|
170
|
+
return flattenHoistNode(tree, [])
|
|
171
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export * from './ast.js'
|
|
2
|
-
export * from './constants.js'
|
|
3
|
-
export * from './generator.js'
|
|
4
|
-
export * from './minifier.js'
|
|
5
|
-
export * from './parser.js'
|
|
6
|
-
export * from './tokenizer.js'
|
|
7
|
-
export * from './visitor.js'
|
|
1
|
+
export * from './ast.js'
|
|
2
|
+
export * from './constants.js'
|
|
3
|
+
export * from './generator.js'
|
|
4
|
+
export * from './minifier.js'
|
|
5
|
+
export * from './parser.js'
|
|
6
|
+
export * from './tokenizer.js'
|
|
7
|
+
export * from './visitor.js'
|