@shaderfrog/core 1.5.3 → 2.0.0-beta.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.
@@ -1,4 +1,4 @@
1
- import type { GlslSyntaxError } from '@shaderfrog/glsl-parser';
1
+ import { type GlslSyntaxError } from '@shaderfrog/glsl-parser';
2
2
  import { AstNode, Program } from '@shaderfrog/glsl-parser/ast';
3
3
  import { Engine, EngineContext } from '../engine';
4
4
  import { NodeInput } from './base-node';
package/graph/graph.d.ts CHANGED
@@ -2,6 +2,7 @@ import { Program } from '@shaderfrog/glsl-parser/ast';
2
2
  import { Engine, EngineContext } from '../engine';
3
3
  import { NodeErrors } from './context';
4
4
  import { ShaderSections } from './shader-sections';
5
+ import { FrogProgram } from '../util/ast';
5
6
  import { DataNode } from './data-nodes';
6
7
  import { Edge } from './edge';
7
8
  import { CodeNode, SourceNode } from './code-nodes';
@@ -15,7 +16,7 @@ export declare const doesLinkThruShader: (graph: Graph, node: GraphNode) => bool
15
16
  export declare const nodeName: (node: GraphNode) => string;
16
17
  export declare const mangleName: (name: string, node: GraphNode, nextSibling?: GraphNode) => string;
17
18
  export declare const mangleVar: (name: string, engine: Engine, node: GraphNode, sibling?: GraphNode) => string;
18
- export declare const mangleEntireProgram: (engine: Engine, ast: Program, node: GraphNode, sibling?: GraphNode) => void;
19
+ export declare const mangleEntireProgram: (engine: Engine, ast: FrogProgram, node: GraphNode, sibling?: GraphNode) => void;
19
20
  export declare const mangleMainFn: (ast: Program, node: GraphNode, sibling?: GraphNode) => void;
20
21
  export declare const ensureFromNode: (graph: Graph, inputEdge: Edge) => GraphNode;
21
22
  export declare const resetGraphIds: (graph: Graph) => Graph;
package/graph/graph.js CHANGED
@@ -130,13 +130,13 @@ export var mangleName = function (name, node, nextSibling) {
130
130
  };
131
131
  export var mangleVar = function (name, engine, node, sibling) { return (engine.preserve.has(name) ? name : mangleName(name, node, sibling)); };
