Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pp.join([...]), which joins an array of strings, preserving vertical alignment #360

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
25 changes: 23 additions & 2 deletions src/common/framework/preprocessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ abstract class Directive {
protected checkDepth(stack: StateStack): void {
assert(
stack.length === this.depth,
`Number of "$"s must match nesting depth, currently ${stack.length} (e.g. $if $$if $$endif $endif)`
`Number of "_"s must match nesting depth, currently ${stack.length} (e.g. _if __if __endif _endif)`
);
}

Expand Down Expand Up @@ -87,6 +87,18 @@ class EndIf extends Directive {
}
}

class Join {
private lines: string[];

constructor(lines: string[]) {
this.lines = lines;
}

join(indentation: number) {
return this.lines.join('\n' + ' '.repeat(indentation));
}
}

/**
* A simple template-based, non-line-based preprocessor implementing if/elif/else/endif.
*
Expand All @@ -104,10 +116,13 @@ class EndIf extends Directive {
*
* @param strings - The array of constant string chunks of the template string.
* @param ...values - The array of interpolated ${} values within the template string.
* If a value is a Directive, it affects the preprocessor state and injects no string.
* If a value is a string[], the array is joined together with newlines, preserving vertical
* alignment by indenting with spaces.
*/
export function pp(
strings: TemplateStringsArray,
...values: ReadonlyArray<Directive | string | number>
...values: ReadonlyArray<Directive | Join | string | number>
): string {
let result = '';
const stateStack: StateStack = [{ allowsFollowingElse: false, state: State.Passing }];
Expand All @@ -121,6 +136,11 @@ export function pp(
const value = values[i];
if (value instanceof Directive) {
value.applyTo(stateStack);
} else if (value instanceof Join) {
const indexOfLastNewline = result.lastIndexOf('\n');
// Vertically align by indenting with spaces. Works even if indexOfLastNewline === -1.
const indentation = result.length - indexOfLastNewline - 1;
result += value.join(indentation);
} else {
if (passing) {
result += value;
Expand All @@ -132,6 +152,7 @@ export function pp(

return result;
}
pp.join = (args: string[]) => new Join(args);
pp._if = (predicate: boolean) => new If(1, predicate);
pp._elif = (predicate: boolean) => new ElseIf(1, predicate);
pp._else = new Else(1);
Expand Down
29 changes: 29 additions & 0 deletions src/unittests/preprocessor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,35 @@ b`;
t.test(act, exp);
});

g.test('join,0').fn(t => {
const act = pp`a ${pp.join([])} b`;
const exp = 'a b';
t.test(act, exp);
});

g.test('join,1').fn(t => {
const act = pp`a ${pp.join(['3'])} b`;
const exp = 'a 3 b';
t.test(act, exp);
});

g.test('join,2').fn(t => {
const act = pp`a ${pp.join(['3', '4'])} b`;
const exp = `\
a 3
4 b`;
t.test(act, exp);
});

g.test('join,22').fn(t => {
const act = pp`a ${pp.join(['33333', '4'])} b ${pp.join(['5', '66'])} d`;
const exp = `\
a 33333
4 b 5
66 d`;
t.test(act, exp);
});

g.test('if,true').fn(t => {
const act = pp`
a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Test Coverage:
`;

import { pbool, poptions, params } from '../../../../common/framework/params_builder.js';
import { pp } from '../../../../common/framework/preprocessor.js';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { assert } from '../../../../common/framework/util/util.js';
import {
Expand Down Expand Up @@ -925,9 +926,9 @@ g.test('unused_bindings_in_pipeline')
entry_point vertex = main;
`;
// TODO: revisit the shader code once 'image' can be supported in wgsl.
const wgslFragment = `
${useBindGroup0 ? '[[set 0, binding 0]] var<image> image0;' : ''}
${useBindGroup1 ? '[[set 1, binding 0]] var<image> image1;' : ''}
const wgslFragment = pp`
${pp._if(useBindGroup0)}[[set 0, binding 0]] var<image> image0;${pp._endif}
${pp._if(useBindGroup1)}[[set 1, binding 0]] var<image> image1;${pp._endif}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not better.

fn main() -> void {
return;
}
Expand All @@ -937,8 +938,8 @@ g.test('unused_bindings_in_pipeline')

// TODO: revisit the shader code once 'image' can be supported in wgsl.
const wgslCompute = `
${useBindGroup0 ? '[[set 0, binding 0]] var<image> image0;' : ''}
${useBindGroup1 ? '[[set 1, binding 0]] var<image> image1;' : ''}
${pp._if(useBindGroup0)}[[set 0, binding 0]] var<image> image0;${pp._endif}
${pp._if(useBindGroup1)}[[set 1, binding 0]] var<image> image1;${pp._endif}
fn main() -> void {
return;
}
Expand Down
9 changes: 4 additions & 5 deletions src/webgpu/util/texture/texelData.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const description = 'Test helpers for texel data produce the expected data in the shader';

import { params, poptions } from '../../../common/framework/params_builder.js';
import { pp } from '../../../common/framework/preprocessor.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { unreachable, assert } from '../../../common/framework/util/util.js';
import {
Expand Down Expand Up @@ -68,20 +69,18 @@ function doTest(
unreachable();
}

const shader = `
const shader = pp`
[[set(0), binding(0)]] var<uniform_constant> tex : texture_2d<${shaderType}>;

[[block]] struct Output {
${rep.componentOrder
.map((C, i) => `[[offset(${i * 4})]] result${C} : ${shaderType};`)
.join('\n')}
${pp.join(rep.componentOrder.map((C, i) => `[[offset(${i * 4})]] result${C} : ${shaderType};`))}
};
[[set(0), binding(1)]] var<storage_buffer> output : Output;

[[stage(compute)]]
fn main() -> void {
var texel : vec4<${shaderType}> = textureLoad(tex, vec2<i32>(0, 0), 0);
${rep.componentOrder.map(C => `output.result${C} = texel.${C.toLowerCase()};`).join('\n')}
${pp.join(rep.componentOrder.map(C => `output.result${C} = texel.${C.toLowerCase()};`))}
return;
}`;

Expand Down