Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
207896e
Add a stub of implementation
aleksanderkatan Apr 22, 2026
26a066a
Add a baseline statement and expression methods
aleksanderkatan Apr 22, 2026
3d48eb4
Add tests
aleksanderkatan Apr 22, 2026
292db54
Implement remaining nodes
aleksanderkatan Apr 22, 2026
eb61683
Better string handling
aleksanderkatan Apr 22, 2026
3a30b58
sanity tests
aleksanderkatan Apr 22, 2026
84fe830
More wrapIfComplex
aleksanderkatan Apr 23, 2026
edbc49f
Handle numeric literal member access
aleksanderkatan Apr 23, 2026
d453c8c
More sanity tests
aleksanderkatan Apr 23, 2026
6b5270b
TS test
aleksanderkatan Apr 23, 2026
5360076
Forbid "!="
aleksanderkatan Apr 23, 2026
a3b916e
Use `stringifyExpression` in some wgslGenerator errors
aleksanderkatan Apr 23, 2026
6024c19
Add a test for parameter assignment
aleksanderkatan Apr 23, 2026
f5c04d6
Update more errors
aleksanderkatan Apr 24, 2026
0f69e19
Update member access error
aleksanderkatan Apr 24, 2026
2510f25
Merge remote-tracking branch 'origin/main' into feat/better-resolutio…
aleksanderkatan Apr 24, 2026
bef7d6f
Update index access error
aleksanderkatan Apr 24, 2026
5d1563c
Rename files
aleksanderkatan Apr 24, 2026
d8b7811
Fix d.ref return
aleksanderkatan Apr 24, 2026
6d4359f
nits
aleksanderkatan Apr 24, 2026
0488701
Update isExpression
aleksanderkatan Apr 24, 2026
da67b28
Use stringifyNode instead of two separate functions
aleksanderkatan Apr 24, 2026
e04b702
Update tests, make indent mandatory
aleksanderkatan Apr 24, 2026
c80dad5
Review fixes
aleksanderkatan Apr 24, 2026
a744562
Merge branch 'main' into feat/better-resolution-error-hints
aleksanderkatan Apr 28, 2026
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
213 changes: 213 additions & 0 deletions packages/typegpu/src/shared/tseynit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import * as tinyest from 'tinyest';

const { NodeTypeCatalog: NODE } = tinyest;

export function stringifyNode(node: tinyest.AnyNode): string {
if (isExpression(node)) {
return stringifyExpression(node, '');
}
return stringifyStatement(node, '');
}

function stringifyStatement(node: tinyest.Statement, ident: string): string {
if (isExpression(node)) {
return `${ident}${stringifyExpression(node, ident)};`;
}

if (node[0] === NODE.block) {
const statements = node[1].map((n) => stringifyStatement(n, ident + ' '));
return `{\n${statements.join('\n')}\n${ident}}`;
}

if (node[0] === NODE.return) {
const expr = node[1] === undefined ? '' : ` ${stringifyExpression(node[1], '')}`;
return `${ident}return${expr};`;
}

if (node[0] === NODE.if) {
const cond = stringifyExpression(node[1], ident);
const then = stringifyStatement(node[2], ident);
const base = `${ident}if (${cond}) ${then}`;
if (node[3] !== undefined) {
return `${base} else ${stringifyStatement(node[3], ident)}`;
}
return base;
}

if (node[0] === NODE.let) {
if (node[2] !== undefined) {
return `${ident}let ${node[1]} = ${stringifyExpression(node[2], ident)};`;
}
return `${ident}let ${node[1]};`;
}

if (node[0] === NODE.const) {
if (node[2] !== undefined) {
return `${ident}const ${node[1]} = ${stringifyExpression(node[2], ident)};`;
}
return `${ident}const ${node[1]};`;
}

if (node[0] === NODE.for) {
const init = node[1] ? stringifyStatement(node[1], '') : ';';
const cond = node[2] ? stringifyExpression(node[2], ident) : '';
const update = node[3] ? stringifyStatement(node[3], '') : '';
const body = stringifyStatement(node[4], ident);
return `${ident}for (${init} ${cond}; ${update.slice(0, -1) /* trim the ';' */}) ${body}`;
}

if (node[0] === NODE.while) {
const cond = stringifyExpression(node[1], ident);
const body = stringifyStatement(node[2], ident);
return `${ident}while (${cond}) ${body}`;
}

if (node[0] === NODE.continue) {
return `${ident}continue;`;
}

if (node[0] === NODE.break) {
return `${ident}break;`;
}

if (node[0] === NODE.forOf) {
const leftKind = node[1][0] === NODE.const ? 'const' : 'let';
const leftName = node[1][1];
const right = stringifyExpression(node[2], ident);
const body = stringifyStatement(node[3], ident);
return `${ident}for (${leftKind} ${leftName} of ${right}) ${body}`;
}

assertExhaustive(node);
}