132
132
  export var mangleEntireProgram = function (engine, ast, node, sibling) {
133
- renameBindings(ast.scopes[0], function (name, n) {
134
- return n.doNotDescope ? name : mangleVar(name, engine, node, sibling);
133
+ ast.scopes[0].bindings = renameBindings(ast.scopes[0].bindings, function (name) {
134
+ return name === ast.outVar ? name : mangleVar(name, engine, node, sibling);
135
135
  });
136
136
  mangleMainFn(ast, node, sibling);
137
137
  };
138
138
  export var mangleMainFn = function (ast, node, sibling) {
139
- renameFunctions(ast.scopes[0], function (name) {
139
+ ast.scopes[0].functions = renameFunctions(ast.scopes[0].functions, function (name) {
140
140
  return name === 'main' ? nodeName(node) : mangleName(name, node, sibling);
141
141
  });
142
142
  };
@@ -365,11 +365,11 @@ export var compileNode = function (engine, graph, edges, engineContext, node, ac
365
365
  ];
366
366
  // @ts-ignore
367
367
  var scope = fc.ast.scopes[0];
368
- renameBindings(scope, function (name, node) {
369
- return node.type !== 'declaration' && name === 'vUv'
370
- ? 'vv'
371
- : name;
372
- });
368
+ // renameBindings(scope, (name, node) => {
369
+ // return node.type !== 'declaration' && name === 'vUv'
370
+ // ? 'vv'
371
+ // : name;
372
+ // });
373
373
  }
374
374
  // })
375
375
  }
@@ -482,7 +482,8 @@ export var compileSource = function (graph, engine, ctx) { return __awaiter(void
482
482
  return [2 /*return*/, result];
483
483
  }
484
484
  compileResult = compileGraph(ctx, engine, graph);
485
- fragmentResult = generate(shaderSectionsToProgram(compileResult.fragment, engine.mergeOptions).program);
485
+ fragmentResult = generate(shaderSectionsToProgram(compileResult.fragment, engine.mergeOptions)
486
+ .program);
486
487
  vertexResult = generate(shaderSectionsToProgram(compileResult.vertex, engine.mergeOptions).program);
487
488
  dataInputs = filterGraphNodes(graph, [compileResult.outputFrag, compileResult.outputVert], { input: isDataInput }).inputs;
488
489
  dataNodes = Object.entries(dataInputs).reduce(function (acc, _a) {
@@ -1,11 +1,52 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __generator = (this && this.__generator) || function (thisArg, body) {
11
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
12
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
+ function verb(n) { return function (v) { return step([n, v]); }; }
14
+ function step(op) {
15
+ if (f) throw new TypeError("Generator is already executing.");
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
+ if (y = 0, t) op = [op[0] & 2, t.value];
19
+ switch (op[0]) {
20
+ case 0: case 1: t = op; break;
21
+ case 4: _.label++; return { value: op[1], done: false };
22
+ case 5: _.label++; y = op[1]; op = [0]; continue;
23
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
+ default:
25
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
+ if (t[2]) _.ops.pop();
30
+ _.trys.pop(); continue;
31
+ }
32
+ op = body.call(thisArg, _);
33
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
+ }
36
+ };
37
+ import { expect, describe, it } from 'vitest';
1
38
  import util from 'util';
2
39
  import { parser } from '@shaderfrog/glsl-parser';
3
40
  import { generate } from '@shaderfrog/glsl-parser';
4
- import { addNode } from './graph-node';
41
+ import { addNode, outputNode, sourceNode } from './graph-node';
5
42
  import { shaderSectionsToProgram, mergeShaderSections, findShaderSections, } from './shader-sections';
6
43
  import { numberNode } from './data-nodes';
7
44
  import { makeEdge } from './edge';
8
45
  import { evaluateNode } from './evaluate';
46
+ import { compileSource } from './graph';
47
+ import { texture2DStrategy } from '../strategy';
48
+ import { isError } from './context';
49
+ import { fail } from '../test-util';
9
50
  var inspect = function (thing) {
10
51
  return console.log(util.inspect(thing, false, null, true));
11
52
  };
@@ -65,60 +106,112 @@ var engine = {
65
106
  preserve: new Set(),
66
107
  parsers: {},
67
108
  };
68
- // it('graph compiler arbitrary helper test', () => {
69
- // const graph: Graph = {
70
- // nodes: [
71
- // outputNode('0', 'Output v', p, 'vertex'),
72
- // outputNode('1', 'Output f', p, 'fragment'),
73
- // makeSourceNode(
74
- // '2',
75
- // `uniform sampler2D image1;
76
- // uniform sampler2D image2;
77
- // void main() {
78
- // vec3 col = texture2D(image1, posTurn - 0.4 * time).rgb + 1.0;
79
- // vec3 col = texture2D(image2, negTurn - 0.4 * time).rgb + 2.0;
80
- // }
81
- // `,
82
- // 'fragment'
83
- // ),
84
- // makeSourceNode(
85
- // '3',
86
- // `void main() {
87
- // return vec4(0.0);
88
- // }
89
- // `,
90
- // 'fragment'
91
- // ),
92
- // makeSourceNode(
93
- // '4',
94
- // `void main() {
95
- // return vec4(1.0);
96
- // }
97
- // `,
98
- // 'fragment'
99
- // ),
100
- // ],
101
- // edges: [
102
- // makeEdge(id(), '2', '1', 'out', 'filler_frogFragOut', 'fragment'),
103
- // makeEdge(id(), '3', '2', 'out', 'filler_image1', 'fragment'),
104
- // makeEdge(id(), '4', '2', 'out', 'filler_image2', 'fragment'),
105
- // ],
106
- // };
107
- // const engineContext: EngineContext = {
108
- // engine: 'three',
109
- // nodes: {},
110
- // runtime: {},
111
- // debuggingNonsense: {},
112
- // };
113
- // const result = compileGraph(engineContext, engine, graph);
114
- // const built = generate(
115
- // shaderSectionsToProgram(result.fragment, {
116
- // includePrecisions: true,
117
- // includeVersion: true,
118
- // }).program
119
- // );
120
- // expect(built).toBe('hi');
121
- // });
109
+ var makeSourceNode = function (id, source, stage, strategies) {
110
+ if (strategies === void 0) { strategies = [texture2DStrategy()]; }
111
+ return sourceNode(id, "Shader ".concat(id), p, {
112
+ version: 2,
113
+ preprocess: false,
114
+ strategies: strategies,
115
+ uniforms: [],
116
+ }, source, stage);
117
+ };
118
+ /**
119
+ * What exactly am I doing here?
120
+ *
121
+ * I opened shaderfrog to start looking at the backfilling case and inlining
122
+ * function calls at the top of functions
123
+ *
124
+ * WHie doing that I found jest not to work well anyore and switched to vitest,
125
+ * which is fine, but with esm by default I can't stub the mangleName() function
126
+ * call, which means mangling happens as-is in the tests.
127
+ *
128
+ * Without changing the mangling strategy, the strategies.test.ts file fails
129
+ * because the uniform strategy looks for a mangled variable name, but the
130
+ * program itself isn't mangled.
131
+ *
132
+ * One way to fix this is to make fillers not have to care about mangling names,
133
+ * which would be simpler on the surface.
134
+ *
135
+ * Then everything in the tests broke and you found out the reason why was
136
+ * trying to use scopes to rename things, and most of the ast manipulation steps
137
+ * don't modify scopes, so you made some of them modify scopes, and now things
138
+ * are fucked
139
+ */
140
+ it('compileSource() fragment produces inlined output', function () { return __awaiter(void 0, void 0, void 0, function () {
141
+ var outV, outF, imageReplacemMe, input1, input2, graph, engineContext, result, imgOut;
142
+ return __generator(this, function (_a) {
143
+ switch (_a.label) {
144
+ case 0:
145
+ outV = outputNode(id(), 'Output v', p, 'vertex');
146
+ outF = outputNode(id(), 'Output f', p, 'fragment');
147
+ imageReplacemMe = makeSourceNode(id(), "uniform sampler2D image1;\nuniform sampler2D image2;\nvoid main() {\n vec3 col1 = texture2D(image1, posTurn - 0.4 * time).rgb + 1.0;\n vec3 col2 = texture2D(image2, negTurn - 0.4 * time).rgb + 2.0;\n gl_FragColor = vec4(col1 + col2, 1.0);\n}\n", 'fragment');
148
+ input1 = makeSourceNode(id(), "float a = 1.0;\nvoid main() {\n gl_FragColor = vec4(0.0);\n}\n", 'fragment');
149
+ input2 = makeSourceNode(id(), "float a = 2.0;\nvoid main() {\n gl_FragColor = vec4(1.0);\n}\n", 'fragment');
150
+ graph = {
151
+ nodes: [outV, outF, imageReplacemMe, input1, input2],
152
+ edges: [
153
+ makeEdge(id(), imageReplacemMe.id, outF.id, 'out', 'filler_frogFragOut', 'fragment'),
154
+ makeEdge(id(), input1.id, imageReplacemMe.id, 'out', 'filler_image1', 'fragment'),
155
+ makeEdge(id(), input2.id, imageReplacemMe.id, 'out', 'filler_image2', 'fragment'),
156
+ makeEdge(id(), input2.id, imageReplacemMe.id, 'out', 'filler_image2', 'fragment'),
157
+ ],
158
+ };
159
+ engineContext = {
160
+ engine: 'three',
161
+ nodes: {},
162
+ runtime: {},
163
+ debuggingNonsense: {},
164
+ };
165
+ return [4 /*yield*/, compileSource(graph, engine, engineContext)];
166
+ case 1:
167
+ result = _a.sent();
168
+ if (isError(result)) {
169
+ fail(result);
170
+ }
171
+ expect(result.fragmentResult).toContain("vec4 main_Shader_".concat(input1.id, "() {"));
172
+ expect(result.fragmentResult).toContain("vec4 main_Shader_".concat(input2.id, "() {"));
173
+ imgOut = "frogOut_".concat(imageReplacemMe.id);
174
+ expect(result.fragmentResult).toContain("vec4 ".concat(imgOut, ";"));
175
+ expect(result.fragmentResult)
176
+ .toContain("vec4 main_Shader_".concat(imageReplacemMe.id, "() {\n vec3 col1 = main_Shader_").concat(input1.id, "().rgb + 1.0;\n vec3 col2 = main_Shader_").concat(input2.id, "().rgb + 2.0;\n ").concat(imgOut, " = vec4(col1 + col2, 1.0);\n return ").concat(imgOut, ";\n}"));
177
+ return [2 /*return*/];
178
+ }
179
+ });
180
+ }); });
181
+ it('compileSource() base case', function () { return __awaiter(void 0, void 0, void 0, function () {
182
+ var outV, outF, imageReplacemMe, graph, engineContext, result, imgOut;
183
+ return __generator(this, function (_a) {
184
+ switch (_a.label) {
185
+ case 0:
186
+ outV = outputNode(id(), 'Output v', p, 'vertex');
187
+ outF = outputNode(id(), 'Output f', p, 'fragment');
188
+ imageReplacemMe = makeSourceNode(id(), "float a = 1.0;\nvoid main() {\n gl_FragColor = vec4(1.0);\n}\n", 'fragment');
189
+ graph = {
190
+ nodes: [outV, outF, imageReplacemMe],
191
+ edges: [
192
+ makeEdge(id(), imageReplacemMe.id, outF.id, 'out', 'filler_frogFragOut', 'fragment'),
193
+ ],
194
+ };
195
+ engineContext = {
196
+ engine: 'three',
197
+ nodes: {},
198
+ runtime: {},
199
+ debuggingNonsense: {},
200
+ };
201
+ return [4 /*yield*/, compileSource(graph, engine, engineContext)];
202
+ case 1:
203
+ result = _a.sent();
204
+ if (isError(result)) {
205
+ fail(result);
206
+ }
207
+ imgOut = "frogOut_".concat(imageReplacemMe.id);
208
+ expect(result.fragmentResult).toContain("vec4 ".concat(imgOut, ";"));
209
+ expect(result.fragmentResult)
210
+ .toContain("vec4 main_Shader_".concat(imageReplacemMe.id, "() {\n ").concat(imgOut, " = vec4(1.0);\n return ").concat(imgOut, ";\n}"));
211
+ return [2 /*return*/];
212
+ }
213
+ });
214
+ }); });
122
215
  describe('evaluateNode()', function () {
123
216
  it('evaluates binary nodes', function () {
124
217
  var finalAdd = addNode(id(), p);
package/graph/parsers.js CHANGED
@@ -24,7 +24,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
24
24
  return to.concat(ar || Array.prototype.slice.call(from));
25
25
  };
26
26
  var _a;
27
- import { generate, parser } from '@shaderfrog/glsl-parser';
27
+ import { parser } from '@shaderfrog/glsl-parser';
28
28
  import { visit, } from '@shaderfrog/glsl-parser/ast';
29
29
  import preprocess from '@shaderfrog/glsl-parser/preprocessor';
30
30
  import { convert300MainToReturn, from2To3, makeExpression, makeExpressionWithScopes, makeFnBodyStatementWithScopes, makeFnStatement, } from '../util/ast';
@@ -86,14 +86,11 @@ export var coreParsers = (_a = {},
86
86
  ast = parser.parse(preprocessed);
87
87
  if (node.config.version === 2 && node.stage) {
88
88
  from2To3(ast, node.stage);
89
- log('converted ', node, 'to version 3', {
90
- code: generate(ast),
91
- });
92
89
  }
93
90
  // This assumes that expressionOnly nodes don't have a stage and that all
94
91
  // fragment source code shades have main function, which is probably wrong
95
92
  if (node.stage === 'fragment') {
96
- convert300MainToReturn(node.id, ast);
93
+ convert300MainToReturn(ast);
97
94
  }
98
95
  }
99
96
  return ast;
@@ -133,7 +130,7 @@ export var coreParsers = (_a = {},
133
130
  nodeInput(MAGIC_OUTPUT_STMTS, "filler_".concat(MAGIC_OUTPUT_STMTS), 'filler', 'rgba', ['code'], false),
134
131
  function (fillerAst) {
135
132
  var fn = ast.program.find(function (stmt) { return stmt.type === 'function'; });
136
- fn === null || fn === void 0 ? void 0 : fn.body.statements.unshift(makeFnStatement(generateFiller(fillerAst)));
133
+ fn === null || fn === void 0 ? void 0 : fn.body.statements.unshift(makeFnStatement(generateFiller(fillerAst))[0]);
137
134
  return ast;
138
135
  },
139
136
  ],
@@ -62,7 +62,7 @@ export var highestPrecisions = function (nodes) {
62
62
  return (__assign(__assign({}, precisions), (_a = {}, _a[stmt.declaration.specifier.specifier.token] = higherPrecision(precisions[stmt.declaration.specifier.specifier.token], stmt.declaration.qualifier.token), _a)));
63
63
  }, {})).map(function (_a) {
64
64
  var _b = __read(_a, 2), typeName = _b[0], precision = _b[1];
65
- return makeStatement("precision ".concat(precision, " ").concat(typeName));
65
+ return makeStatement("precision ".concat(precision, " ").concat(typeName))[0];
66
66
  });
67
67
  };
68
68
  export var dedupeQualifiedStatements = function (statements, qualifier) {
@@ -74,7 +74,7 @@ export var dedupeQualifiedStatements = function (statements, qualifier) {
74
74
  }, {})), _a)));
75
75
  }, {})).map(function (_a) {
76
76
  var _b = __read(_a, 2), type = _b[0], varNames = _b[1];
77
- return makeStatement("".concat(qualifier, " ").concat(type, " ").concat(Object.keys(varNames).join(', ')));
77
+ return makeStatement("".concat(qualifier, " ").concat(type, " ").concat(Object.keys(varNames).join(', ')))[0];
78
78
  });
