Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,869 changes: 3,174 additions & 695 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 5 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"prepare": "npm run build && ./prepublish.sh",
"postpublish": "./postbuild.sh",
"build": "./build.sh",
"watch-test": "jest --watch",
"test": "jest --colors"
"watch-test": "vitest --watch",
"test": "vitest --colors"
},
"files": [
"ast",
Expand Down Expand Up @@ -42,13 +42,12 @@
"@babel/preset-typescript": "^7.15.0",
"@swc/core": "^1.4.11",
"@swc/jest": "^0.2.36",
"@types/jest": "^27.0.2",
"@types/node": "^16.10.2",
"jest": "^29.7.0",
"@types/node": "^24.3.0",
"peggy": "^1.2.0",
"prettier": "^2.1.2",
"ts-jest": "^29.1.2",
"ts-jest-resolver": "^2.0.1",
"typescript": "^5.5.3"
"typescript": "^5.5.3",
"vitest": "^3.2.4"
}
}
2 changes: 2 additions & 0 deletions src/ast/ast.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { expect, test } from 'vitest';

import {
AstNode,
BinaryNode,
Expand Down
16 changes: 9 additions & 7 deletions src/parser/parse.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { expect, test, beforeAll, vi } from 'vitest';

import { FunctionCallNode, visit } from '../ast/index.js';
import { GlslSyntaxError } from '../error.js';
import { buildParser } from './test-helpers.js';

let c!: ReturnType<typeof buildParser>;
beforeAll(() => (c = buildParser()));
let c!: Awaited<ReturnType<typeof buildParser>>;
beforeAll(async () => (c = await buildParser()));

test('parse error', () => {
let error: GlslSyntaxError | undefined;
Expand Down Expand Up @@ -354,7 +356,7 @@ test('Locations with location disabled', () => {
});

test('built-in function names should be identified as keywords', () => {
console.warn = jest.fn();
console.warn = vi.fn();

const src = `
void main() {
Expand Down Expand Up @@ -442,7 +444,7 @@ test('exotic precision statements', () => {
});

test('warns when grammar stage is unknown', () => {
const consoleWarnMock = jest
const consoleWarnMock = vi
.spyOn(console, 'warn')
.mockImplementation(() => {});

Expand All @@ -458,7 +460,7 @@ test('warns when grammar stage is unknown', () => {
});

test('does not warn on built in stage variable', () => {
const consoleWarnMock = jest
const consoleWarnMock = vi
.spyOn(console, 'warn')
.mockImplementation(() => {});

Expand All @@ -476,7 +478,7 @@ test('does not warn on built in stage variable', () => {
});

test('does not warn on built in either stage variable', () => {
const consoleWarnMock = jest
const consoleWarnMock = vi
.spyOn(console, 'warn')
.mockImplementation(() => {});

Expand All @@ -495,7 +497,7 @@ test('does not warn on built in either stage variable', () => {
});

test('warn on variable from wrong stage', () => {
const consoleWarnMock = jest
const consoleWarnMock = vi
.spyOn(console, 'warn')
.mockImplementation(() => {});

Expand Down
2 changes: 2 additions & 0 deletions src/parser/scope.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { expect, test, beforeAll } from 'vitest';

import generate from './generator.js';
import { renameBindings, renameFunctions, renameTypes } from './utils.js';
import { UNKNOWN_TYPE } from './grammar.js';
Expand All @@ -7,7 +9,7 @@
beforeAll(() => (c = buildParser()));

test('scope bindings and type names', () => {
const ast = c.parseSrc(`

Check failure on line 12 in src/parser/scope.test.ts

View workflow job for this annotation

GitHub Actions / Run tests (18.x)

Property 'parseSrc' does not exist on type 'Promise<{ parse: Parse; parser: typeof import("/home/runner/work/glsl-parser/glsl-parser/src/parser/parser", { with: { "resolution-mode": "import" } }); parseSrc: ParseSrc; debugSrc: (src: string) => void; debugStatement: (stmt: AstNode) => void; expectParsedStatement: (src: string, options?: {}) => void; parseState...'.
float selfref, b = 1.0, c = selfref;
vec2 texcoord1, texcoord2;
vec3 position;
Expand Down Expand Up @@ -47,7 +49,7 @@
});

test('scope references', () => {
const ast = c.parseSrc(

Check failure on line 52 in src/parser/scope.test.ts

View workflow job for this annotation

GitHub Actions / Run tests (18.x)

Property 'parseSrc' does not exist on type 'Promise<{ parse: Parse; parser: typeof import("/home/runner/work/glsl-parser/glsl-parser/src/parser/parser", { with: { "resolution-mode": "import" } }); parseSrc: ParseSrc; debugSrc: (src: string) => void; debugStatement: (stmt: AstNode) => void; expectParsedStatement: (src: string, options?: {}) => void; parseState...'.
`
float selfref, b = 1.0, c = selfref;
mat2x2 myMat = mat2( vec2( 1.0, 0.0 ), vec2( 0.0, 1.0 ) );
Expand Down Expand Up @@ -124,7 +126,7 @@
});

test('scope binding declarations', () => {
const ast = c.parseSrc(

Check failure on line 129 in src/parser/scope.test.ts

View workflow job for this annotation

GitHub Actions / Run tests (18.x)

Property 'parseSrc' does not exist on type 'Promise<{ parse: Parse; parser: typeof import("/home/runner/work/glsl-parser/glsl-parser/src/parser/parser", { with: { "resolution-mode": "import" } }); parseSrc: ParseSrc; debugSrc: (src: string) => void; debugStatement: (stmt: AstNode) => void; expectParsedStatement: (src: string, options?: {}) => void; parseState...'.
`
float selfref, b = 1.0, c = selfref;
void main() {
Expand All @@ -144,7 +146,7 @@
});

test('struct constructor identified in scope', () => {
const ast = c.parseSrc(`

Check failure on line 149 in src/parser/scope.test.ts

View workflow job for this annotation

GitHub Actions / Run tests (18.x)

Property 'parseSrc' does not exist on type 'Promise<{ parse: Parse; parser: typeof import("/home/runner/work/glsl-parser/glsl-parser/src/parser/parser", { with: { "resolution-mode": "import" } }); parseSrc: ParseSrc; debugSrc: (src: string) => void; debugStatement: (stmt: AstNode) => void; expectParsedStatement: (src: string, options?: {}) => void; parseState...'.
struct light {
float intensity;
vec3 position;
Expand All @@ -155,7 +157,7 @@
});

test('function overloaded scope', () => {
const ast = c.parseSrc(`

Check failure on line 160 in src/parser/scope.test.ts

View workflow job for this annotation

GitHub Actions / Run tests (18.x)

Property 'parseSrc' does not exist on type 'Promise<{ parse: Parse; parser: typeof import("/home/runner/work/glsl-parser/glsl-parser/src/parser/parser", { with: { "resolution-mode": "import" } }); parseSrc: ParseSrc; debugSrc: (src: string) => void; debugStatement: (stmt: AstNode) => void; expectParsedStatement: (src: string, options?: {}) => void; parseState...'.
vec4 overloaded(vec4 x) {
return x;
}
Expand All @@ -167,7 +169,7 @@

test('overriding glsl builtin function', () => {
// "noise" is a built-in GLSL function that should be identified and renamed
const ast = c.parseSrc(`

Check failure on line 172 in src/parser/scope.test.ts

View workflow job for this annotation

GitHub Actions / Run tests (18.x)

Property 'parseSrc' does not exist on type 'Promise<{ parse: Parse; parser: typeof import("/home/runner/work/glsl-parser/glsl-parser/src/parser/parser", { with: { "resolution-mode": "import" } }); parseSrc: ParseSrc; debugSrc: (src: string) => void; debugStatement: (stmt: AstNode) => void; expectParsedStatement: (src: string, options?: {}) => void; parseState...'.
float noise() {}
float fn() {
vec2 uv;
Expand All @@ -190,7 +192,7 @@
});

test('rename bindings and functions', () => {
const ast = c.parseSrc(

Check failure on line 195 in src/parser/scope.test.ts

View workflow job for this annotation

GitHub Actions / Run tests (18.x)

Property 'parseSrc' does not exist on type 'Promise<{ parse: Parse; parser: typeof import("/home/runner/work/glsl-parser/glsl-parser/src/parser/parser", { with: { "resolution-mode": "import" } }); parseSrc: ParseSrc; debugSrc: (src: string) => void; debugStatement: (stmt: AstNode) => void; expectParsedStatement: (src: string, options?: {}) => void; parseState...'.
`
float selfref, b = 1.0, c = selfref;
mat2x2 myMat = mat2( vec2( 1.0, 0.0 ), vec2( 0.0, 1.0 ) );
Expand Down Expand Up @@ -290,7 +292,7 @@
});

test('detecting struct scope and usage', () => {
const ast = c.parseSrc(`

Check failure on line 295 in src/parser/scope.test.ts

View workflow job for this annotation

GitHub Actions / Run tests (18.x)

Property 'parseSrc' does not exist on type 'Promise<{ parse: Parse; parser: typeof import("/home/runner/work/glsl-parser/glsl-parser/src/parser/parser", { with: { "resolution-mode": "import" } }); parseSrc: ParseSrc; debugSrc: (src: string) => void; debugStatement: (stmt: AstNode) => void; expectParsedStatement: (src: string, options?: {}) => void; parseState...'.
struct StructName {
vec3 color;
};
Expand Down Expand Up @@ -362,7 +364,7 @@
});

test('fn args shadowing global scope identified as separate bindings', () => {
const ast = c.parseSrc(`

Check failure on line 367 in src/parser/scope.test.ts

View workflow job for this annotation

GitHub Actions / Run tests (18.x)

Property 'parseSrc' does not exist on type 'Promise<{ parse: Parse; parser: typeof import("/home/runner/work/glsl-parser/glsl-parser/src/parser/parser", { with: { "resolution-mode": "import" } }); parseSrc: ParseSrc; debugSrc: (src: string) => void; debugStatement: (stmt: AstNode) => void; expectParsedStatement: (src: string, options?: {}) => void; parseState...'.
attribute vec3 position;
vec3 func(vec3 position) {
return position;
Expand All @@ -379,7 +381,7 @@
});

test('I do not yet know what to do with layout()', () => {
const ast = c.parseSrc(`

Check failure on line 384 in src/parser/scope.test.ts

View workflow job for this annotation

GitHub Actions / Run tests (18.x)

Property 'parseSrc' does not exist on type 'Promise<{ parse: Parse; parser: typeof import("/home/runner/work/glsl-parser/glsl-parser/src/parser/parser", { with: { "resolution-mode": "import" } }); parseSrc: ParseSrc; debugSrc: (src: string) => void; debugStatement: (stmt: AstNode) => void; expectParsedStatement: (src: string, options?: {}) => void; parseState...'.
layout(std140,column_major) uniform;
float a;
uniform Material
Expand Down
12 changes: 6 additions & 6 deletions src/parser/test-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { expect, vi } from 'vitest';
import { execSync } from 'child_process';
import { GrammarError } from 'peggy';
import util from 'util';
import generate from './generator.js';
import { AstNode, FunctionNode, Program } from '../ast/index.js';
import { Parse, ParserOptions } from './parser.js';
import { FunctionScopeIndex, Scope, ScopeIndex } from './scope.js';

export const inspect = (arg: any) =>
console.log(util.inspect(arg, false, null, true));

export const nextWarn = () => {
console.warn = jest.fn();
console.warn = vi.fn();
let i = 0;
// @ts-ignore
const mock = console.warn.mock;
Expand All @@ -22,11 +22,11 @@ type Context = {
parseSrc: ParseSrc;
};

export const buildParser = () => {
export const buildParser = async () => {
execSync(
'npx peggy --cache --format es -o src/parser/parser.js src/parser/glsl-grammar.pegjs'
);
const parser = require('./parser');
const parser = await import('./parser.js');
const parse = parser.parse as Parse;
const ps = parseSrc(parse);
const ctx: Context = {
Expand All @@ -45,11 +45,11 @@ export const buildParser = () => {
};
};

export const buildPreprocessorParser = () => {
export const buildPreprocessorParser = async () => {
execSync(
'npx peggy --cache --format es -o src/preprocessor/preprocessor-parser.js src/preprocessor/preprocessor-grammar.pegjs'
);
const { parse, parser } = require('../preprocessor');
const { parse, parser } = await import('../preprocessor/index.js');
return {
parse,
parser,
Expand Down
3 changes: 2 additions & 1 deletion src/preprocessor/generator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { makeGenerator, NodeGenerators } from '../ast/index.js';
import { makeGenerator } from '../ast/index.js';
import { PreprocessorProgram } from './preprocessor.js';
import { PreprocessorAstNode } from './preprocessor-node.js';

Expand Down Expand Up @@ -91,6 +91,7 @@ const generators: NodePreprocessorGenerators = {
generate(node.rp) +
generate(node.body) +
generate(node.wsEnd),
replacement_list: (node) => generate(node.list) + generate(node.wsEnd),

conditional: (node) =>
generate(node.wsStart) +
Expand Down
24 changes: 23 additions & 1 deletion src/preprocessor/preprocessor-grammar.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,28 @@ text_or_control_lines =
return node('segment', { blocks });
}

punctuator = wsStart:_? punctuator:$(
"[" / "]" / "(" / ")" / "{" / "}" / "." / "->"
"++" / "--" / "&" / "*" / "+" / "-" / "~" / "!"
"/" / "%" / "<<" / ">>" / "<" / ">" / "<=" / "==" / "!=" / "^" / "|" / "&&" / "||"
"?" / ":" / ";" / "..."
"=" / "*=" / "/=" / "%=" / "+=" / "-=" / "<<=" / ">>=" / "&=" / "^=" / "|="
"," / "###"
"<:" / ":>" / "%<" / "%>" / "%:" / "%:%:"
) _:_? {
return node('literal', { literal: punctuator, wsEnd: _ });
}

replacement_list "replacement list"
= list:(
IDENTIFIER
/ integer_constant
/ punctuator
/ token_string
)+ {
return node('replacement_list', { list: list });
}

// Any preprocessor or directive line
control_line "control line"
= conditional
Expand All @@ -146,7 +168,7 @@ control_line "control line"
body:token_string? {
return node('define_arguments', { define, identifier, lp, args: args || [], rp, body } )
}
/ define:DEFINE identifier:IDENTIFIER body:token_string? {
/ define:DEFINE identifier:IDENTIFIER body:replacement_list? {
return node('define', { define, identifier, body } )
}
/ line:LINE value:$digit+ {
Expand Down
7 changes: 7 additions & 0 deletions src/preprocessor/preprocessor-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export interface PreprocessorDefineNode extends IPreprocessorNode {
body: string;
}

export interface PreprocessorReplacementListNode extends IPreprocessorNode {
type: 'replacement_list';
list: PreprocessorAstNode[];
wsEnd: string;
}

export interface PreprocessorElseNode extends IPreprocessorNode {
type: 'else';
token: string;
Expand Down Expand Up @@ -172,6 +178,7 @@ export type PreprocessorAstNode =
| PreprocessorConditionalNode
| PreprocessorDefineArgumentsNode
| PreprocessorDefineNode
| PreprocessorReplacementListNode
| PreprocessorElseNode
| PreprocessorElseIfNode
| PreprocessorErrorNode
Expand Down
91 changes: 77 additions & 14 deletions src/preprocessor/preprocessor.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { expect, test, beforeAll } from 'vitest';

import util from 'util';
import {
preprocessComments,
Expand All @@ -10,10 +12,10 @@ import { GlslSyntaxError } from '../error.js';

import { buildPreprocessorParser } from '../parser/test-helpers.js';

let c!: ReturnType<typeof buildPreprocessorParser>;
let c!: Awaited<ReturnType<typeof buildPreprocessorParser>>;
let parse: (src: string, options?: PreprocessorOptions) => PreprocessorProgram;
beforeAll(() => {
c = buildPreprocessorParser();
beforeAll(async () => {
c = await buildPreprocessorParser();
parse = c.parse;
});

Expand All @@ -40,6 +42,36 @@ const expectParsedProgram = (
// test('pre test file', () => {
// expectParsedProgram(fileContents('./preprocess-test-grammar.glsl'));
// });
test('evaluates binary expression with simple macro', () => {
const program = `
#define A 1
#if A + 1 == 2
true
#endif
`;

const ast = parse(program);
preprocessAst(ast);
expect(generate(ast)).toBe(`
true
`);
});

test('evaluates binary expression with nested macro', () => {
const program = `
#define A 1
#define B A + 1
#if B == 2
true
#endif
`;

const ast = parse(program);
preprocessAst(ast);
expect(generate(ast)).toBe(`
true
`);
});

test('#preprocessComments', () => {
// Should strip comments and replace single-line comments with a single space
Expand Down Expand Up @@ -130,6 +162,37 @@ true
`);
});

test('evaluates binary expression with simple macro', () => {
const program = `
#define A 1
#if A + 1 == 2
true
#endif
`;

const ast = parse(program);
preprocessAst(ast);
expect(generate(ast)).toBe(`
true
`);
});

test('evaluates binary expression with nested macro', () => {
const program = `
#define A 1
#define B A + 1
#if B == 2
true
#endif
`;

const ast = parse(program);
preprocessAst(ast);
expect(generate(ast)).toBe(`
true
`);
});

test('define inside if/else is properly expanded when the if branch is chosen', () => {
const program = `
#define MACRO
Expand Down Expand Up @@ -489,17 +552,17 @@ bar()`);
// for now. The current preprocessor results in bar(bar(1.0)). To achieve the
// correct result here I likely need to redo the expansion system to use "blue
// painting" https://en.wikipedia.org/wiki/Painted_blue
xtest('nested function macros that reference each other', () => {
const program = `
#define foo(x) bar(x)
#define bar(x) foo(x)
bar(foo(1.0))`;

const ast = parse(program);
preprocessAst(ast);
expect(generate(ast)).toBe(`
bar(foo(1.0))`);
});
// xtest('nested function macros that reference each other', () => {
// const program = `
// #define foo(x) bar(x)
// #define bar(x) foo(x)
// bar(foo(1.0))`;

// const ast = parse(program);
// preprocessAst(ast);
// expect(generate(ast)).toBe(`
// bar(foo(1.0))`);
// });

test('multi-pass cross-referencing object macros', () => {
const program = `
Expand Down
5 changes: 3 additions & 2 deletions src/preprocessor/preprocessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
PreprocessorLiteralNode,
PreprocessorSegmentNode,
} from './preprocessor-node.js';
import generate from './generator.js';

export type PreprocessorProgram = {
type: string;
Expand Down Expand Up @@ -581,7 +582,7 @@ const preprocessAst = (
args,
} = path.node;

macros[identifier] = { args, body };
macros[identifier] = { args, body: generate(body) };
!preserveNode(path) && path.remove();
},
},
Expand All @@ -593,7 +594,7 @@ const preprocessAst = (
body,
} = path.node;

macros[identifier] = { body };
macros[identifier] = { body: generate(body) };
!preserveNode(path) && path.remove();
},
},
Expand Down
9 changes: 9 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from 'vite';

export default defineConfig({
server: {
watch: {
ignored: ['**/*.js'],
},
},
});