diff --git a/package.json b/package.json index d051124..8ab1f4a 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "engines": { "node": ">=16" }, - "version": "4.1.1", + "version": "5.0.0-beta.1", "type": "module", "description": "A GLSL ES 1.0 and 3.0 parser and preprocessor that can preserve whitespace and comments", "scripts": { diff --git a/src/parser/scope.test.ts b/src/parser/scope.test.ts index ed0cc56..4ae2e5c 100644 --- a/src/parser/scope.test.ts +++ b/src/parser/scope.test.ts @@ -176,7 +176,10 @@ float fn() { `); expect(ast.scopes[0].functions.noise); - renameFunctions(ast.scopes[0], (name) => `${name}_FUNCTION`); + ast.scopes[0].functions = renameFunctions( + ast.scopes[0].functions, + (name) => `${name}_FUNCTION` + ); expect(generate(ast)).toBe(` float noise_FUNCTION() {} float fn_FUNCTION() { @@ -233,8 +236,14 @@ vec4 linearToOutputTexel( vec4 value ) { return LinearToLinear( value ); } { quiet: true } ); - renameBindings(ast.scopes[0], (name) => `${name}_VARIABLE`); - renameFunctions(ast.scopes[0], (name) => `${name}_FUNCTION`); + ast.scopes[0].bindings = renameBindings( + ast.scopes[0].bindings, + (name) => `${name}_VARIABLE` + ); + ast.scopes[0].functions = renameFunctions( + ast.scopes[0].functions, + (name) => `${name}_FUNCTION` + ); expect(generate(ast)).toBe(` float selfref_VARIABLE, b_VARIABLE = 1.0, c_VARIABLE = selfref_VARIABLE; @@ -306,7 +315,8 @@ StructName main(in StructName x, StructName[3] y) { float a2 = 1.0 + StructName(1.0).color.x; } `); - renameTypes(ast.scopes[0], (name) => `${name}_x`); + ast.scopes[0].types = renameTypes(ast.scopes[0].types, (name) => `${name}_x`); + expect(generate(ast)).toBe(` struct StructName_x { vec3 color; @@ -342,22 +352,43 @@ StructName_x main(in StructName_x x, StructName_x[3] y) { ]); expect(Object.keys(ast.scopes[0].bindings)).toEqual(['reflectedLight']); expect(Object.keys(ast.scopes[0].types)).toEqual([ - 'StructName', - 'OtherStruct', + 'StructName_x', + 'OtherStruct_x', ]); - expect(ast.scopes[0].types.StructName.references).toHaveLength(16); + expect(ast.scopes[0].types.StructName_x.references).toHaveLength(16); // Inner struct definition should be found in inner fn scope expect(Object.keys(ast.scopes[2].types)).toEqual(['StructName']); }); +test('shangus', () => { + const ast = c.parseSrc(` +struct MyStruct { float y; }; +attribute vec3 position; +vec3 func() {}`); + + ast.scopes[0].bindings = renameBindings( + ast.scopes[0].bindings, + (name) => `${name}_x` + ); + ast.scopes[0].functions = renameFunctions( + ast.scopes[0].functions, + (name) => `${name}_y` + ); + ast.scopes[0].types = renameTypes(ast.scopes[0].types, (name) => `${name}_z`); + + expect(Object.keys(ast.scopes[0].bindings)).toEqual(['position_x']); + expect(Object.keys(ast.scopes[0].functions)).toEqual(['func_y']); + expect(Object.keys(ast.scopes[0].types)).toEqual(['MyStruct_z']); +}); + test('fn args shadowing global scope identified as separate bindings', () => { const ast = c.parseSrc(` attribute vec3 position; vec3 func(vec3 position) { return position; }`); - renameBindings(ast.scopes[0], (name) => + ast.scopes[0].bindings = renameBindings(ast.scopes[0].bindings, (name) => name === 'position' ? 'renamed' : name ); // The func arg "position" shadows the global binding, it should be untouched @@ -378,7 +409,10 @@ uniform vec2 vProp; };`); // This shouldn't crash - see the comment block in renameBindings() - renameBindings(ast.scopes[0], (name) => `${name}_x`); + ast.scopes[0].bindings = renameBindings( + ast.scopes[0].bindings, + (name) => `${name}_x` + ); expect(generate(ast)).toBe(` layout(std140,column_major) uniform; float a_x; diff --git a/src/parser/utils.ts b/src/parser/utils.ts index 78438b7..2a9901a 100644 --- a/src/parser/utils.ts +++ b/src/parser/utils.ts @@ -1,18 +1,24 @@ import type { AstNode } from '../ast/index.js'; -import { Scope } from './scope.js'; +import { + FunctionScopeIndex, + Scope, + ScopeIndex, + TypeScopeIndex, +} from './scope.js'; export const renameBindings = ( - scope: Scope, - mangle: (name: string, node: AstNode) => string -) => { - Object.entries(scope.bindings).forEach(([name, binding]) => { + bindings: ScopeIndex, + mangle: (name: string) => string +) => + Object.entries(bindings).reduce((acc, [name, binding]) => { + const mangled = mangle(name); binding.references.forEach((node) => { if (node.type === 'declaration') { - node.identifier.identifier = mangle(node.identifier.identifier, node); + node.identifier.identifier = mangled; } else if (node.type === 'identifier') { - node.identifier = mangle(node.identifier, node); + node.identifier = mangled; } else if (node.type === 'parameter_declaration' && node.identifier) { - node.identifier.identifier = mangle(node.identifier.identifier, node); + node.identifier.identifier = mangled; /* Ignore case of: layout(std140,column_major) uniform; uniform Material @@ -25,72 +31,80 @@ export const renameBindings = ( throw new Error(`Binding for type ${node.type} not recognized`); } }); - }); -}; + return { + ...acc, + [mangled]: binding, + }; + }, {}); export const renameTypes = ( - scope: Scope, - mangle: (name: string, node: AstNode) => string -) => { - Object.entries(scope.types).forEach(([name, type]) => { + types: TypeScopeIndex, + mangle: (name: string) => string +) => + Object.entries(types).reduce((acc, [name, type]) => { + const mangled = mangle(name); type.references.forEach((node) => { if (node.type === 'type_name') { - node.identifier = mangle(node.identifier, node); + node.identifier = mangled; } else { console.warn('Unknown type node', node); throw new Error(`Type ${node.type} not recognized`); } }); - }); -}; + return { + ...acc, + [mangled]: type, + }; + }, {}); export const renameFunctions = ( - scope: Scope, - mangle: (name: string, node: AstNode) => string -) => { - Object.entries(scope.functions).forEach(([fnName, overloads]) => { - Object.entries(overloads).forEach(([signature, overload]) => { - overload.references.forEach((node) => { - if (node.type === 'function') { - node['prototype'].header.name.identifier = mangle( - node['prototype'].header.name.identifier, - node - ); - } else if ( - node.type === 'function_call' && - node.identifier.type === 'postfix' - ) { - // @ts-ignore - const specifier = node.identifier.expression.identifier.specifier; - if (specifier) { - specifier.identifier = mangle(specifier.identifier, node); + functions: FunctionScopeIndex, + mangle: (name: string) => string +) => + Object.entries(functions).reduce( + (acc, [fnName, overloads]) => { + const mangled = mangle(fnName); + Object.entries(overloads).forEach(([signature, overload]) => { + overload.references.forEach((node) => { + if (node.type === 'function') { + node['prototype'].header.name.identifier = mangled; + } else if ( + node.type === 'function_call' && + node.identifier.type === 'postfix' + ) { + // @ts-ignore + const specifier = node.identifier.expression.identifier.specifier; + if (specifier) { + specifier.identifier = mangled; + } else { + console.warn('Unknown function node to rename', node); + throw new Error( + `Function specifier type ${node.type} not recognized` + ); + } + } else if ( + node.type === 'function_call' && + 'specifier' in node.identifier && + 'identifier' in node.identifier.specifier + ) { + node.identifier.specifier.identifier = mangled; + } else if ( + node.type === 'function_call' && + node.identifier.type === 'identifier' + ) { + node.identifier.identifier = mangled; } else { console.warn('Unknown function node to rename', node); - throw new Error( - `Function specifier type ${node.type} not recognized` - ); + throw new Error(`Function for type ${node.type} not recognized`); } - } else if ( - node.type === 'function_call' && - 'specifier' in node.identifier && - 'identifier' in node.identifier.specifier - ) { - node.identifier.specifier.identifier = mangle( - node.identifier.specifier.identifier, - node - ); - } else if ( - node.type === 'function_call' && - node.identifier.type === 'identifier' - ) { - node.identifier.identifier = mangle(node.identifier.identifier, node); - } else { - console.warn('Unknown function node to rename', node); - throw new Error(`Function for type ${node.type} not recognized`); - } + }); }); - }); - }); -}; + return { + ...acc, + [mangled]: overloads, + }; + }, + {} + ); export const xor = (a: any, b: any): boolean => (a || b) && !(a && b);