79
79
  };
80
80
  /**
@@ -165,7 +165,7 @@ export var dedupeUniforms = function (statements) {
165
165
  var _b = __read(_a, 2), type = _b[0], variables = _b[1];
166
166
  return makeStatement("uniform ".concat(type, " ").concat(Object.values(variables)
167
167
  .map(function (v) { return v.generated; })
168
- .join(', ')));
168
+ .join(', ')))[0];
169
169
  });
170
170
  };
171
171
  export var mergeShaderSections = function (s1, s2) {
@@ -237,11 +237,6 @@ export var findShaderSections = function (ast) {
237
237
  else if (node.type === 'declaration_statement' &&
238
238
  'specified_type' in node.declaration &&
239
239
  ((_m = (_l = (_k = node.declaration) === null || _k === void 0 ? void 0 : _k.specified_type) === null || _l === void 0 ? void 0 : _l.qualifiers) === null || _m === void 0 ? void 0 : _m.find(function (n) { return 'token' in n && n.token === 'in'; }))) {
240
- if (generate(node).includes('main_Fireball')) {
241
- console.log('findShaderSections\n', generate(ast));
242
- console.log("Tracking inStatement \"".concat(generate(node), "\""), node);
243
- debugger;
244
- }
245
240
  return __assign(__assign({}, sections), { inStatements: sections.inStatements.concat(node) });
246
241
  }
247
242
  else if (node.type === 'declaration_statement' &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shaderfrog/core",
3
- "version": "1.5.3",
3
+ "version": "2.0.0-beta.1",
4
4
  "description": "Shaderfrog core",
5
5
  "type": "module",
6
6
  "files": [
@@ -17,8 +17,8 @@
17
17
  "prepare": "npm run build && ./prepublish.sh",
18
18
  "postpublish": "./postbuild.sh",
19
19
  "build": "./build.sh",
20
- "watch-test": "jest --watch",
21
- "test": "jest --colors"
20
+ "watch-test": "vitest --watch",
21
+ "test": "vitest"
22
22
  },
23
23
  "repository": {
24
24
  "type": "git",
@@ -34,21 +34,20 @@
34
34
  "@babel/core": "^7.21.8",
35
35
  "@babel/preset-env": "^7.21.5",
36
36
  "@babel/preset-typescript": "^7.21.5",
37
- "@types/jest": "^29.5.1",
37
+ "@swc/core": "^1.6.7",
38
38
  "@types/lodash.groupby": "^4.6.7",
39
39
  "@types/three": "^0.156.0",
40
- "babel-jest": "^29.5.0",
41
40
  "babylonjs": "^6.2.0",
42
- "jest": "^29.5.0",
43
- "prettier": "^2.8.8",
41
+ "prettier": "^3.3.2",
44
42
  "three": "^0.156.1",
45
- "typescript": "^5.0.4"
43
+ "typescript": "^5.5.3",
44
+ "vitest": "^1.6.0"
46
45
  },
47
46
  "dependencies": {
48
47
  "lodash.groupby": "^4.6.0"
49
48
  },
50
49
  "peerDependencies": {
51
- "@shaderfrog/glsl-parser": "^4.0.0",
50
+ "@shaderfrog/glsl-parser": "^5.0.0-beta.3",
52
51
  "babylonjs": ">=4",
53
52
  "playcanvas": "^1.65.3",
54
53
  "three": ">=0.50"
@@ -22,7 +22,7 @@ var importers = {
22
22
  // Babylon has no normalmatrix. They do have a normal attribute. So undo any
23
23
  // multiplication by normalMatrix?
24
24
  var seen = {};
25
- renameBindings(ast.scopes[0], function (name) {
25
+ ast.scopes[0].bindings = renameBindings(ast.scopes[0].bindings, function (name) {
26
26
  // console.log({ name }, 'seen:', seen[name]);
27
27
  var renamed = name === 'vUv'
28
28
  ? 'vMainUV1'
@@ -2,7 +2,7 @@ import { renameBindings } from '@shaderfrog/glsl-parser/parser/utils';
2
2
  var importers = {
3
3
  babylon: {
4
4
  convertAst: function (ast, type) {
5
- renameBindings(ast.scopes[0], function (name) {
5
+ ast.scopes[0].bindings = renameBindings(ast.scopes[0].bindings, function (name) {
6
6
  return name === 'vMainUV1' ? 'vUv' : name === 'vNormalW' ? 'vNormal' : name;
7
7
  });
8
8
  },
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,89 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __generator = (this && this.__generator) || function (thisArg, body) {
11
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
12
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
+ function verb(n) { return function (v) { return step([n, v]); }; }
14
+ function step(op) {
15
+ if (f) throw new TypeError("Generator is already executing.");
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
+ if (y = 0, t) op = [op[0] & 2, t.value];
19
+ switch (op[0]) {
20
+ case 0: case 1: t = op; break;
21
+ case 4: _.label++; return { value: op[1], done: false };
22
+ case 5: _.label++; y = op[1]; op = [0]; continue;
23
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
+ default:
25
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
+ if (t[2]) _.ops.pop();
30
+ _.trys.pop(); continue;
31
+ }
32
+ op = body.call(thisArg, _);
33
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
+ }
36
+ };
37
+ import { expect, it } from 'vitest';
38
+ import { outputNode, sourceNode } from '../../graph/graph-node';
39
+ import { makeEdge } from '../../graph/edge';
40
+ import { compileSource } from '../../graph/graph';
41
+ import { texture2DStrategy } from '../../strategy';
42
+ import { isError } from '../../graph/context';
43
+ import { threngine } from './threngine';
44
+ import { makeId } from '../../util/id';
45
+ import { fail } from '../../test-util';
46
+ var p = { x: 0, y: 0 };
47
+ var makeSourceNode = function (id, source, stage, strategies) {
48
+ if (strategies === void 0) { strategies = [texture2DStrategy()]; }
49
+ return sourceNode(id, "Shader ".concat(id), p, {
50
+ version: 2,
51
+ preprocess: false,
52
+ strategies: strategies,
53
+ uniforms: [],
54
+ }, source, stage);
55
+ };
56
+ it('threngine compileSource() and manipulateAst()', function () { return __awaiter(void 0, void 0, void 0, function () {
57
+ var outV, outF, vertInput, graph, engineContext, result;
58
+ return __generator(this, function (_a) {
59
+ switch (_a.label) {
60
+ case 0:
61
+ outV = outputNode(makeId(), 'Output v', p, 'vertex');
62
+ outF = outputNode(makeId(), 'Output f', p, 'fragment');
63
+ vertInput = makeSourceNode(makeId(), "uniform vec4 modelViewMatrix;\nattribute vec3 position;\nfloat a = 2.0;\nvoid main() {\n gl_Position = modelViewMatrix * vec4(position, 1.0);\n}\n", 'vertex');
64
+ graph = {
65
+ nodes: [outV, outF, vertInput],
66
+ edges: [
67
+ makeEdge(makeId(), vertInput.id, outV.id, 'out', 'filler_gl_Position', 'vertex'),
68
+ ],
69
+ };
70
+ engineContext = {
71
+ engine: 'three',
72
+ nodes: {},
73
+ runtime: {},
74
+ debuggingNonsense: {},
75
+ };
76
+ return [4 /*yield*/, compileSource(graph, threngine, engineContext)];
77
+ case 1:
78
+ result = _a.sent();
79
+ if (isError(result)) {
80
+ fail(result);
81
+ }
82
+ // Threngine has parsers for vertex shaders, make sure that is set properly
83
+ expect(result.vertexResult).toContain("vec4 main_Shader_".concat(vertInput.id, "() {\n vec4 frogOut = modelViewMatrix * vec4(position, 1.0);\n return frogOut;\n}"));
84
+ // Check that it inlned. For fun.
85
+ expect(result.vertexResult).toContain("gl_Position = main_Shader_".concat(vertInput.id, "();"));
86
+ return [2 /*return*/];
87
+ }
88
+ });
89
+ }); });
@@ -3,7 +3,8 @@ export interface AssignemntToStrategy extends BaseStrategy {
3
3
  type: StrategyType.ASSIGNMENT_TO;
4
4
  config: {
5
5
  assignTo: string;
6
+ nth?: number;
6
7
  };
7
8
  }
