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/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
- const EOL_REGEX = /\n$/
3
+ function formatLayout(layout: Record<string, string | boolean> | null): string {
4
+ if (!layout) return ''
31
5
 
32
- // Punctuates expression statements
33
- function punctuate(line: string): string {
34
- if (!line.endsWith('\n')) line = line + ';\n'
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: GLSL-only
11
+ // TODO: restore comments/whitespace with sourcemaps, WGSL support
39
12
  function format(node: AST | null): string {
40
- let line = ''
41
-
42
- if (node instanceof Literal || node instanceof Identifier) {
43
- line = node.value
44
- } else if (node instanceof Type) {
45
- line = node.parameters ? `${node.name}<${node.parameters.map(format).join(', ')}>` : node.name
46
- } else if (node instanceof BlockStatement) {
47
- const cr = node.body.length ? '\n' : ''
48
- line = `{${cr}${node.body.map((node) => ' ' + punctuate(format(node))).join('')}}\n`
49
- } else if (node instanceof VariableDeclaration) {
50
- let layout = ''
51
- if (node.layout) {
52
- const args: string[] = []
53
- for (const key in node.layout) {
54
- const value = node.layout[key]
55
- if (typeof value === 'string') args.push(`${key} = ${value}`)
56
- else args.push(key)
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
- if (declaration.value instanceof ArrayExpression) {
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
- line = `${layout}${qualifiers}${type} ${body};\n`.trimStart()
89
- } else if (node instanceof FunctionDeclaration) {
90
- const qualifiers = node.qualifiers.length ? `${node.qualifiers.join(' ')} ` : ''
91
- const args = node.args.map((node) => format(node).replace(';\n', '')).join(', ')
92
- const body = node.body ? ` ${format(node.body)}` : ';\n'
93
- line = `${qualifiers}${format(node.type)} ${node.name}(${args})${body}`
94
- } else if (node instanceof CallExpression) {
95
- line = `${format(node.callee)}(${node.args.map(format).join(', ')})`
96
- } else if (node instanceof MemberExpression) {
97
- line = `${format(node.object)}.${format(node.property)}`
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
- line = `#${node.name}${value}\n`
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(ast: AST[], options: GenerateOptions): string {
163
- let code = '#version 300 es\n'
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 './minifier'
4
- export * from './parser'
5
- export * from './tokenizer'
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(/^(binding|group|layout|uniform|in|out|attribute|varying)$/)
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
- minified += ' '
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 === '.') {