summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/common/util/preprocessor.ts
blob: 7dc2822498d843cf39450c9b1b516debb783db11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { assert } from './util.js';

// The state of the preprocessor is a stack of States.
type StateStack = { allowsFollowingElse: boolean; state: State }[];
const enum State {
  Seeking, // Still looking for a passing condition
  Passing, // Currently inside a passing condition (the root is always in this state)
  Skipping, // Have already seen a passing condition; now skipping the rest
}

// The transitions in the state space are the following preprocessor directives:
// - Sibling elif
// - Sibling else
// - Sibling endif
// - Child if
abstract class Directive {
  private readonly depth: number;

  constructor(depth: number) {
    this.depth = depth;
  }

  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)`
    );
  }

  abstract applyTo(stack: StateStack): void;
}

class If extends Directive {
  private readonly predicate: boolean;

  constructor(depth: number, predicate: boolean) {
    super(depth);
    this.predicate = predicate;
  }

  applyTo(stack: StateStack) {
    this.checkDepth(stack);
    const parentState = stack[stack.length - 1].state;
    stack.push({
      allowsFollowingElse: true,
      state:
        parentState !== State.Passing
          ? State.Skipping
          : this.predicate
          ? State.Passing
          : State.Seeking,
    });
  }
}

class ElseIf extends If {
  applyTo(stack: StateStack) {
    assert(stack.length >= 1);
    const { allowsFollowingElse, state: siblingState } = stack.pop()!;
    this.checkDepth(stack);
    assert(allowsFollowingElse, 'pp.elif after pp.else');
    if (siblingState !== State.Seeking) {
      stack.push({ allowsFollowingElse: true, state: State.Skipping });
    } else {
      super.applyTo(stack);
    }
  }
}

class Else extends Directive {
  applyTo(stack: StateStack) {
    assert(stack.length >= 1);
    const { allowsFollowingElse, state: siblingState } = stack.pop()!;
    this.checkDepth(stack);
    assert(allowsFollowingElse, 'pp.else after pp.else');
    stack.push({
      allowsFollowingElse: false,
      state: siblingState === State.Seeking ? State.Passing : State.Skipping,
    });
  }
}

class EndIf extends Directive {
  applyTo(stack: StateStack) {
    stack.pop();
    this.checkDepth(stack);
  }
}

/**
 * A simple template-based, non-line-based preprocessor implementing if/elif/else/endif.
 *
 * @example
 * ```
 *     const shader = pp`
 * ${pp._if(expr)}
 *   const x: ${type} = ${value};
 * ${pp._elif(expr)}
 * ${pp.__if(expr)}
 * ...
 * ${pp.__else}
 * ...
 * ${pp.__endif}
 * ${pp._endif}`;
 * ```
 *
 * @param strings - The array of constant string chunks of the template string.
 * @param ...values - The array of interpolated `${}` values within the template string.
 */
export function pp(
  strings: TemplateStringsArray,
  ...values: ReadonlyArray<Directive | string | number>
): string {
  let result = '';
  const stateStack: StateStack = [{ allowsFollowingElse: false, state: State.Passing }];

  for (let i = 0; i < values.length; ++i) {
    const passing = stateStack[stateStack.length - 1].state === State.Passing;
    if (passing) {
      result += strings[i];
    }

    const value = values[i];
    if (value instanceof Directive) {
      value.applyTo(stateStack);
    } else {
      if (passing) {
        result += value;
      }
    }
  }
  assert(stateStack.length === 1, 'Unterminated preprocessor condition at end of file');
  result += strings[values.length];

  return result;
}
pp._if = (predicate: boolean) => new If(1, predicate);
pp._elif = (predicate: boolean) => new ElseIf(1, predicate);
pp._else = new Else(1);
pp._endif = new EndIf(1);
pp.__if = (predicate: boolean) => new If(2, predicate);
pp.__elif = (predicate: boolean) => new ElseIf(2, predicate);
pp.__else = new Else(2);
pp.__endif = new EndIf(2);
pp.___if = (predicate: boolean) => new If(3, predicate);
pp.___elif = (predicate: boolean) => new ElseIf(3, predicate);
pp.___else = new Else(3);
pp.___endif = new EndIf(3);
// Add more if needed.