shaderkit 0.6.4 → 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/src/tokenizer.ts CHANGED
@@ -1,89 +1,105 @@
1
- import { WGSL_KEYWORDS, WGSL_SYMBOLS, GLSL_KEYWORDS, GLSL_SYMBOLS } from './constants.js'
2
-
3
- export type TokenType = 'whitespace' | 'comment' | 'symbol' | 'bool' | 'float' | 'int' | 'identifier' | 'keyword'
4
-
5
- export interface Token<T = TokenType, V = string> {
6
- type: T
7
- value: V
8
- }
9
-
10
- // Checks for WGSL-specific `fn foo(`, `var bar =`, `let baz =`, `const qux =`
11
- const WGSL_REGEX = /\bfn\s+\w+\s*\(|\b(var|let|const)\s+\w+\s*[:=]/
12
-
13
- const FLOAT_REGEX = /((\d+\.\d*|\d*\.\d+)([eEpP][-+]?\d+)?|\d+[eEpP][-+]?\d+)[fFhH]?/y
14
- const INT_REGEX = /(0[xX][\w\d]+|\d+)[iIuU]?/y
15
- const BOOL_REGEX = /^(true|false)$/
16
-
17
- const ZERO = 48
18
- const NINE = 57
19
- const A = 65
20
- const Z = 90
21
- const LF = 10
22
- const CR = 13
23
- const TAB = 9
24
- const SPACE = 32
25
- const UNDERSCORE = 95
26
- const DOT = 46
27
- const SLASH = 47
28
- const STAR = 42
29
- const HASH = 35
30
- const AT = 64
31
-
32
- const isDigit = (c: number) => ZERO <= c && c <= NINE
33
- const isAlpha = (c: number) => ((c &= ~32), A <= c && c <= Z)
34
- const isLine = (c: number) => c === LF || c === CR
35
- const isSpace = (c: number) => isLine(c) || c === TAB || c === SPACE
36
- const isIdent = (c: number) => isAlpha(c) || isDigit(c) || c === UNDERSCORE
37
- const isMacro = (c: number) => c === HASH || c === AT
38
-
39
- // https://mrale.ph/blog/2016/11/23/making-less-dart-faster.html
40
- function matchAsPrefix(regex: RegExp, string: string, start: number): string | undefined {
41
- regex.lastIndex = start
42
- return regex.exec(string)?.[0]
43
- }
44
-
45
- /**
46
- * Tokenizes a string of GLSL or WGSL code.
47
- */
48
- export function tokenize(code: string, index: number = 0): Token[] {
49
- const tokens: Token[] = []
50
-
51
- let prev: number = -1
52
- const [KEYWORDS, SYMBOLS] = WGSL_REGEX.test(code) ? [WGSL_KEYWORDS, WGSL_SYMBOLS] : [GLSL_KEYWORDS, GLSL_SYMBOLS]
53
- while (index < code.length) {
54
- let value = code[index]
55
- const char = code.charCodeAt(index++)
56
-
57
- if (isSpace(char)) {
58
- while (isSpace(code.charCodeAt(index))) value += code[index++]
59
- tokens.push({ type: 'whitespace', value })
60
- } else if (isDigit(char) || (char === DOT && isDigit(code.charCodeAt(index)))) {
61
- if ((value = matchAsPrefix(FLOAT_REGEX, code, index - 1)!)) {
62
- index = FLOAT_REGEX.lastIndex
63
- tokens.push({ type: 'float', value })
64
- } else if ((value = matchAsPrefix(INT_REGEX, code, index - 1)!)) {
65
- index = INT_REGEX.lastIndex
66
- tokens.push({ type: 'int', value })
67
- }
68
- } else if (isIdent(char)) {
69
- while (isIdent(code.charCodeAt(index))) value += code[index++]
70
- if (BOOL_REGEX.test(value)) tokens.push({ type: 'bool', value })
71
- else if (KEYWORDS.includes(isMacro(prev) ? String.fromCharCode(prev) + value : value))
72
- tokens.push({ type: 'keyword', value })
73
- else tokens.push({ type: 'identifier', value })
74
- } else if (char === SLASH && (code.charCodeAt(index) === SLASH || code.charCodeAt(index) === STAR)) {
75
- const terminator = code.charCodeAt(index) === STAR ? '*/' : '\n'
76
- while (index < code.length && !value.endsWith(terminator)) value += code[index++]
77
- tokens.push({ type: 'comment', value: value.trim() })
78
- } else {
79
- for (const symbol of SYMBOLS) {
80
- if (symbol.length > value.length && code.startsWith(symbol, index - 1)) value = symbol
81
- }
82
- index += value.length - 1
83
- tokens.push({ type: 'symbol', value })
84
- }
85
- prev = char
86
- }
87
-
88
- return tokens
89
- }
1
+ import { WGSL_KEYWORDS, WGSL_SYMBOLS, GLSL_KEYWORDS, GLSL_SYMBOLS } from './constants.js'
2
+
3
+ export type TokenType = 'whitespace' | 'comment' | 'symbol' | 'bool' | 'float' | 'int' | 'identifier' | 'keyword'
4
+
5
+ export interface Token<T = TokenType, V = string> {
6
+ type: T
7
+ value: V
8
+ }
9
+
10
+ // Checks for WGSL-specific `fn foo(`, `var bar =`, `let baz =`, `const qux =`
11
+ const WGSL_REGEX = /\bfn\s+\w+\s*\(|\b(var|let|const)\s+\w+\s*[:=]/
12
+
13
+ const FLOAT_REGEX = /((\d+\.\d*|\d*\.\d+)([eEpP][-+]?\d+)?|\d+[eEpP][-+]?\d+)[fFhH]?/y
14
+ const INT_REGEX = /(0[xX][\w\d]+|\d+)[iIuU]?/y
15
+ const BOOL_REGEX = /^(true|false)$/
16
+
17
+ const ZERO = 48
18
+ const NINE = 57
19
+ const A = 65
20
+ const Z = 90
21
+ const LF = 10
22
+ const CR = 13
23
+ const TAB = 9
24
+ const SPACE = 32
25
+ const UNDERSCORE = 95
26
+ const DOT = 46
27
+ const SLASH = 47
28
+ const STAR = 42
29
+ const HASH = 35
30
+ const AT = 64
31
+
32
+ const isDigit = (c: number): boolean => ZERO <= c && c <= NINE
33
+ const isAlpha = (c: number): boolean => ((c &= ~32), A <= c && c <= Z)
34
+ const isLine = (c: number): boolean => c === LF || c === CR
35
+ const isSpace = (c: number): boolean => isLine(c) || c === TAB || c === SPACE
36
+ const isIdent = (c: number): boolean => isAlpha(c) || isDigit(c) || c === UNDERSCORE
37
+ const isMacro = (c: number): boolean => c === HASH || c === AT
38
+
39
+ // https://mrale.ph/blog/2016/11/23/making-less-dart-faster.html
40
+ function matchAsPrefix(regex: RegExp, string: string, start: number): string | undefined {
41
+ regex.lastIndex = start
42
+ return regex.exec(string)?.[0]
43
+ }
44
+
45
+ /**
46
+ * Tokenizes a string of GLSL or WGSL code.
47
+ */
48
+ export function tokenize(code: string, index: number = 0): Token[] {
49
+ const [KEYWORDS, SYMBOLS] = WGSL_REGEX.test(code) ? [WGSL_KEYWORDS, WGSL_SYMBOLS] : [GLSL_KEYWORDS, GLSL_SYMBOLS]
50
+
51
+ const KEYWORDS_LIST = new Set(KEYWORDS)
52
+ const SYMBOLS_REGEX = new RegExp(
53
+ SYMBOLS.sort((a, b) => b.length - a.length)
54
+ .map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
55
+ .join('|'),
56
+ 'y',
57
+ )
58
+
59
+ const tokens: Token[] = []
60
+
61
+ let value: string | undefined
62
+ let start: number
63
+ let char: number
64
+
65
+ while (index < code.length) {
66
+ start = index
67
+ char = code.charCodeAt(index++)
68
+
69
+ if (isSpace(char)) {
70
+ while (isSpace(code.charCodeAt(index))) index++
71
+ value = code.slice(start, index)
72
+ tokens.push({ type: 'whitespace', value })
73
+ } else if (isDigit(char) || (char === DOT && isDigit(code.charCodeAt(index)))) {
74
+ if ((value = matchAsPrefix(FLOAT_REGEX, code, start))) {
75
+ index = FLOAT_REGEX.lastIndex
76
+ tokens.push({ type: 'float', value })
77
+ } else if ((value = matchAsPrefix(INT_REGEX, code, start))) {
78
+ index = INT_REGEX.lastIndex
79
+ tokens.push({ type: 'int', value })
80
+ }
81
+ } else if (isIdent(char)) {
82
+ while (isIdent(code.charCodeAt(index))) index++
83
+ value = code.slice(start, index)
84
+ if (BOOL_REGEX.test(value)) tokens.push({ type: 'bool', value })
85
+ else if (KEYWORDS_LIST.has(isMacro(code.charCodeAt(start - 1)) ? code[start - 1] + value : value))
86
+ tokens.push({ type: 'keyword', value })
87
+ else tokens.push({ type: 'identifier', value })
88
+ } else if (char === SLASH && code.charCodeAt(index) === SLASH) {
89
+ while (index < code.length && code.charCodeAt(index) !== LF) index++
90
+ value = code.slice(start, index)
91
+ index++ // consume LF
92
+ tokens.push({ type: 'comment', value })
93
+ } else if (char === SLASH && code.charCodeAt(index) === STAR) {
94
+ while (index < code.length && (code.charCodeAt(index - 2) !== STAR || code.charCodeAt(index - 1) !== SLASH))
95
+ index++
96
+ value = code.slice(start, index)
97
+ tokens.push({ type: 'comment', value })
98
+ } else if ((value = matchAsPrefix(SYMBOLS_REGEX, code, start))) {
99
+ index += value.length - 1
100
+ tokens.push({ type: 'symbol', value })
101
+ }
102
+ }
103
+
104
+ return tokens
105
+ }
package/src/visitor.ts CHANGED
@@ -1,131 +1,131 @@
1
- import { type AST } from './ast'
2
-
3
- export type Visitors = Partial<{
4
- [K in AST['type']]:
5
- | ((node: Extract<AST, { type: K }>, ancestors: AST[]) => void)
6
- | {
7
- enter?(node: Extract<AST, { type: K }>, ancestors: AST[]): void
8
- exit?(node: Extract<AST, { type: K }>, ancestors: AST[]): void
9
- }
10
- }>
11
-
12
- /**
13
- * Recurses through an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree), calling a visitor object on matching nodes.
14
- */
15
- export function visit(node: AST, visitors: Visitors, ancestors: AST[] = []): void {
16
- const parentAncestors = ancestors
17
- const visitor = visitors[node.type]
18
-
19
- // @ts-ignore
20
- ;(visitor?.enter ?? visitor)?.(node, parentAncestors)
21
-
22
- ancestors = [...ancestors, node]
23
-
24
- switch (node.type) {
25
- case 'ArraySpecifier':
26
- visit(node.typeSpecifier, visitors, ancestors)
27
- for (const dimension of node.dimensions) if (dimension) visit(dimension, visitors, ancestors)
28
- break
29
- case 'ExpressionStatement':
30
- visit(node.expression, visitors, ancestors)
31
- break
32
- case 'BlockStatement':
33
- for (const statement of node.body) visit(statement, visitors, ancestors)
34
- break
35
- case 'PreprocessorStatement':
36
- if (node.value) for (const expression of node.value) visit(expression, visitors, ancestors)
37
- break
38
- case 'PrecisionQualifierStatement':
39
- visit(node.typeSpecifier, visitors, ancestors)
40
- break
41
- case 'InvariantQualifierStatement':
42
- visit(node.typeSpecifier, visitors, ancestors)
43
- break
44
- case 'ReturnStatement':
45
- if (node.argument) visit(node.argument, visitors, ancestors)
46
- break
47
- case 'IfStatement':
48
- visit(node.test, visitors, ancestors)
49
- visit(node.consequent, visitors, ancestors)
50
- if (node.alternate) visit(node.alternate, visitors, ancestors)
51
- break
52
- case 'SwitchStatement':
53
- visit(node.discriminant, visitors, ancestors)
54
- for (const kase of node.cases) visit(kase, visitors, ancestors)
55
- break
56
- case 'SwitchCase':
57
- if (node.test) visit(node.test, visitors, ancestors)
58
- for (const statement of node.consequent) visit(statement, visitors, ancestors)
59
- break
60
- case 'WhileStatement':
61
- case 'DoWhileStatement':
62
- visit(node.test, visitors, ancestors)
63
- visit(node.body, visitors, ancestors)
64
- break
65
- case 'ForStatement':
66
- if (node.init) visit(node.init, visitors, ancestors)
67
- if (node.test) visit(node.test, visitors, ancestors)
68
- if (node.update) visit(node.update, visitors, ancestors)
69
- visit(node.body, visitors, ancestors)
70
- break
71
- case 'FunctionDeclaration':
72
- visit(node.typeSpecifier, visitors, ancestors)
73
- visit(node.id, visitors, ancestors)
74
- if (node.body) visit(node.body, visitors, ancestors)
75
- break
76
- case 'FunctionParameter':
77
- visit(node.typeSpecifier, visitors, ancestors)
78
- if (node.id) visit(node.id, visitors, ancestors)
79
- break
80
- case 'VariableDeclaration':
81
- for (const declaration of node.declarations) visit(declaration, visitors, ancestors)
82
- break
83
- case 'VariableDeclarator':
84
- visit(node.typeSpecifier, visitors, ancestors)
85
- visit(node.id, visitors, ancestors)
86
- if (node.init) visit(node.init, visitors, ancestors)
87
- break
88
- case 'StructuredBufferDeclaration':
89
- visit(node.typeSpecifier, visitors, ancestors)
90
- for (const member of node.members) visit(member, visitors, ancestors)
91
- if (node.id) visit(node.id, visitors, ancestors)
92
- break
93
- case 'StructDeclaration':
94
- visit(node.id, visitors, ancestors)
95
- for (const member of node.members) visit(member, visitors, ancestors)
96
- break
97
- case 'ArrayExpression':
98
- visit(node.typeSpecifier, visitors, ancestors)
99
- for (const element of node.elements) visit(element, visitors, ancestors)
100
- break
101
- case 'UnaryExpression':
102
- case 'UpdateExpression':
103
- visit(node.argument, visitors, ancestors)
104
- break
105
- case 'BinaryExpression':
106
- case 'AssignmentExpression':
107
- case 'LogicalExpression':
108
- visit(node.left, visitors, ancestors)
109
- visit(node.right, visitors, ancestors)
110
- break
111
- case 'MemberExpression':
112
- visit(node.object, visitors, ancestors)
113
- visit(node.property, visitors, ancestors)
114
- break
115
- case 'ConditionalExpression':
116
- visit(node.test, visitors, ancestors)
117
- visit(node.consequent, visitors, ancestors)
118
- visit(node.alternate, visitors, ancestors)
119
- break
120
- case 'CallExpression':
121
- visit(node.callee, visitors, ancestors)
122
- for (const argument of node.arguments) visit(argument, visitors, ancestors)
123
- break
124
- case 'Program':
125
- for (const statement of node.body) visit(statement, visitors, ancestors)
126
- break
127
- }
128
-
129
- // @ts-ignore
130
- visitor?.exit?.(node, parentAncestors)
131
- }
1
+ import { type AST } from './ast'
2
+
3
+ export type Visitors = Partial<{
4
+ [K in AST['type']]:
5
+ | ((node: Extract<AST, { type: K }>, ancestors: AST[]) => void)
6
+ | {
7
+ enter?(node: Extract<AST, { type: K }>, ancestors: AST[]): void
8
+ exit?(node: Extract<AST, { type: K }>, ancestors: AST[]): void
9
+ }
10
+ }>
11
+
12
+ /**
13
+ * Recurses through an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree), calling a visitor object on matching nodes.
14
+ */
15
+ export function visit(node: AST, visitors: Visitors, ancestors: AST[] = []): void {
16
+ const parentAncestors = ancestors
17
+ const visitor = visitors[node.type]
18
+
19
+ // @ts-ignore
20
+ ;(visitor?.enter ?? visitor)?.(node, parentAncestors)
21
+
22
+ ancestors = [...ancestors, node]
23
+
24
+ switch (node.type) {
25
+ case 'ArraySpecifier':
26
+ visit(node.typeSpecifier, visitors, ancestors)
27
+ for (const dimension of node.dimensions) if (dimension) visit(dimension, visitors, ancestors)
28
+ break
29
+ case 'ExpressionStatement':
30
+ visit(node.expression, visitors, ancestors)
31
+ break
32
+ case 'BlockStatement':
33
+ for (const statement of node.body) visit(statement, visitors, ancestors)
34
+ break
35
+ case 'PreprocessorStatement':
36
+ if (node.value) for (const expression of node.value) visit(expression, visitors, ancestors)
37
+ break
38
+ case 'PrecisionQualifierStatement':
39
+ visit(node.typeSpecifier, visitors, ancestors)
40
+ break
41
+ case 'InvariantQualifierStatement':
42
+ visit(node.typeSpecifier, visitors, ancestors)
43
+ break
44
+ case 'ReturnStatement':
45
+ if (node.argument) visit(node.argument, visitors, ancestors)
46
+ break
47
+ case 'IfStatement':
48
+ visit(node.test, visitors, ancestors)
49
+ visit(node.consequent, visitors, ancestors)
50
+ if (node.alternate) visit(node.alternate, visitors, ancestors)
51
+ break
52
+ case 'SwitchStatement':
53
+ visit(node.discriminant, visitors, ancestors)
54
+ for (const kase of node.cases) visit(kase, visitors, ancestors)
55
+ break
56
+ case 'SwitchCase':
57
+ if (node.test) visit(node.test, visitors, ancestors)
58
+ for (const statement of node.consequent) visit(statement, visitors, ancestors)
59
+ break
60
+ case 'WhileStatement':
61
+ case 'DoWhileStatement':
62
+ visit(node.test, visitors, ancestors)
63
+ visit(node.body, visitors, ancestors)
64
+ break
65
+ case 'ForStatement':
66
+ if (node.init) visit(node.init, visitors, ancestors)
67
+ if (node.test) visit(node.test, visitors, ancestors)
68
+ if (node.update) visit(node.update, visitors, ancestors)
69
+ visit(node.body, visitors, ancestors)
70
+ break
71
+ case 'FunctionDeclaration':
72
+ visit(node.typeSpecifier, visitors, ancestors)
73
+ visit(node.id, visitors, ancestors)
74
+ if (node.body) visit(node.body, visitors, ancestors)
75
+ break
76
+ case 'FunctionParameter':
77
+ visit(node.typeSpecifier, visitors, ancestors)
78
+ if (node.id) visit(node.id, visitors, ancestors)
79
+ break
80
+ case 'VariableDeclaration':
81
+ for (const declaration of node.declarations) visit(declaration, visitors, ancestors)
82
+ break
83
+ case 'VariableDeclarator':
84
+ visit(node.typeSpecifier, visitors, ancestors)
85
+ visit(node.id, visitors, ancestors)
86
+ if (node.init) visit(node.init, visitors, ancestors)
87
+ break
88
+ case 'StructuredBufferDeclaration':
89
+ visit(node.typeSpecifier, visitors, ancestors)
90
+ for (const member of node.members) visit(member, visitors, ancestors)
91
+ if (node.id) visit(node.id, visitors, ancestors)
92
+ break
93
+ case 'StructDeclaration':
94
+ visit(node.id, visitors, ancestors)
95
+ for (const member of node.members) visit(member, visitors, ancestors)
96
+ break
97
+ case 'ArrayExpression':
98
+ visit(node.typeSpecifier, visitors, ancestors)
99
+ for (const element of node.elements) visit(element, visitors, ancestors)
100
+ break
101
+ case 'UnaryExpression':
102
+ case 'UpdateExpression':
103
+ visit(node.argument, visitors, ancestors)
104
+ break
105
+ case 'BinaryExpression':
106
+ case 'AssignmentExpression':
107
+ case 'LogicalExpression':
108
+ visit(node.left, visitors, ancestors)
109
+ visit(node.right, visitors, ancestors)
110
+ break
111
+ case 'MemberExpression':
112
+ visit(node.object, visitors, ancestors)
113
+ visit(node.property, visitors, ancestors)
114
+ break
115
+ case 'ConditionalExpression':
116
+ visit(node.test, visitors, ancestors)
117
+ visit(node.consequent, visitors, ancestors)
118
+ visit(node.alternate, visitors, ancestors)
119
+ break
120
+ case 'CallExpression':
121
+ visit(node.callee, visitors, ancestors)
122
+ for (const argument of node.arguments) visit(argument, visitors, ancestors)
123
+ break
124
+ case 'Program':
125
+ for (const statement of node.body) visit(statement, visitors, ancestors)
126
+ break
127
+ }
128
+
129
+ // @ts-ignore
130
+ visitor?.exit?.(node, parentAncestors)
131
+ }