@@ -41,6 +41,7 @@ import type { ShaderGenerator } from './shaderGenerator.ts';
4141import { createPtrFromOrigin , implicitFrom , ptrFn } from '../data/ptr.ts' ;
4242import { RefOperator } from '../data/ref.ts' ;
4343import { constant } from '../core/constant/tgpuConstant.ts' ;
44+ import { arrayLength } from '../std/array.ts' ;
4445import { AutoStruct } from '../data/autoStruct.ts' ;
4546import { mathToStd } from './math.ts' ;
4647
@@ -1137,6 +1138,131 @@ ${this.ctx.pre}else ${alternate}`;
11371138 return `${ this . ctx . pre } while (${ conditionStr } ) ${ bodyStr } ` ;
11381139 }
11391140
1141+ if ( statement [ 0 ] === NODE . forOf ) {
1142+ const [ _ , loopVar , iterable , body ] = statement ;
1143+ const iterableSnippet = this . expression ( iterable ) ;
1144+
1145+ if ( isEphemeralSnippet ( iterableSnippet ) ) {
1146+ throw new Error (
1147+ '`for ... of ...` loops only support iterables stored in variables' ,
1148+ ) ;
1149+ }
1150+
1151+ // Our index name will be some element from infinite sequence (i, ii, iii, ...).
1152+ // If user defines `i` and `ii` before `for ... of ...` loop, then our index name will be `iii`.
1153+ // If user defines `i` inside `for ... of ...` then it will be scoped to a new block,
1154+ // so we can safely use `i`.
1155+ let index = 'i' ; // it will be valid name, no need to call this.ctx.makeNameValid
1156+ while ( this . ctx . getById ( index ) !== null ) {
1157+ index += 'i' ;
1158+ }
1159+
1160+ const elementSnippet = accessIndex (
1161+ iterableSnippet ,
1162+ snip ( index , u32 , 'runtime' ) ,
1163+ ) ;
1164+ if ( ! elementSnippet ) {
1165+ throw new WgslTypeError (
1166+ '`for ... of ...` loops only support array or vector iterables' ,
1167+ ) ;
1168+ }
1169+
1170+ const iterableDataType = iterableSnippet . dataType ;
1171+ let elementCountSnippet : Snippet ;
1172+ let elementType = elementSnippet . dataType ;
1173+
1174+ if ( elementType === UnknownData ) {
1175+ throw new WgslTypeError (
1176+ stitch `The elements in iterable ${ iterableSnippet } are of unknown type` ,
1177+ ) ;
1178+ }
1179+
1180+ if ( wgsl . isWgslArray ( iterableDataType ) ) {
1181+ elementCountSnippet = iterableDataType . elementCount > 0
1182+ ? snip (
1183+ `${ iterableDataType . elementCount } ` ,
1184+ u32 ,
1185+ 'constant' ,
1186+ )
1187+ : arrayLength [ $gpuCallable ] . call ( this . ctx , [ iterableSnippet ] ) ;
1188+ } else if ( wgsl . isVec ( iterableDataType ) ) {
1189+ elementCountSnippet = snip (
1190+ `${ Number ( iterableDataType . type . match ( / \d / ) ) } ` ,
1191+ u32 ,
1192+ 'constant' ,
1193+ ) ;
1194+ } else {
1195+ throw new WgslTypeError (
1196+ '`for ... of ...` loops only support array or vector iterables' ,
1197+ ) ;
1198+ }
1199+
1200+ if ( loopVar [ 0 ] !== NODE . const ) {
1201+ throw new WgslTypeError (
1202+ 'Only `for (const ... of ... )` loops are supported' ,
1203+ ) ;
1204+ }
1205+
1206+ // If it's ephemeral, it's a value that cannot change. If it's a reference, we take
1207+ // an implicit pointer to it
1208+ let loopVarKind = 'let' ;
1209+ const loopVarName = this . ctx . makeNameValid ( loopVar [ 1 ] ) ;
1210+
1211+ if ( ! isEphemeralSnippet ( elementSnippet ) ) {
1212+ if ( elementSnippet . origin === 'constant-tgpu-const-ref' ) {
1213+ loopVarKind = 'const' ;
1214+ } else if ( elementSnippet . origin === 'runtime-tgpu-const-ref' ) {
1215+ loopVarKind = 'let' ;
1216+ } else {
1217+ loopVarKind = 'let' ;
1218+ if ( ! wgsl . isPtr ( elementType ) ) {
1219+ const ptrType = createPtrFromOrigin (
1220+ elementSnippet . origin ,
1221+ concretize ( elementType as wgsl . AnyWgslData ) as wgsl . StorableData ,
1222+ ) ;
1223+ invariant (
1224+ ptrType !== undefined ,
1225+ `Creating pointer type from origin ${ elementSnippet . origin } ` ,
1226+ ) ;
1227+ elementType = ptrType ;
1228+ }
1229+
1230+ elementType = implicitFrom ( elementType as wgsl . Ptr ) ;
1231+ }
1232+ }
1233+
1234+ const loopVarSnippet = snip (
1235+ loopVarName ,
1236+ elementType ,
1237+ elementSnippet . origin ,
1238+ ) ;
1239+ this . ctx . defineVariable ( loopVarName , loopVarSnippet ) ;
1240+
1241+ const forStr = stitch `${ this . ctx . pre } for (var ${ index } = 0u; ${ index } < ${
1242+ tryConvertSnippet ( this . ctx , elementCountSnippet , u32 , false )
1243+ } ; ${ index } ++) {`;
1244+
1245+ this . ctx . indent ( ) ;
1246+
1247+ const loopVarDeclStr =
1248+ stitch `${ this . ctx . pre } ${ loopVarKind } ${ loopVarName } = ${
1249+ tryConvertSnippet (
1250+ this . ctx ,
1251+ elementSnippet ,
1252+ elementType ,
1253+ false ,
1254+ )
1255+ } ;`;
1256+
1257+ const bodyStr = `${ this . ctx . pre } ${
1258+ this . block ( blockifySingleStatement ( body ) )
1259+ } `;
1260+
1261+ this . ctx . dedent ( ) ;
1262+
1263+ return stitch `${ forStr } \n${ loopVarDeclStr } \n${ bodyStr } \n${ this . ctx . pre } }` ;
1264+ }
1265+
11401266 if ( statement [ 0 ] === NODE . continue ) {
11411267 return `${ this . ctx . pre } continue;` ;
11421268 }
0 commit comments