function stringifyExpression(node: tinyest.Expression, ident: string): string {
if (typeof node === 'string') {
return node;
}

if (typeof node === 'boolean') {
return `${node}`;
}

if (node[0] === NODE.numericLiteral) {
return node[1];
}

if (node[0] === NODE.stringLiteral) {
return JSON.stringify(node[1]);
}

if (node[0] === NODE.arrayExpr) {
const elements = node[1].map((n) => stringifyExpression(n, ident));
return `[${elements.join(', ')}]`;
}

if (node[0] === NODE.binaryExpr) {
return `${wrapIfComplex(node[1], ident)} ${node[2]} ${wrapIfComplex(node[3], ident)}`;
}

if (node[0] === NODE.assignmentExpr) {
return `${stringifyExpression(node[1], ident)} ${node[2]} ${stringifyExpression(node[3], ident)}`;
}

if (node[0] === NODE.logicalExpr) {
return `${wrapIfComplex(node[1], ident)} ${node[2]} ${wrapIfComplex(node[3], ident)}`;
}

if (node[0] === NODE.unaryExpr) {
// Unary word operators like void, instanceof and delete require a space
const sep = node[1].length > 1 ? ' ' : '';
return `${node[1]}${sep}${wrapIfComplex(node[2], ident)}`;
}

if (node[0] === NODE.call) {
const callee = wrapIfComplex(node[1], ident);
const args = node[2].map((n) => stringifyExpression(n, ident)).join(', ');
return `${callee}(${args})`;
}

if (node[0] === NODE.memberAccess) {
if (Array.isArray(node[1]) && node[1][0] === NODE.numericLiteral) {
return `(${stringifyExpression(node[1], ident)}).${node[2]}`;
}
return `${wrapIfComplex(node[1], ident)}.${node[2]}`;
}

if (node[0] === NODE.indexAccess) {
return `${wrapIfComplex(node[1], ident)}[${stringifyExpression(node[2], ident)}]`;
}

if (node[0] === NODE.preUpdate) {
return `${node[1]}${wrapIfComplex(node[2], ident)}`;
}

if (node[0] === NODE.postUpdate) {
return `${wrapIfComplex(node[2], ident)}${node[1]}`;
}

if (node[0] === NODE.objectExpr) {
const entries = Object.entries(node[1]).map(
([key, val]) => `${key}: ${stringifyExpression(val, ident)}`,
);
return `{ ${entries.join(', ')} }`;
}

if (node[0] === NODE.conditionalExpr) {
return `${wrapIfComplex(node[1], ident)} ? ${wrapIfComplex(node[2], ident)} : ${wrapIfComplex(node[3], ident)}`;
}

assertExhaustive(node);
}

function assertExhaustive(value: never): never {
throw new Error(`'${JSON.stringify(value)}' was not handled by the stringify function.`);
}

function isExpression(node: tinyest.AnyNode): node is tinyest.Expression {
if (
typeof node === 'string' ||
typeof node === 'boolean' ||
node[0] === NODE.numericLiteral ||
node[0] === NODE.stringLiteral ||
node[0] === NODE.arrayExpr ||
node[0] === NODE.binaryExpr ||
node[0] === NODE.assignmentExpr ||
node[0] === NODE.logicalExpr ||
node[0] === NODE.unaryExpr ||
node[0] === NODE.call ||
node[0] === NODE.memberAccess ||
node[0] === NODE.indexAccess ||
node[0] === NODE.preUpdate ||
node[0] === NODE.postUpdate ||
node[0] === NODE.objectExpr ||
node[0] === NODE.conditionalExpr
) {
node satisfies tinyest.Expression;
return true;
}
node satisfies Exclude<tinyest.AnyNode, tinyest.Expression>;
return false;
}

const SIMPLE_NODES: number[] = [
NODE.memberAccess, // highest precedence
NODE.indexAccess, // highest precedence
NODE.call, // highest precedence
NODE.arrayExpr, // [] make things not ambiguous
NODE.stringLiteral,
NODE.numericLiteral,
];
/**
* Stringifies expression, and wraps it in parentheses if they cannot be trivially omitted
*/
function wrapIfComplex(node: tinyest.Expression, ident: string): string {
const s = stringifyExpression(node, ident);
if (typeof node === 'string' || typeof node === 'boolean') {
return s;
}
if (SIMPLE_NODES.includes(node[0])) {
return s;
}
return `(${s})`;
}
Loading
Loading