UNPKG

@shaderfrog/glsl-parser

Version:

A GLSL ES 1.0 and 3.0 parser and preprocessor that can preserve whitespace and comments

185 lines (184 loc) 10.8 kB
import { visit } from '../ast/index.js'; import { buildParser } from './test-helpers.js'; var c; beforeAll(function () { return (c = buildParser()); }); test('parse error', function () { var error; // Missing a semicolon var text = "float a\nfloat b"; try { c.parse(text); } catch (e) { error = e; } expect(error).toBeInstanceOf(c.parser.SyntaxError); expect(error.location.start.line).toBe(2); expect(error.location.end.line).toBe(2); }); test('declarations', function () { c.expectParsedProgram("\n float a, b = 1.0, c = a;\n vec2 texcoord1, texcoord2;\n vec3 position;\n vec4 myRGBA;\n ivec2 textureLookup;\n bvec3 less;\n "); }); test('headers', function () { // The following includes the varying/attribute case which only works in GL // ES 1.00, and will need to be updated when the switch is implemented c.expectParsedProgram("\n precision mediump float;\n precision highp int;\n\n in vec4 varName1;\n out vec4 varName2;\n\n varying vec4 varName3, blarName;\n uniform vec4 varName4;\n attribute vec4 varName5;\n "); }); test('if statement', function () { c.expectParsedStatement("if(i != 0) { aFunction(); }\nelse if(i == 2) { bFunction(); }\nelse { cFunction(); }", { quiet: true, }); }); test('do while loop', function () { c.expectParsedStatement("\n do {\n aFunction();\n break;\n continue;\n return;\n } while(i <= 99);\n ", { quiet: true }); }); test('standard while loop', function () { c.expectParsedStatement("\n while(i <= 99) {\n aFunction();\n break;\n continue;\n return;\n }\n ", { quiet: true }); }); test('for loops', function () { // Infinite for loop c.expectParsedStatement("\n for(;;) {\n }\n "); // For loop with jump statements c.expectParsedStatement("\n for(int a = 0; b <= 99; c++) {\n break;\n continue;\n return;\n aFunction();\n }\n ", { quiet: true }); // Loop with condition variable declaration (GLSL ES 3.00 only) c.expectParsedStatement("\n for(int i = 0; bool x = false; i++) {}\n "); }); test('switch error', function () { // Test the semantic analysis case expect(function () { return c.parse("void main() {\n switch (easingId) {\n result = cubicIn();\n }\n}", { quiet: true }); }).toThrow(/must start with a case or default label/); }); test('switch statement', function () { c.expectParsedStatement("\n switch (easingId) {\n case 0:\n result = cubicIn();\n break;\n case 1:\n result = cubicOut();\n break;\n default:\n result = 1.0;\n }\n ", { quiet: true }); }); test('qualifier declarations', function () { // The expected node here is "qualifier_declarator", which would be nice to // test for at some point, maybe when doing more AST analysis c.expectParsedProgram("\n invariant precise in a, b,c;\n "); }); test('number notations', function () { // Integer hex notation c.expectParsedStatement("highp uint value = 0x1234u;"); c.expectParsedStatement("uint c = 0xffffffff;"); c.expectParsedStatement("uint d = 0xffffffffU;"); // Octal c.expectParsedStatement("uint d = 021234;"); // Double precision floating point c.expectParsedStatement("double c, d = 2.0LF;"); // uint c.expectParsedStatement("uint k = 3u;"); c.expectParsedStatement("uint f = -1u;"); }); test('layout', function () { c.expectParsedProgram(" \n layout(location = 4, component = 2) in vec2 a;\n layout(location = 3) in vec4 normal1;\n layout(location = 9) in mat4 transforms[2];\n layout(location = 3) in vec4 normal2;\n\n const int start = 6;\n layout(location = start + 2) in vec4 p;\n\n layout(location = 3) in struct S\n {\n vec3 a; // gets location 3\n mat2 b; // gets locations 4 and 5\n vec4 c[2]; // gets locations 6 and 7\n layout(location = 8) vec2 A; // ERROR, can't use on struct member\n } s;\n\n layout(location = 4) in block\n {\n vec4 d; // gets location 4\n vec4 e; // gets location 5\n layout(location = 7) vec4 f; // gets location 7\n vec4 g; // gets location 8\n layout(location = 1) vec4 h; // gets location 1\n vec4 i; // gets location 2\n vec4 j; // gets location 3\n vec4 k; // ERROR, location 4 already used\n };\n\n // From the grammar but I think it's a typo\n // https://github.com/KhronosGroup/GLSL/issues/161\n // layout(location = start + 2) int vec4 p;\n\n layout(std140,column_major) uniform;\n "); }); test('parses comments', function () { c.expectParsedProgram("\n /* starting comment */\n // hi\n void main(float x) {\n /* comment */// hi\n /* comment */ // hi\n statement(); // hi\n /* start */ statement(); /* end */\n }\n ", { quiet: true }); }); test('parses functions', function () { c.expectParsedProgram("\n // Prototypes\n vec4 f(in vec4 x, out vec4 y);\n int newFunction(in bvec4 aBvec4, // read-only\n out vec3 aVec3, // write-only\n inout int aInt); // read-write\n highp float rand( const in vec2 uv ) {}\n highp float otherFn( const in vec3 rectCoords[ 4 ] ) {}\n "); }); test('parses function_call . postfix_expression', function () { c.expectParsedStatement('texture().rgb;', { quiet: true }); }); test('parses postfix_expression as function_identifier', function () { c.expectParsedStatement('a().length();', { quiet: true }); }); test('parses postfix expressions after non-function calls (aka map.length())', function () { c.expectParsedProgram("\nvoid main() {\n float y = x().length();\n float x = map.length();\n for (int i = 0; i < map.length(); i++) {\n }\n}\n", { quiet: true }); }); test('postfix, unary, binary expressions', function () { c.expectParsedStatement('x ++ + 1.0 + + 2.0;', { quiet: true }); }); test('operators', function () { c.expectParsedStatement('1 || 2 && 2 ^^ 3 >> 4 << 5;'); }); test('declaration', function () { c.expectParsedStatement('const float x = 1.0, y = 2.0;'); }); test('assignment', function () { c.expectParsedStatement('x |= 1.0;', { quiet: true }); }); test('ternary', function () { c.expectParsedStatement('float y = x == 1.0 ? x == 2.0 ? 1.0 : 3.0 : x == 3.0 ? 4.0 : 5.0;', { quiet: true }); }); test('structs', function () { c.expectParsedProgram("\n struct light {\n float intensity;\n vec3 position, color;\n } lightVar;\n light lightVar2;\n\n struct S { float f; };\n "); }); test('buffer variables', function () { c.expectParsedProgram("\n buffer b {\n float u[];\n vec4 v[];\n } name[3]; \n "); }); test('arrays', function () { c.expectParsedProgram("\n float frequencies[3];\n uniform vec4 lightPosition[4];\n struct light { int a; };\n light lights[];\n const int numLights = 2;\n light lights2[numLights];\n\n buffer b {\n float u[]; \n vec4 v[];\n } name[3];\n\n // Array initializers\n float array[3] = float[3](1.0, 2.0, 3.0);\n float array2[3] = float[](1.0, 2.0, 3.0);\n\n // Function with array as return type\n float[5] foo() { }\n "); }); test('initializer list', function () { c.expectParsedProgram("\n vec4 a[3][2] = {\n vec4[2](vec4(0.0), vec4(1.0)),\n vec4[2](vec4(0.0), vec4(1.0)),\n vec4[2](vec4(0.0), vec4(1.0))\n };\n "); }); test('subroutines', function () { c.expectParsedProgram("\n subroutine vec4 colorRedBlue();\n\n // option 1\n subroutine (colorRedBlue ) vec4 redColor() {\n return vec4(1.0, 0.0, 0.0, 1.0);\n }\n\n // // option 2\n subroutine (colorRedBlue ) vec4 blueColor() {\n return vec4(0.0, 0.0, 1.0, 1.0);\n }\n "); }); test('Locations with location disabled', function () { var src = "void main() {}"; var ast = c.parseSrc(src); // default argument is no location information expect(ast.program[0].location).toBe(undefined); expect(ast.scopes[0].location).toBe(undefined); }); test('built-in function names should be identified as keywords', function () { console.warn = jest.fn(); var src = "\nvoid main() {\n void x = texture2D();\n}"; var ast = c.parseSrc(src); // Built-ins should not appear in scope expect(ast.scopes[0].functions).not.toHaveProperty('texture2D'); expect(ast.scopes[1].functions).not.toHaveProperty('texture2D'); var call; visit(ast, { function_call: { enter: function (path) { call = path.node; }, }, }); // Builtins like texture2D should be recognized as a identifier since that's // how user defined functions are treated expect(call.identifier.type).toBe('identifier'); // Should not warn about built in function call being undefined expect(console.warn).not.toHaveBeenCalled(); }); test('Parser locations', function () { var src = "// Some comment\nvoid main() {\n float x = 1.0;\n\n {\n float x = 1.0;\n }\n}"; var ast = c.parseSrc(src, { includeLocation: true }); // The main fn location should start at "void" expect(ast.program[0].location).toStrictEqual({ start: { line: 2, column: 1, offset: 16 }, end: { line: 8, column: 2, offset: 76 }, }); // The global scope is the entire program expect(ast.scopes[0].location).toStrictEqual({ start: { line: 1, column: 1, offset: 0 }, end: { line: 8, column: 2, offset: 76 }, }); // The scope created by the main fn should start at the open paren of the fn // header, because fn scopes include fn arguments expect(ast.scopes[1].location).toStrictEqual({ start: { line: 2, column: 10, offset: 25 }, end: { line: 8, column: 1, offset: 75 }, }); // The inner compound statement { scope } expect(ast.scopes[2].location).toStrictEqual({ start: { line: 5, column: 3, offset: 50 }, end: { line: 7, column: 3, offset: 73 }, }); }); test('fails on error', function () { expect(function () { return c.parse("float a;\n float a;", { failOnWarn: true }); }).toThrow(/duplicate variable declaration: "a"/); }); test('exotic precision statements', function () { // Regression to test for upper/loweracse typos in specific keywords expect(c.parse("precision highp sampler2DArrayShadow;").program[0].declaration .specifier.specifier.token).toBe('sampler2DArrayShadow'); expect(c.parse("precision highp sampler2DRectShadow;").program[0].declaration .specifier.specifier.token).toBe('sampler2DRectShadow'); });