shaderkit 0.2.1 → 0.3.1
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 +1 -1
- package/README.md +350 -153
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -14
- package/src/ast.ts +343 -77
- package/src/constants.ts +4 -4
- package/src/generator.ts +102 -150
- package/src/index.ts +6 -5
- package/src/minifier.ts +27 -8
- package/src/parser.ts +497 -405
- package/src/tokenizer.ts +24 -13
- package/dist/index.cjs +0 -5
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -1
package/src/generator.ts
CHANGED
|
@@ -1,155 +1,113 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type AST,
|
|
3
|
-
Literal,
|
|
4
|
-
Identifier,
|
|
5
|
-
Type,
|
|
6
|
-
BlockStatement,
|
|
7
|
-
VariableDeclaration,
|
|
8
|
-
FunctionDeclaration,
|
|
9
|
-
CallExpression,
|
|
10
|
-
MemberExpression,
|
|
11
|
-
ArrayExpression,
|
|
12
|
-
IfStatement,
|
|
13
|
-
WhileStatement,
|
|
14
|
-
ForStatement,
|
|
15
|
-
DoWhileStatement,
|
|
16
|
-
SwitchStatement,
|
|
17
|
-
SwitchCase,
|
|
18
|
-
StructDeclaration,
|
|
19
|
-
UnaryExpression,
|
|
20
|
-
BinaryExpression,
|
|
21
|
-
TernaryExpression,
|
|
22
|
-
ReturnStatement,
|
|
23
|
-
PrecisionStatement,
|
|
24
|
-
ContinueStatement,
|
|
25
|
-
BreakStatement,
|
|
26
|
-
DiscardStatement,
|
|
27
|
-
PreprocessorStatement,
|
|
28
|
-
} from './ast'
|
|
1
|
+
import { type AST, type Program } from './ast.js'
|
|
29
2
|
|
|
30
|
-
|
|
3
|
+
function formatLayout(layout: Record<string, string | boolean> | null): string {
|
|
4
|
+
if (!layout) return ''
|
|
31
5
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return line
|
|
6
|
+
return `layout(${Object.entries(layout)
|
|
7
|
+
.map(([k, v]) => (v === true ? k : `${k}=${v}`))
|
|
8
|
+
.join(',')})`
|
|
36
9
|
}
|
|
37
10
|
|
|
38
|
-
// TODO:
|
|
11
|
+
// TODO: restore comments/whitespace with sourcemaps, WGSL support
|
|
39
12
|
function format(node: AST | null): string {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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(format).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 value = ` ${node.value.map(format).join(' ')}`
|
|
57
34
|
}
|
|
58
|
-
layout = `layout(${args.join(', ')}) `
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const qualifiers = node.qualifiers.length ? `${node.qualifiers.join(' ')} ` : ''
|
|
62
|
-
|
|
63
|
-
let type = format(node.type)
|
|
64
|
-
|
|
65
|
-
let body = ''
|
|
66
|
-
if (node.declarations.length) {
|
|
67
|
-
const members: string[] = []
|
|
68
|
-
for (const declaration of node.declarations) {
|
|
69
|
-
let value = ''
|
|
70
35
|
|
|
71
|
-
|
|
72
|
-
const t = declaration.value.type
|
|
73
|
-
const params = t.parameters ? t.parameters?.map(format).join(', ') : ''
|
|
74
|
-
value = `[${params}]`
|
|
75
|
-
|
|
76
|
-
if (declaration.value.members.length) {
|
|
77
|
-
value += ` = ${type}[${params}](${declaration.value.members.map(format).join(', ')})`
|
|
78
|
-
}
|
|
79
|
-
} else if (declaration.value) {
|
|
80
|
-
value = ` = ${format(declaration.value)}`
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
members.push(`${declaration.name}${value}`)
|
|
84
|
-
}
|
|
85
|
-
body = members.join(', ')
|
|
36
|
+
return `\n#${node.name}${value}\n`
|
|
86
37
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
} else if (node instanceof ArrayExpression) {
|
|
99
|
-
const params = node.type.parameters ? node.type.parameters?.map(format).join(', ') : ''
|
|
100
|
-
line = `${node.type.name}[${params}](${node.members.map(format).join(', ')})`
|
|
101
|
-
} else if (node instanceof IfStatement) {
|
|
102
|
-
const consequent = format(node.consequent).replace(EOL_REGEX, '')
|
|
103
|
-
const alternate = node.alternate ? ` else ${format(node.alternate).replace(EOL_REGEX, '')}` : ''
|
|
104
|
-
line = `if (${format(node.test)}) ${consequent}${alternate}\n`
|
|
105
|
-
} else if (node instanceof WhileStatement) {
|
|
106
|
-
line = `while (${format(node.test)}) ${format(node.body)}`
|
|
107
|
-
} else if (node instanceof ForStatement) {
|
|
108
|
-
const init = format(node.init).replace(';\n', '')
|
|
109
|
-
line = `for (${init}; ${format(node.test)}; ${format(node.update)}) ${format(node.body)}`
|
|
110
|
-
} else if (node instanceof DoWhileStatement) {
|
|
111
|
-
line = `do ${format(node.body).replace(EOL_REGEX, '')} while (${format(node.test)});\n`
|
|
112
|
-
} else if (node instanceof SwitchStatement) {
|
|
113
|
-
const cr = node.cases.length ? '\n' : ''
|
|
114
|
-
line = `switch (${format(node.discriminant)}) {${cr}${node.cases.map(format).join('')}}\n`
|
|
115
|
-
} else if (node instanceof SwitchCase) {
|
|
116
|
-
const header = node.test ? `case ${format(node.test)}:` : 'default:'
|
|
117
|
-
line = ` ${header}\n${node.consequent.map((node) => ` ${punctuate(format(node))}`).join('')}`
|
|
118
|
-
} else if (node instanceof StructDeclaration) {
|
|
119
|
-
const cr = node.members.length ? '\n' : ''
|
|
120
|
-
line = `struct ${node.name} {${cr}${node.members.map((node) => ` ${format(node)}`).join('')}};\n`
|
|
121
|
-
} else if (node instanceof UnaryExpression) {
|
|
122
|
-
line = node.left ? `${format(node.left)}${node.operator}` : `${node.operator}${format(node.right)}`
|
|
123
|
-
} else if (node instanceof BinaryExpression) {
|
|
124
|
-
line = `${format(node.left)} ${node.operator} ${format(node.right)}`
|
|
125
|
-
} else if (node instanceof TernaryExpression) {
|
|
126
|
-
line = `${format(node.test)} ? ${format(node.consequent)} : ${format(node.alternate)}`
|
|
127
|
-
} else if (node instanceof ReturnStatement) {
|
|
128
|
-
line = node.argument ? `return ${format(node.argument)};\n` : `return;\n`
|
|
129
|
-
} else if (node instanceof PrecisionStatement) {
|
|
130
|
-
line = `precision ${node.precision} ${node.type.name};\n`
|
|
131
|
-
} else if (node instanceof ContinueStatement) {
|
|
132
|
-
line = 'continue;\n'
|
|
133
|
-
} else if (node instanceof BreakStatement) {
|
|
134
|
-
line = 'break;\n'
|
|
135
|
-
} else if (node instanceof DiscardStatement) {
|
|
136
|
-
line = 'discard;\n'
|
|
137
|
-
} else if (node instanceof PreprocessorStatement) {
|
|
138
|
-
let value = ''
|
|
139
|
-
if (node.value) {
|
|
140
|
-
if (node.name === 'include') {
|
|
141
|
-
value = ` <${format(node.value[0])}>`
|
|
142
|
-
} else if (node.name === 'extension') {
|
|
143
|
-
value = ` ${node.value.map(format).join(': ')}`
|
|
144
|
-
} else {
|
|
145
|
-
value = ` ${node.value.map(format).join(' ')}`
|
|
146
|
-
}
|
|
38
|
+
case 'PrecisionStatement':
|
|
39
|
+
return `precision ${node.precision} ${node.typeSpecifier.name};`
|
|
40
|
+
case 'ReturnStatement':
|
|
41
|
+
return node.argument ? `return ${format(node.argument)};` : 'return;'
|
|
42
|
+
case 'BreakStatement':
|
|
43
|
+
return 'break;'
|
|
44
|
+
case 'ContinueStatement':
|
|
45
|
+
return 'continue;'
|
|
46
|
+
case 'IfStatement': {
|
|
47
|
+
const alternate = node.alternate ? ` else${format(node.consequent)}` : ''
|
|
48
|
+
return `if(${format(node.test)})${format(node.consequent)}${alternate}`
|
|
147
49
|
}
|
|
148
|
-
|
|
149
|
-
|
|
50
|
+
case 'SwitchStatement':
|
|
51
|
+
return `switch(${format(node.discriminant)}){${node.cases.map(format).join('')}}`
|
|
52
|
+
case 'SwitchCase':
|
|
53
|
+
return `case ${node.test ? format(node.test) : 'default'}:{${node.consequent.map(format).join(';')}}`
|
|
54
|
+
case 'WhileStatement':
|
|
55
|
+
return `while (${format(node.test)}) ${format(node.body)}`
|
|
56
|
+
case 'DoWhileStatement':
|
|
57
|
+
return `do ${format(node.body)}while(${format(node.test)})`
|
|
58
|
+
case 'ForStatement':
|
|
59
|
+
return `for(${format(node.init)};${format(node.test)};${format(node.update)})${format(node.body)}`
|
|
60
|
+
case 'FunctionDeclaration': {
|
|
61
|
+
const qualifiers = node.qualifiers.length ? `${node.qualifiers.join(' ')} ` : '' // precision
|
|
62
|
+
const body = node.body ? format(node.body) : ';'
|
|
63
|
+
return `${qualifiers}${format(node.typeSpecifier)} ${format(node.id)}(${node.params
|
|
64
|
+
.map(format)
|
|
65
|
+
.join(',')})${body}`
|
|
66
|
+
}
|
|
67
|
+
case 'FunctionParameter': {
|
|
68
|
+
const qualifiers = node.qualifiers.length ? `${node.qualifiers.join(' ')} ` : ''
|
|
69
|
+
return `${qualifiers}${format(node.typeSpecifier)} ${format(node.id)}`
|
|
70
|
+
}
|
|
71
|
+
case 'VariableDeclaration': {
|
|
72
|
+
const head = node.declarations[0]
|
|
73
|
+
const layout = formatLayout(head.layout)
|
|
74
|
+
const qualifiers = head.qualifiers.length ? `${head.qualifiers.join(' ')} ` : ''
|
|
75
|
+
return `${layout}${qualifiers}${format(head.typeSpecifier)} ${node.declarations.map(format).join(',')};`
|
|
76
|
+
}
|
|
77
|
+
case 'VariableDeclarator': {
|
|
78
|
+
const init = node.init ? `=${format(node.init)}` : ''
|
|
79
|
+
return `${format(node.id)}${init}`
|
|
80
|
+
}
|
|
81
|
+
case 'UniformDeclarationBlock': {
|
|
82
|
+
const layout = formatLayout(node.layout)
|
|
83
|
+
const qualifiers = node.qualifiers.length ? `${node.qualifiers.join(' ')} ` : ''
|
|
84
|
+
const scope = node.id ? `${format(node.id)}` : ''
|
|
85
|
+
return `${layout}${qualifiers}${format(node.typeSpecifier)}{${node.members.map(format).join('')}}${scope};`
|
|
86
|
+
}
|
|
87
|
+
case 'StructDeclaration':
|
|
88
|
+
return `struct ${format(node.id)}{${node.members.map(format).join('')}};`
|
|
89
|
+
case 'ArrayExpression':
|
|
90
|
+
return `${format(node.typeSpecifier)}(${node.elements.map(format).join(',')})`
|
|
91
|
+
case 'UnaryExpression':
|
|
92
|
+
case 'UpdateExpression':
|
|
93
|
+
return node.prefix ? `${node.operator}${format(node.argument)}` : `${format(node.argument)}${node.operator}`
|
|
94
|
+
case 'BinaryExpression':
|
|
95
|
+
case 'AssignmentExpression':
|
|
96
|
+
case 'LogicalExpression':
|
|
97
|
+
return `${format(node.left)}${node.operator}${format(node.right)}`
|
|
98
|
+
case 'MemberExpression':
|
|
99
|
+
return node.computed
|
|
100
|
+
? `${format(node.object)}[${format(node.property)}]`
|
|
101
|
+
: `${format(node.object)}.${format(node.property)}`
|
|
102
|
+
case 'ConditionalExpression':
|
|
103
|
+
return `${format(node.test)}?${format(node.consequent)}:${format(node.alternate)}`
|
|
104
|
+
case 'CallExpression':
|
|
105
|
+
return `${format(node.callee)}(${node.arguments.map(format).join(',')})`
|
|
106
|
+
case 'Program':
|
|
107
|
+
return `${node.body.map(format).join('')}`
|
|
108
|
+
default:
|
|
109
|
+
return node satisfies never
|
|
150
110
|
}
|
|
151
|
-
|
|
152
|
-
return line
|
|
153
111
|
}
|
|
154
112
|
|
|
155
113
|
export interface GenerateOptions {
|
|
@@ -159,12 +117,6 @@ export interface GenerateOptions {
|
|
|
159
117
|
/**
|
|
160
118
|
* Generates a string of GLSL (WGSL WIP) code from an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree).
|
|
161
119
|
*/
|
|
162
|
-
export function generate(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
for (let i = 0; i < ast.length; i++) {
|
|
166
|
-
code += punctuate(format(ast[i]))
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return code.trim()
|
|
120
|
+
export function generate(program: Program, options: GenerateOptions): string {
|
|
121
|
+
return format(program).replaceAll('\n\n', '\n').trim()
|
|
170
122
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export * from './ast'
|
|
2
|
-
export * from './constants'
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './
|
|
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'
|
package/src/minifier.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Token, tokenize } from './tokenizer'
|
|
1
|
+
import { type Token, tokenize } from './tokenizer.js'
|
|
2
2
|
|
|
3
3
|
export type MangleMatcher = (token: Token, index: number, tokens: Token[]) => boolean
|
|
4
4
|
|
|
@@ -11,11 +11,13 @@ export interface MinifyOptions {
|
|
|
11
11
|
mangleExternals: boolean
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const isWord = RegExp.prototype.test.bind(/^\w/)
|
|
15
|
-
const isSymbol = RegExp.prototype.test.bind(/[^\w\\]/)
|
|
16
|
-
const isName = RegExp.prototype.test.bind(/^[_A-Za-z]/)
|
|
17
|
-
const isScoped = RegExp.prototype.test.bind(/[;{}\\@]/)
|
|
18
|
-
const isStorage = RegExp.prototype.test.bind(
|
|
14
|
+
const isWord = /* @__PURE__ */ RegExp.prototype.test.bind(/^\w/)
|
|
15
|
+
const isSymbol = /* @__PURE__ */ RegExp.prototype.test.bind(/[^\w\\]/)
|
|
16
|
+
const isName = /* @__PURE__ */ RegExp.prototype.test.bind(/^[_A-Za-z]/)
|
|
17
|
+
const isScoped = /* @__PURE__ */ RegExp.prototype.test.bind(/[;{}\\@]/)
|
|
18
|
+
const isStorage = /* @__PURE__ */ RegExp.prototype.test.bind(
|
|
19
|
+
/^(binding|group|layout|uniform|in|out|attribute|varying)$/,
|
|
20
|
+
)
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Minifies a string of GLSL or WGSL code.
|
|
@@ -51,9 +53,26 @@ export function minify(
|
|
|
51
53
|
if (
|
|
52
54
|
isSymbol(token.value) &&
|
|
53
55
|
((tokens[i - 2]?.value === '#' && tokens[i - 1]?.value === 'include') ||
|
|
56
|
+
(tokens[i - 2]?.value === '#' && tokens[i - 1]?.value === 'if') ||
|
|
57
|
+
(tokens[i - 2]?.value === '#' && tokens[i - 1]?.value === 'elif') ||
|
|
54
58
|
(tokens[i - 3]?.value === '#' && tokens[i - 2]?.value === 'define'))
|
|
55
|
-
)
|
|
56
|
-
|
|
59
|
+
) {
|
|
60
|
+
// Move padding after #define arguments
|
|
61
|
+
if (token.value === '(') {
|
|
62
|
+
while (i < tokens.length) {
|
|
63
|
+
const next = tokens[i++]
|
|
64
|
+
minified += next.value
|
|
65
|
+
|
|
66
|
+
if (next.value === ')') break
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
minified += ' ' + tokens[i].value
|
|
70
|
+
|
|
71
|
+
continue
|
|
72
|
+
} else {
|
|
73
|
+
minified += ' '
|
|
74
|
+
}
|
|
75
|
+
}
|
|
57
76
|
|
|
58
77
|
let prefix = token.value
|
|
59
78
|
if (tokens[i - 1]?.value === '.') {
|