8
- export declare const assignemntToStrategy: (assignTo: string) => AssignemntToStrategy;
9
+ export declare const assignemntToStrategy: (assignTo: string, nth?: number) => AssignemntToStrategy;
9
10
  export declare const applyAssignmentToStrategy: ApplyStrategy<AssignemntToStrategy>;
@@ -2,13 +2,16 @@ import { findAssignmentTo } from '../util/ast';
2
2
  import { nodeInput } from '../graph/base-node';
3
3
  import { StrategyType } from '.';
4
4
  // Constructor
5
- export var assignemntToStrategy = function (assignTo) { return ({
6
- type: StrategyType.ASSIGNMENT_TO,
7
- config: { assignTo: assignTo },
8
- }); };
5
+ export var assignemntToStrategy = function (assignTo, nth) {
6
+ if (nth === void 0) { nth = 1; }
7
+ return ({
8
+ type: StrategyType.ASSIGNMENT_TO,
9
+ config: { assignTo: assignTo, nth: nth },
10
+ });
11
+ };
9
12
  // Apply the strategy
10
13
  export var applyAssignmentToStrategy = function (strategy, ast, graphNode, siblingNode) {
11
- var assignNode = findAssignmentTo(ast, strategy.config.assignTo);
14
+ var assignNode = findAssignmentTo(ast, strategy.config.assignTo, strategy.config.nth || 1);
12
15
  var name = strategy.config.assignTo;
13
16
  return assignNode
14
17
  ? [
@@ -11,5 +11,5 @@ export interface InjectStrategy extends BaseStrategy {
11
11
  * Inject source code into an AST, in a find-and-replace style. This only
12
12
  * operates on statements right now
13
13
  */
14
- export declare const injectStrategy: (config: InjectStrategy['config']) => InjectStrategy;
14
+ export declare const injectStrategy: (config: InjectStrategy["config"]) => InjectStrategy;
15
15
  export declare const applyInjectStrategy: ApplyStrategy<InjectStrategy>;
@@ -1,14 +1,3 @@
1
- var __assign = (this && this.__assign) || function () {
2
- __assign = Object.assign || function(t) {
3
- for (var s, i = 1, n = arguments.length; i < n; i++) {
4
- s = arguments[i];
5
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
- t[p] = s[p];
7
- }
8
- return t;
9
- };
10
- return __assign.apply(this, arguments);
11
- };
12
1
  var __read = (this && this.__read) || function (o, n) {
13
2
  var m = typeof Symbol === "function" && o[Symbol.iterator];
14
3
  if (!m) return o;
@@ -37,6 +26,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
37
26
  import { generate } from '@shaderfrog/glsl-parser';
38
27
  import { nodeInput } from '../graph/base-node';
39
28
  import { StrategyType } from '.';
29
+ import { transferWhitespace } from '../util/whitespace';
40
30
  /**
41
31
  * Inject source code into an AST, in a find-and-replace style. This only
42
32
  * operates on statements right now
@@ -45,26 +35,6 @@ export var injectStrategy = function (config) { return ({
45
35
  type: StrategyType.INJECT,
46
36
  config: config,
47
37
  }); };
48
- // Typescript fucked up flat https://stackoverflow.com/a/61420611/743464
49
- var combineWs = function (a, b) {
50
- return [a, b].flat(Infinity);
51
- };
52
- // Move whitespace from one node to another. Since whitespace is trailing, if a
53
- // node is injected after a previous node, move the whitespace from the earlier
54
- // node to the later one. This keeps comments in the same place
55
- var transferWhitespace = function (to, from) {
56
- return 'semi' in to && 'semi' in from
57
- ? [
58
- __assign(__assign({}, to), { semi: __assign(__assign({}, to.semi), { whitespace: from.semi.whitespace }) }),
59
- __assign(__assign({}, from), { semi: __assign(__assign({}, from.semi), { whitespace: '\n' }) }),
60
- ]
61
- : 'whitespace' in to && 'semi' in from
62
- ? [
63
- __assign(__assign({}, to), { whitespace: combineWs(to.whitespace, from.semi.whitespace) }),
64
- __assign(__assign({}, from), { semi: __assign(__assign({}, from.semi), { whitespace: '\n' }) }),
65
- ]
66
- : [to, from];
67
- };
68
38
  export var applyInjectStrategy = function (strategy, ast, graphNode, siblingNode) {
69
39
  var program = ast.program;
70
40
  var _a = strategy.config, find = _a.find, count = _a.count, insert = _a.insert;
@@ -14,23 +14,12 @@ var __read = (this && this.__read) || function (o, n) {
14
14
  }
15
15
  return ar;
16
16
  };
17
+ import { expect, it } from 'vitest';
17
18
  import { parser } from '@shaderfrog/glsl-parser';
18
19
  import { generate } from '@shaderfrog/glsl-parser';
19
20
  import { applyStrategy, StrategyType } from '.';
20
- import * as graphModule from '../graph/graph';
21
21
  import { makeExpression } from '../util/ast';
22
22
  import preprocess from '@shaderfrog/glsl-parser/preprocessor';
23
- var orig;
24
- beforeEach(function () {
25
- orig = graphModule.mangleName;
26
- // Terrible hack. in the real world, strategies are applied after mangling
27
- // @ts-ignore
28
- graphModule.mangleName = function (name) { return name; };
29
- });
30
- afterEach(function () {
31
- // @ts-ignore
32
- graphModule.mangleName = orig;
33
- });
34
23
  it('named attribute strategy`', function () {
35
24
  var source = "\nin vec3 replaceThisAtrribute;\nvoid main() {\n vec2 y = replaceThisAtrribute;\n}\n";
36
25
  var ast = parser.parse(source, { quiet: true });
@@ -92,9 +81,65 @@ it('inject strategy before', function () {
92
81
  // Should fill references
93
82
  expect(result).toBe("\nuniform float x;\n// Some comment\nvoid main() {\n/* some comment */\nsomeOtherCall(x, y, z);\nsomeOtherCall(x, y, z);\nre(x, y, z);\n// Middle comment\nsomeOtherCall(x, y, z);\nsomeOtherCall(x, y, z);\nre(x, y, z);\n// Final comment\n}");
94
83
  });
84
+ var constructor = function () { return ({
85
+ config: {
86
+ version: 3,
87
+ preprocess: false,
88
+ strategies: [],
89
+ uniforms: [],
90
+ },
91
+ id: '1',
92
+ name: '1',
93
+ engine: true,
94
+ type: '',
95
+ inputs: [],
96
+ outputs: [],
97
+ position: { x: 0, y: 0 },
98
+ source: '',
99
+ stage: undefined,
100
+ }); };
101
+ var engine = {
102
+ name: 'three',
103
+ displayName: 'Three.js',
104
+ evaluateNode: function (node) {
105
+ if (node.type === 'number') {
106
+ return parseFloat(node.value);
107
+ }
108
+ return node.value;
109
+ },
110
+ constructors: {
111
+ physical: constructor,
112
+ toon: constructor,
113
+ },
114
+ mergeOptions: {
115
+ includePrecisions: true,
116
+ includeVersion: true,
117
+ },
118
+ importers: {},
119
+ preserve: new Set(),
120
+ parsers: {},
121
+ };
95
122
  it('correctly fills with uniform strategy', function () {
96
123
  var _a, _b, _c;
97
124
  var ast = parser.parse("\nlayout(std140,column_major) uniform;\nuniform sampler2D image;\nuniform vec4 input, output, other;\nuniform vec4 zenput;\nuniform Light0 { vec4 y; } x;\nvec3 topLevel = vec3(0.0);\nvoid other(in vec3 param) {}\nvoid main() {\n vec4 computed = texture2D(image, uvPow * 1.0);\n vec4 x = input;\n vec4 y = output;\n vec4 z = zenput;\n}", { quiet: true });
125
+ // TODO: Experimenting with strategy tests where we mangle in the test to
126
+ // avoid having to mangle in the strategy, in service of maybe mangling the
127
+ // AST as part of producing context. But as the test shows -
128
+ // mangleEntireProgram does NOT modify binding names
129
+ //
130
+ // You started updating binding names in the parser but realized that
131
+ // technically a mangler can produce different results for different nodes
132
+ // during the rename, since the parser takes in the node to mangle.
133
+ //
134
+ // which raised the question about why pass in the node at all to the mangler?
135
+ // looks like it's for "doNotDescope" hack to avoid renaming a specific
136
+ // varaible.
137
+ //
138
+ // But maybe that could be done here instead? And mangleEntireProgram could be
139
+ // aware of the output varaibles to ignore? Which means we need to track the
140
+ // output varialbe names somewhere... do we alredy?
141
+ var node = { name: 'fake', id: '1' };
142
+ // mangleEntireProgram(engine, ast, node);
98
143
  var fillers = applyStrategy({ type: StrategyType.UNIFORM, config: {} }, ast, {}, {});
99
144
  // It should find uniforms with simple types, excluding sampler2D
100
145
  expect(fillers.map(function (_a) {
@@ -160,5 +205,5 @@ it('Make sure texture2D finds preprocessed texture() call', function () {
160
205
  expect(applyStrategy({ type: StrategyType.TEXTURE_2D, config: {} }, ast, {}, {}).map(function (_a) {
161
206
  var _b = __read(_a, 1), name = _b[0].displayName;
162
207
  return name;
163
- })).toEqual(['normalMapx']);
208
+ })).toEqual(['normalMap']);
164
209
  });
@@ -14,7 +14,6 @@ var __read = (this && this.__read) || function (o, n) {
14
14
  }
15
15
  return ar;
16
16
  };
17
- import { mangleName } from '../graph/graph';
18
17
  import { nodeInput } from '../graph/base-node';
19
18
  import { StrategyType } from '.';
20
19
  import { generateFiller } from '../util/ast';
@@ -155,7 +154,8 @@ export var applyUniformStrategy = function (strategy, ast, graphNode, siblingNod
155
154
  return names.map(function (name) { return [
156
155
  nodeInput(name, "uniform_".concat(name), 'uniform', graphDataType, ['code', 'data'], true),
157
156
  function (filler) {
158
- var mangledName = mangleName(name, graphNode, siblingNode);
157
+ //const mangledName = mangleName(name, graphNode, siblingNode);
158
+ var mangledName = name;
159
159
  // Remove the declaration line, or the declared uniform
160
160
  if (declarations_1.length === 1) {
161
161
  program.program.splice(program.program.indexOf(node), 1);
package/util/ast.d.ts CHANGED
@@ -1,18 +1,30 @@
1
- /**
2
- * Utility functions to work with ASTs
3
- */
4
- import { Filler } from '../graph/parsers';
5
- import { AstNode, ExpressionStatementNode, FunctionNode, DeclarationNode } from '@shaderfrog/glsl-parser/ast';
1
+ import { AstNode, ExpressionStatementNode, FunctionNode, DeclarationStatementNode, DeclarationNode } from '@shaderfrog/glsl-parser/ast';
6
2
  import { Program } from '@shaderfrog/glsl-parser/ast';
7
3
  import { ShaderStage } from '../graph/graph-types';
8
4
  import { Scope } from '@shaderfrog/glsl-parser/parser/scope';
5
+ import { Filler } from '../graph/parsers';
6
+ export interface FrogProgram extends Program {
7
+ outVar?: string;
8
+ }
9
9
  export declare const findVec4Constructor: (ast: AstNode) => AstNode | undefined;
10
- export declare const findAssignmentTo: (ast: AstNode | Program, assignTo: string) => DeclarationNode | ExpressionStatementNode;
10
+ export declare const findAssignmentTo: (ast: AstNode | Program, assignTo: string, nth?: number) => DeclarationNode | ExpressionStatementNode;
11
11
  export declare const findDeclarationOf: (ast: AstNode | Program, declarationOf: string) => DeclarationNode | undefined;
12
12
  export declare const from2To3: (ast: Program, stage: ShaderStage) => void;
13
- export declare const outDeclaration: (name: string) => Object;
14
- export declare const makeStatement: (stmt: string) => AstNode;
15
- export declare const makeFnStatement: (fnStmt: string) => AstNode;
13
+ export declare const makeStatement: (stmt: string) => readonly [DeclarationStatementNode | FunctionNode | import("@shaderfrog/glsl-parser/ast").PreprocessorNode, Scope];
14
+ export declare const makeFnStatement: (fnStmt: string) => readonly [AstNode, Scope];
15
+ /**
16
+ * Add a new scope into an existing one. Meant to be used for adding net new
17
+ * lines of coe to an AST, and wanting to add the new scope generated from those
18
+ * lines.
19
+ *
20
+ * DO NOT USE THIS TO MERGE SCOPES! If both the left and right scope contain the
21
+ * same binding name, this will override the left scope outright, rather than
22
+ * merge the binding.references.
23
+ *
24
+ * One reason I chose not to make a full merge: What happens if both sides
25
+ * contain a binding.declaration?
26
+ */
27
+ export declare const addNewScope: (left: Scope, right: Scope) => Scope;
16
28
  export declare const makeExpression: (expr: string) => AstNode;
17
29
  export declare const makeExpressionWithScopes: (expr: string) => {
18
30
  scope: Scope;
@@ -22,9 +34,16 @@ export declare const makeFnBodyStatementWithScopes: (body: string) => {
22
34
  scope: Scope;
23
35
  statements: AstNode[];
24
36
  };
25
- export declare const findFn: (ast: Program, name: string) => FunctionNode | undefined;
37
+ export declare const findFn: (name: string) => (ast: Program) => FunctionNode | undefined;
38
+ export declare const findMain: (ast: Program) => FunctionNode | undefined;
39
+ export declare const findMainOrThrow: (ast: Program) => FunctionNode;
26
40
  export declare const returnGlPosition: (fnName: string, ast: Program) => void;
27
41
  export declare const returnGlPositionHardCoded: (fnName: string, ast: Program, returnType: string, hardCodedReturn: string) => void;
28
42
  export declare const returnGlPositionVec3Right: (fnName: string, ast: Program) => void;
29
- export declare const convert300MainToReturn: (suffix: string, ast: Program) => void;
43
+ /**
44
+ * For either a fragment or vertex AST, convert the main() function that sets
45
+ * gl_FragColor, gl_Position, or "out vec4 ____" into a main() function that
46
+ * returns a vec4.
47
+ */
48
+ export declare const convert300MainToReturn: (ast: FrogProgram) => void;
30
49
  export declare const generateFiller: (filler: Filler) => string;
package/util/ast.js CHANGED
@@ -1,3 +1,14 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
1
12
  var __read = (this && this.__read) || function (o, n) {
2
13
  var m = typeof Symbol === "function" && o[Symbol.iterator];
3
14
  if (!m) return o;
@@ -25,6 +36,8 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
25
36
  };
26
37
  import { parser, generate } from '@shaderfrog/glsl-parser';
27
38
  import { visit, } from '@shaderfrog/glsl-parser/ast';
39
+ import { addFnStmtWithIndent } from './whitespace';
40
+ import { renameBinding, } from '@shaderfrog/glsl-parser/parser/utils';
28
41
  var log = function () {
29
42
  var _a;
30
43
  var args = [];
@@ -49,14 +62,20 @@ export var findVec4Constructor = function (ast) {
49
62
  visit(ast, visitors);
50
63
  return parent;
51
64
  };
52
- export var findAssignmentTo = function (ast, assignTo) {
65
+ export var findAssignmentTo = function (ast, assignTo, nth) {
66
+ if (nth === void 0) { nth = 1; }
53
67
  var assign;
68
+ var foundth = 0;
54
69
  var visitors = {
55
70
  expression_statement: {
56
71
  enter: function (path) {
57
72
  var _a, _b;
58
73
  if (((_b = (_a = path.node.expression) === null || _a === void 0 ? void 0 : _a.left) === null || _b === void 0 ? void 0 : _b.identifier) === assignTo) {
59
- assign = path.node;
74
+ foundth++;
75
+ if (foundth === nth) {
76
+ assign = path.node;
77
+ path.stop();
78
+ }
60
79
  }
61
80
  path.skip();
62
81
  },
@@ -66,7 +85,11 @@ export var findAssignmentTo = function (ast, assignTo) {
66
85
  var _a, _b;
67
86
  var foundDecl = (_b = (_a = path.node.declaration) === null || _a === void 0 ? void 0 : _a.declarations) === null || _b === void 0 ? void 0 : _b.find(function (decl) { var _a; return ((_a = decl === null || decl === void 0 ? void 0 : decl.identifier) === null || _a === void 0 ? void 0 : _a.identifier) === assignTo; });
68
87
  if (foundDecl === null || foundDecl === void 0 ? void 0 : foundDecl.initializer) {
69
- assign = foundDecl;
88
+ foundth++;
89
+ if (foundth === nth) {
90
+ assign = foundDecl;
91
+ path.stop();
92
+ }
70
93
  }
71
94
  path.skip();
72
95
  },
@@ -101,7 +124,11 @@ export var from2To3 = function (ast, stage) {
101
124
  // _: '\n',
102
125
  // });
103
126
  if (stage === 'fragment') {
104
- ast.program.unshift(makeStatement("out vec4 ".concat(glOut)));
127
+ // Add in "out vec4 fragmentColor" to convert gl_FragColor to an out statement
128
+ var _a = __read(makeStatement("out vec4 ".concat(glOut)), 2), outStmt = _a[0], scope = _a[1];
129
+ ast.program.unshift(outStmt);
130
+ // Add the out statement variable to the scope
131
+ ast.scopes[0] = addNewScope(ast.scopes[0], scope);
105
132
  }
106
133
  visit(ast, {
107
134
  function_call: {
@@ -117,6 +144,7 @@ export var from2To3 = function (ast, stage) {
117
144
  enter: function (path) {
118
145
  if (path.node.identifier === 'gl_FragColor') {
119
146
  path.node.identifier = glOut;
147
+ ast.scopes[0].bindings[glOut].references.push(path.node);
120
148
  }
121
149
  },
122
150
  },
@@ -131,36 +159,6 @@ export var from2To3 = function (ast, stage) {
131
159
  },
132
160
  });
133
161
  };
134
- export var outDeclaration = function (name) { return ({
135
- type: 'declaration_statement',
136
- declaration: {
137
- type: 'declarator_list',
138
- specified_type: {
139
- type: 'fully_specified_type',
140
- qualifiers: [{ type: 'keyword', token: 'out', whitespace: ' ' }],
141
- specifier: {
142
- type: 'type_specifier',
143
- specifier: { type: 'keyword', token: 'vec4', whitespace: ' ' },
144
- quantifier: null,
145
- },
146
- },
147
- declarations: [
148
- {
149
- type: 'declaration',
150
- identifier: {
151
- type: 'identifier',
152
- identifier: name,
153
- whitespace: undefined,
154
- },
155
- quantifier: null,
156
- operator: undefined,
157
- initializer: undefined,
158
- },
159
- ],
160
- commas: [],
161
- },
162
- semi: { type: 'literal', literal: ';', whitespace: '\n ' },
163
- }); };
164
162
  export var makeStatement = function (stmt) {
165
163
  // log(`Parsing "${stmt}"`);
166
164
  var ast;
@@ -172,20 +170,38 @@ export var makeStatement = function (stmt) {
172
170
  throw new Error("Error parsing stmt \"".concat(stmt, "\": ").concat(error === null || error === void 0 ? void 0 : error.message));
173
171
  }
174
172
  // log(util.inspect(ast, false, null, true));
175
- return ast.program[0];
173
+ return [ast.program[0], ast.scopes[0]];
176
174
  };
177
175
  export var makeFnStatement = function (fnStmt) {
178
176
  var ast;
179
177
  try {
180
- ast = parser.parse("\n void main() {\n ".concat(fnStmt, ";\n }"), { quiet: true });
178
+ // Create a statement with no trailing nor leading whitespace
179
+ ast = parser.parse("void main() {".concat(fnStmt, ";}"), { quiet: true });
181
180
  }
182
181
  catch (error) {
183
182
  console.error({ fnStmt: fnStmt, error: error });
184
183
  throw new Error("Error parsing fnStmt \"".concat(fnStmt, "\": ").concat(error === null || error === void 0 ? void 0 : error.message));
185
184
  }
186
185
  // log(util.inspect(ast, false, null, true));
187
- return ast.program[0].body.statements[0];
186
+ var n = ast.program[0].body.statements[0];
187
+ n.semi.whitespace = '';
188
+ return [n, ast.scopes[1]];
188
189
  };
190
+ /**
191
+ * Add a new scope into an existing one. Meant to be used for adding net new
192
+ * lines of coe to an AST, and wanting to add the new scope generated from those
193
+ * lines.
194
+ *
195
+ * DO NOT USE THIS TO MERGE SCOPES! If both the left and right scope contain the
196
+ * same binding name, this will override the left scope outright, rather than
197
+ * merge the binding.references.
198
+ *
199
+ * One reason I chose not to make a full merge: What happens if both sides
200
+ * contain a binding.declaration?
201
+ */
202
+ export var addNewScope = function (left, right) { return (__assign(__assign({}, left), {
203
+ // name, parent comes from left
204
+ bindings: __assign(__assign({}, left.bindings), right.bindings), types: __assign(__assign({}, left.types), right.types), functions: __assign(__assign({}, left.functions), right.functions) })); };
189
205
  export var makeExpression = function (expr) {
190
206
  var ast;
191
207
  try {
@@ -229,10 +245,21 @@ export var makeFnBodyStatementWithScopes = function (body) {
229
245
  statements: ast.program[0].body.statements,
230
246
  };
231
247
  };
232
- export var findFn = function (ast, name) {
233
- return ast.program.find(function (stmt) {
234
- return stmt.type === 'function' && stmt.prototype.header.name.identifier === name;
235
- });
248
+ export var findFn = function (name) {
249
+ return function (ast) {
250
+ return ast.program.find(function (stmt) {
251
+ return stmt.type === 'function' &&
252
+ stmt.prototype.header.name.identifier === name;
253
+ });
254
+ };
255
+ };
256
+ export var findMain = findFn('main');
257
+ export var findMainOrThrow = function (ast) {
258
+ var main = findMain(ast);
259
+ if (!main) {
260
+ throw new Error('No main function found!');
261
+ }
262
+ return main;
236
263
  };
237
264
  export var returnGlPosition = function (fnName, ast) {
238
265
  return convertVertexMain(fnName, ast, 'vec4', function (assign) { return assign.expression.right; });
@@ -264,9 +291,10 @@ export var returnGlPositionVec3Right = function (fnName, ast) {
264
291
  return found;
265
292
  });
266
293
  };
294
+ var replacedReturn = 'frogOut';
267
295
  var convertVertexMain = function (fnName, ast, returnType, generateRight) {
268
296
  var mainReturnVar = "frogOut";
269
- var main = findFn(ast, fnName);
297
+ var main = findFn(fnName)(ast);
270
298
  if (!main) {
271
299
  throw new Error("No ".concat(fnName, " fn found!"));
272
300
  }
@@ -282,14 +310,23 @@ var convertVertexMain = function (fnName, ast, returnType, generateRight) {
282
310
  if (!assign) {
283
311
  throw new Error("No gl position assign found in main fn!");
284
312
  }
285
- var rtnStmt = makeFnStatement("".concat(returnType, " ").concat(mainReturnVar, " = 1.0"));
313
+ var rtnStmt = makeFnStatement("".concat(returnType, " ").concat(mainReturnVar, " = 1.0"))[0];
286
314
  rtnStmt.declaration.declarations[0].initializer =
287
315
  generateRight(assign);
316
+ rtnStmt.semi.whitespace = '\n';
288
317
  main.body.statements.splice(main.body.statements.indexOf(assign), 1, rtnStmt);
289
- main.body.statements.push(makeFnStatement("return ".concat(mainReturnVar)));
318
+ main.body.statements = addFnStmtWithIndent(main, "return ".concat(mainReturnVar));
290
319
  };
291
- export var convert300MainToReturn = function (suffix, ast) {
292
- var mainReturnVar = "frogOut_".concat(suffix);
320
+ /**
321
+ * For either a fragment or vertex AST, convert the main() function that sets
322
+ * gl_FragColor, gl_Position, or "out vec4 ____" into a main() function that
323
+ * returns a vec4.
324
+ */
325
+ export var convert300MainToReturn = function (ast) {
326
+ // Convert the main function to return a vec4
327
+ var main = findMainOrThrow(ast);
328
+ main.prototype.header.returnType.specifier.specifier.token =
329
+ 'vec4';
293
330
  // Find the output variable, as in "pc_fragColor" from "out highp vec4 pc_fragColor;"
294
331
  var outName;
295
332
  ast.program.find(function (line, index) {
@@ -300,7 +337,8 @@ export var convert300MainToReturn = function (suffix, ast) {
300
337
  ((_b = (_a = declaration === null || declaration === void 0 ? void 0 : declaration.specified_type) === null || _a === void 0 ? void 0 : _a.qualifiers) === null || _b === void 0 ? void 0 : _b.find(function (n) { return n.token === 'out'; })) &&
301
338
  declaration.specified_type.specifier.specifier.token ===
302
339
  'vec4') {
303
- // Remove the out declaration
340
+ // Remove the out declaration. This does NOT yet remove the declaration
341
+ // from the scope, that's done below
304
342
  ast.program.splice(index, 1);
305
343
  outName = declaration.declarations[0].identifier.identifier;
306
344
  return true;
@@ -310,27 +348,23 @@ export var convert300MainToReturn = function (suffix, ast) {
310
348
  console.error(generate(ast));
311
349
  throw new Error('No "out vec4" line found in the fragment shader');
312
350
  }
313
- ast.program.unshift(makeStatement("vec4 ".concat(mainReturnVar)));
314
- visit(ast, {
315
- identifier: {
316
- enter: function (path) {
317
- if (path.node.identifier === outName) {
318
- path.node.identifier = mainReturnVar;
319
- // @ts-ignore
320
- path.node.doNotDescope = true; // hack because this var is in the scope which gets renamed later
321
- }
322
- },
323
- },
324
- function: {
325
- enter: function (path) {
326
- if (path.node.prototype.header.name.identifier === 'main') {
327
- path.node.prototype.header.returnType.specifier
328
- .specifier.token = 'vec4';
329
- path.node.body.statements.push(makeFnStatement("return ".concat(mainReturnVar)));
330
- }
331
- },
332
- },
333
- });
351
+ // Store the variable to avoid descoping it later
352
+ ast.outVar = outName;
353
+ // Rename the scope entry of "out vec4 ___" to our return variable, and rename
354
+ // all references to our new variable
355
+ ast.scopes[0].bindings[replacedReturn] = renameBinding(ast.scopes[0].bindings[outName], replacedReturn);
356
+ delete ast.scopes[0].bindings[outName];
357
+ // Add the declaration of the return variable to the top of the program, and
358
+ // add it to the AST scope, including the declaration
359
+ var decl = makeStatement("vec4 ".concat(replacedReturn))[0];
360
+ ast.program.unshift(decl);
361
+ ast.scopes[0].bindings[replacedReturn].declaration = decl;
362
+ ast.scopes[0].bindings[replacedReturn].references.push(decl.declaration.declarations[0]);
363
+ // Add a return statement to the main() function and add the return variable
364
+ // to scope
365
+ var rtn = makeFnStatement("return ".concat(replacedReturn))[0];
366
+ main.body.statements = addFnStmtWithIndent(main, rtn);
367
+ ast.scopes[0].bindings[replacedReturn].references.push(rtn.expression);
334
368
  };
335
369
  export var generateFiller = function (filler) {
336
370
  if (!filler) {
package/util/ensure.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const ensure: <T>(argument: T, message?: string) => T;
1
+ export declare const ensure: <T>(argument: T | undefined | null, message?: string) => T;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Utility functions to work with whitespace on nodes.
3
+ *
4
+ * This is for manual manipulaiton of code where adding new lines should
5
+ * attempt to keep the indentation of the original program.
6
+ *
7
+ * Another overal option is simply to pretty print, which this library
8
+ * does not yet support.
9
+ */
10
+ import { AstNode, FunctionNode, Whitespace } from '@shaderfrog/glsl-parser/ast';
11
+ export declare const combineWs: (a: string | string[], b: string | string[]) => string[];
12
+ export declare const transferWhitespace: (to: AstNode, from: AstNode) => [AstNode, AstNode];
13
+ export declare const getLiteralIndent: (node: {
14
+ whitespace: Whitespace;
15
+ }) => string;
16
+ export declare const tryAddTrailingWhitespace: <T extends AstNode>(node: T, ws: string) => T;
17
+ export declare const guessFnIndent: (fnBody: FunctionNode) => string;
18
+ export declare const addFnStmtWithIndent: (fnBody: FunctionNode, newNode: string | AstNode) => AstNode[];
@@ -0,0 +1,91 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __read = (this && this.__read) || function (o, n) {
13
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
14
+ if (!m) return o;
15
+ var i = m.call(o), r, ar = [], e;
16
+ try {
17
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
18
+ }
19
+ catch (error) { e = { error: error }; }
20
+ finally {
21
+ try {
22
+ if (r && !r.done && (m = i["return"])) m.call(i);
23
+ }
24
+ finally { if (e) throw e.error; }
25
+ }
26
+ return ar;
27
+ };
28
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
29
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
30
+ if (ar || !(i in from)) {
31
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
32
+ ar[i] = from[i];
33
+ }
34
+ }
35
+ return to.concat(ar || Array.prototype.slice.call(from));
36
+ };
37
+ import { makeFnStatement } from './ast';
38
+ var log = function () {
39
+ var _a;
40
+ var args = [];
41
+ for (var _i = 0; _i < arguments.length; _i++) {
42
+ args[_i] = arguments[_i];
43
+ }
44
+ return (_a = console.log).call.apply(_a, __spreadArray([console, '\x1b[31m(core.whitespace)\x1b[0m'], __read(args), false));
45
+ };
46
+ // Typescript fucked up flat https://stackoverflow.com/a/61420611/743464
47
+ export var combineWs = function (a, b) { return [a, b].flat(Infinity); };
48
+ // Move whitespace from one node to another. Since whitespace is trailing, if a
49
+ // node is injected after a previous node, move the whitespace from the earlier
50
+ // node to the later one. This keeps comments in the same place.
51
+ export var transferWhitespace = function (to, from) {
52
+ return 'semi' in to && 'semi' in from
53
+ ? [
54
+ __assign(__assign({}, to), { semi: __assign(__assign({}, to.semi), { whitespace: from.semi.whitespace }) }),
55
+ __assign(__assign({}, from), { semi: __assign(__assign({}, from.semi), { whitespace: '\n' }) }),
56
+ ]
57
+ : 'whitespace' in to && 'semi' in from
58
+ ? [
59
+ __assign(__assign({}, to), { whitespace: combineWs(to.whitespace, from.semi.whitespace) }),
60
+ __assign(__assign({}, from), { semi: __assign(__assign({}, from.semi), { whitespace: '\n' }) }),
61
+ ]
62
+ : [to, from];
63
+ };
64
+ export var getLiteralIndent = function (node) {
65
+ return [(node === null || node === void 0 ? void 0 : node.whitespace) || '']
66
+ .flat(Infinity)
67
+ .join('')
68
+ .split(/\r|\n/)
69
+ .sort()
70
+ .at(-1);
71
+ };
72
+ export var tryAddTrailingWhitespace = function (node, ws) {
73
+ return 'semi' in node
74
+ ? __assign(__assign({}, node), { semi: __assign(__assign({}, node.semi), { whitespace: combineWs(node.semi.whitespace, ws) }) }) : node;
75
+ };
76
+ export var guessFnIndent = function (fnBody) {
77
+ return getLiteralIndent(fnBody.body.lb) ||
78
+ fnBody.body.statements.reduce(function (ws, n) {
79
+ return ws || getLiteralIndent(n.semi);
80
+ }, '');
81
+ };
82
+ export var addFnStmtWithIndent = function (fnBody, newNode) {
83
+ var statements = fnBody.body.statements;
84
+ var indent = guessFnIndent(fnBody);
85
+ return __spreadArray(__spreadArray([], __read(statements), false), [
86
+ // This simple hack is way easier than trying to modify the function body
87
+ // opening brace and/or the previous statement
88
+ { type: 'literal', literal: '', whitespace: indent },
89
+ tryAddTrailingWhitespace(typeof newNode === 'string' ? makeFnStatement(newNode)[0] : newNode, "\n"),
90
+ ], false);
91
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import { expect, it } from 'vitest';
2
+ import { parser } from '@shaderfrog/glsl-parser';
3
+ import { generate } from '@shaderfrog/glsl-parser';
4
+ import { addFnStmtWithIndent } from './whitespace';
5
+ import { findMainOrThrow } from './ast';
6
+ it("addFnStmtWithIndent", function () {
7
+ var source = "void main() {\n vec2 y;\n}\n";
8
+ var ast = parser.parse(source, { quiet: true });
9
+ var m = findMainOrThrow(ast);
10
+ m.body.statements = addFnStmtWithIndent(m, "return x");
11
+ // Should line up the whitespace properly!
12
+ expect(generate(m)).toBe("void main() {\n vec2 y;\n return x;\n}\n");
13
+ });