MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement explicit resource management support

Add parser/compiler/runtime support for throw expressions, using and await using
declarations, disposal stacks, dispose symbols, and SuppressedError handling.
Wire disposal cleanup through blocks, returns, breaks, continues, and for-of
loops, including async disposal paths and suppressed errors.

Also add DisposableStack and AsyncDisposableStack builtins, AsyncIterator helper
plumbing, and focused tests/spec coverage for resource management, suppression,
throw expressions, and async iterators.

+3039 -177
+36 -36
examples/results.txt
··· 1141 1141 compat-table/es6/typed-arrays.prototype.Symbol.iterator.js: OK 1142 1142 compat-table/es6/typed-arrays.prototype.copyWithin.js: OK 1143 1143 compat-table/es6/typed-arrays.prototype.entries.js: OK 1144 - compat-table/es6/typed-arrays.prototype.every.js: failed 1144 + compat-table/es6/typed-arrays.prototype.every.js: OK 1145 1145 compat-table/es6/typed-arrays.prototype.fill.js: OK 1146 1146 compat-table/es6/typed-arrays.prototype.filter.js: failed 1147 1147 compat-table/es6/typed-arrays.prototype.findIndex.js: failed ··· 1470 1470 compat-table/intl/Object.prototype.toLocaleString.js: OK 1471 1471 compat-table/intl/String.prototype.localeCompare.js: OK 1472 1472 compat-table/next/Array.isTemplateObject.js: TypeError: undefined is not a function 1473 - compat-table/next/AsyncDisposableStack.js: ReferenceError: 'AsyncDisposableStack' is not defined 1474 - compat-table/next/AsyncIterator.extends.js: ReferenceError: 'AsyncIterator' is not defined 1475 - compat-table/next/AsyncIterator.from.async-iterable.js: ReferenceError: 'AsyncIterator' is not defined 1476 - compat-table/next/AsyncIterator.from.iterable.js: ReferenceError: 'AsyncIterator' is not defined 1477 - compat-table/next/AsyncIterator.from.iterator.js: ReferenceError: 'AsyncIterator' is not defined 1478 - compat-table/next/AsyncIterator.instanceof.js: ReferenceError: 'AsyncIterator' is not defined 1479 - compat-table/next/AsyncIterator.prototype.Symbol.toStringTag.js: ReferenceError: 'AsyncIterator' is not defined 1480 - compat-table/next/AsyncIterator.prototype.drop.js: TIMEOUT: >5s 1481 - compat-table/next/AsyncIterator.prototype.every.js: TypeError: undefined is not a function 1482 - compat-table/next/AsyncIterator.prototype.filter.js: TIMEOUT: >5s 1483 - compat-table/next/AsyncIterator.prototype.find.js: TIMEOUT: >5s 1484 - compat-table/next/AsyncIterator.prototype.flatMap.js: TIMEOUT: >5s 1485 - compat-table/next/AsyncIterator.prototype.forEach.js: TIMEOUT: >5s 1486 - compat-table/next/AsyncIterator.prototype.map.js: TIMEOUT: >5s 1487 - compat-table/next/AsyncIterator.prototype.reduce.js: TIMEOUT: >5s 1488 - compat-table/next/AsyncIterator.prototype.some.js: TIMEOUT: >5s 1489 - compat-table/next/AsyncIterator.prototype.take.js: failed 1490 - compat-table/next/AsyncIterator.prototype.toArray.js: TIMEOUT: >5s 1491 - compat-table/next/DisposableStack.js: ReferenceError: 'DisposableStack' is not defined 1473 + compat-table/next/AsyncDisposableStack.js: OK 1474 + compat-table/next/AsyncIterator.extends.js: OK 1475 + compat-table/next/AsyncIterator.from.async-iterable.js: OK 1476 + compat-table/next/AsyncIterator.from.iterable.js: OK 1477 + compat-table/next/AsyncIterator.from.iterator.js: OK 1478 + compat-table/next/AsyncIterator.instanceof.js: OK 1479 + compat-table/next/AsyncIterator.prototype.Symbol.toStringTag.js: OK 1480 + compat-table/next/AsyncIterator.prototype.drop.js: OK 1481 + compat-table/next/AsyncIterator.prototype.every.js: OK 1482 + compat-table/next/AsyncIterator.prototype.filter.js: OK 1483 + compat-table/next/AsyncIterator.prototype.find.js: OK 1484 + compat-table/next/AsyncIterator.prototype.flatMap.js: OK 1485 + compat-table/next/AsyncIterator.prototype.forEach.js: OK 1486 + compat-table/next/AsyncIterator.prototype.map.js: OK 1487 + compat-table/next/AsyncIterator.prototype.reduce.js: OK 1488 + compat-table/next/AsyncIterator.prototype.some.js: OK 1489 + compat-table/next/AsyncIterator.prototype.take.js: OK 1490 + compat-table/next/AsyncIterator.prototype.toArray.js: OK 1491 + compat-table/next/DisposableStack.js: OK 1492 1492 compat-table/next/Map.prototype.upsert.js: OK 1493 1493 compat-table/next/RegExp.1-9.js: OK 1494 1494 compat-table/next/RegExp.lastMatch.js: OK 1495 1495 compat-table/next/ShadowRealm.js: failed 1496 - compat-table/next/SuppressedError.js: ReferenceError: 'SuppressedError' is not defined 1497 - compat-table/next/Symbol.dispose.bound.js: ReferenceError: 'using' is not defined 1498 - compat-table/next/Uint8Array.fromBase64.js: TypeError: undefined is not a function 1499 - compat-table/next/Uint8Array.fromHex.js: TypeError: undefined is not a function 1500 - compat-table/next/Uint8Array.setFromBase64.js: TypeError: undefined is not a function 1501 - compat-table/next/Uint8Array.setFromHex.js: TypeError: undefined is not a function 1502 - compat-table/next/Uint8Array.toBase64.js: TypeError: undefined is not a function 1503 - compat-table/next/Uint8Array.toHex.js: TypeError: undefined is not a function 1496 + compat-table/next/SuppressedError.js: OK 1497 + compat-table/next/Symbol.dispose.bound.js: OK 1498 + compat-table/next/Uint8Array.fromBase64.js: OK 1499 + compat-table/next/Uint8Array.fromHex.js: OK 1500 + compat-table/next/Uint8Array.setFromBase64.js: OK 1501 + compat-table/next/Uint8Array.setFromHex.js: OK 1502 + compat-table/next/Uint8Array.toBase64.js: OK 1503 + compat-table/next/Uint8Array.toHex.js: OK 1504 1504 compat-table/next/WeakMap.prototype.upsert.js: OK 1505 - compat-table/next/await-using.for-of.js: ReferenceError: 'using' is not defined 1506 - compat-table/next/await-using.js: ReferenceError: 'using' is not defined 1505 + compat-table/next/await-using.for-of.js: OK 1506 + compat-table/next/await-using.js: OK 1507 1507 compat-table/next/class-decorators.js: failed 1508 1508 compat-table/next/function.sent.js: failed 1509 - compat-table/next/throw-expr.arrow.js: SyntaxError: Unexpected token 'throw' 1510 - compat-table/next/throw-expr.conditional.js: SyntaxError: Unexpected token 'throw' 1511 - compat-table/next/throw-expr.logical.js: SyntaxError: Unexpected token 'throw' 1512 - compat-table/next/throw-expr.param-init.js: SyntaxError: Unexpected token 'throw' 1513 - compat-table/next/using.for-of.js: ReferenceError: 'using' is not defined 1514 - compat-table/next/using.js: ReferenceError: 'using' is not defined 1509 + compat-table/next/throw-expr.arrow.js: OK 1510 + compat-table/next/throw-expr.conditional.js: OK 1511 + compat-table/next/throw-expr.logical.js: OK 1512 + compat-table/next/throw-expr.param-init.js: OK 1513 + compat-table/next/using.for-of.js: OK 1514 + compat-table/next/using.js: OK
+225
examples/spec/async_iterators.js
··· 1 + import { test, testDeep, summary } from './helpers.js'; 2 + 3 + console.log('Async Iterator Tests\n'); 4 + 5 + test('AsyncIterator global exists', typeof AsyncIterator, 'function'); 6 + test('AsyncIterator prototype async iterator tag', AsyncIterator.prototype[Symbol.toStringTag], 'AsyncIterator'); 7 + 8 + class CustomAsyncIterator extends AsyncIterator { 9 + constructor(values) { 10 + super(); 11 + this.values = values; 12 + this.index = 0; 13 + } 14 + 15 + next() { 16 + if (this.index >= this.values.length) return Promise.resolve({ done: true }); 17 + return Promise.resolve({ done: false, value: this.values[this.index++] }); 18 + } 19 + } 20 + 21 + const custom = new CustomAsyncIterator([4, 5]); 22 + test('class extends AsyncIterator', custom[Symbol.asyncIterator](), custom); 23 + test('class instance instanceof AsyncIterator', custom instanceof AsyncIterator, true); 24 + testDeep('custom async iterator toArray', await custom.toArray(), [4, 5]); 25 + 26 + const asyncSource = AsyncIterator.from( 27 + (async function* () { 28 + yield 1; 29 + yield 2; 30 + yield 3; 31 + })() 32 + ); 33 + test('AsyncIterator.from async iterable instanceof', asyncSource instanceof AsyncIterator, true); 34 + testDeep('AsyncIterator.from async iterable values', await asyncSource.toArray(), [1, 2, 3]); 35 + 36 + testDeep('AsyncIterator.from iterable values', await AsyncIterator.from([1, 2, 3]).toArray(), [1, 2, 3]); 37 + testDeep('AsyncIterator.from iterator values', await AsyncIterator.from([1, 2, 3].values()).toArray(), [1, 2, 3]); 38 + 39 + const promisedStep = await AsyncIterator.from([Promise.resolve(7)]).next(); 40 + test('AsyncIterator.from sync iterable awaits promised value', promisedStep.value, 7); 41 + test('AsyncIterator.from sync iterable promised value done', promisedStep.done, false); 42 + 43 + let rejectedValueCaught; 44 + try { 45 + await AsyncIterator.from([Promise.reject(new Error('sync value rejection'))]).next(); 46 + } catch (e) { 47 + rejectedValueCaught = e; 48 + } 49 + test('AsyncIterator.from sync iterable rejects promised value', rejectedValueCaught.message, 'sync value rejection'); 50 + 51 + const syncReturnSource = { 52 + next() { 53 + return { done: false, value: 0 }; 54 + }, 55 + return() { 56 + return { done: true, value: Promise.resolve(7) }; 57 + }, 58 + [Symbol.iterator]() { 59 + return this; 60 + }, 61 + }; 62 + test('AsyncIterator.from sync return awaits value', (await AsyncIterator.from(syncReturnSource).return()).value, 7); 63 + 64 + const syncThrowSource = { 65 + next() { 66 + return { done: false, value: 0 }; 67 + }, 68 + throw() { 69 + return { done: true, value: Promise.resolve(8) }; 70 + }, 71 + [Symbol.iterator]() { 72 + return this; 73 + }, 74 + }; 75 + test('AsyncIterator.from sync throw awaits value', (await AsyncIterator.from(syncThrowSource).throw(new Error('x'))).value, 8); 76 + 77 + testDeep( 78 + 'async iterator map', 79 + await AsyncIterator.from([1, 2, 3]) 80 + .map(x => x * x) 81 + .toArray(), 82 + [1, 4, 9] 83 + ); 84 + testDeep( 85 + 'async iterator map awaits callback', 86 + await AsyncIterator.from([1, 2]) 87 + .map(async x => x + 1) 88 + .toArray(), 89 + [2, 3] 90 + ); 91 + testDeep( 92 + 'async iterator filter', 93 + await AsyncIterator.from([1, 2, 3, 4]) 94 + .filter(x => x % 2) 95 + .toArray(), 96 + [1, 3] 97 + ); 98 + testDeep( 99 + 'async iterator filter awaits callback', 100 + await AsyncIterator.from([1, 2, 3, 4]) 101 + .filter(async x => x > 2) 102 + .toArray(), 103 + [3, 4] 104 + ); 105 + testDeep('async iterator take', await AsyncIterator.from([1, 2, 3]).take(2).toArray(), [1, 2]); 106 + testDeep('async iterator drop', await AsyncIterator.from([1, 2, 3]).drop(1).toArray(), [2, 3]); 107 + testDeep( 108 + 'async iterator flatMap sync inner', 109 + await AsyncIterator.from([1, 2, 3]) 110 + .flatMap(x => [x, 0]) 111 + .toArray(), 112 + [1, 0, 2, 0, 3, 0] 113 + ); 114 + testDeep( 115 + 'async iterator flatMap awaits callback', 116 + await AsyncIterator.from([1, 2]) 117 + .flatMap(async x => [x, x + 10]) 118 + .toArray(), 119 + [1, 11, 2, 12] 120 + ); 121 + 122 + async function* doubleAsync(x) { 123 + yield x; 124 + yield x * 2; 125 + } 126 + 127 + testDeep('async iterator flatMap async inner', await AsyncIterator.from([2, 3]).flatMap(doubleAsync).toArray(), [2, 4, 3, 6]); 128 + 129 + test('async iterator every true', await AsyncIterator.from([1, 2, 3]).every(x => typeof x === 'number'), true); 130 + test('async iterator every awaits callback', await AsyncIterator.from([1, 2, 3]).every(async x => x < 4), true); 131 + test('async iterator some true', await AsyncIterator.from([1, 2, 3]).some(x => x === 2), true); 132 + test('async iterator some awaits callback', await AsyncIterator.from([1, 2, 3]).some(async x => x === 3), true); 133 + test('async iterator find', await AsyncIterator.from([1, 2, 3]).find(x => x > 1), 2); 134 + test('async iterator find awaits callback', await AsyncIterator.from([1, 2, 3]).find(async x => x > 2), 3); 135 + 136 + function closableAsyncIterator(log) { 137 + return { 138 + index: 0, 139 + next() { 140 + return Promise.resolve({ done: false, value: ++this.index }); 141 + }, 142 + return() { 143 + log.push('closed'); 144 + return Promise.resolve({ done: true }); 145 + }, 146 + [Symbol.asyncIterator]() { 147 + return this; 148 + }, 149 + }; 150 + } 151 + 152 + let everyCloseLog = []; 153 + test('async iterator every closes on false', await AsyncIterator.from(closableAsyncIterator(everyCloseLog)).every(x => x < 1), false); 154 + testDeep('async iterator every close log', everyCloseLog, ['closed']); 155 + 156 + let someCloseLog = []; 157 + test('async iterator some closes on match', await AsyncIterator.from(closableAsyncIterator(someCloseLog)).some(x => x === 1), true); 158 + testDeep('async iterator some close log', someCloseLog, ['closed']); 159 + 160 + let findCloseLog = []; 161 + test('async iterator find closes on match', await AsyncIterator.from(closableAsyncIterator(findCloseLog)).find(x => x === 1), 1); 162 + testDeep('async iterator find close log', findCloseLog, ['closed']); 163 + 164 + let throwCloseLog = []; 165 + let throwCaught = false; 166 + try { 167 + await AsyncIterator.from(closableAsyncIterator(throwCloseLog)).some(() => { 168 + throw new Error('sync callback failure'); 169 + }); 170 + } catch (err) { 171 + throwCaught = err.message === 'sync callback failure'; 172 + } 173 + test('async iterator closes on sync callback throw', throwCaught, true); 174 + testDeep('async iterator sync callback throw close log', throwCloseLog, ['closed']); 175 + 176 + let rejectCloseLog = []; 177 + let rejectCaught = false; 178 + try { 179 + await AsyncIterator.from(closableAsyncIterator(rejectCloseLog)).find(async () => { 180 + throw new Error('async callback failure'); 181 + }); 182 + } catch (err) { 183 + rejectCaught = err.message === 'async callback failure'; 184 + } 185 + test('async iterator closes on async callback rejection', rejectCaught, true); 186 + testDeep('async iterator async callback rejection close log', rejectCloseLog, ['closed']); 187 + 188 + const syncAsyncLike = { 189 + index: 0, 190 + next() { 191 + if (this.index >= 5000) return { done: true }; 192 + return { done: false, value: this.index++ }; 193 + }, 194 + [Symbol.asyncIterator]() { 195 + return this; 196 + }, 197 + }; 198 + test('async iterator handles sync next results without recursive stack growth', (await AsyncIterator.from(syncAsyncLike).toArray()).length, 5000); 199 + 200 + let forEachSum = 0; 201 + await AsyncIterator.from([1, 2, 3]).forEach(x => { 202 + forEachSum += x; 203 + }); 204 + test('async iterator forEach', forEachSum, 6); 205 + 206 + let asyncForEachSum = 0; 207 + await AsyncIterator.from([1, 2, 3]).forEach(async x => { 208 + asyncForEachSum += x; 209 + }); 210 + test('async iterator forEach awaits callback', asyncForEachSum, 6); 211 + 212 + test('async iterator reduce', await AsyncIterator.from([1, 2, 3]).reduce((a, b) => a + b), 6); 213 + test('async iterator reduce awaits callback', await AsyncIterator.from([1, 2, 3]).reduce(async (a, b) => a + b), 6); 214 + testDeep( 215 + 'async generator helpers inherited', 216 + await (async function* () { 217 + yield 2; 218 + yield 4; 219 + })() 220 + .map(x => x + 1) 221 + .toArray(), 222 + [3, 5] 223 + ); 224 + 225 + summary();
+331
examples/spec/explicit_resource_management.js
··· 1 + import { test, testDeep, summary } from './helpers.js'; 2 + 3 + console.log('Explicit Resource Management Tests\n'); 4 + 5 + test('Symbol.dispose exists', typeof Symbol.dispose, 'symbol'); 6 + test('Symbol.asyncDispose exists', typeof Symbol.asyncDispose, 'symbol'); 7 + test('SuppressedError constructor exists', typeof SuppressedError, 'function'); 8 + 9 + const suppressed = new SuppressedError('error', 'suppressed', 'message'); 10 + test('SuppressedError name', suppressed.name, 'SuppressedError'); 11 + test('SuppressedError message', suppressed.message, 'message'); 12 + test('SuppressedError error property', suppressed.error, 'error'); 13 + test('SuppressedError suppressed property', suppressed.suppressed, 'suppressed'); 14 + test('SuppressedError instanceof Error', suppressed instanceof Error, true); 15 + test('SuppressedError callable', SuppressedError('e', 's') instanceof SuppressedError, true); 16 + 17 + let stackLog = []; 18 + const stack = new DisposableStack(); 19 + const used = { 20 + [Symbol.dispose]() { 21 + stackLog.push('use'); 22 + } 23 + }; 24 + test('DisposableStack use returns resource', stack.use(used), used); 25 + test( 26 + 'DisposableStack adopt returns value', 27 + stack.adopt('value', value => stackLog.push('adopt:' + value)), 28 + 'value' 29 + ); 30 + test( 31 + 'DisposableStack defer returns undefined', 32 + stack.defer(() => stackLog.push('defer')), 33 + undefined 34 + ); 35 + test('DisposableStack move returns stack', stack.move() instanceof DisposableStack, true); 36 + 37 + const stack2 = new DisposableStack(); 38 + stack2.use({ 39 + [Symbol.dispose]() { 40 + stackLog.push('second use'); 41 + } 42 + }); 43 + stack2.adopt('second', value => stackLog.push(value)); 44 + stack2.defer(() => stackLog.push('second defer')); 45 + stack2.dispose(); 46 + testDeep('DisposableStack disposes LIFO', stackLog.slice(-3), ['second defer', 'second', 'second use']); 47 + 48 + let suppressedErr1 = new Error('first'); 49 + let suppressedErr2 = new Error('second'); 50 + let suppressedCaught; 51 + try { 52 + const throwingStack = new DisposableStack(); 53 + throwingStack.use({ 54 + [Symbol.dispose]() { 55 + throw suppressedErr2; 56 + } 57 + }); 58 + throwingStack.use({ 59 + [Symbol.dispose]() { 60 + throw suppressedErr1; 61 + } 62 + }); 63 + throwingStack.dispose(); 64 + } catch (e) { 65 + suppressedCaught = e; 66 + } 67 + test('DisposableStack suppression error', suppressedCaught.error, suppressedErr2); 68 + test('DisposableStack suppression suppressed', suppressedCaught.suppressed, suppressedErr1); 69 + 70 + let asyncLog = []; 71 + const asyncStack = new AsyncDisposableStack(); 72 + const asyncResource = { 73 + async [Symbol.asyncDispose]() { 74 + asyncLog.push('use'); 75 + } 76 + }; 77 + test('AsyncDisposableStack use returns resource', asyncStack.use(asyncResource), asyncResource); 78 + test( 79 + 'AsyncDisposableStack adopt returns value', 80 + asyncStack.adopt('value', async value => asyncLog.push('adopt:' + value)), 81 + 'value' 82 + ); 83 + test( 84 + 'AsyncDisposableStack defer returns undefined', 85 + asyncStack.defer(async () => asyncLog.push('defer')), 86 + undefined 87 + ); 88 + await asyncStack.disposeAsync(); 89 + testDeep('AsyncDisposableStack disposes LIFO', asyncLog, ['defer', 'adopt:value', 'use']); 90 + 91 + function usingBlock(resource) { 92 + { 93 + using _ = resource; 94 + } 95 + } 96 + 97 + const usingResource = { 98 + disposed: false, 99 + [Symbol.dispose]() { 100 + this.disposed = true; 101 + } 102 + }; 103 + usingBlock(usingResource); 104 + test('using block disposes', usingResource.disposed, true); 105 + 106 + function usingReturn(resource) { 107 + { 108 + using _ = resource; 109 + return 'done'; 110 + } 111 + } 112 + 113 + const returnResource = { 114 + disposed: false, 115 + [Symbol.dispose]() { 116 + this.disposed = true; 117 + } 118 + }; 119 + test('using return value', usingReturn(returnResource), 'done'); 120 + test('using return disposes', returnResource.disposed, true); 121 + 122 + let throwingReturnCalls = 0; 123 + const throwingReturnError = new Error('dispose return'); 124 + try { 125 + (function () { 126 + { 127 + using _ = { 128 + [Symbol.dispose]() { 129 + throwingReturnCalls++; 130 + throw throwingReturnError; 131 + } 132 + }; 133 + return 'skipped'; 134 + } 135 + })(); 136 + } catch (e) { 137 + test('using return cleanup throws original error', e, throwingReturnError); 138 + } 139 + test('using return cleanup throws once', throwingReturnCalls, 1); 140 + 141 + function usingFunctionScope(resource) { 142 + using _ = resource; 143 + test('using function scope alive before return', resource.disposed, false); 144 + return 'function'; 145 + } 146 + 147 + const functionScopeResource = { 148 + disposed: false, 149 + [Symbol.dispose]() { 150 + this.disposed = true; 151 + } 152 + }; 153 + test('using function scope return value', usingFunctionScope(functionScopeResource), 'function'); 154 + test('using function scope disposes on return', functionScopeResource.disposed, true); 155 + 156 + let breakResource; 157 + while (true) { 158 + { 159 + using _ = (breakResource = { 160 + disposed: false, 161 + [Symbol.dispose]() { 162 + this.disposed = true; 163 + } 164 + }); 165 + break; 166 + } 167 + } 168 + test('using break disposes', breakResource.disposed, true); 169 + 170 + let continueResource; 171 + let continueCount = 0; 172 + while (continueCount++ < 1) { 173 + { 174 + using _ = (continueResource = { 175 + disposed: false, 176 + [Symbol.dispose]() { 177 + this.disposed = true; 178 + } 179 + }); 180 + continue; 181 + } 182 + } 183 + test('using continue disposes', continueResource.disposed, true); 184 + 185 + async function awaitUsingBlock(resource) { 186 + { 187 + await using _ = resource; 188 + } 189 + } 190 + 191 + const awaitUsingResource = { 192 + disposed: false, 193 + async [Symbol.asyncDispose]() { 194 + this.disposed = true; 195 + } 196 + }; 197 + await awaitUsingBlock(awaitUsingResource); 198 + test('await using block disposes', awaitUsingResource.disposed, true); 199 + 200 + async function awaitUsingReturn(resource) { 201 + { 202 + await using _ = resource; 203 + return 'async'; 204 + } 205 + } 206 + 207 + const awaitReturnResource = { 208 + disposed: false, 209 + async [Symbol.asyncDispose]() { 210 + this.disposed = true; 211 + } 212 + }; 213 + test('await using return value', await awaitUsingReturn(awaitReturnResource), 'async'); 214 + test('await using return disposes', awaitReturnResource.disposed, true); 215 + 216 + let throwingAwaitReturnCalls = 0; 217 + const throwingAwaitReturnError = new Error('async dispose return'); 218 + try { 219 + await (async function () { 220 + { 221 + await using _ = { 222 + async [Symbol.asyncDispose]() { 223 + throwingAwaitReturnCalls++; 224 + throw throwingAwaitReturnError; 225 + } 226 + }; 227 + return 'skipped'; 228 + } 229 + })(); 230 + } catch (e) { 231 + test('await using return cleanup throws original error', e, throwingAwaitReturnError); 232 + } 233 + test('await using return cleanup throws once', throwingAwaitReturnCalls, 1); 234 + 235 + const forOfLog = []; 236 + for (using item of [ 237 + { 238 + [Symbol.dispose]() { 239 + forOfLog.push('a'); 240 + } 241 + }, 242 + { 243 + [Symbol.dispose]() { 244 + forOfLog.push('b'); 245 + } 246 + } 247 + ]); 248 + testDeep('using for-of disposes each iteration', forOfLog, ['a', 'b']); 249 + 250 + const forOfBreakLog = []; 251 + for (using item of [ 252 + { 253 + [Symbol.dispose]() { 254 + forOfBreakLog.push('break'); 255 + } 256 + } 257 + ]) { 258 + break; 259 + } 260 + testDeep('using for-of break disposes current item', forOfBreakLog, ['break']); 261 + 262 + const forOfThrowLog = []; 263 + try { 264 + for (using item of [ 265 + { 266 + [Symbol.dispose]() { 267 + forOfThrowLog.push('throw'); 268 + } 269 + } 270 + ]) { 271 + throw new Error('body'); 272 + } 273 + } catch (e) { 274 + test('using for-of body throw preserved', e.message, 'body'); 275 + } 276 + testDeep('using for-of throw disposes current item', forOfThrowLog, ['throw']); 277 + 278 + const awaitForOfLog = []; 279 + for (await using item of [ 280 + { 281 + async [Symbol.asyncDispose]() { 282 + awaitForOfLog.push('a'); 283 + } 284 + }, 285 + { 286 + async [Symbol.asyncDispose]() { 287 + awaitForOfLog.push('b'); 288 + } 289 + } 290 + ]); 291 + testDeep('await using for-of disposes each iteration', awaitForOfLog, ['a', 'b']); 292 + 293 + const awaitForOfBreakLog = []; 294 + for (await using item of [ 295 + { 296 + async [Symbol.asyncDispose]() { 297 + awaitForOfBreakLog.push('break'); 298 + } 299 + } 300 + ]) { 301 + break; 302 + } 303 + testDeep('await using for-of break disposes current item', awaitForOfBreakLog, ['break']); 304 + 305 + const err1 = new Error('dispose 1'); 306 + const err2 = new Error('dispose 2'); 307 + const err3 = new Error('body'); 308 + let usingSuppressed; 309 + try { 310 + { 311 + using _1 = { 312 + [Symbol.dispose]() { 313 + throw err1; 314 + } 315 + }, 316 + _2 = { 317 + [Symbol.dispose]() { 318 + throw err2; 319 + } 320 + }; 321 + throw err3; 322 + } 323 + } catch (e) { 324 + usingSuppressed = e; 325 + } 326 + 327 + test('using suppression outer error', usingSuppressed.error, err1); 328 + test('using suppression inner error', usingSuppressed.suppressed.error, err2); 329 + test('using suppression body error', usingSuppressed.suppressed.suppressed, err3); 330 + 331 + summary();
+39
examples/spec/throw_expressions.js
··· 1 + import { test, testThrows, summary } from './helpers.js'; 2 + 3 + console.log('Throw Expression Tests\n'); 4 + 5 + const must = value => value || throw new Error('missing'); 6 + test('throw expression in arrow skip', must('ok'), 'ok'); 7 + testThrows('throw expression in arrow throws', () => must('')); 8 + 9 + function fallback(value) { 10 + return value ?? throw new Error('nullish'); 11 + } 12 + 13 + test('throw expression in nullish skip', fallback('value'), 'value'); 14 + testThrows('throw expression in nullish throws', () => fallback(null)); 15 + 16 + function choose(value) { 17 + return value ? value : throw new Error('bad'); 18 + } 19 + 20 + test('throw expression in conditional skip', choose(7), 7); 21 + testThrows('throw expression in conditional throws', () => choose(0)); 22 + 23 + function defaultParam(value = throw new Error('default')) { 24 + return value; 25 + } 26 + 27 + test('throw expression in parameter initializer skip', defaultParam(3), 3); 28 + testThrows('throw expression in parameter initializer throws', () => defaultParam()); 29 + 30 + let caught; 31 + try { 32 + const value = false || throw 'sentinel'; 33 + void value; 34 + } catch (e) { 35 + caught = e; 36 + } 37 + test('throw expression throws raw value', caught, 'sentinel'); 38 + 39 + summary();
+3 -1
include/common.h
··· 129 129 BRAND_MODULE_NAMESPACE, 130 130 BRAND_ABORT_SIGNAL, 131 131 BRAND_EVENTEMITTER, 132 - BRAND_EVENTTARGET 132 + BRAND_EVENTTARGET, 133 + BRAND_DISPOSABLE_STACK, 134 + BRAND_ASYNC_DISPOSABLE_STACK 133 135 } object_brand_id_t; 134 136 135 137 static inline void *mantissa_chk(void *p, const char *func) {
+1
include/internal.h
··· 199 199 ant_value_t array_iterator_proto; 200 200 ant_value_t string_iterator_proto; 201 201 ant_value_t generator_proto; 202 + ant_value_t async_iterator_proto; 202 203 } sym; 203 204 204 205 ant_offset_t max_size;
+1
include/modules/iterator.h
··· 2 2 #define ITERATOR_H 3 3 4 4 void init_iterator_module(void); 5 + void init_async_iterator_helpers(void); 5 6 6 7 #endif
+2
include/modules/symbol.h
··· 46 46 X(split, "Symbol.split") \ 47 47 X(matchAll, "Symbol.matchAll") \ 48 48 X(isConcatSpreadable, "Symbol.isConcatSpreadable") \ 49 + X(dispose, "Symbol.dispose") \ 50 + X(asyncDispose, "Symbol.asyncDispose") \ 49 51 X(observable, "Symbol.observable") \ 50 52 X(toPrimitive, "Symbol.toPrimitive") \ 51 53 X(species, "Symbol.species") \
+2
include/silver/ast.h
··· 84 84 SV_VAR_VAR, 85 85 SV_VAR_LET, 86 86 SV_VAR_CONST, 87 + SV_VAR_USING, 88 + SV_VAR_AWAIT_USING, 87 89 } sv_var_kind_t; 88 90 89 91 enum {
+12
include/silver/compiler.h
··· 41 41 } sv_deferred_export_t; 42 42 43 43 typedef struct { 44 + int stack_local; 45 + int scope_depth; 46 + bool is_async; 47 + } sv_using_cleanup_t; 48 + 49 + typedef struct { 44 50 int loop_start; 45 51 sv_patch_list_t breaks; 46 52 sv_patch_list_t continues; ··· 116 122 int strict_args_local; 117 123 int new_target_local; 118 124 int super_local; 125 + int using_stack_local; 126 + int using_cleanup_count; 127 + int using_cleanup_cap; 128 + 129 + bool using_stack_async; 130 + sv_using_cleanup_t *using_cleanups; 119 131 120 132 const char *pending_label; 121 133 uint32_t pending_label_len;
+10
include/silver/opcode.h
··· 187 187 OP_DEF( FINALLY_RET, 1, 1, 0, none) /* return from finally */ 188 188 OP_DEF( NIP_CATCH, 1, 2, 1, none) /* catch ... a -> a */ 189 189 190 + OP_DEF( USING_PUSH, 1, 2, 1, none) /* entries resource -> resource */ 191 + OP_DEF( USING_PUSH_ASYNC, 1, 2, 1, none) /* entries resource -> resource */ 192 + OP_DEF( DISPOSE_RESOURCE, 1, 1, 1, none) /* resource -> completion */ 193 + OP_DEF( DISPOSE_RESOURCE_ASYNC, 1, 1, 1, none) /* resource -> promise/completion */ 194 + 195 + OP_DEF( USING_DISPOSE, 1, 1, 1, none) /* entries -> completion */ 196 + OP_DEF( USING_DISPOSE_ASYNC, 1, 1, 1, none) /* entries -> promise/completion */ 197 + OP_DEF( USING_DISPOSE_SUPPRESSED, 1, 2, 1, none) /* entries completion -> completion */ 198 + OP_DEF( USING_DISPOSE_ASYNC_SUPPRESSED, 1, 2, 1, none) /* entries completion -> promise/completion */ 199 + 190 200 OP_DEF( FOR_IN, 1, 1, 1, none) /* obj -> iterator */ 191 201 OP_DEF( FOR_OF, 1, 1, 3, none) /* iterable -> iter next catch_off */ 192 202 OP_DEF( FOR_AWAIT_OF, 1, 1, 3, none) /* async iterable -> iter next catch_off */
+392 -2
src/ant.c
··· 31 31 #include "silver/lexer.h" 32 32 #include "silver/compiler.h" 33 33 #include "silver/engine.h" 34 + #include "silver/ops/using.h" 34 35 35 36 #include <uv.h> 36 37 #include <assert.h> ··· 558 559 ant_flat_string_t *flat = ant_str_flat_ptr(v); 559 560 return flat ? flat->len : 0; 560 561 } 562 + 563 + static ant_value_t make_data_cfunc( 564 + ant_t *js, ant_value_t data, 565 + ant_value_t (*fn)(ant_t *, ant_value_t *, int) 566 + ); 561 567 562 568 static ant_value_t proxy_read_target(ant_t *js, ant_value_t obj); 563 569 static ant_offset_t proxy_aware_length(ant_t *js, ant_value_t obj); ··· 5979 5985 return this_val; 5980 5986 } 5981 5987 5988 + static ant_value_t builtin_SuppressedError(ant_t *js, ant_value_t *args, int nargs) { 5989 + bool is_new = (vtype(js->new_target) != T_UNDEF); 5990 + ant_value_t this_val = js->this_val; 5991 + 5992 + if (!is_new) { 5993 + this_val = js_mkobj(js); 5994 + ant_offset_t proto_off = lkp_interned(js, js_func_obj(js->current_func), js->intern.prototype, 9); 5995 + if (proto_off) js_set_proto_init(this_val, propref_load(js, proto_off)); 5996 + else js_set_proto_init(this_val, get_ctor_proto(js, "SuppressedError", 15)); 5997 + } 5998 + 5999 + ant_value_t error = nargs > 0 ? args[0] : js_mkundef(); 6000 + ant_value_t suppressed = nargs > 1 ? args[1] : js_mkundef(); 6001 + 6002 + js_mkprop_fast(js, this_val, "error", 5, error); 6003 + js_mkprop_fast(js, this_val, "suppressed", 10, suppressed); 6004 + 6005 + if (nargs > 2 && vtype(args[2]) != T_UNDEF) { 6006 + ant_value_t msg = args[2]; 6007 + if (vtype(msg) != T_STR) { 6008 + const char *str = js_str(js, msg); 6009 + msg = js_mkstr(js, str, strlen(str)); 6010 + } 6011 + js_mkprop_fast(js, this_val, "message", 7, msg); 6012 + } 6013 + 6014 + js_mkprop_fast(js, this_val, "name", 4, ANT_STRING("SuppressedError")); 6015 + set_slot(this_val, SLOT_ERROR_BRAND, js_true); 6016 + js_capture_stack(js, this_val); 6017 + 6018 + return this_val; 6019 + } 6020 + 6021 + static ant_value_t disposable_stack_init(ant_t *js, ant_value_t obj, int brand, ant_value_t proto) { 6022 + if (vtype(obj) != T_OBJ) obj = js_mkobj(js); 6023 + if (is_err(obj)) return obj; 6024 + if (is_object_type(proto)) js_set_proto_init(obj, proto); 6025 + 6026 + ant_value_t entries = js_mkarr(js); 6027 + if (is_err(entries)) return entries; 6028 + set_slot(obj, SLOT_BRAND, js_mknum((double)brand)); 6029 + set_slot(obj, SLOT_ENTRIES, entries); 6030 + set_slot(obj, SLOT_SETTLED, js_false); 6031 + 6032 + return obj; 6033 + } 6034 + 6035 + static ant_value_t builtin_DisposableStack(ant_t *js, ant_value_t *args, int nargs) { 6036 + if (vtype(js->new_target) == T_UNDEF) { 6037 + return js_mkerr_typed(js, JS_ERR_TYPE, "DisposableStack constructor requires 'new'"); 6038 + } 6039 + 6040 + ant_value_t proto = js_get_ctor_proto(js, "DisposableStack", 15); 6041 + ant_value_t instance_proto = js_instance_proto_from_new_target(js, proto); 6042 + 6043 + return disposable_stack_init(js, js->this_val, BRAND_DISPOSABLE_STACK, instance_proto); 6044 + } 6045 + 6046 + static ant_value_t builtin_AsyncDisposableStack(ant_t *js, ant_value_t *args, int nargs) { 6047 + if (vtype(js->new_target) == T_UNDEF) { 6048 + return js_mkerr_typed(js, JS_ERR_TYPE, "AsyncDisposableStack constructor requires 'new'"); 6049 + } 6050 + 6051 + ant_value_t proto = js_get_ctor_proto(js, "AsyncDisposableStack", 20); 6052 + ant_value_t instance_proto = js_instance_proto_from_new_target(js, proto); 6053 + 6054 + return disposable_stack_init(js, js->this_val, BRAND_ASYNC_DISPOSABLE_STACK, instance_proto); 6055 + } 6056 + 6057 + static bool disposable_stack_has_brand(ant_value_t obj, int brand) { 6058 + if (!is_object_type(obj)) return false; 6059 + ant_value_t actual = get_slot(js_as_obj(obj), SLOT_BRAND); 6060 + return vtype(actual) == T_NUM && (int)js_getnum(actual) == brand; 6061 + } 6062 + 6063 + static ant_value_t disposable_stack_entries_checked(ant_t *js, ant_value_t stack, int brand, const char *name) { 6064 + if (!disposable_stack_has_brand(stack, brand)) { 6065 + return js_mkerr_typed(js, JS_ERR_TYPE, "%s method called on incompatible receiver", name); 6066 + } 6067 + 6068 + if (get_slot(js_as_obj(stack), SLOT_SETTLED) == js_true) { 6069 + return js_mkerr_typed(js, JS_ERR_TYPE, "%s is already disposed", name); 6070 + } 6071 + 6072 + ant_value_t entries = get_slot(js_as_obj(stack), SLOT_ENTRIES); 6073 + if (vtype(entries) != T_ARR) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid %s", name); 6074 + return entries; 6075 + } 6076 + 6077 + static ant_value_t disposable_stack_push_record( 6078 + ant_t *js, ant_value_t stack, int brand, const char *name, 6079 + sv_disposal_record_kind_t kind, ant_value_t value, ant_value_t method 6080 + ) { 6081 + if (!is_callable(method)) { 6082 + return js_mkerr_typed(js, JS_ERR_TYPE, "%s requires a callable disposer", name); 6083 + } 6084 + 6085 + GC_ROOT_SAVE(root_mark, js); 6086 + GC_ROOT_PIN(js, stack); 6087 + GC_ROOT_PIN(js, value); 6088 + GC_ROOT_PIN(js, method); 6089 + 6090 + ant_value_t entries = disposable_stack_entries_checked(js, stack, brand, name); 6091 + GC_ROOT_PIN(js, entries); 6092 + if (is_err(entries)) { 6093 + GC_ROOT_RESTORE(js, root_mark); 6094 + return entries; 6095 + } 6096 + 6097 + ant_value_t record = js_mkarr(js); 6098 + GC_ROOT_PIN(js, record); 6099 + if (is_err(record)) { 6100 + GC_ROOT_RESTORE(js, root_mark); 6101 + return record; 6102 + } 6103 + 6104 + js_arr_push(js, record, js_mknum((double)kind)); 6105 + js_arr_push(js, record, value); 6106 + js_arr_push(js, record, method); 6107 + js_arr_push(js, entries, record); 6108 + 6109 + GC_ROOT_RESTORE(js, root_mark); 6110 + return js_mkundef(); 6111 + } 6112 + 6113 + static ant_value_t builtin_DisposableStack_defer(ant_t *js, ant_value_t *args, int nargs) { 6114 + ant_value_t fn = nargs > 0 ? args[0] : js_mkundef(); 6115 + ant_value_t result = disposable_stack_push_record( 6116 + js, js->this_val, BRAND_DISPOSABLE_STACK, 6117 + "DisposableStack", SV_DISPOSAL_RECORD_DEFER, js_mkundef(), fn 6118 + ); 6119 + return is_err(result) ? result : js_mkundef(); 6120 + } 6121 + 6122 + static ant_value_t builtin_DisposableStack_adopt(ant_t *js, ant_value_t *args, int nargs) { 6123 + ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); 6124 + ant_value_t fn = nargs > 1 ? args[1] : js_mkundef(); 6125 + ant_value_t result = disposable_stack_push_record( 6126 + js, js->this_val, BRAND_DISPOSABLE_STACK, 6127 + "DisposableStack", SV_DISPOSAL_RECORD_ADOPT, value, fn 6128 + ); 6129 + return is_err(result) ? result : value; 6130 + } 6131 + 6132 + static ant_value_t builtin_AsyncDisposableStack_defer(ant_t *js, ant_value_t *args, int nargs) { 6133 + ant_value_t fn = nargs > 0 ? args[0] : js_mkundef(); 6134 + ant_value_t result = disposable_stack_push_record( 6135 + js, js->this_val, BRAND_ASYNC_DISPOSABLE_STACK, 6136 + "AsyncDisposableStack", SV_DISPOSAL_RECORD_DEFER, js_mkundef(), fn 6137 + ); 6138 + return is_err(result) ? result : js_mkundef(); 6139 + } 6140 + 6141 + static ant_value_t builtin_AsyncDisposableStack_adopt(ant_t *js, ant_value_t *args, int nargs) { 6142 + ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); 6143 + ant_value_t fn = nargs > 1 ? args[1] : js_mkundef(); 6144 + ant_value_t result = disposable_stack_push_record( 6145 + js, js->this_val, BRAND_ASYNC_DISPOSABLE_STACK, 6146 + "AsyncDisposableStack", SV_DISPOSAL_RECORD_ADOPT, value, fn 6147 + ); 6148 + return is_err(result) ? result : value; 6149 + } 6150 + 6151 + static ant_value_t disposable_stack_use( 6152 + ant_t *js, ant_value_t stack, int brand, const char *name, 6153 + ant_value_t resource, ant_value_t dispose_sym, ant_value_t fallback_sym 6154 + ) { 6155 + ant_value_t entries = disposable_stack_entries_checked(js, stack, brand, name); 6156 + if (is_err(entries)) return entries; 6157 + 6158 + if (vtype(resource) == T_NULL || vtype(resource) == T_UNDEF) return resource; 6159 + 6160 + ant_value_t method = js_get_sym(js, resource, dispose_sym); 6161 + if ((vtype(method) == T_UNDEF || vtype(method) == T_NULL) && vtype(fallback_sym) == T_SYMBOL) { 6162 + method = js_get_sym(js, resource, fallback_sym); 6163 + } 6164 + if (!is_callable(method)) { 6165 + return js_mkerr_typed(js, JS_ERR_TYPE, "%s resource is not disposable", name); 6166 + } 6167 + 6168 + ant_value_t result = disposable_stack_push_record( 6169 + js, stack, brand, name, SV_DISPOSAL_RECORD_USE, resource, method 6170 + ); 6171 + return is_err(result) ? result : resource; 6172 + } 6173 + 6174 + static ant_value_t builtin_DisposableStack_use(ant_t *js, ant_value_t *args, int nargs) { 6175 + ant_value_t resource = nargs > 0 ? args[0] : js_mkundef(); 6176 + return disposable_stack_use( 6177 + js, js->this_val, BRAND_DISPOSABLE_STACK, "DisposableStack", resource, get_dispose_sym(), js_mkundef() 6178 + ); 6179 + } 6180 + 6181 + static ant_value_t builtin_AsyncDisposableStack_use(ant_t *js, ant_value_t *args, int nargs) { 6182 + ant_value_t resource = nargs > 0 ? args[0] : js_mkundef(); 6183 + return disposable_stack_use( 6184 + js, js->this_val, BRAND_ASYNC_DISPOSABLE_STACK, "AsyncDisposableStack", 6185 + resource, get_asyncDispose_sym(), get_dispose_sym() 6186 + ); 6187 + } 6188 + 6189 + static ant_value_t disposable_stack_move(ant_t *js, int brand, const char *name) { 6190 + GC_ROOT_SAVE(root_mark, js); 6191 + ant_value_t stack = js->this_val; 6192 + GC_ROOT_PIN(js, stack); 6193 + 6194 + ant_value_t entries = disposable_stack_entries_checked(js, stack, brand, name); 6195 + GC_ROOT_PIN(js, entries); 6196 + if (is_err(entries)) { 6197 + GC_ROOT_RESTORE(js, root_mark); 6198 + return entries; 6199 + } 6200 + 6201 + ant_value_t proto = js_get_ctor_proto(js, name, strlen(name)); 6202 + GC_ROOT_PIN(js, proto); 6203 + ant_value_t moved = disposable_stack_init(js, js_mkundef(), brand, proto); 6204 + GC_ROOT_PIN(js, moved); 6205 + if (is_err(moved)) { 6206 + GC_ROOT_RESTORE(js, root_mark); 6207 + return moved; 6208 + } 6209 + 6210 + set_slot(moved, SLOT_ENTRIES, entries); 6211 + set_slot(js_as_obj(stack), SLOT_ENTRIES, js_mkarr(js)); 6212 + set_slot(js_as_obj(stack), SLOT_SETTLED, js_true); 6213 + 6214 + GC_ROOT_RESTORE(js, root_mark); 6215 + return moved; 6216 + } 6217 + 6218 + static ant_value_t builtin_DisposableStack_move(ant_t *js, ant_value_t *args, int nargs) { 6219 + (void)args; (void)nargs; 6220 + return disposable_stack_move(js, BRAND_DISPOSABLE_STACK, "DisposableStack"); 6221 + } 6222 + 6223 + static ant_value_t builtin_AsyncDisposableStack_move(ant_t *js, ant_value_t *args, int nargs) { 6224 + (void)args; (void)nargs; 6225 + return disposable_stack_move(js, BRAND_ASYNC_DISPOSABLE_STACK, "AsyncDisposableStack"); 6226 + } 6227 + 6228 + static ant_value_t builtin_DisposableStack_dispose(ant_t *js, ant_value_t *args, int nargs) { 6229 + ant_value_t stack = js->this_val; 6230 + if (!disposable_stack_has_brand(stack, BRAND_DISPOSABLE_STACK)) { 6231 + return js_mkerr_typed(js, JS_ERR_TYPE, "DisposableStack method called on incompatible receiver"); 6232 + } 6233 + if (get_slot(js_as_obj(stack), SLOT_SETTLED) == js_true) return js_mkundef(); 6234 + 6235 + GC_ROOT_SAVE(root_mark, js); 6236 + GC_ROOT_PIN(js, stack); 6237 + ant_value_t entries = get_slot(js_as_obj(stack), SLOT_ENTRIES); 6238 + 6239 + GC_ROOT_PIN(js, entries); 6240 + ant_value_t completion = js_mkundef(); 6241 + 6242 + GC_ROOT_PIN(js, completion); 6243 + set_slot(js_as_obj(stack), SLOT_SETTLED, js_true); 6244 + 6245 + ant_offset_t len = vtype(entries) == T_ARR ? js_arr_len(js, entries) : 0; 6246 + for (ant_offset_t i = len; i > 0; i--) { 6247 + ant_value_t record = js_arr_get(js, entries, i - 1); 6248 + GC_ROOT_PIN(js, record); 6249 + ant_value_t result = sv_disposal_record_call(js, record); 6250 + 6251 + if (is_err(result) || js->thrown_exists) { 6252 + ant_value_t error = sv_disposal_error_value(js, result); 6253 + GC_ROOT_PIN(js, error); 6254 + completion = sv_suppress_disposal_error(js, error, completion); 6255 + 6256 + if (is_err(completion)) { 6257 + GC_ROOT_RESTORE(js, root_mark); 6258 + return completion; 6259 + } 6260 + } 6261 + } 6262 + 6263 + set_slot(js_as_obj(stack), SLOT_ENTRIES, js_mkarr(js)); 6264 + if (vtype(completion) != T_UNDEF) { 6265 + ant_value_t thrown = js_throw(js, completion); 6266 + GC_ROOT_RESTORE(js, root_mark); 6267 + return thrown; 6268 + } 6269 + 6270 + GC_ROOT_RESTORE(js, root_mark); 6271 + return js_mkundef(); 6272 + } 6273 + 6274 + static ant_value_t builtin_AsyncDisposableStack_disposeAsync(ant_t *js, ant_value_t *args, int nargs) { 6275 + ant_value_t stack = js->this_val; 6276 + ant_value_t result_promise = js_mkpromise(js); 6277 + if (is_err(result_promise)) return result_promise; 6278 + 6279 + if (!disposable_stack_has_brand(stack, BRAND_ASYNC_DISPOSABLE_STACK)) { 6280 + ant_value_t error = js_make_error_silent( 6281 + js, JS_ERR_TYPE, "AsyncDisposableStack method called on incompatible receiver" 6282 + ); 6283 + js_reject_promise(js, result_promise, error); 6284 + return result_promise; 6285 + } 6286 + 6287 + if (get_slot(js_as_obj(stack), SLOT_SETTLED) == js_true) { 6288 + js_resolve_promise(js, result_promise, js_mkundef()); 6289 + return result_promise; 6290 + } 6291 + 6292 + GC_ROOT_SAVE(root_mark, js); 6293 + GC_ROOT_PIN(js, stack); 6294 + GC_ROOT_PIN(js, result_promise); 6295 + 6296 + ant_value_t entries = get_slot(js_as_obj(stack), SLOT_ENTRIES); 6297 + GC_ROOT_PIN(js, entries); 6298 + set_slot(js_as_obj(stack), SLOT_SETTLED, js_true); 6299 + 6300 + ant_value_t state = js_mkobj(js); 6301 + GC_ROOT_PIN(js, state); 6302 + if (is_err(state)) { 6303 + GC_ROOT_RESTORE(js, root_mark); 6304 + return state; 6305 + } 6306 + 6307 + set_slot(state, SLOT_ENTRIES, entries); 6308 + set_slot(state, SLOT_DATA, result_promise); 6309 + set_slot(state, SLOT_AUX, js_mkundef()); 6310 + set_slot(state, SLOT_ITER_STATE, js_mknum((double)(vtype(entries) == T_ARR ? js_arr_len(js, entries) : 0))); 6311 + set_slot(js_as_obj(stack), SLOT_ENTRIES, js_mkarr(js)); 6312 + 6313 + ant_value_t result = sv_async_dispose_continue(js, state, false, js_mkundef()); 6314 + GC_ROOT_RESTORE(js, root_mark); 6315 + 6316 + return result; 6317 + } 6318 + 5982 6319 5983 6320 typedef ant_value_t (*dynamic_kv_mapper_fn)( 5984 6321 ant_t *js, ··· 13177 13514 return mkval(T_BOOL, 0); 13178 13515 } 13179 13516 13180 - if (ltype != T_OBJ && ltype != T_ARR && ltype != T_FUNC && ltype != T_PROMISE) { 13517 + if (ltype != T_OBJ && ltype != T_ARR && ltype != T_FUNC && ltype != T_PROMISE && ltype != T_GENERATOR) { 13181 13518 return mkval(T_BOOL, 0); 13182 13519 } 13183 13520 ··· 13313 13650 return mkval(T_BOOL, vdata(ctor_proto) == vdata(type_proto) ? 1 : 0); 13314 13651 } 13315 13652 13316 - if (ltype != T_OBJ && ltype != T_ARR && ltype != T_FUNC && ltype != T_PROMISE) { 13653 + if (ltype != T_OBJ && ltype != T_ARR && ltype != T_FUNC && ltype != T_PROMISE && ltype != T_GENERATOR) { 13317 13654 return mkval(T_BOOL, 0); 13318 13655 } 13319 13656 ··· 14513 14850 js_setprop(js, proto, ANT_STRING("constructor"), js_obj_to_func(ctor)); 14514 14851 js_set_descriptor(js, proto, "constructor", 11, JS_DESC_W | JS_DESC_C); 14515 14852 js_setprop(js, glob, ANT_STRING("AggregateError"), js_obj_to_func(ctor)); 14853 + 14854 + ant_value_t suppressed_proto = js_mkobj(js); 14855 + set_proto(js, suppressed_proto, error_proto); 14856 + js_setprop(js, suppressed_proto, ANT_STRING("name"), ANT_STRING("SuppressedError")); 14857 + 14858 + ant_value_t suppressed_ctor = mkobj(js, 0); 14859 + set_proto(js, suppressed_ctor, function_proto); 14860 + set_slot(suppressed_ctor, SLOT_CFUNC, js_mkfun(builtin_SuppressedError)); 14861 + js_setprop_nonconfigurable(js, suppressed_ctor, "prototype", 9, suppressed_proto); 14862 + js_setprop(js, suppressed_ctor, ANT_STRING("name"), ANT_STRING("SuppressedError")); 14863 + 14864 + ant_value_t suppressed_ctor_func = js_obj_to_func(suppressed_ctor); 14865 + js_setprop(js, suppressed_proto, ANT_STRING("constructor"), suppressed_ctor_func); 14866 + js_set_descriptor(js, suppressed_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); 14867 + js_setprop(js, glob, ANT_STRING("SuppressedError"), suppressed_ctor_func); 14868 + 14869 + ant_value_t disposable_stack_proto = js_mkobj(js); 14870 + set_proto(js, disposable_stack_proto, object_proto); 14871 + defmethod(js, disposable_stack_proto, "use", 3, js_mkfun(builtin_DisposableStack_use)); 14872 + defmethod(js, disposable_stack_proto, "adopt", 5, js_mkfun(builtin_DisposableStack_adopt)); 14873 + defmethod(js, disposable_stack_proto, "defer", 5, js_mkfun(builtin_DisposableStack_defer)); 14874 + defmethod(js, disposable_stack_proto, "move", 4, js_mkfun(builtin_DisposableStack_move)); 14875 + defmethod(js, disposable_stack_proto, "dispose", 7, js_mkfun(builtin_DisposableStack_dispose)); 14876 + 14877 + ant_value_t disposable_stack_ctor = mkobj(js, 0); 14878 + set_proto(js, disposable_stack_ctor, function_proto); 14879 + set_slot(disposable_stack_ctor, SLOT_CFUNC, js_mkfun(builtin_DisposableStack)); 14880 + js_setprop_nonconfigurable(js, disposable_stack_ctor, "prototype", 9, disposable_stack_proto); 14881 + js_setprop(js, disposable_stack_ctor, ANT_STRING("name"), ANT_STRING("DisposableStack")); 14882 + 14883 + ant_value_t disposable_stack_ctor_func = js_obj_to_func(disposable_stack_ctor); 14884 + js_setprop(js, disposable_stack_proto, ANT_STRING("constructor"), disposable_stack_ctor_func); 14885 + js_set_descriptor(js, disposable_stack_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); 14886 + js_setprop(js, glob, ANT_STRING("DisposableStack"), disposable_stack_ctor_func); 14887 + 14888 + ant_value_t async_disposable_stack_proto = js_mkobj(js); 14889 + set_proto(js, async_disposable_stack_proto, object_proto); 14890 + defmethod(js, async_disposable_stack_proto, "use", 3, js_mkfun(builtin_AsyncDisposableStack_use)); 14891 + defmethod(js, async_disposable_stack_proto, "adopt", 5, js_mkfun(builtin_AsyncDisposableStack_adopt)); 14892 + defmethod(js, async_disposable_stack_proto, "defer", 5, js_mkfun(builtin_AsyncDisposableStack_defer)); 14893 + defmethod(js, async_disposable_stack_proto, "move", 4, js_mkfun(builtin_AsyncDisposableStack_move)); 14894 + defmethod(js, async_disposable_stack_proto, "disposeAsync", 12, js_mkfun(builtin_AsyncDisposableStack_disposeAsync)); 14895 + 14896 + ant_value_t async_disposable_stack_ctor = mkobj(js, 0); 14897 + set_proto(js, async_disposable_stack_ctor, function_proto); 14898 + set_slot(async_disposable_stack_ctor, SLOT_CFUNC, js_mkfun(builtin_AsyncDisposableStack)); 14899 + js_setprop_nonconfigurable(js, async_disposable_stack_ctor, "prototype", 9, async_disposable_stack_proto); 14900 + js_setprop(js, async_disposable_stack_ctor, ANT_STRING("name"), ANT_STRING("AsyncDisposableStack")); 14901 + 14902 + ant_value_t async_disposable_stack_ctor_func = js_obj_to_func(async_disposable_stack_ctor); 14903 + js_setprop(js, async_disposable_stack_proto, ANT_STRING("constructor"), async_disposable_stack_ctor_func); 14904 + js_set_descriptor(js, async_disposable_stack_proto, "constructor", 11, JS_DESC_W | JS_DESC_C); 14905 + js_setprop(js, glob, ANT_STRING("AsyncDisposableStack"), async_disposable_stack_ctor_func); 14516 14906 14517 14907 ant_value_t promise_proto = js_mkobj(js); 14518 14908 set_proto(js, promise_proto, object_proto);
+7 -3
src/esm/library.c
··· 13 13 ant_library_init_fn init_fn; 14 14 ant_value_t cached_ns; 15 15 bool ns_initialized; 16 + bool root_registered; 16 17 struct ant_library_entry *canonical; 17 18 UT_hash_handle hh; 18 19 } ant_library_entry_t; ··· 80 81 if (loaded) *loaded = false; 81 82 return js_mkundef(); 82 83 } 83 - if (loaded) *loaded = true; 84 84 85 + if (loaded) *loaded = true; 85 86 ant_library_entry_t *canon = lib->canonical; 86 87 if (canon->ns_initialized) return canon->cached_ns; 88 + 89 + if (!canon->root_registered) { 90 + gc_register_root(&canon->cached_ns); 91 + canon->root_registered = true; 92 + } 87 93 88 94 canon->cached_ns = canon->init_fn(js); 89 95 if (is_object_type(canon->cached_ns)) { ··· 94 100 } 95 101 96 102 canon->ns_initialized = true; 97 - gc_register_root(&canon->cached_ns); 98 - 99 103 return canon->cached_ns; 100 104 }
+9 -4
src/modules/generator.c
··· 11 11 #include "gc/roots.h" 12 12 #include "silver/engine.h" 13 13 #include "modules/generator.h" 14 + #include "modules/iterator.h" 14 15 #include "modules/symbol.h" 15 16 16 17 enum { GENERATOR_NATIVE_TAG = 0x47454e52u }; // GENR ··· 481 482 482 483 js->sym.generator_proto = proto; 483 484 js_set_proto_init(proto, js->sym.iterator_proto); 485 + js_set_proto_wb(js, js->sym.async_iterator_proto, proto); 484 486 485 487 js_set(js, proto, "next", js_mkfun(generator_next)); 486 488 js_set(js, proto, "return", js_mkfun(generator_return)); 487 489 js_set(js, proto, "throw", js_mkfun(generator_throw)); 488 490 js_set_sym(js, proto, get_toStringTag_sym(), js_mkstr(js, "Generator", 9)); 491 + init_async_iterator_helpers(); 489 492 } 490 493 491 494 ant_value_t sv_call_generator_closure_dispatch( ··· 574 577 js_set_native_tag(gen, GENERATOR_NATIVE_TAG); 575 578 js_set_finalizer(gen, generator_finalize); 576 579 577 - ant_value_t instance_proto = js_get(js, callee_func, "prototype"); 578 - if (is_object_type(instance_proto)) js_set_proto_wb(js, gen, instance_proto); 579 - if (data->is_async) 580 - js_set_sym(js, gen, get_asyncIterator_sym(), js_mkfun(sym_this_cb)); 580 + if (data->is_async && is_object_type(js->sym.async_iterator_proto)) 581 + js_set_proto_wb(js, gen, js->sym.async_iterator_proto); 582 + else { 583 + ant_value_t instance_proto = js_get(js, callee_func, "prototype"); 584 + if (is_object_type(instance_proto)) js_set_proto_wb(js, gen, instance_proto); 585 + } 581 586 582 587 return gen; 583 588 }
+972 -77
src/modules/iterator.c
··· 5 5 #include "errors.h" 6 6 #include "runtime.h" 7 7 #include "internal.h" 8 + #include "ptr.h" 8 9 #include "silver/engine.h" 9 10 #include "descriptors.h" 10 11 12 + #include "modules/assert.h" 11 13 #include "modules/iterator.h" 12 14 #include "modules/symbol.h" 13 15 ··· 17 19 WRAP_TAKE = 2, 18 20 WRAP_DROP = 3, 19 21 WRAP_FLATMAP = 4, 22 + WRAP_PASS = 5, 23 + WRAP_FROM_SYNC = 6, 24 + }; 25 + 26 + enum { 27 + ASYNC_TERM_EVERY = 0, 28 + ASYNC_TERM_SOME = 1, 29 + ASYNC_TERM_FIND = 2, 30 + ASYNC_TERM_FOREACH = 3, 31 + ASYNC_TERM_REDUCE = 4, 32 + ASYNC_TERM_TOARRAY = 5, 20 33 }; 21 34 22 35 static ant_value_t g_wrap_iter_proto = 0; 36 + static ant_value_t g_async_wrap_iter_proto = 0; 37 + 38 + enum { ASYNC_TERMINAL_STATE_TAG = 0x41544954u }; // ATIT 39 + 40 + typedef struct { 41 + double index; 42 + int mode; 43 + bool has_acc; 44 + } async_terminal_state_t; 45 + 46 + static inline ant_value_t call_indexed_callback(ant_t *js, ant_value_t fn, ant_value_t value, double index) { 47 + ant_value_t call_args[2] = { value, js_mknum(index) }; 48 + return sv_vm_call(js->vm, js, fn, js_mkundef(), call_args, 2, NULL, false); 49 + } 50 + 51 + static inline ant_value_t set_iter_result(ant_t *js, ant_value_t result, ant_value_t value, bool done) { 52 + js_set(js, result, "done", done ? js_true : js_false); 53 + js_set(js, result, "value", value); 54 + return result; 55 + } 23 56 24 57 static ant_value_t wrap_iter_next(ant_t *js, ant_value_t *args, int nargs) { 25 58 ant_value_t self = js->this_val; ··· 29 62 uint32_t state = (vtype(state_v) == T_NUM) ? (uint32_t)js_getnum(state_v) : 0; 30 63 uint32_t kind = ITER_STATE_KIND(state); 31 64 uint32_t count = ITER_STATE_INDEX(state); 32 - ant_value_t cb = js_get_slot(self, SLOT_CTOR); 33 - 65 + 34 66 ant_value_t result = js_mkobj(js); 67 + ant_value_t cb = js_get_slot(self, SLOT_CTOR); 35 68 ant_value_t next_fn = js_getprop_fallback(js, source, "next"); 36 69 37 70 for (;;) { ··· 42 75 ant_value_t inner_next = js_getprop_fallback(js, inner, "next"); 43 76 ant_value_t inner_step = sv_vm_call(js->vm, js, inner_next, inner, NULL, 0, NULL, false); 44 77 if (!is_err(inner_step)) { 45 - ant_value_t inner_done = js_getprop_fallback(js, inner_step, "done"); 46 - if (!js_truthy(js, inner_done)) { 47 - js_set(js, result, "done", js_false); 48 - js_set(js, result, "value", js_getprop_fallback(js, inner_step, "value")); 49 - return result; 50 - }} 78 + ant_value_t inner_done = js_getprop_fallback(js, inner_step, "done"); 79 + if (!js_truthy(js, inner_done)) return set_iter_result( 80 + js, result, js_getprop_fallback(js, inner_step, "value" 81 + ), false); 82 + } 51 83 52 84 js_set_slot(self, SLOT_ENTRIES, js_mkundef()); 53 85 }} ··· 70 102 js_set_slot(self, SLOT_ENTRIES, js_mkundef()); 71 103 }} 72 104 73 - js_set(js, result, "done", js_true); 74 - js_set(js, result, "value", js_mkundef()); 75 - 76 - return result; 105 + return set_iter_result(js, result, js_mkundef(), true); 77 106 } 78 107 79 108 ant_value_t value = js_getprop_fallback(js, step, "value"); ··· 82 111 case WRAP_MAP: { 83 112 ant_value_t out_val; 84 113 if (is_callable(cb)) { 85 - ant_value_t call_args[2] = { value, js_mknum((double)count) }; 86 - out_val = sv_vm_call(js->vm, js, cb, js_mkundef(), call_args, 2, NULL, false); 114 + out_val = call_indexed_callback(js, cb, value, (double)count); 87 115 if (is_err(out_val)) return out_val; 88 116 } else out_val = value; 89 117 90 118 count++; 91 119 js_set_slot(self, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, count))); 92 - js_set(js, result, "done", js_false); 93 - js_set(js, result, "value", out_val); 94 - 95 - return result; 120 + return set_iter_result(js, result, out_val, false); 96 121 } 97 122 98 123 case WRAP_FILTER: { 99 - ant_value_t call_args[2] = { value, js_mknum((double)count) }; 100 - ant_value_t test = sv_vm_call(js->vm, js, cb, js_mkundef(), call_args, 2, NULL, false); 124 + ant_value_t test = call_indexed_callback(js, cb, value, (double)count); 101 125 if (is_err(test)) return test; 102 126 count++; 103 127 js_set_slot(self, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, count))); 104 128 if (js_truthy(js, test)) { 105 - js_set(js, result, "done", js_false); 106 - js_set(js, result, "value", value); 107 - return result; 129 + return set_iter_result(js, result, value, false); 108 130 } 109 131 continue; 110 132 } ··· 112 134 case WRAP_TAKE: { 113 135 uint32_t limit = (vtype(cb) == T_NUM) ? (uint32_t)js_getnum(cb) : 0; 114 136 if (count >= limit) { 115 - js_set(js, result, "done", js_true); 116 - js_set(js, result, "value", js_mkundef()); 117 - return result; 137 + return set_iter_result(js, result, js_mkundef(), true); 118 138 } 119 139 js_set_slot(self, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, count + 1))); 120 - js_set(js, result, "done", js_false); 121 - js_set(js, result, "value", value); 122 - return result; 140 + return set_iter_result(js, result, value, false); 123 141 } 124 142 125 143 case WRAP_DROP: { ··· 127 145 count++; 128 146 js_set_slot(self, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, count))); 129 147 if (count <= limit) continue; 130 - js_set(js, result, "done", js_false); 131 - js_set(js, result, "value", value); 132 - return result; 148 + return set_iter_result(js, result, value, false); 133 149 } 134 150 135 151 case WRAP_FLATMAP: { 136 - ant_value_t call_args[2] = { value, js_mknum((double)count) }; 137 - ant_value_t mapped = sv_vm_call(js->vm, js, cb, js_mkundef(), call_args, 2, NULL, false); 152 + ant_value_t mapped = call_indexed_callback(js, cb, value, (double)count); 138 153 if (is_err(mapped)) return mapped; 139 154 count++; 140 155 js_set_slot(self, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, count))); 141 156 142 157 ant_value_t iter_fn = js_get_sym(js, mapped, get_iterator_sym()); 143 158 if (!is_callable(iter_fn)) { 144 - js_set(js, result, "done", js_false); 145 - js_set(js, result, "value", mapped); 146 - return result; 159 + return set_iter_result(js, result, mapped, false); 147 160 } 148 161 149 162 ant_value_t inner = sv_vm_call(js->vm, js, iter_fn, mapped, NULL, 0, NULL, false); ··· 155 168 ant_value_t inner_done = js_getprop_fallback(js, inner_step, "done"); 156 169 if (!js_truthy(js, inner_done)) { 157 170 js_set_slot_wb(js, self, SLOT_ENTRIES, inner); 158 - js_set(js, result, "done", js_false); 159 - js_set(js, result, "value", js_getprop_fallback(js, inner_step, "value")); 160 - return result; 171 + return set_iter_result(js, result, js_getprop_fallback(js, inner_step, "value"), false); 161 172 } 162 173 continue; 163 174 } 164 175 165 176 default: 166 - js_set(js, result, "done", js_false); 167 - js_set(js, result, "value", value); 168 - return result; 177 + return set_iter_result(js, result, value, false); 169 178 } 170 179 } 171 180 } ··· 192 201 return sv_vm_call(js->vm, js, iter_fn, self, NULL, 0, NULL, false); 193 202 } 194 203 195 - static ant_value_t iter_map(ant_t *js, ant_value_t *args, int nargs) { 196 - if (nargs < 1 || !is_callable(args[0])) 197 - return js_mkerr_typed(js, JS_ERR_TYPE, "Iterator.prototype.map requires a callable"); 204 + static ant_value_t iter_make_helper(ant_t *js, int kind, ant_value_t cb) { 198 205 ant_value_t source = get_source_iter(js); 199 206 if (is_err(source)) return source; 200 - return make_wrap_iter(js, source, WRAP_MAP, args[0]); 207 + return make_wrap_iter(js, source, kind, cb); 208 + } 209 + 210 + static ant_value_t iter_make_callable_helper( 211 + ant_t *js, 212 + ant_value_t *args, 213 + int nargs, 214 + int kind, 215 + const char *method 216 + ) { 217 + if (nargs < 1 || !is_callable(args[0])) 218 + return js_mkerr_typed(js, JS_ERR_TYPE, "%s requires a callable", method); 219 + return iter_make_helper(js, kind, args[0]); 220 + } 221 + 222 + static ant_value_t iter_make_count_helper( 223 + ant_t *js, ant_value_t *args, 224 + int nargs, int kind, 225 + const char *method 226 + ) { 227 + double limit = (nargs >= 1 && vtype(args[0]) == T_NUM) ? js_getnum(args[0]) : 0; 228 + if (limit < 0) return js_mkerr(js, "%s requires a non-negative number", method); 229 + return iter_make_helper(js, kind, js_mknum(limit)); 230 + } 231 + 232 + static ant_value_t iter_map(ant_t *js, ant_value_t *args, int nargs) { 233 + return iter_make_callable_helper(js, args, nargs, WRAP_MAP, "Iterator.prototype.map"); 201 234 } 202 235 203 236 static ant_value_t iter_filter(ant_t *js, ant_value_t *args, int nargs) { 204 - if (nargs < 1 || !is_callable(args[0])) 205 - return js_mkerr_typed(js, JS_ERR_TYPE, "Iterator.prototype.filter requires a callable"); 206 - ant_value_t source = get_source_iter(js); 207 - if (is_err(source)) return source; 208 - return make_wrap_iter(js, source, WRAP_FILTER, args[0]); 237 + return iter_make_callable_helper(js, args, nargs, WRAP_FILTER, "Iterator.prototype.filter"); 209 238 } 210 239 211 240 static ant_value_t iter_take(ant_t *js, ant_value_t *args, int nargs) { 212 - double limit = (nargs >= 1 && vtype(args[0]) == T_NUM) ? js_getnum(args[0]) : 0; 213 - if (limit < 0) return js_mkerr(js, "Iterator.prototype.take requires a non-negative number"); 214 - ant_value_t source = get_source_iter(js); 215 - if (is_err(source)) return source; 216 - return make_wrap_iter(js, source, WRAP_TAKE, js_mknum(limit)); 241 + return iter_make_count_helper(js, args, nargs, WRAP_TAKE, "Iterator.prototype.take"); 217 242 } 218 243 219 244 static ant_value_t iter_drop(ant_t *js, ant_value_t *args, int nargs) { 220 - double limit = (nargs >= 1 && vtype(args[0]) == T_NUM) ? js_getnum(args[0]) : 0; 221 - if (limit < 0) return js_mkerr(js, "Iterator.prototype.drop requires a non-negative number"); 222 - ant_value_t source = get_source_iter(js); 223 - if (is_err(source)) return source; 224 - return make_wrap_iter(js, source, WRAP_DROP, js_mknum(limit)); 245 + return iter_make_count_helper(js, args, nargs, WRAP_DROP, "Iterator.prototype.drop"); 225 246 } 226 247 227 248 static ant_value_t iter_flatMap(ant_t *js, ant_value_t *args, int nargs) { 228 - if (nargs < 1 || !is_callable(args[0])) 229 - return js_mkerr_typed(js, JS_ERR_TYPE, "Iterator.prototype.flatMap requires a callable"); 230 - ant_value_t source = get_source_iter(js); 231 - if (is_err(source)) return source; 232 - return make_wrap_iter(js, source, WRAP_FLATMAP, args[0]); 249 + return iter_make_callable_helper(js, args, nargs, WRAP_FLATMAP, "Iterator.prototype.flatMap"); 233 250 } 234 251 235 252 static ant_value_t iter_every(ant_t *js, ant_value_t *args, int nargs) { ··· 244 261 ant_value_t value; 245 262 uint32_t counter = 0; 246 263 while (js_iter_next(js, &it, &value)) { 247 - ant_value_t call_args[2] = { value, js_mknum((double)counter++) }; 248 - ant_value_t test = sv_vm_call(js->vm, js, fn, js_mkundef(), call_args, 2, NULL, false); 264 + ant_value_t test = call_indexed_callback(js, fn, value, (double)counter++); 249 265 if (is_err(test)) { js_iter_close(js, &it); return test; } 250 266 if (!js_truthy(js, test)) { js_iter_close(js, &it); return js_false; } 251 267 } ··· 264 280 ant_value_t value; 265 281 uint32_t counter = 0; 266 282 while (js_iter_next(js, &it, &value)) { 267 - ant_value_t call_args[2] = { value, js_mknum((double)counter++) }; 268 - ant_value_t test = sv_vm_call(js->vm, js, fn, js_mkundef(), call_args, 2, NULL, false); 283 + ant_value_t test = call_indexed_callback(js, fn, value, (double)counter++); 269 284 if (is_err(test)) { js_iter_close(js, &it); return test; } 270 285 if (js_truthy(js, test)) { js_iter_close(js, &it); return js_true; } 271 286 } ··· 284 299 ant_value_t value; 285 300 uint32_t counter = 0; 286 301 while (js_iter_next(js, &it, &value)) { 287 - ant_value_t call_args[2] = { value, js_mknum((double)counter++) }; 288 - ant_value_t test = sv_vm_call(js->vm, js, fn, js_mkundef(), call_args, 2, NULL, false); 302 + ant_value_t test = call_indexed_callback(js, fn, value, (double)counter++); 289 303 if (is_err(test)) { js_iter_close(js, &it); return test; } 290 304 if (js_truthy(js, test)) { js_iter_close(js, &it); return value; } 291 305 } ··· 304 318 ant_value_t value; 305 319 uint32_t counter = 0; 306 320 while (js_iter_next(js, &it, &value)) { 307 - ant_value_t call_args[2] = { value, js_mknum((double)counter++) }; 308 - ant_value_t r = sv_vm_call(js->vm, js, fn, js_mkundef(), call_args, 2, NULL, false); 321 + ant_value_t r = call_indexed_callback(js, fn, value, (double)counter++); 309 322 if (is_err(r)) { js_iter_close(js, &it); return r; } 310 323 } 311 324 return js_mkundef(); ··· 381 394 return obj; 382 395 } 383 396 397 + static ant_value_t async_iter_ctor(ant_t *js, ant_value_t *args, int nargs) { 398 + if (vtype(js->new_target) == T_UNDEF) 399 + return js_mkerr_typed(js, JS_ERR_TYPE, "AsyncIterator constructor requires 'new'"); 400 + 401 + ant_value_t obj = js_mkobj(js); 402 + ant_value_t proto = js_instance_proto_from_new_target(js, js->sym.async_iterator_proto); 403 + if (is_object_type(proto)) js_set_proto_init(obj, proto); 404 + 405 + return obj; 406 + } 407 + 408 + static inline ant_value_t iter_result(ant_t *js, ant_value_t value, bool done) { 409 + ant_value_t result = js_mkobj(js); 410 + js_set(js, result, "done", done ? js_true : js_false); 411 + js_set(js, result, "value", value); 412 + return result; 413 + } 414 + 415 + static inline ant_value_t fulfilled_promise(ant_t *js, ant_value_t value) { 416 + ant_value_t promise = js_mkpromise(js); 417 + js_resolve_promise(js, promise, value); 418 + return promise; 419 + } 420 + 421 + static inline ant_value_t rejected_promise(ant_t *js, ant_value_t reason) { 422 + ant_value_t promise = js_mkpromise(js); 423 + js_reject_promise(js, promise, reason); 424 + return promise; 425 + } 426 + 427 + static inline ant_value_t promise_from_call_result(ant_t *js, ant_value_t result) { 428 + if (vtype(result) == T_PROMISE) return result; 429 + if (is_err(result)) return rejected_promise(js, result); 430 + return fulfilled_promise(js, result); 431 + } 432 + 433 + static ant_value_t async_iter_call_method( 434 + ant_t *js, ant_value_t receiver, 435 + const char *name, ant_value_t *args, int nargs, bool *missing 436 + ) { 437 + ant_value_t fn = js_getprop_fallback(js, receiver, name); 438 + if (missing) *missing = !is_callable(fn); 439 + if (!is_callable(fn)) return js_mkundef(); 440 + return sv_vm_call(js->vm, js, fn, receiver, args, nargs, NULL, false); 441 + } 442 + 443 + static ant_value_t make_async_wrap_iter(ant_t *js, ant_value_t source, int kind, ant_value_t cb) { 444 + ant_value_t iter = js_mkobj(js); 445 + js_set_proto_init(iter, g_async_wrap_iter_proto); 446 + js_set_slot_wb(js, iter, SLOT_DATA, source); 447 + js_set_slot(iter, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, 0))); 448 + js_set_slot_wb(js, iter, SLOT_CTOR, cb); 449 + js_set_slot(iter, SLOT_ENTRIES, js_mkundef()); 450 + return iter; 451 + } 452 + 453 + static ant_value_t async_wrap_advance( 454 + ant_t *js, 455 + ant_value_t iter, 456 + ant_value_t promise 457 + ); 458 + 459 + static ant_value_t async_wrap_handle_step( 460 + ant_t *js, 461 + ant_value_t iter, 462 + ant_value_t promise, 463 + ant_value_t step 464 + ); 465 + 466 + static ant_value_t async_wrap_handle_callback_result( 467 + ant_t *js, 468 + ant_value_t iter, 469 + ant_value_t promise, 470 + uint32_t kind, 471 + ant_value_t value, 472 + ant_value_t result 473 + ); 474 + 475 + static ant_value_t async_wrap_on_step(ant_t *js, ant_value_t *args, int nargs) { 476 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 477 + ant_value_t iter = js_get(js, state, "iter"); 478 + ant_value_t promise = js_get(js, state, "promise"); 479 + ant_value_t step = nargs > 0 ? args[0] : js_mkundef(); 480 + return async_wrap_handle_step(js, iter, promise, step); 481 + } 482 + 483 + static ant_value_t async_wrap_on_reject(ant_t *js, ant_value_t *args, int nargs) { 484 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 485 + ant_value_t promise = js_get(js, state, "promise"); 486 + js_reject_promise(js, promise, nargs > 0 ? args[0] : js_mkundef()); 487 + return js_mkundef(); 488 + } 489 + 490 + static ant_value_t async_wrap_on_callback(ant_t *js, ant_value_t *args, int nargs) { 491 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 492 + ant_value_t iter = js_get(js, state, "iter"); 493 + ant_value_t promise = js_get(js, state, "promise"); 494 + uint32_t kind = (uint32_t)js_getnum(js_get(js, state, "kind")); 495 + ant_value_t value = js_get(js, state, "value"); 496 + ant_value_t result = nargs > 0 ? args[0] : js_mkundef(); 497 + return async_wrap_handle_callback_result(js, iter, promise, kind, value, result); 498 + } 499 + 500 + static ant_value_t async_wrap_on_sync_value(ant_t *js, ant_value_t *args, int nargs) { 501 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 502 + ant_value_t promise = js_get(js, state, "promise"); 503 + bool done = js_truthy(js, js_get(js, state, "done")); 504 + ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); 505 + js_resolve_promise(js, promise, iter_result(js, value, done)); 506 + return js_mkundef(); 507 + } 508 + 509 + static bool async_wrap_chain_step(ant_t *js, ant_value_t iter, ant_value_t promise, ant_value_t next_result) { 510 + if (is_err(next_result)) { 511 + js_reject_promise(js, promise, next_result); 512 + return true; 513 + } 514 + 515 + if (vtype(next_result) != T_PROMISE) { 516 + async_wrap_handle_step(js, iter, promise, next_result); 517 + return true; 518 + } 519 + 520 + ant_value_t state = js_mkobj(js); 521 + js_set(js, state, "iter", iter); 522 + js_set(js, state, "promise", promise); 523 + ant_value_t on_resolve = js_heavy_mkfun(js, async_wrap_on_step, state); 524 + ant_value_t on_reject = js_heavy_mkfun(js, async_wrap_on_reject, state); 525 + ant_value_t then_result = js_promise_then(js, next_result, on_resolve, on_reject); 526 + promise_mark_handled(then_result); 527 + return true; 528 + } 529 + 530 + static ant_value_t async_wrap_handle_inner_step( 531 + ant_t *js, 532 + ant_value_t iter, 533 + ant_value_t promise, 534 + ant_value_t step 535 + ) { 536 + if (!is_object_type(step)) { 537 + js_reject_promise(js, promise, js_mkerr_typed(js, JS_ERR_TYPE, "iterator result is not an object")); 538 + return js_mkundef(); 539 + } 540 + 541 + if (!js_truthy(js, js_getprop_fallback(js, step, "done"))) { 542 + js_resolve_promise(js, promise, iter_result(js, js_getprop_fallback(js, step, "value"), false)); 543 + return js_mkundef(); 544 + } 545 + 546 + js_set_slot(iter, SLOT_ENTRIES, js_mkundef()); 547 + async_wrap_advance(js, iter, promise); 548 + return js_mkundef(); 549 + } 550 + 551 + static ant_value_t async_wrap_on_inner_step(ant_t *js, ant_value_t *args, int nargs) { 552 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 553 + ant_value_t iter = js_get(js, state, "iter"); 554 + ant_value_t promise = js_get(js, state, "promise"); 555 + ant_value_t step = nargs > 0 ? args[0] : js_mkundef(); 556 + return async_wrap_handle_inner_step(js, iter, promise, step); 557 + } 558 + 559 + static ant_value_t async_wrap_advance_inner(ant_t *js, ant_value_t iter, ant_value_t promise) { 560 + ant_value_t inner = js_get_slot(iter, SLOT_ENTRIES); 561 + if (vtype(inner) == T_UNDEF || vtype(inner) == T_NULL) return async_wrap_advance(js, iter, promise); 562 + 563 + bool missing = false; 564 + ant_value_t next_result = async_iter_call_method(js, inner, "next", NULL, 0, &missing); 565 + if (missing) { 566 + js_set_slot(iter, SLOT_ENTRIES, js_mkundef()); 567 + return async_wrap_advance(js, iter, promise); 568 + } 569 + 570 + if (is_err(next_result)) { 571 + js_reject_promise(js, promise, next_result); 572 + return js_mkundef(); 573 + } 574 + 575 + if (vtype(next_result) == T_PROMISE) { 576 + ant_value_t state = js_mkobj(js); 577 + js_set(js, state, "iter", iter); 578 + js_set(js, state, "promise", promise); 579 + ant_value_t on_resolve = js_heavy_mkfun(js, async_wrap_on_inner_step, state); 580 + ant_value_t on_reject = js_heavy_mkfun(js, async_wrap_on_reject, state); 581 + ant_value_t then_result = js_promise_then(js, next_result, on_resolve, on_reject); 582 + promise_mark_handled(then_result); 583 + return js_mkundef(); 584 + } 585 + 586 + return async_wrap_handle_inner_step(js, iter, promise, next_result); 587 + } 588 + 589 + static ant_value_t async_wrap_handle_step( 590 + ant_t *js, 591 + ant_value_t iter, 592 + ant_value_t promise, 593 + ant_value_t step 594 + ) { 595 + if (!is_object_type(step)) { 596 + js_reject_promise(js, promise, js_mkerr_typed(js, JS_ERR_TYPE, "iterator result is not an object")); 597 + return js_mkundef(); 598 + } 599 + 600 + bool done = js_truthy(js, js_getprop_fallback(js, step, "done")); 601 + ant_value_t state_v = js_get_slot(iter, SLOT_ITER_STATE); 602 + 603 + uint32_t state = (vtype(state_v) == T_NUM) ? (uint32_t)js_getnum(state_v) : 0; 604 + uint32_t kind = ITER_STATE_KIND(state); 605 + uint32_t count = ITER_STATE_INDEX(state); 606 + ant_value_t value = js_getprop_fallback(js, step, "value"); 607 + 608 + if (kind == WRAP_FROM_SYNC && vtype(value) == T_PROMISE) { 609 + ant_value_t state_obj = js_mkobj(js); 610 + js_set(js, state_obj, "promise", promise); 611 + js_set(js, state_obj, "done", done ? js_true : js_false); 612 + 613 + ant_value_t on_resolve = js_heavy_mkfun(js, async_wrap_on_sync_value, state_obj); 614 + ant_value_t on_reject = js_heavy_mkfun(js, async_wrap_on_reject, state_obj); 615 + ant_value_t then_result = js_promise_then(js, value, on_resolve, on_reject); 616 + 617 + promise_mark_handled(then_result); 618 + return js_mkundef(); 619 + } 620 + 621 + if (done) { 622 + js_resolve_promise( 623 + js, promise, 624 + iter_result(js, kind == WRAP_FROM_SYNC ? value : js_mkundef(), true) 625 + ); 626 + return js_mkundef(); 627 + } 628 + 629 + ant_value_t cb = js_get_slot(iter, SLOT_CTOR); 630 + 631 + switch (kind) { 632 + case WRAP_MAP: { 633 + ant_value_t mapped = call_indexed_callback(js, cb, value, (double)count); 634 + if (is_err(mapped)) { 635 + js_reject_promise(js, promise, mapped); 636 + return js_mkundef(); 637 + } 638 + js_set_slot(iter, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, count + 1))); 639 + return async_wrap_handle_callback_result(js, iter, promise, kind, value, mapped); 640 + } 641 + 642 + case WRAP_FILTER: { 643 + ant_value_t test = call_indexed_callback(js, cb, value, (double)count); 644 + if (is_err(test)) { 645 + js_reject_promise(js, promise, test); 646 + return js_mkundef(); 647 + } 648 + js_set_slot(iter, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, count + 1))); 649 + return async_wrap_handle_callback_result(js, iter, promise, kind, value, test); 650 + } 651 + 652 + case WRAP_TAKE: 653 + js_set_slot(iter, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, count + 1))); 654 + js_resolve_promise(js, promise, iter_result(js, value, false)); 655 + return js_mkundef(); 656 + 657 + case WRAP_DROP: { 658 + double limit = (vtype(cb) == T_NUM) ? js_getnum(cb) : 0; 659 + js_set_slot(iter, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, count + 1))); 660 + if ((double)(count + 1) <= limit) async_wrap_advance(js, iter, promise); 661 + else js_resolve_promise(js, promise, iter_result(js, value, false)); 662 + return js_mkundef(); 663 + } 664 + 665 + case WRAP_FLATMAP: { 666 + ant_value_t mapped = call_indexed_callback(js, cb, value, (double)count); 667 + if (is_err(mapped)) { 668 + js_reject_promise(js, promise, mapped); 669 + return js_mkundef(); 670 + } 671 + js_set_slot(iter, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, count + 1))); 672 + return async_wrap_handle_callback_result(js, iter, promise, kind, value, mapped); 673 + } 674 + 675 + default: 676 + js_resolve_promise(js, promise, iter_result(js, value, false)); 677 + return js_mkundef(); 678 + } 679 + } 680 + 681 + static ant_value_t async_wrap_handle_callback_result( 682 + ant_t *js, 683 + ant_value_t iter, 684 + ant_value_t promise, 685 + uint32_t kind, 686 + ant_value_t value, 687 + ant_value_t result 688 + ) { 689 + if (vtype(result) == T_PROMISE) { 690 + ant_value_t state = js_mkobj(js); 691 + js_set(js, state, "iter", iter); 692 + js_set(js, state, "promise", promise); 693 + js_set(js, state, "kind", js_mknum((double)kind)); 694 + js_set(js, state, "value", value); 695 + ant_value_t on_resolve = js_heavy_mkfun(js, async_wrap_on_callback, state); 696 + ant_value_t on_reject = js_heavy_mkfun(js, async_wrap_on_reject, state); 697 + ant_value_t then_result = js_promise_then(js, result, on_resolve, on_reject); 698 + promise_mark_handled(then_result); 699 + return js_mkundef(); 700 + } 701 + 702 + if (kind == WRAP_MAP) { 703 + js_resolve_promise(js, promise, iter_result(js, result, false)); 704 + return js_mkundef(); 705 + } 706 + 707 + if (kind == WRAP_FILTER) { 708 + if (js_truthy(js, result)) js_resolve_promise(js, promise, iter_result(js, value, false)); 709 + else async_wrap_advance(js, iter, promise); 710 + return js_mkundef(); 711 + } 712 + 713 + if (kind == WRAP_FLATMAP) { 714 + ant_value_t iter_fn = js_get_sym(js, result, get_asyncIterator_sym()); 715 + if (!is_callable(iter_fn)) iter_fn = js_get_sym(js, result, get_iterator_sym()); 716 + if (!is_callable(iter_fn)) { 717 + js_resolve_promise(js, promise, iter_result(js, result, false)); 718 + return js_mkundef(); 719 + } 720 + 721 + ant_value_t inner = sv_vm_call(js->vm, js, iter_fn, result, NULL, 0, NULL, false); 722 + if (is_err(inner)) { 723 + js_reject_promise(js, promise, inner); 724 + return js_mkundef(); 725 + } 726 + js_set_slot_wb(js, iter, SLOT_ENTRIES, inner); 727 + return async_wrap_advance_inner(js, iter, promise); 728 + } 729 + 730 + js_resolve_promise(js, promise, iter_result(js, result, false)); 731 + return js_mkundef(); 732 + } 733 + 734 + static ant_value_t async_wrap_advance(ant_t *js, ant_value_t iter, ant_value_t promise) { 735 + ant_value_t state_v = js_get_slot(iter, SLOT_ITER_STATE); 736 + uint32_t state = (vtype(state_v) == T_NUM) ? (uint32_t)js_getnum(state_v) : 0; 737 + uint32_t kind = ITER_STATE_KIND(state); 738 + ant_value_t cb = js_get_slot(iter, SLOT_CTOR); 739 + 740 + if (kind == WRAP_TAKE) { 741 + double limit = (vtype(cb) == T_NUM) ? js_getnum(cb) : 0; 742 + if ((double)ITER_STATE_INDEX(state) >= limit) { 743 + js_resolve_promise(js, promise, iter_result(js, js_mkundef(), true)); 744 + return js_mkundef(); 745 + } 746 + } 747 + 748 + if (kind == WRAP_FLATMAP && vtype(js_get_slot(iter, SLOT_ENTRIES)) != T_UNDEF) 749 + return async_wrap_advance_inner(js, iter, promise); 750 + 751 + ant_value_t source = js_get_slot(iter, SLOT_DATA); 752 + bool missing = false; 753 + ant_value_t next_result = async_iter_call_method(js, source, "next", NULL, 0, &missing); 754 + if (missing) js_reject_promise(js, promise, js_mkerr_typed(js, JS_ERR_TYPE, "object is not async iterable")); 755 + else async_wrap_chain_step(js, iter, promise, next_result); 756 + return js_mkundef(); 757 + } 758 + 759 + static ant_value_t async_wrap_next(ant_t *js, ant_value_t *args, int nargs) { 760 + (void)args; (void)nargs; 761 + ant_value_t promise = js_mkpromise(js); 762 + async_wrap_advance(js, js->this_val, promise); 763 + return promise; 764 + } 765 + 766 + static ant_value_t async_wrap_return(ant_t *js, ant_value_t *args, int nargs) { 767 + ant_value_t source = js_get_slot(js->this_val, SLOT_DATA); 768 + bool missing = false; 769 + ant_value_t result = async_iter_call_method(js, source, "return", args, nargs, &missing); 770 + if (missing) return fulfilled_promise(js, iter_result(js, nargs > 0 ? args[0] : js_mkundef(), true)); 771 + ant_value_t state_v = js_get_slot(js->this_val, SLOT_ITER_STATE); 772 + uint32_t state = (vtype(state_v) == T_NUM) ? (uint32_t)js_getnum(state_v) : 0; 773 + if (ITER_STATE_KIND(state) == WRAP_FROM_SYNC) { 774 + ant_value_t promise = js_mkpromise(js); 775 + async_wrap_chain_step(js, js->this_val, promise, result); 776 + return promise; 777 + } 778 + return promise_from_call_result(js, result); 779 + } 780 + 781 + static ant_value_t async_wrap_throw(ant_t *js, ant_value_t *args, int nargs) { 782 + ant_value_t source = js_get_slot(js->this_val, SLOT_DATA); 783 + bool missing = false; 784 + ant_value_t result = async_iter_call_method(js, source, "throw", args, nargs, &missing); 785 + if (missing) return rejected_promise(js, nargs > 0 ? args[0] : js_mkundef()); 786 + ant_value_t state_v = js_get_slot(js->this_val, SLOT_ITER_STATE); 787 + uint32_t state = (vtype(state_v) == T_NUM) ? (uint32_t)js_getnum(state_v) : 0; 788 + if (ITER_STATE_KIND(state) == WRAP_FROM_SYNC) { 789 + ant_value_t promise = js_mkpromise(js); 790 + async_wrap_chain_step(js, js->this_val, promise, result); 791 + return promise; 792 + } 793 + return promise_from_call_result(js, result); 794 + } 795 + 796 + static ant_value_t async_iter_from(ant_t *js, ant_value_t *args, int nargs) { 797 + if (nargs < 1 || vtype(args[0]) == T_UNDEF || vtype(args[0]) == T_NULL) 798 + return js_mkerr_typed(js, JS_ERR_TYPE, "AsyncIterator.from requires an object"); 799 + 800 + ant_value_t obj = args[0]; 801 + ant_value_t iter_fn = js_get_sym(js, obj, get_asyncIterator_sym()); 802 + if (is_callable(iter_fn)) { 803 + ant_value_t iterator = sv_vm_call(js->vm, js, iter_fn, obj, NULL, 0, NULL, false); 804 + if (is_err(iterator)) return iterator; 805 + return make_async_wrap_iter(js, iterator, WRAP_PASS, js_mkundef()); 806 + } 807 + 808 + iter_fn = js_get_sym(js, obj, get_iterator_sym()); 809 + if (is_callable(iter_fn)) { 810 + ant_value_t iterator = sv_vm_call(js->vm, js, iter_fn, obj, NULL, 0, NULL, false); 811 + if (is_err(iterator)) return iterator; 812 + return make_async_wrap_iter(js, iterator, WRAP_FROM_SYNC, js_mkundef()); 813 + } 814 + 815 + ant_value_t next = js_getprop_fallback(js, obj, "next"); 816 + if (is_callable(next)) return make_async_wrap_iter(js, obj, WRAP_FROM_SYNC, js_mkundef()); 817 + 818 + return js_mkerr_typed(js, JS_ERR_TYPE, "object is not async iterable"); 819 + } 820 + 821 + static ant_value_t get_async_source_iter(ant_t *js) { 822 + ant_value_t self = js->this_val; 823 + ant_value_t next = js_getprop_fallback(js, self, "next"); 824 + if (is_callable(next)) return self; 825 + 826 + ant_value_t iter_fn = js_get_sym(js, self, get_asyncIterator_sym()); 827 + if (!is_callable(iter_fn)) return js_mkerr_typed(js, JS_ERR_TYPE, "object is not async iterable"); 828 + 829 + return sv_vm_call(js->vm, js, iter_fn, self, NULL, 0, NULL, false); 830 + } 831 + 832 + static ant_value_t async_iter_make_helper(ant_t *js, int kind, ant_value_t cb) { 833 + ant_value_t source = get_async_source_iter(js); 834 + if (is_err(source)) return source; 835 + return make_async_wrap_iter(js, source, kind, cb); 836 + } 837 + 838 + static ant_value_t async_iter_make_callable_helper( 839 + ant_t *js, 840 + ant_value_t *args, 841 + int nargs, 842 + int kind, 843 + const char *method 844 + ) { 845 + if (nargs < 1 || !is_callable(args[0])) 846 + return js_mkerr_typed(js, JS_ERR_TYPE, "%s requires a callable", method); 847 + return async_iter_make_helper(js, kind, args[0]); 848 + } 849 + 850 + static ant_value_t async_iter_make_count_helper( 851 + ant_t *js, 852 + ant_value_t *args, 853 + int nargs, 854 + int kind, 855 + const char *method 856 + ) { 857 + double limit = (nargs >= 1 && vtype(args[0]) == T_NUM) ? js_getnum(args[0]) : 0; 858 + if (limit < 0) return js_mkerr_typed(js, JS_ERR_TYPE, "%s requires a non-negative number", method); 859 + return async_iter_make_helper(js, kind, js_mknum(limit)); 860 + } 861 + 862 + static ant_value_t async_iter_map(ant_t *js, ant_value_t *args, int nargs) { 863 + return async_iter_make_callable_helper(js, args, nargs, WRAP_MAP, "AsyncIterator.prototype.map"); 864 + } 865 + 866 + static ant_value_t async_iter_filter(ant_t *js, ant_value_t *args, int nargs) { 867 + return async_iter_make_callable_helper(js, args, nargs, WRAP_FILTER, "AsyncIterator.prototype.filter"); 868 + } 869 + 870 + static ant_value_t async_iter_take(ant_t *js, ant_value_t *args, int nargs) { 871 + return async_iter_make_count_helper(js, args, nargs, WRAP_TAKE, "AsyncIterator.prototype.take"); 872 + } 873 + 874 + static ant_value_t async_iter_drop(ant_t *js, ant_value_t *args, int nargs) { 875 + return async_iter_make_count_helper(js, args, nargs, WRAP_DROP, "AsyncIterator.prototype.drop"); 876 + } 877 + 878 + static ant_value_t async_iter_flatMap(ant_t *js, ant_value_t *args, int nargs) { 879 + return async_iter_make_callable_helper(js, args, nargs, WRAP_FLATMAP, "AsyncIterator.prototype.flatMap"); 880 + } 881 + 882 + static ant_value_t async_terminal_advance(ant_t *js, ant_value_t state); 883 + static ant_value_t async_terminal_finish_callback(ant_t *js, ant_value_t state, ant_value_t result); 884 + static void async_terminal_close_and_reject(ant_t *js, ant_value_t state, ant_value_t reason); 885 + 886 + static void async_terminal_state_finalize(ant_t *js, ant_object_t *obj) { 887 + free(obj->native.ptr); 888 + obj->native.ptr = NULL; 889 + } 890 + 891 + static inline async_terminal_state_t *async_terminal_state(ant_value_t state) { 892 + if (!js_check_native_tag(state, ASYNC_TERMINAL_STATE_TAG)) return NULL; 893 + return (async_terminal_state_t *)js_get_native_ptr(state); 894 + } 895 + 896 + static inline int async_terminal_mode(ant_value_t state) { 897 + async_terminal_state_t *st = async_terminal_state(state); 898 + return st ? st->mode : ASYNC_TERM_TOARRAY; 899 + } 900 + 901 + static inline double async_terminal_index(ant_value_t state) { 902 + async_terminal_state_t *st = async_terminal_state(state); 903 + return st ? st->index : 0; 904 + } 905 + 906 + static inline bool async_terminal_has_acc(ant_value_t state) { 907 + async_terminal_state_t *st = async_terminal_state(state); 908 + return st && st->has_acc; 909 + } 910 + 911 + static ant_value_t async_terminal_on_reject(ant_t *js, ant_value_t *args, int nargs) { 912 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 913 + ant_value_t promise = js_get_slot(state, SLOT_CTOR); 914 + js_reject_promise(js, promise, nargs > 0 ? args[0] : js_mkundef()); 915 + return js_mkundef(); 916 + } 917 + 918 + static ant_value_t async_terminal_on_callback_reject(ant_t *js, ant_value_t *args, int nargs) { 919 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 920 + async_terminal_close_and_reject(js, state, nargs > 0 ? args[0] : js_mkundef()); 921 + return js_mkundef(); 922 + } 923 + 924 + static ant_value_t async_terminal_on_callback(ant_t *js, ant_value_t *args, int nargs) { 925 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 926 + ant_value_t result = nargs > 0 ? args[0] : js_mkundef(); 927 + return async_terminal_finish_callback(js, state, result); 928 + } 929 + 930 + static ant_value_t async_terminal_on_close(ant_t *js, ant_value_t *args, int nargs) { 931 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 932 + ant_value_t promise = js_get_slot(state, SLOT_CTOR); 933 + js_resolve_promise(js, promise, js_get_slot(state, SLOT_AUX)); 934 + return js_mkundef(); 935 + } 936 + 937 + static ant_value_t async_terminal_on_close_reject(ant_t *js, ant_value_t *args, int nargs) { 938 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 939 + ant_value_t promise = js_get_slot(state, SLOT_CTOR); 940 + js_reject_promise(js, promise, js_get_slot(state, SLOT_AUX)); 941 + return js_mkundef(); 942 + } 943 + 944 + static void async_terminal_close_and_resolve(ant_t *js, ant_value_t state, ant_value_t value) { 945 + ant_value_t promise = js_get_slot(state, SLOT_CTOR); 946 + ant_value_t iter = js_get_slot(state, SLOT_DATA); 947 + js_set_slot_wb(js, state, SLOT_AUX, value); 948 + 949 + bool missing = false; 950 + ant_value_t result = async_iter_call_method(js, iter, "return", NULL, 0, &missing); 951 + if (missing) { 952 + js_resolve_promise(js, promise, value); 953 + return; 954 + } 955 + 956 + if (is_err(result)) { 957 + js_reject_promise(js, promise, result); 958 + return; 959 + } 960 + 961 + if (vtype(result) == T_PROMISE) { 962 + ant_value_t on_resolve = js_heavy_mkfun(js, async_terminal_on_close, state); 963 + ant_value_t on_reject = js_heavy_mkfun(js, async_terminal_on_reject, state); 964 + ant_value_t then_result = js_promise_then(js, result, on_resolve, on_reject); 965 + promise_mark_handled(then_result); 966 + return; 967 + } 968 + 969 + js_resolve_promise(js, promise, value); 970 + } 971 + 972 + static void async_terminal_close_and_reject(ant_t *js, ant_value_t state, ant_value_t reason) { 973 + ant_value_t promise = js_get_slot(state, SLOT_CTOR); 974 + ant_value_t iter = js_get_slot(state, SLOT_DATA); 975 + js_set_slot_wb(js, state, SLOT_AUX, reason); 976 + 977 + bool missing = false; 978 + ant_value_t result = async_iter_call_method(js, iter, "return", NULL, 0, &missing); 979 + if (missing) { 980 + js_reject_promise(js, promise, reason); 981 + return; 982 + } 983 + 984 + if (is_err(result)) { 985 + js_reject_promise(js, promise, result); 986 + return; 987 + } 988 + 989 + if (vtype(result) == T_PROMISE) { 990 + ant_value_t on_resolve = js_heavy_mkfun(js, async_terminal_on_close_reject, state); 991 + ant_value_t on_reject = js_heavy_mkfun(js, async_terminal_on_reject, state); 992 + ant_value_t then_result = js_promise_then(js, result, on_resolve, on_reject); 993 + promise_mark_handled(then_result); 994 + return; 995 + } 996 + 997 + js_reject_promise(js, promise, reason); 998 + } 999 + 1000 + static bool async_terminal_apply_callback_result(ant_t *js, ant_value_t state, ant_value_t result) { 1001 + int mode = async_terminal_mode(state); 1002 + 1003 + if (mode == ASYNC_TERM_REDUCE) { 1004 + js_set_slot_wb(js, state, SLOT_SET, result); 1005 + async_terminal_state_t *st = async_terminal_state(state); 1006 + if (st) st->has_acc = true; 1007 + return true; 1008 + } 1009 + 1010 + switch (mode) { 1011 + case ASYNC_TERM_EVERY: 1012 + if (!js_truthy(js, result)) { 1013 + async_terminal_close_and_resolve(js, state, js_false); 1014 + return false; 1015 + } 1016 + return true; 1017 + case ASYNC_TERM_SOME: 1018 + if (js_truthy(js, result)) { 1019 + async_terminal_close_and_resolve(js, state, js_true); 1020 + return false; 1021 + } 1022 + return true; 1023 + case ASYNC_TERM_FIND: 1024 + if (js_truthy(js, result)) { 1025 + async_terminal_close_and_resolve(js, state, js_get_slot(state, SLOT_AUX)); 1026 + return false; 1027 + } 1028 + return true; 1029 + default: 1030 + return true; 1031 + } 1032 + } 1033 + 1034 + static bool async_terminal_handle_step(ant_t *js, ant_value_t state, ant_value_t step) { 1035 + ant_value_t promise = js_get_slot(state, SLOT_CTOR); 1036 + 1037 + if (!is_object_type(step)) { 1038 + js_reject_promise(js, promise, js_mkerr_typed(js, JS_ERR_TYPE, "iterator result is not an object")); 1039 + return false; 1040 + } 1041 + 1042 + int mode = async_terminal_mode(state); 1043 + ant_value_t done = js_getprop_fallback(js, step, "done"); 1044 + ant_value_t value = js_getprop_fallback(js, step, "value"); 1045 + 1046 + if (js_truthy(js, done)) { 1047 + switch (mode) { 1048 + case ASYNC_TERM_EVERY: js_resolve_promise(js, promise, js_true); break; 1049 + case ASYNC_TERM_SOME: js_resolve_promise(js, promise, js_false); break; 1050 + 1051 + case ASYNC_TERM_FIND: js_resolve_promise(js, promise, js_mkundef()); break; 1052 + case ASYNC_TERM_FOREACH: js_resolve_promise(js, promise, js_mkundef()); break; 1053 + 1054 + case ASYNC_TERM_REDUCE: 1055 + if (!async_terminal_has_acc(state)) { 1056 + js_reject_promise(js, promise, js_mkerr_typed(js, JS_ERR_TYPE, "reduce of empty iterator with no initial value")); 1057 + } else js_resolve_promise(js, promise, js_get_slot(state, SLOT_SET)); 1058 + break; 1059 + 1060 + default: 1061 + js_resolve_promise(js, promise, js_get_slot(state, SLOT_ENTRIES)); 1062 + break; 1063 + } 1064 + 1065 + return false; 1066 + } 1067 + 1068 + double index = async_terminal_index(state); 1069 + async_terminal_state_t *st = async_terminal_state(state); 1070 + if (st) st->index = index + 1; 1071 + 1072 + if (mode == ASYNC_TERM_TOARRAY) { 1073 + js_arr_push(js, js_get_slot(state, SLOT_ENTRIES), value); 1074 + return true; 1075 + } 1076 + 1077 + ant_value_t fn = js_get_slot(state, SLOT_MAP); 1078 + if (!is_callable(fn)) { 1079 + js_reject_promise(js, promise, js_mkerr_typed(js, JS_ERR_TYPE, "callback is not callable")); 1080 + return false; 1081 + } 1082 + 1083 + if (mode == ASYNC_TERM_REDUCE) { 1084 + if (!async_terminal_has_acc(state)) { 1085 + js_set_slot_wb(js, state, SLOT_SET, value); 1086 + if (st) st->has_acc = true; 1087 + return true; 1088 + } 1089 + 1090 + ant_value_t call_args[3] = { js_get_slot(state, SLOT_SET), value, js_mknum(index) }; 1091 + ant_value_t next_acc = sv_vm_call(js->vm, js, fn, js_mkundef(), call_args, 3, NULL, false); 1092 + if (is_err(next_acc)) { 1093 + async_terminal_close_and_reject(js, state, next_acc); 1094 + return false; 1095 + } 1096 + 1097 + if (vtype(next_acc) == T_PROMISE) { 1098 + ant_value_t on_resolve = js_heavy_mkfun(js, async_terminal_on_callback, state); 1099 + ant_value_t on_reject = js_heavy_mkfun(js, async_terminal_on_callback_reject, state); 1100 + ant_value_t then_result = js_promise_then(js, next_acc, on_resolve, on_reject); 1101 + promise_mark_handled(then_result); 1102 + return false; 1103 + } 1104 + return async_terminal_apply_callback_result(js, state, next_acc); 1105 + } 1106 + 1107 + ant_value_t result = call_indexed_callback(js, fn, value, (double)index); 1108 + if (is_err(result)) { 1109 + async_terminal_close_and_reject(js, state, result); 1110 + return false; 1111 + } 1112 + 1113 + js_set_slot_wb(js, state, SLOT_AUX, value); 1114 + if (vtype(result) == T_PROMISE) { 1115 + ant_value_t on_resolve = js_heavy_mkfun(js, async_terminal_on_callback, state); 1116 + ant_value_t on_reject = js_heavy_mkfun(js, async_terminal_on_callback_reject, state); 1117 + ant_value_t then_result = js_promise_then(js, result, on_resolve, on_reject); 1118 + promise_mark_handled(then_result); 1119 + return false; 1120 + } 1121 + 1122 + return async_terminal_apply_callback_result(js, state, result); 1123 + } 1124 + 1125 + static ant_value_t async_terminal_on_step(ant_t *js, ant_value_t *args, int nargs) { 1126 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 1127 + ant_value_t step = nargs > 0 ? args[0] : js_mkundef(); 1128 + if (async_terminal_handle_step(js, state, step)) return async_terminal_advance(js, state); 1129 + return js_mkundef(); 1130 + } 1131 + 1132 + static ant_value_t async_terminal_finish_callback( 1133 + ant_t *js, 1134 + ant_value_t state, 1135 + ant_value_t result 1136 + ) { 1137 + if (vtype(result) == T_PROMISE) { 1138 + ant_value_t on_resolve = js_heavy_mkfun(js, async_terminal_on_callback, state); 1139 + ant_value_t on_reject = js_heavy_mkfun(js, async_terminal_on_callback_reject, state); 1140 + ant_value_t then_result = js_promise_then(js, result, on_resolve, on_reject); 1141 + promise_mark_handled(then_result); 1142 + return js_mkundef(); 1143 + } 1144 + 1145 + if (async_terminal_apply_callback_result(js, state, result)) return async_terminal_advance(js, state); 1146 + return js_mkundef(); 1147 + } 1148 + 1149 + static ant_value_t async_terminal_advance(ant_t *js, ant_value_t state) { 1150 + for (;;) { 1151 + ant_value_t iter = js_get_slot(state, SLOT_DATA); 1152 + ant_value_t promise = js_get_slot(state, SLOT_CTOR); 1153 + bool missing = false; 1154 + ant_value_t next_result = async_iter_call_method(js, iter, "next", NULL, 0, &missing); 1155 + 1156 + if (missing) { 1157 + js_reject_promise(js, promise, js_mkerr_typed(js, JS_ERR_TYPE, "object is not async iterable")); 1158 + return js_mkundef(); 1159 + } 1160 + 1161 + if (is_err(next_result)) { 1162 + js_reject_promise(js, promise, next_result); 1163 + return js_mkundef(); 1164 + } 1165 + 1166 + if (vtype(next_result) == T_PROMISE) { 1167 + ant_value_t on_resolve = js_heavy_mkfun(js, async_terminal_on_step, state); 1168 + ant_value_t on_reject = js_heavy_mkfun(js, async_terminal_on_reject, state); 1169 + ant_value_t then_result = js_promise_then(js, next_result, on_resolve, on_reject); 1170 + promise_mark_handled(then_result); 1171 + return js_mkundef(); 1172 + } 1173 + 1174 + if (!async_terminal_handle_step(js, state, next_result)) return js_mkundef(); 1175 + }} 1176 + 1177 + static ant_value_t async_iter_terminal(ant_t *js, ant_value_t *args, int nargs, int mode) { 1178 + if (mode != ASYNC_TERM_TOARRAY && (nargs < 1 || !is_callable(args[0]))) 1179 + return js_mkerr_typed(js, JS_ERR_TYPE, "AsyncIterator helper requires a callable"); 1180 + 1181 + ant_value_t iter = get_async_source_iter(js); 1182 + if (is_err(iter)) return rejected_promise(js, iter); 1183 + 1184 + ant_value_t promise = js_mkpromise(js); 1185 + ant_value_t state = js_mkobj(js); 1186 + async_terminal_state_t *st = calloc(1, sizeof(*st)); 1187 + 1188 + if (!st) { 1189 + js_reject_promise(js, promise, js_mkerr(js, "out of memory")); 1190 + return promise; 1191 + } 1192 + 1193 + st->mode = mode; 1194 + st->index = 0; 1195 + st->has_acc = (mode == ASYNC_TERM_REDUCE && nargs > 1); 1196 + 1197 + js_set_native_tag(state, ASYNC_TERMINAL_STATE_TAG); 1198 + js_set_native_ptr(state, st); 1199 + js_set_finalizer(state, async_terminal_state_finalize); 1200 + 1201 + js_set_slot_wb(js, state, SLOT_DATA, iter); 1202 + js_set_slot_wb(js, state, SLOT_CTOR, promise); 1203 + js_set_slot_wb(js, state, SLOT_MAP, mode == ASYNC_TERM_TOARRAY ? js_mkundef() : args[0]); 1204 + js_set_slot_wb(js, state, SLOT_ENTRIES, js_mkarr(js)); 1205 + js_set_slot_wb(js, state, SLOT_SET, (mode == ASYNC_TERM_REDUCE && nargs > 1) ? args[1] : js_mkundef()); 1206 + js_set_slot(state, SLOT_AUX, js_mkundef()); 1207 + 1208 + async_terminal_advance(js, state); 1209 + return promise; 1210 + } 1211 + 1212 + static ant_value_t async_iter_every(ant_t *js, ant_value_t *args, int nargs) { 1213 + return async_iter_terminal(js, args, nargs, ASYNC_TERM_EVERY); 1214 + } 1215 + 1216 + static ant_value_t async_iter_some(ant_t *js, ant_value_t *args, int nargs) { 1217 + return async_iter_terminal(js, args, nargs, ASYNC_TERM_SOME); 1218 + } 1219 + 1220 + static ant_value_t async_iter_find(ant_t *js, ant_value_t *args, int nargs) { 1221 + return async_iter_terminal(js, args, nargs, ASYNC_TERM_FIND); 1222 + } 1223 + 1224 + static ant_value_t async_iter_forEach(ant_t *js, ant_value_t *args, int nargs) { 1225 + return async_iter_terminal(js, args, nargs, ASYNC_TERM_FOREACH); 1226 + } 1227 + 1228 + static ant_value_t async_iter_reduce(ant_t *js, ant_value_t *args, int nargs) { 1229 + return async_iter_terminal(js, args, nargs, ASYNC_TERM_REDUCE); 1230 + } 1231 + 1232 + static ant_value_t async_iter_toArray(ant_t *js, ant_value_t *args, int nargs) { 1233 + return async_iter_terminal(js, args, nargs, ASYNC_TERM_TOARRAY); 1234 + } 1235 + 384 1236 void init_iterator_module(void) { 385 1237 ant_t *js = rt->js; 386 1238 ant_value_t g = js_glob(js); ··· 408 1260 js_mkprop_fast(js, ctor_obj, "prototype", 9, iter_proto); 409 1261 js_mkprop_fast(js, ctor_obj, "name", 4, js_mkstr(js, "Iterator", 8)); 410 1262 js_set_descriptor(js, ctor_obj, "name", 4, 0); 411 - js_set(js, ctor_obj, "from", js_mkfun(iter_from)); 412 - 1263 + 413 1264 ant_value_t ctor = js_obj_to_func(ctor_obj); 1265 + js_set(js, ctor, "from", js_mkfun(iter_from)); 414 1266 js_set(js, iter_proto, "constructor", ctor); 415 1267 js_set(js, g, "Iterator", ctor); 1268 + 1269 + ant_value_t async_iter_proto = js_mkobj(js); 1270 + js->sym.async_iterator_proto = async_iter_proto; 1271 + js_set_proto_init(async_iter_proto, js->sym.object_proto); 1272 + js_set_sym(js, async_iter_proto, get_asyncIterator_sym(), js_mkfun(sym_this_cb)); 1273 + js_set_sym(js, async_iter_proto, get_toStringTag_sym(), js_mkstr(js, "AsyncIterator", 13)); 1274 + 1275 + ant_value_t async_ctor_obj = js_mkobj(js); 1276 + js_set_slot(async_ctor_obj, SLOT_CFUNC, js_mkfun(async_iter_ctor)); 1277 + js_mkprop_fast(js, async_ctor_obj, "prototype", 9, async_iter_proto); 1278 + js_mkprop_fast(js, async_ctor_obj, "name", 4, js_mkstr(js, "AsyncIterator", 13)); 1279 + js_set_descriptor(js, async_ctor_obj, "name", 4, 0); 1280 + 1281 + ant_value_t async_ctor = js_obj_to_func(async_ctor_obj); 1282 + js_set(js, async_iter_proto, "constructor", async_ctor); 1283 + js_set(js, g, "AsyncIterator", async_ctor); 1284 + 1285 + g_async_wrap_iter_proto = js_mkobj(js); 1286 + js_set_proto_init(g_async_wrap_iter_proto, async_iter_proto); 1287 + js_set(js, g_async_wrap_iter_proto, "next", js_mkfun(async_wrap_next)); 1288 + js_set(js, g_async_wrap_iter_proto, "return", js_mkfun(async_wrap_return)); 1289 + js_set(js, g_async_wrap_iter_proto, "throw", js_mkfun(async_wrap_throw)); 1290 + } 1291 + 1292 + void init_async_iterator_helpers(void) { 1293 + ant_t *js = rt->js; 1294 + ant_value_t g = js_glob(js); 1295 + 1296 + ant_value_t ctor = js_get(js, g, "AsyncIterator"); 1297 + ant_value_t proto = js->sym.async_iterator_proto; 1298 + 1299 + js_set(js, ctor, "from", js_mkfun(async_iter_from)); 1300 + js_set(js, proto, "map", js_mkfun(async_iter_map)); 1301 + js_set(js, proto, "filter", js_mkfun(async_iter_filter)); 1302 + js_set(js, proto, "take", js_mkfun(async_iter_take)); 1303 + js_set(js, proto, "drop", js_mkfun(async_iter_drop)); 1304 + js_set(js, proto, "flatMap", js_mkfun(async_iter_flatMap)); 1305 + js_set(js, proto, "every", js_mkfun(async_iter_every)); 1306 + js_set(js, proto, "some", js_mkfun(async_iter_some)); 1307 + js_set(js, proto, "find", js_mkfun(async_iter_find)); 1308 + js_set(js, proto, "forEach", js_mkfun(async_iter_forEach)); 1309 + js_set(js, proto, "reduce", js_mkfun(async_iter_reduce)); 1310 + js_set(js, proto, "toArray", js_mkfun(async_iter_toArray)); 416 1311 }
+3
src/modules/symbol.c
··· 305 305 js->sym.array_iterator_proto = js_mkundef(); 306 306 js->sym.string_iterator_proto = js_mkundef(); 307 307 js->sym.generator_proto = js_mkundef(); 308 + js->sym.async_iterator_proto = js_mkundef(); 308 309 309 310 gc_register_root(&js->sym.iterator_proto); 310 311 gc_register_root(&js->sym.array_iterator_proto); 311 312 gc_register_root(&js->sym.string_iterator_proto); 312 313 gc_register_root(&js->sym.generator_proto); 314 + gc_register_root(&js->sym.async_iterator_proto); 313 315 314 316 #define INIT_SYM(name, desc) g_##name = js_mksym_well_known(js, desc); 315 317 WELLKNOWN_SYMBOLS(INIT_SYM) ··· 380 382 mark(js, js->sym.array_iterator_proto); 381 383 mark(js, js->sym.string_iterator_proto); 382 384 mark(js, js->sym.generator_proto); 385 + mark(js, js->sym.async_iterator_proto); 383 386 384 387 #define GC_SYM(name, _desc) mark(js, g_##name); 385 388 WELLKNOWN_SYMBOLS(GC_SYM)
+56 -5
src/silver/ast.c
··· 210 210 return tok >= TOK_IDENTIFIER && tok < TOK_IDENT_LIKE_END; 211 211 } 212 212 213 + static inline bool is_using_tok(P) { 214 + return TOK == TOK_IDENTIFIER && TLEN == 5 && memcmp(tok_str(p), "using", 5) == 0; 215 + } 216 + 213 217 static inline bool sv_strict_forbidden_binding_ident(const char *s, uint32_t len) { 214 218 return is_eval_or_arguments_name(s, len) || is_strict_reserved_name(s, len); 215 219 } ··· 1172 1176 n->flags = 1; 1173 1177 return n; 1174 1178 } 1179 + if (la == TOK_THROW) { 1180 + CONSUME(); 1181 + sv_ast_t *n = mk(N_THROW); 1182 + n->right = parse_assign(p); 1183 + return n; 1184 + } 1175 1185 return parse_postfix(p); 1176 1186 } 1177 1187 ··· 1524 1534 sv_strict_check_binding_ident(p, decl->left->str, decl->left->len); 1525 1535 CONSUME(); 1526 1536 } 1527 - 1528 1537 if (NEXT() == TOK_ASSIGN) { 1529 1538 CONSUME(); 1530 1539 decl->right = parse_assign(p); 1531 - } else if (kind == SV_VAR_CONST && !allow_uninit_const) { 1540 + } else if ((kind == SV_VAR_CONST || kind == SV_VAR_USING || kind == SV_VAR_AWAIT_USING) && !allow_uninit_const) { 1532 1541 SV_MKERR_TYPED(JS, JS_ERR_SYNTAX, "Missing initializer in const declaration"); 1533 1542 } 1534 1543 sv_ast_list_push(&var->args, decl); ··· 1871 1880 [TOK_IMPORT] = &&l_import, 1872 1881 }; 1873 1882 1883 + if (is_using_tok(p)) { 1884 + CONSUME(); 1885 + sv_ast_t *n = parse_var_decl(p, SV_VAR_USING, false); 1886 + if (NEXT() == TOK_SEMICOLON) CONSUME(); 1887 + return n; 1888 + } 1889 + 1890 + if (TOK == TOK_AWAIT) { 1891 + sv_lexer_state_t saved; 1892 + sv_lexer_save_state(&p->lx, &saved); 1893 + CONSUME(); 1894 + NEXT(); 1895 + if (is_using_tok(p)) { 1896 + CONSUME(); 1897 + sv_ast_t *n = parse_var_decl(p, SV_VAR_AWAIT_USING, false); 1898 + if (NEXT() == TOK_SEMICOLON) CONSUME(); 1899 + return n; 1900 + } 1901 + sv_lexer_restore_state(&p->lx, &saved); 1902 + } 1903 + 1874 1904 if (TOK < TOK_MAX && dispatch[TOK]) 1875 1905 goto *dispatch[TOK]; 1876 1906 goto l_expr_stmt; ··· 1944 1974 sv_ast_t *init_node = NULL; 1945 1975 1946 1976 NEXT(); 1947 - if (TOK == TOK_VAR || TOK == TOK_LET || TOK == TOK_CONST) { 1977 + if (TOK == TOK_AWAIT) { 1978 + sv_lexer_state_t saved; 1979 + sv_lexer_save_state(&p->lx, &saved); 1980 + CONSUME(); 1981 + NEXT(); 1982 + if (is_using_tok(p)) { 1983 + CONSUME(); 1984 + p->no_in = true; 1985 + init_node = parse_var_decl(p, SV_VAR_AWAIT_USING, true); 1986 + p->no_in = false; 1987 + } else sv_lexer_restore_state(&p->lx, &saved); 1988 + } 1989 + 1990 + if (!init_node && is_using_tok(p)) { 1991 + CONSUME(); 1992 + p->no_in = true; 1993 + init_node = parse_var_decl(p, SV_VAR_USING, true); 1994 + p->no_in = false; 1995 + } else if (!init_node && (TOK == TOK_VAR || TOK == TOK_LET || TOK == TOK_CONST)) { 1948 1996 sv_var_kind_t kind = ( 1949 1997 TOK == TOK_VAR) ? SV_VAR_VAR : 1950 1998 (TOK == TOK_LET) ? SV_VAR_LET : SV_VAR_CONST; ··· 1952 2000 p->no_in = true; 1953 2001 init_node = parse_var_decl(p, kind, true); 1954 2002 p->no_in = false; 1955 - } else if (TOK != TOK_SEMICOLON) { 2003 + } else if (!init_node && TOK != TOK_SEMICOLON) { 1956 2004 p->no_in = true; 1957 2005 init_node = parse_expr(p); 1958 2006 p->no_in = false; ··· 1979 2027 return n; 1980 2028 } 1981 2029 1982 - if (init_node && init_node->type == N_VAR && init_node->var_kind == SV_VAR_CONST) { 2030 + if (init_node && init_node->type == N_VAR && ( 2031 + init_node->var_kind == SV_VAR_CONST || 2032 + init_node->var_kind == SV_VAR_USING || 2033 + init_node->var_kind == SV_VAR_AWAIT_USING)) { 1983 2034 for (int i = 0; i < init_node->args.count; i++) { 1984 2035 sv_ast_t *decl = init_node->args.items[i]; 1985 2036 if (decl && decl->type == N_VARDECL && !decl->right) {
+3
src/silver/compile_ctx.c
··· 41 41 ctx->strict_args_local = -1; 42 42 ctx->new_target_local = -1; 43 43 ctx->super_local = -1; 44 + ctx->using_stack_local = -1; 44 45 ctx->line_table = line_table; 45 46 } 46 47 ··· 65 66 ctx->strict_args_local = -1; 66 67 ctx->new_target_local = -1; 67 68 ctx->super_local = -1; 69 + ctx->using_stack_local = -1; 68 70 ctx->param_count = node ? node->args.count : 0; 69 71 } 70 72 ··· 79 81 free(ctx->srcpos); 80 82 free(ctx->slot_types); 81 83 free(ctx->deferred_exports); 84 + free(ctx->using_cleanups); 82 85 83 86 const_dedup_entry_t *entry; 84 87 const_dedup_entry_t *tmp;
+319 -47
src/silver/compiler.c
··· 1164 1164 if (!decl_node) continue; 1165 1165 1166 1166 if (decl_node->type == N_VAR && decl_node->var_kind != SV_VAR_VAR) { 1167 - bool is_const = (decl_node->var_kind == SV_VAR_CONST); 1167 + bool is_const = 1168 + (decl_node->var_kind == SV_VAR_CONST || 1169 + decl_node->var_kind == SV_VAR_USING || 1170 + decl_node->var_kind == SV_VAR_AWAIT_USING); 1168 1171 int lb = c->local_count; 1169 1172 for (int j = 0; j < decl_node->args.count; j++) { 1170 1173 sv_ast_t *decl = decl_node->args.items[j]; ··· 1503 1506 else emit_op(c, OP_UNDEF); 1504 1507 emit_op(c, OP_YIELD); 1505 1508 } 1509 + break; 1510 + 1511 + case N_THROW: 1512 + compile_expr(c, node->right); 1513 + emit_op(c, OP_THROW); 1506 1514 break; 1507 1515 1508 1516 case N_TAGGED_TEMPLATE: { ··· 2777 2785 if (consume_source) emit_op(c, OP_POP); 2778 2786 } 2779 2787 2780 - void compile_array_destructure(sv_compiler_t *c, sv_ast_t *pat, 2781 - bool keep) { 2788 + void compile_array_destructure(sv_compiler_t *c, sv_ast_t *pat, bool keep) { 2782 2789 compile_destructure_pattern(c, pat, keep, true, DESTRUCTURE_ASSIGN, SV_VAR_LET); 2783 2790 } 2784 2791 2785 - void compile_object_destructure(sv_compiler_t *c, sv_ast_t *pat, 2786 - bool keep) { 2792 + void compile_object_destructure(sv_compiler_t *c, sv_ast_t *pat, bool keep) { 2787 2793 compile_destructure_pattern(c, pat, keep, true, DESTRUCTURE_ASSIGN, SV_VAR_LET); 2788 2794 } 2789 2795 2790 2796 static bool is_tail_callable(sv_compiler_t *c, sv_ast_t *node) { 2791 2797 if (c->try_depth > 0) return false; 2798 + if (c->using_cleanup_count > 0) return false; 2792 2799 if (node->type != N_CALL) return false; 2793 2800 if (call_has_spread_arg(node)) return false; 2801 + 2794 2802 sv_ast_t *callee = node->left; 2795 - if (callee->type == N_IDENT && callee->len == 5 && memcmp(callee->str, "super", 5) == 0) return false; 2803 + if ( 2804 + callee->type == N_IDENT && 2805 + callee->len == 5 && memcmp(callee->str, "super", 5) == 0 2806 + ) return false; 2807 + 2796 2808 return true; 2797 2809 } 2798 2810 2811 + static void emit_using_dispose_call( 2812 + sv_compiler_t *c, 2813 + int stack_local, 2814 + int completion_local, 2815 + bool is_async, 2816 + bool suppressed_completion 2817 + ) { 2818 + emit_get_local(c, stack_local); 2819 + if (suppressed_completion) { 2820 + emit_get_local(c, completion_local); 2821 + } 2822 + if (is_async && suppressed_completion) emit_op(c, OP_USING_DISPOSE_ASYNC_SUPPRESSED); 2823 + else if (is_async) emit_op(c, OP_USING_DISPOSE_ASYNC); 2824 + else if (suppressed_completion) emit_op(c, OP_USING_DISPOSE_SUPPRESSED); 2825 + else emit_op(c, OP_USING_DISPOSE); 2826 + if (is_async) emit_op(c, OP_AWAIT); 2827 + } 2828 + 2829 + static void emit_using_cleanups_to_depth(sv_compiler_t *c, int target_depth) { 2830 + for (int i = c->using_cleanup_count - 1; i >= 0; i--) { 2831 + sv_using_cleanup_t *cleanup = &c->using_cleanups[i]; 2832 + if (cleanup->scope_depth <= target_depth) break; 2833 + emit_using_dispose_call(c, cleanup->stack_local, -1, cleanup->is_async, false); 2834 + emit_op(c, OP_POP); 2835 + }} 2836 + 2837 + static void emit_return_from_stack(sv_compiler_t *c) { 2838 + emit_using_cleanups_to_depth(c, -1); 2839 + emit_close_upvals(c); 2840 + emit_op(c, OP_RETURN); 2841 + } 2842 + 2799 2843 static void compile_tail_call(sv_compiler_t *c, sv_ast_t *node) { 2800 2844 sv_ast_t *callee = node->left; 2801 2845 2802 2846 if (callee->type == N_OPTIONAL) { 2803 2847 compile_call(c, node); 2804 - emit_close_upvals(c); 2805 - emit_op(c, OP_RETURN); 2848 + emit_return_from_stack(c); 2806 2849 return; 2807 2850 } 2808 2851 ··· 2826 2869 return; 2827 2870 } 2828 2871 2829 - if (expr->type == N_AWAIT && c->is_async && c->try_depth == 0 && !c->is_tla) { 2872 + if (expr->type == N_AWAIT && c->is_async && c->try_depth == 0 && !c->is_tla && 2873 + c->using_cleanup_count == 0) { 2830 2874 compile_expr(c, expr->right); 2831 - emit_close_upvals(c); 2832 - emit_op(c, OP_RETURN); 2875 + emit_return_from_stack(c); 2833 2876 return; 2834 2877 } 2835 2878 ··· 2839 2882 } 2840 2883 2841 2884 compile_expr(c, expr); 2842 - emit_close_upvals(c); 2843 - emit_op(c, OP_RETURN); 2885 + emit_return_from_stack(c); 2844 2886 } 2845 2887 2846 2888 void compile_stmts(sv_compiler_t *c, sv_ast_list_t *list) { 2847 - for (int i = 0; i < list->count; i++) 2848 - compile_stmt(c, list->items[i]); 2889 + for (int i = 0; i < list->count; i++) compile_stmt(c, list->items[i]); 2890 + } 2891 + 2892 + static bool stmt_list_has_using_decl(sv_ast_list_t *list, bool *has_await_using) { 2893 + bool found = false; 2894 + for (int i = 0; i < list->count; i++) { 2895 + sv_ast_t *node = list->items[i]; 2896 + if (!node) continue; 2897 + 2898 + sv_ast_t *decl = (node->type == N_EXPORT) ? node->left : node; 2899 + if (!decl || decl->type != N_VAR) continue; 2900 + 2901 + if (decl->var_kind == SV_VAR_USING || decl->var_kind == SV_VAR_AWAIT_USING) { 2902 + if (decl->var_kind == SV_VAR_AWAIT_USING && has_await_using) *has_await_using = true; 2903 + found = true; 2904 + } 2905 + } 2906 + 2907 + return found; 2908 + } 2909 + 2910 + static void emit_empty_disposal_stack(sv_compiler_t *c) { 2911 + emit_op(c, OP_ARRAY); 2912 + emit_u16(c, 0); 2913 + } 2914 + 2915 + static void emit_using_push(sv_compiler_t *c, bool is_async) { 2916 + emit_op(c, is_async ? OP_USING_PUSH_ASYNC : OP_USING_PUSH); 2917 + } 2918 + 2919 + static void pop_using_cleanup(sv_compiler_t *c) { 2920 + if (c->using_cleanup_count > 0) c->using_cleanup_count--; 2921 + } 2922 + 2923 + static void emit_dispose_resource(sv_compiler_t *c, bool is_async) { 2924 + emit_op(c, is_async ? OP_DISPOSE_RESOURCE_ASYNC : OP_DISPOSE_RESOURCE); 2925 + if (is_async) emit_op(c, OP_AWAIT); 2926 + } 2927 + 2928 + static void push_using_cleanup( 2929 + sv_compiler_t *c, 2930 + int stack_local, 2931 + int scope_depth, 2932 + bool is_async 2933 + ) { 2934 + if (c->using_cleanup_count >= c->using_cleanup_cap) { 2935 + int cap = c->using_cleanup_cap ? c->using_cleanup_cap * 2 : 4; 2936 + c->using_cleanups = realloc(c->using_cleanups, (size_t)cap * sizeof(sv_using_cleanup_t)); 2937 + c->using_cleanup_cap = cap; 2938 + } 2939 + 2940 + c->using_cleanups[c->using_cleanup_count++] = (sv_using_cleanup_t){ 2941 + .stack_local = stack_local, 2942 + .scope_depth = scope_depth, 2943 + .is_async = is_async, 2944 + }; 2945 + } 2946 + 2947 + static void compile_block_with_using(sv_compiler_t *c, sv_ast_t *node) { 2948 + bool has_await_using = false; 2949 + bool has_using = stmt_list_has_using_decl(&node->args, &has_await_using); 2950 + 2951 + begin_scope(c); 2952 + hoist_lexical_decls(c, &node->args); 2953 + hoist_func_decls(c, &node->args); 2954 + 2955 + if (!has_using) { 2956 + compile_stmts(c, &node->args); 2957 + end_scope(c); 2958 + return; 2959 + } 2960 + 2961 + emit_empty_disposal_stack(c); 2962 + int stack_local = add_local(c, "", 0, false, c->scope_depth); 2963 + 2964 + emit_put_local(c, stack_local); 2965 + int err_local = add_local(c, "", 0, false, c->scope_depth); 2966 + 2967 + int old_using_stack = c->using_stack_local; 2968 + bool old_using_async = c->using_stack_async; 2969 + 2970 + c->using_stack_local = stack_local; 2971 + c->using_stack_async = has_await_using; 2972 + push_using_cleanup(c, stack_local, c->scope_depth, has_await_using); 2973 + 2974 + c->try_depth++; 2975 + int try_jump = emit_jump(c, OP_TRY_PUSH); 2976 + compile_stmts(c, &node->args); 2977 + emit_op(c, OP_TRY_POP); 2978 + c->try_depth--; 2979 + 2980 + emit_using_dispose_call(c, stack_local, -1, has_await_using, false); 2981 + emit_op(c, OP_POP); 2982 + int end_jump = emit_jump(c, OP_JMP); 2983 + 2984 + patch_jump(c, try_jump); 2985 + int catch_tag = emit_jump(c, OP_CATCH); 2986 + 2987 + emit_put_local(c, err_local); 2988 + emit_using_dispose_call(c, stack_local, err_local, has_await_using, true); 2989 + 2990 + if (!has_await_using) emit_op(c, OP_THROW); 2991 + patch_jump(c, catch_tag); 2992 + patch_jump(c, end_jump); 2993 + 2994 + c->using_stack_local = old_using_stack; 2995 + c->using_stack_async = old_using_async; 2996 + 2997 + pop_using_cleanup(c); 2998 + end_scope(c); 2849 2999 } 2850 3000 2851 3001 void compile_stmt(sv_compiler_t *c, sv_ast_t *node) { ··· 2858 3008 break; 2859 3009 2860 3010 case N_BLOCK: 2861 - begin_scope(c); 2862 - hoist_lexical_decls(c, &node->args); 2863 - hoist_func_decls(c, &node->args); 2864 - compile_stmts(c, &node->args); 2865 - end_scope(c); 3011 + compile_block_with_using(c, node); 2866 3012 break; 2867 3013 2868 3014 case N_VAR: ··· 2910 3056 if (node->right) { 2911 3057 compile_tail_return_expr(c, node->right); 2912 3058 } else { 2913 - emit_close_upvals(c); 2914 - emit_op(c, OP_RETURN_UNDEF); 3059 + emit_op(c, OP_UNDEF); 3060 + emit_return_from_stack(c); 2915 3061 } 2916 3062 break; 2917 3063 ··· 3187 3333 3188 3334 void compile_var_decl(sv_compiler_t *c, sv_ast_t *node) { 3189 3335 sv_var_kind_t kind = node->var_kind; 3190 - bool is_const = (kind == SV_VAR_CONST); 3336 + bool is_using = (kind == SV_VAR_USING || kind == SV_VAR_AWAIT_USING); 3337 + bool is_await_using = (kind == SV_VAR_AWAIT_USING); 3338 + bool is_const = (kind == SV_VAR_CONST || is_using); 3191 3339 bool repl_top = is_repl_top_level(c); 3192 3340 3193 3341 for (int i = 0; i < node->args.count; i++) { ··· 3237 3385 if (decl->right || !is_const) { 3238 3386 emit_put_local_typed(c, idx, init_type); 3239 3387 c->locals[idx].is_tdz = false; 3388 + if (is_using) { 3389 + if (c->using_stack_local >= 0) { 3390 + emit_get_local(c, c->using_stack_local); 3391 + emit_get_local(c, idx); 3392 + emit_using_push(c, is_await_using); 3393 + } else { 3394 + emit_get_local(c, idx); 3395 + emit_dispose_resource(c, is_await_using); 3396 + } 3397 + emit_op(c, OP_POP); 3398 + } 3240 3399 } 3241 3400 } else { 3242 3401 if (decl->right) { ··· 3517 3676 if (target->type == N_IDENT) { 3518 3677 int loc = resolve_local(c, target->str, target->len); 3519 3678 if (loc == -1) { 3520 - bool is_const = (lhs->var_kind == SV_VAR_CONST); 3679 + bool is_const = (lhs->var_kind == SV_VAR_CONST || 3680 + lhs->var_kind == SV_VAR_USING || 3681 + lhs->var_kind == SV_VAR_AWAIT_USING); 3521 3682 loc = add_local(c, target->str, target->len, is_const, c->scope_depth); 3522 3683 } 3523 3684 emit_put_local(c, loc); ··· 3538 3699 compile_lhs_set(c, lhs, false); 3539 3700 } 3540 3701 3702 + static void compile_using_dispose_target(sv_compiler_t *c, sv_ast_t *lhs) { 3703 + if (!lhs || lhs->type != N_VAR) return; 3704 + bool is_await_using = lhs->var_kind == SV_VAR_AWAIT_USING; 3705 + if (lhs->var_kind != SV_VAR_USING && !is_await_using) return; 3706 + if (lhs->args.count == 0) return; 3707 + 3708 + sv_ast_t *decl = lhs->args.items[0]; 3709 + if (!decl || decl->type != N_VARDECL || !decl->left || decl->left->type != N_IDENT) return; 3710 + 3711 + emit_get_var(c, decl->left->str, decl->left->len); 3712 + emit_dispose_resource(c, is_await_using); 3713 + emit_op(c, OP_POP); 3714 + } 3715 + 3716 + static bool compile_using_push_target(sv_compiler_t *c, sv_ast_t *lhs, int stack_local) { 3717 + if (!lhs || lhs->type != N_VAR) return false; 3718 + bool is_await_using = lhs->var_kind == SV_VAR_AWAIT_USING; 3719 + if (lhs->var_kind != SV_VAR_USING && !is_await_using) return false; 3720 + if (lhs->args.count == 0) return false; 3721 + 3722 + sv_ast_t *decl = lhs->args.items[0]; 3723 + if (!decl || decl->type != N_VARDECL || !decl->left || decl->left->type != N_IDENT) return false; 3724 + 3725 + emit_get_local(c, stack_local); 3726 + emit_get_var(c, decl->left->str, decl->left->len); 3727 + emit_using_push(c, is_await_using); 3728 + emit_op(c, OP_POP); 3729 + 3730 + return true; 3731 + } 3732 + 3541 3733 static void compile_for_each(sv_compiler_t *c, sv_ast_t *node, bool is_for_of) { 3542 3734 begin_scope(c); 3543 3735 ··· 3547 3739 3548 3740 if (node->left && node->left->type == N_VAR && 3549 3741 node->left->var_kind != SV_VAR_VAR) { 3550 - bool is_const = (node->left->var_kind == SV_VAR_CONST); 3742 + bool is_const = 3743 + (node->left->var_kind == SV_VAR_CONST || 3744 + node->left->var_kind == SV_VAR_USING || 3745 + node->left->var_kind == SV_VAR_AWAIT_USING); 3551 3746 int lb = c->local_count; 3552 3747 for (int i = 0; i < node->left->args.count; i++) { 3553 3748 sv_ast_t *decl = node->left->args.items[i]; ··· 3588 3783 int iter_err_local = -1; 3589 3784 int break_close_slot = -1; 3590 3785 int iter_inner_start = -1; 3786 + int using_stack_local = -1; 3591 3787 3592 3788 bool is_for_await = (node->type == N_FOR_AWAIT_OF); 3789 + bool is_using_loop = node->left && node->left->type == N_VAR && 3790 + (node->left->var_kind == SV_VAR_USING || node->left->var_kind == SV_VAR_AWAIT_USING); 3791 + bool is_await_using_loop = is_using_loop && node->left->var_kind == SV_VAR_AWAIT_USING; 3792 + int old_using_stack = c->using_stack_local; 3793 + bool old_using_async = c->using_stack_async; 3593 3794 uint8_t iter_hint = 0; 3594 3795 if (is_for_of && !is_for_await) 3595 3796 iter_hint = iter_hint_for_type(infer_expr_type(c, node->right)); 3596 3797 3798 + if (is_using_loop) { 3799 + emit_empty_disposal_stack(c); 3800 + using_stack_local = add_local(c, "", 0, false, c->scope_depth); 3801 + emit_put_local(c, using_stack_local); 3802 + } 3803 + 3597 3804 compile_expr(c, node->right); 3598 3805 if (is_for_of) { 3599 3806 emit_op(c, is_for_await ? OP_FOR_AWAIT_OF : OP_FOR_OF); ··· 3612 3819 3613 3820 int loop_start = c->code_len; 3614 3821 push_loop(c, loop_start, NULL, 0, false); 3822 + 3823 + if (is_using_loop) { 3824 + emit_empty_disposal_stack(c); 3825 + emit_put_local(c, using_stack_local); 3826 + } 3615 3827 3616 3828 if (is_for_of) { 3617 3829 if (is_for_await) { ··· 3648 3860 } 3649 3861 } 3650 3862 3651 - compile_stmt(c, node->body); 3863 + if (is_using_loop) { 3864 + c->using_stack_local = using_stack_local; 3865 + c->using_stack_async = is_await_using_loop; 3866 + push_using_cleanup(c, using_stack_local, c->scope_depth, is_await_using_loop); 3867 + compile_using_push_target(c, node->left, using_stack_local); 3868 + } 3652 3869 3870 + compile_stmt(c, node->body); 3653 3871 sv_loop_t *loop = &c->loops[c->loop_count - 1]; 3654 3872 for (int i = 0; i < loop->continues.count; i++) 3655 3873 patch_jump(c, loop->continues.offsets[i]); 3656 3874 3875 + if (is_using_loop) { 3876 + emit_using_dispose_call(c, using_stack_local, -1, is_await_using_loop, false); 3877 + emit_op(c, OP_POP); 3878 + 3879 + c->using_stack_local = old_using_stack; 3880 + c->using_stack_async = old_using_async; 3881 + pop_using_cleanup(c); 3882 + } else compile_using_dispose_target(c, node->left); 3883 + 3657 3884 if (iter_count > 0) { 3658 3885 for (int i = 0; i < iter_count; i++) { 3659 3886 int inner_idx = iter_inner_start + i; ··· 3694 3921 3695 3922 patch_jump(c, try_jump_for_of); 3696 3923 int catch_tag = emit_jump(c, OP_CATCH); 3697 - emit_put_local(c, iter_err_local); 3924 + emit_put_local(c, iter_err_local); 3925 + 3926 + if (is_using_loop) { 3927 + emit_using_dispose_call(c, using_stack_local, iter_err_local, is_await_using_loop, true); 3928 + emit_put_local(c, iter_err_local); 3929 + } 3930 + 3698 3931 emit_op(c, OP_ITER_CLOSE); 3699 3932 emit_get_local(c, iter_err_local); 3700 3933 emit_op(c, OP_THROW); ··· 3734 3967 if (c->loop_count == 0) return; 3735 3968 3736 3969 int target = c->loop_count - 1; 3737 - if (node->str) { 3738 - for (int i = c->loop_count - 1; i >= 0; i--) { 3739 - if (c->loops[i].label && 3740 - c->loops[i].label_len == node->len && 3741 - memcmp(c->loops[i].label, node->str, node->len) == 0) { 3742 - target = i; 3743 - break; 3744 - } 3745 - } 3746 - } 3970 + if (node->str) for (int i = c->loop_count - 1; i >= 0; i--) if ( 3971 + c->loops[i].label && 3972 + c->loops[i].label_len == node->len && 3973 + memcmp(c->loops[i].label, node->str, node->len) == 0 3974 + ) { target = i; break; } 3747 3975 3748 3976 emit_close_upvals_to_depth(c, c->loops[target].scope_depth); 3977 + emit_using_cleanups_to_depth(c, c->loops[target].scope_depth); 3978 + 3749 3979 int offset = emit_jump(c, OP_JMP); 3750 3980 patch_list_add(&c->loops[target].breaks, offset); 3751 3981 } 3752 3982 3753 3983 3754 3984 void compile_continue(sv_compiler_t *c, sv_ast_t *node) { 3755 - for (int i = c->loop_count - 1; i >= 0; i--) { 3756 - if (node->str) { 3985 + for (int i = c->loop_count - 1; i >= 0; i--) if (node->str) { 3757 3986 if ( 3758 3987 c->loops[i].label && 3759 3988 c->loops[i].label_len == node->len && 3760 - memcmp(c->loops[i].label, node->str, node->len) == 0) { 3989 + memcmp(c->loops[i].label, node->str, node->len) == 0 3990 + ) { 3761 3991 emit_close_upvals_to_depth(c, c->loops[i].scope_depth); 3992 + emit_using_cleanups_to_depth(c, c->loops[i].scope_depth); 3762 3993 patch_list_add(&c->loops[i].continues, emit_jump(c, OP_JMP)); 3763 3994 return; 3764 3995 } 3765 3996 } else if (!c->loops[i].is_switch) { 3766 3997 emit_close_upvals_to_depth(c, c->loops[i].scope_depth); 3998 + emit_using_cleanups_to_depth(c, c->loops[i].scope_depth); 3767 3999 patch_list_add(&c->loops[i].continues, emit_jump(c, OP_JMP)); 3768 4000 return; 3769 4001 } 3770 - }} 4002 + } 3771 4003 3772 4004 static void compile_finally_block(sv_compiler_t *c, sv_ast_t *finally_body) { 3773 4005 int finally_jump = emit_jump(c, OP_FINALLY); ··· 4576 4808 emit_field_inits(&comp, enclosing->field_inits, enclosing->field_init_count); 4577 4809 } 4578 4810 4811 + bool body_has_await_using = false; 4812 + bool body_has_using = node->body && node->body->type == N_BLOCK && 4813 + stmt_list_has_using_decl(&node->body->args, &body_has_await_using); 4814 + int body_using_try_jump = -1; 4815 + int body_using_err_local = -1; 4816 + int old_using_stack = comp.using_stack_local; 4817 + bool old_using_async = comp.using_stack_async; 4818 + 4819 + if (body_has_using) { 4820 + emit_empty_disposal_stack(&comp); 4821 + int body_using_stack = add_local(&comp, "", 0, false, comp.scope_depth); 4822 + emit_put_local(&comp, body_using_stack); 4823 + body_using_err_local = add_local(&comp, "", 0, false, comp.scope_depth); 4824 + 4825 + comp.using_stack_local = body_using_stack; 4826 + comp.using_stack_async = body_has_await_using; 4827 + push_using_cleanup(&comp, body_using_stack, comp.scope_depth, body_has_await_using); 4828 + 4829 + comp.try_depth++; 4830 + body_using_try_jump = emit_jump(&comp, OP_TRY_PUSH); 4831 + } 4832 + 4579 4833 if (node->body) { 4580 4834 if (node->body->type == N_BLOCK) { 4581 4835 int last_expr_idx = -1; ··· 4584 4838 if (sv_ast_can_be_expression_statement(last)) 4585 4839 last_expr_idx = node->body->args.count - 1; 4586 4840 } 4587 - 4588 4841 for (int i = 0; i < node->body->args.count; i++) { 4589 4842 sv_ast_t *stmt = node->body->args.items[i]; 4590 4843 if (i == last_expr_idx) { 4591 4844 compile_expr(&comp, stmt); 4592 - emit_close_upvals(&comp); 4593 - emit_op(&comp, OP_RETURN); 4594 - } else { 4595 - compile_stmt(&comp, stmt); 4596 - } 4845 + emit_return_from_stack(&comp); 4846 + } else compile_stmt(&comp, stmt); 4597 4847 } 4598 4848 } else compile_tail_return_expr(&comp, node->body); 4599 4849 } ··· 4602 4852 sv_deferred_export_t *e = &comp.deferred_exports[i]; 4603 4853 compile_export_emit(&comp, e->name, e->len); 4604 4854 } 4855 + 4856 + if (body_has_using) { 4857 + emit_op(&comp, OP_TRY_POP); 4858 + comp.try_depth--; 4859 + 4860 + emit_using_dispose_call(&comp, comp.using_stack_local, -1, body_has_await_using, false); 4861 + emit_op(&comp, OP_POP); 4862 + int end_jump = emit_jump(&comp, OP_JMP); 4863 + 4864 + patch_jump(&comp, body_using_try_jump); 4865 + int catch_tag = emit_jump(&comp, OP_CATCH); 4866 + emit_put_local(&comp, body_using_err_local); 4867 + emit_using_dispose_call(&comp, comp.using_stack_local, body_using_err_local, body_has_await_using, true); 4868 + if (!body_has_await_using) emit_op(&comp, OP_THROW); 4869 + patch_jump(&comp, catch_tag); 4870 + patch_jump(&comp, end_jump); 4871 + 4872 + pop_using_cleanup(&comp); 4873 + comp.using_stack_local = old_using_stack; 4874 + comp.using_stack_async = old_using_async; 4875 + } 4605 4876 4877 + emit_using_cleanups_to_depth(&comp, -1); 4606 4878 emit_close_upvals(&comp); 4607 4879 emit_op(&comp, OP_RETURN_UNDEF); 4608 4880
+12 -2
src/silver/engine.c
··· 25 25 #include "ops/exceptions.h" 26 26 #include "ops/async.h" 27 27 #include "ops/iteration.h" 28 + #include "ops/using.h" 28 29 #include "ops/objects.h" 29 30 #include "ops/coercion.h" 30 31 ··· 1680 1681 L_TRY_POP: { sv_op_try_pop(vm); NEXT(1); } 1681 1682 L_CATCH: { sv_op_catch(vm, sv_err, ip); NEXT(5); } 1682 1683 L_FINALLY: { VM_CHECK(sv_op_finally(vm, js, ip)); NEXT(5); } 1683 - 1684 + L_NIP_CATCH: { sv_op_nip_catch(vm); NEXT(1); } 1685 + 1684 1686 L_FINALLY_RET: { 1685 1687 uint8_t *resume_ip = NULL; 1686 1688 ant_value_t completion = js_mkundef(); ··· 1712 1714 DISPATCH(); 1713 1715 } 1714 1716 1715 - L_NIP_CATCH: { sv_op_nip_catch(vm); NEXT(1); } 1717 + L_USING_PUSH: { VM_CHECK(sv_op_using_push(vm, js, false)); NEXT(1); } 1718 + L_USING_PUSH_ASYNC: { VM_CHECK(sv_op_using_push(vm, js, true)); NEXT(1); } 1719 + L_DISPOSE_RESOURCE: { VM_CHECK(sv_op_dispose_resource(vm, js, false)); NEXT(1); } 1720 + L_DISPOSE_RESOURCE_ASYNC: { VM_CHECK(sv_op_dispose_resource(vm, js, true)); NEXT(1); } 1721 + L_USING_DISPOSE: { VM_CHECK(sv_op_using_dispose(vm, js, false, false)); NEXT(1); } 1722 + L_USING_DISPOSE_ASYNC: { VM_CHECK(sv_op_using_dispose(vm, js, true, false)); NEXT(1); } 1723 + L_USING_DISPOSE_SUPPRESSED: { VM_CHECK(sv_op_using_dispose(vm, js, false, true)); NEXT(1); } 1724 + L_USING_DISPOSE_ASYNC_SUPPRESSED: { VM_CHECK(sv_op_using_dispose(vm, js, true, true)); NEXT(1); } 1725 + 1716 1726 L_FOR_IN: { VM_CHECK(sv_op_for_in(vm, js)); NEXT(1); } 1717 1727 L_FOR_OF: { VM_CHECK(sv_op_for_of(vm, js)); NEXT(1); } 1718 1728 L_FOR_AWAIT_OF: { VM_CHECK(sv_op_for_await_of(vm, js)); NEXT(1); }
+410
src/silver/ops/using.h
··· 1 + #ifndef SV_USING_H 2 + #define SV_USING_H 3 + 4 + #include "silver/engine.h" 5 + #include "errors.h" 6 + #include "gc/roots.h" 7 + #include "modules/symbol.h" 8 + 9 + typedef enum { 10 + SV_DISPOSAL_RECORD_DEFER = 0, 11 + SV_DISPOSAL_RECORD_ADOPT = 1, 12 + SV_DISPOSAL_RECORD_USE = 2 13 + } sv_disposal_record_kind_t; 14 + 15 + static inline void sv_using_array_clear(ant_value_t arr) { 16 + ant_object_t *ptr = js_obj_ptr(js_as_obj(arr)); 17 + if (ptr && ptr->type_tag == T_ARR) ptr->u.array.len = 0; 18 + } 19 + 20 + static inline ant_value_t sv_make_suppressed_error_value( 21 + ant_t *js, ant_value_t error, ant_value_t suppressed 22 + ) { 23 + GC_ROOT_SAVE(root_mark, js); 24 + GC_ROOT_PIN(js, error); 25 + GC_ROOT_PIN(js, suppressed); 26 + 27 + ant_value_t obj = js_mkobj(js); 28 + GC_ROOT_PIN(js, obj); 29 + if (is_err(obj)) { 30 + GC_ROOT_RESTORE(js, root_mark); 31 + return obj; 32 + } 33 + 34 + ant_value_t proto = js_get_ctor_proto(js, "SuppressedError", 15); 35 + if (is_object_type(proto)) js_set_proto_init(obj, proto); 36 + 37 + js_mkprop_fast(js, obj, "error", 5, error); 38 + js_mkprop_fast(js, obj, "suppressed", 10, suppressed); 39 + js_mkprop_fast(js, obj, "message", 7, ANT_STRING("An error was suppressed during disposal.")); 40 + js_mkprop_fast(js, obj, "name", 4, ANT_STRING("SuppressedError")); 41 + js_set_slot(obj, SLOT_ERROR_BRAND, js_true); 42 + js_capture_stack(js, obj); 43 + 44 + GC_ROOT_RESTORE(js, root_mark); 45 + return obj; 46 + } 47 + 48 + static inline ant_value_t sv_disposal_error_value(ant_t *js, ant_value_t result) { 49 + if (js->thrown_exists) { 50 + ant_value_t thrown = js->thrown_value; 51 + js->thrown_value = js_mkundef(); 52 + js->thrown_stack = js_mkundef(); 53 + js->thrown_exists = false; 54 + return thrown; 55 + } 56 + 57 + if (is_err(result)) { 58 + if (vdata(result) != 0) return mkval(T_OBJ, vdata(result)); 59 + return js_make_error_silent(js, JS_ERR_INTERNAL, "unknown disposal error"); 60 + } 61 + 62 + return result; 63 + } 64 + 65 + static inline ant_value_t sv_suppress_disposal_error( 66 + ant_t *js, ant_value_t error, ant_value_t previous 67 + ) { 68 + if (vtype(previous) == T_UNDEF) return error; 69 + return sv_make_suppressed_error_value(js, error, previous); 70 + } 71 + 72 + static inline ant_value_t sv_disposal_record_call(ant_t *js, ant_value_t record) { 73 + ant_value_t kind_v = js_arr_get(js, record, 0); 74 + ant_value_t value = js_arr_get(js, record, 1); 75 + ant_value_t method = js_arr_get(js, record, 2); 76 + 77 + int kind = vtype(kind_v) == T_NUM 78 + ? (int)js_getnum(kind_v) : -1; 79 + 80 + ant_value_t this_arg = kind == SV_DISPOSAL_RECORD_USE ? value : js_mkundef(); 81 + ant_value_t arg = value; 82 + 83 + ant_value_t *args = kind == SV_DISPOSAL_RECORD_ADOPT ? &arg : NULL; 84 + int nargs = kind == SV_DISPOSAL_RECORD_ADOPT ? 1 : 0; 85 + 86 + if (!is_callable(method)) 87 + return js_mkerr_typed(js, JS_ERR_TYPE, "disposer is not callable"); 88 + 89 + if (vtype(method) == T_CFUNC) { 90 + ant_value_t saved_this = js->this_val; 91 + js->this_val = this_arg; 92 + ant_value_t result = js_as_cfunc(method)(js, args, nargs); 93 + js->this_val = saved_this; 94 + return result; 95 + } 96 + 97 + return sv_vm_call(js->vm, js, method, this_arg, args, nargs, NULL, false); 98 + } 99 + 100 + static inline ant_value_t sv_dispose_resource(ant_t *js, ant_value_t resource, bool is_async) { 101 + if (vtype(resource) == T_NULL || vtype(resource) == T_UNDEF) return js_mkundef(); 102 + 103 + ant_value_t method = js_get_sym(js, resource, is_async ? get_asyncDispose_sym() : get_dispose_sym()); 104 + if (is_async && (vtype(method) == T_UNDEF || vtype(method) == T_NULL)) method = js_get_sym(js, resource, get_dispose_sym()); 105 + 106 + if (!is_callable(method)) return js_mkerr_typed( 107 + js, JS_ERR_TYPE, is_async 108 + ? "resource is not async disposable" 109 + : "resource is not disposable" 110 + ); 111 + 112 + if (vtype(method) == T_CFUNC) { 113 + ant_value_t saved_this = js->this_val; 114 + js->this_val = resource; 115 + ant_value_t result = js_as_cfunc(method)(js, NULL, 0); 116 + js->this_val = saved_this; 117 + return result; 118 + } 119 + 120 + return sv_vm_call(js->vm, js, method, resource, NULL, 0, NULL, false); 121 + } 122 + 123 + static inline ant_value_t sv_using_push( 124 + ant_t *js, ant_value_t entries, ant_value_t resource, bool is_async 125 + ) { 126 + if (vtype(resource) == T_NULL || vtype(resource) == T_UNDEF) 127 + return resource; 128 + 129 + if (vtype(entries) != T_ARR) 130 + return js_mkerr_typed(js, JS_ERR_TYPE, "invalid using disposal stack"); 131 + 132 + GC_ROOT_SAVE(root_mark, js); 133 + GC_ROOT_PIN(js, entries); 134 + GC_ROOT_PIN(js, resource); 135 + 136 + ant_value_t method = js_get_sym(js, resource, is_async ? get_asyncDispose_sym() : get_dispose_sym()); 137 + if (is_async && (vtype(method) == T_UNDEF || vtype(method) == T_NULL)) method = js_get_sym(js, resource, get_dispose_sym()); 138 + 139 + GC_ROOT_PIN(js, method); 140 + if (!is_callable(method)) { 141 + GC_ROOT_RESTORE(js, root_mark); 142 + return js_mkerr_typed(js, JS_ERR_TYPE, "resource is not disposable"); 143 + } 144 + 145 + ant_value_t record = js_mkarr(js); 146 + GC_ROOT_PIN(js, record); 147 + if (is_err(record)) { 148 + GC_ROOT_RESTORE(js, root_mark); 149 + return record; 150 + } 151 + 152 + js_arr_push(js, record, js_mknum((double)SV_DISPOSAL_RECORD_USE)); 153 + js_arr_push(js, record, resource); 154 + js_arr_push(js, record, method); 155 + js_arr_push(js, entries, record); 156 + GC_ROOT_RESTORE(js, root_mark); 157 + 158 + return resource; 159 + } 160 + 161 + static inline ant_value_t sv_using_dispose_sync( 162 + ant_t *js, ant_value_t entries, ant_value_t completion, bool throw_completion 163 + ) { 164 + if (vtype(entries) != T_ARR) return js_mkerr_typed(js, JS_ERR_TYPE, "invalid using disposal stack"); 165 + 166 + GC_ROOT_SAVE(root_mark, js); 167 + GC_ROOT_PIN(js, entries); 168 + GC_ROOT_PIN(js, completion); 169 + 170 + ant_offset_t len = js_arr_len(js, entries); 171 + ant_value_t work = js_mkarr(js); 172 + GC_ROOT_PIN(js, work); 173 + 174 + if (is_err(work)) { 175 + GC_ROOT_RESTORE(js, root_mark); 176 + return work; 177 + } 178 + 179 + for (ant_offset_t i = 0; i < len; i++) { 180 + GC_ROOT_SAVE(copy_mark, js); 181 + ant_value_t record = js_arr_get(js, entries, i); 182 + GC_ROOT_PIN(js, record); 183 + js_arr_push(js, work, record); 184 + GC_ROOT_RESTORE(js, copy_mark); 185 + } 186 + 187 + sv_using_array_clear(entries); 188 + len = js_arr_len(js, work); 189 + 190 + for (ant_offset_t i = len; i > 0; i--) { 191 + GC_ROOT_SAVE(iter_mark, js); 192 + ant_value_t record = js_arr_get(js, work, i - 1); 193 + 194 + GC_ROOT_PIN(js, record); 195 + ant_value_t result = sv_disposal_record_call(js, record); 196 + 197 + if (is_err(result) || js->thrown_exists) { 198 + ant_value_t error = sv_disposal_error_value(js, result); 199 + GC_ROOT_PIN(js, error); 200 + completion = sv_suppress_disposal_error(js, error, completion); 201 + 202 + if (is_err(completion)) { 203 + GC_ROOT_RESTORE(js, root_mark); 204 + return completion; 205 + } 206 + } 207 + 208 + GC_ROOT_RESTORE(js, iter_mark); 209 + } 210 + 211 + if (throw_completion && vtype(completion) != T_UNDEF) { 212 + ant_value_t thrown = js_throw(js, completion); 213 + GC_ROOT_RESTORE(js, root_mark); 214 + return thrown; 215 + } 216 + 217 + GC_ROOT_RESTORE(js, root_mark); 218 + return completion; 219 + } 220 + 221 + static inline ant_value_t sv_async_dispose_continue( 222 + ant_t *js, 223 + ant_value_t state, 224 + bool rejected, 225 + ant_value_t reason 226 + ); 227 + 228 + static inline ant_value_t sv_async_dispose_on_fulfilled( 229 + ant_t *js, 230 + ant_value_t *args, 231 + int nargs 232 + ) { 233 + (void)args; (void)nargs; 234 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 235 + return sv_async_dispose_continue(js, state, false, js_mkundef()); 236 + } 237 + 238 + static inline ant_value_t sv_async_dispose_on_rejected( 239 + ant_t *js, 240 + ant_value_t *args, 241 + int nargs 242 + ) { 243 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 244 + return sv_async_dispose_continue(js, state, true, nargs > 0 ? args[0] : js_mkundef()); 245 + } 246 + 247 + static inline ant_value_t sv_async_dispose_continue( 248 + ant_t *js, 249 + ant_value_t state, 250 + bool rejected, 251 + ant_value_t reason 252 + ) { 253 + GC_ROOT_SAVE(root_mark, js); 254 + GC_ROOT_PIN(js, state); 255 + GC_ROOT_PIN(js, reason); 256 + 257 + ant_value_t entries = js_get_slot(state, SLOT_ENTRIES); 258 + ant_value_t result_promise = js_get_slot(state, SLOT_DATA); 259 + ant_value_t completion = js_get_slot(state, SLOT_AUX); 260 + GC_ROOT_PIN(js, entries); 261 + GC_ROOT_PIN(js, result_promise); 262 + GC_ROOT_PIN(js, completion); 263 + 264 + if (rejected) { 265 + completion = sv_suppress_disposal_error(js, reason, completion); 266 + GC_ROOT_PIN(js, completion); 267 + js_set_slot(state, SLOT_AUX, completion); 268 + } 269 + 270 + ant_value_t idx_v = js_get_slot(state, SLOT_ITER_STATE); 271 + ant_offset_t idx = vtype(idx_v) == T_NUM ? (ant_offset_t)js_getnum(idx_v) : 0; 272 + 273 + while (idx > 0) { 274 + GC_ROOT_SAVE(iter_mark, js); 275 + idx--; 276 + js_set_slot(state, SLOT_ITER_STATE, js_mknum((double)idx)); 277 + 278 + ant_value_t record = js_arr_get(js, entries, idx); 279 + GC_ROOT_PIN(js, record); 280 + ant_value_t result = sv_disposal_record_call(js, record); 281 + GC_ROOT_PIN(js, result); 282 + 283 + if (is_err(result) || js->thrown_exists) { 284 + ant_value_t error = sv_disposal_error_value(js, result); 285 + GC_ROOT_PIN(js, error); 286 + completion = sv_suppress_disposal_error(js, error, completion); 287 + js_set_slot(state, SLOT_AUX, completion); 288 + GC_ROOT_RESTORE(js, iter_mark); 289 + continue; 290 + } 291 + 292 + if (vtype(result) == T_PROMISE) { 293 + ant_value_t on_fulfilled = js_heavy_mkfun(js, sv_async_dispose_on_fulfilled, state); 294 + GC_ROOT_PIN(js, on_fulfilled); 295 + ant_value_t on_rejected = js_heavy_mkfun(js, sv_async_dispose_on_rejected, state); 296 + GC_ROOT_PIN(js, on_rejected); 297 + js_promise_then(js, result, on_fulfilled, on_rejected); 298 + GC_ROOT_RESTORE(js, iter_mark); 299 + GC_ROOT_RESTORE(js, root_mark); 300 + return result_promise; 301 + } 302 + 303 + GC_ROOT_RESTORE(js, iter_mark); 304 + } 305 + 306 + if (vtype(completion) != T_UNDEF) js_reject_promise(js, result_promise, completion); 307 + else js_resolve_promise(js, result_promise, js_mkundef()); 308 + 309 + GC_ROOT_RESTORE(js, root_mark); 310 + return result_promise; 311 + } 312 + 313 + static inline ant_value_t sv_using_dispose_async( 314 + ant_t *js, 315 + ant_value_t entries, 316 + ant_value_t completion 317 + ) { 318 + ant_value_t result_promise = js_mkpromise(js); 319 + if (is_err(result_promise)) return result_promise; 320 + 321 + GC_ROOT_SAVE(root_mark, js); 322 + GC_ROOT_PIN(js, result_promise); 323 + GC_ROOT_PIN(js, completion); 324 + 325 + if (vtype(entries) != T_ARR) { 326 + ant_value_t error = js_make_error_silent(js, JS_ERR_TYPE, "invalid using disposal stack"); 327 + GC_ROOT_PIN(js, error); 328 + js_reject_promise(js, result_promise, error); 329 + GC_ROOT_RESTORE(js, root_mark); 330 + return result_promise; 331 + } 332 + 333 + GC_ROOT_PIN(js, entries); 334 + 335 + ant_offset_t len = js_arr_len(js, entries); 336 + ant_value_t work = js_mkarr(js); 337 + GC_ROOT_PIN(js, work); 338 + if (is_err(work)) { 339 + GC_ROOT_RESTORE(js, root_mark); 340 + return work; 341 + } 342 + 343 + for (ant_offset_t i = 0; i < len; i++) { 344 + GC_ROOT_SAVE(copy_mark, js); 345 + ant_value_t record = js_arr_get(js, entries, i); 346 + GC_ROOT_PIN(js, record); 347 + js_arr_push(js, work, record); 348 + GC_ROOT_RESTORE(js, copy_mark); 349 + } 350 + 351 + sv_using_array_clear(entries); 352 + ant_value_t state = js_mkobj(js); 353 + 354 + GC_ROOT_PIN(js, state); 355 + if (is_err(state)) { 356 + GC_ROOT_RESTORE(js, root_mark); 357 + return state; 358 + } 359 + 360 + js_set_slot(state, SLOT_ENTRIES, work); 361 + js_set_slot(state, SLOT_DATA, result_promise); 362 + js_set_slot(state, SLOT_AUX, completion); 363 + js_set_slot(state, SLOT_ITER_STATE, js_mknum((double)js_arr_len(js, work))); 364 + 365 + ant_value_t result = sv_async_dispose_continue(js, state, false, js_mkundef()); 366 + GC_ROOT_RESTORE(js, root_mark); 367 + return result; 368 + } 369 + 370 + static inline ant_value_t sv_using_dispose( 371 + ant_t *js, 372 + ant_value_t entries, 373 + ant_value_t completion, 374 + bool is_async, 375 + bool suppressed 376 + ) { 377 + ant_value_t actual_completion = suppressed ? completion : js_mkundef(); 378 + if (is_async) return sv_using_dispose_async(js, entries, actual_completion); 379 + return sv_using_dispose_sync(js, entries, actual_completion, !suppressed); 380 + } 381 + 382 + static inline ant_value_t sv_op_using_push(sv_vm_t *vm, ant_t *js, bool is_async) { 383 + ant_value_t resource = vm->stack[--vm->sp]; 384 + ant_value_t entries = vm->stack[--vm->sp]; 385 + ant_value_t result = sv_using_push(js, entries, resource, is_async); 386 + if (!is_err(result)) vm->stack[vm->sp++] = result; 387 + return result; 388 + } 389 + 390 + static inline ant_value_t sv_op_dispose_resource(sv_vm_t *vm, ant_t *js, bool is_async) { 391 + ant_value_t resource = vm->stack[--vm->sp]; 392 + ant_value_t result = sv_dispose_resource(js, resource, is_async); 393 + if (!is_err(result)) vm->stack[vm->sp++] = result; 394 + return result; 395 + } 396 + 397 + static inline ant_value_t sv_op_using_dispose( 398 + sv_vm_t *vm, 399 + ant_t *js, 400 + bool is_async, 401 + bool suppressed 402 + ) { 403 + ant_value_t completion = suppressed ? vm->stack[--vm->sp] : js_mkundef(); 404 + ant_value_t entries = vm->stack[--vm->sp]; 405 + ant_value_t result = sv_using_dispose(js, entries, completion, is_async, suppressed); 406 + if (!is_err(result)) vm->stack[vm->sp++] = result; 407 + return result; 408 + } 409 + 410 + #endif
+118
tests/test_disposable_stack.cjs
··· 1 + const assert = require("assert"); 2 + 3 + { 4 + const calls = []; 5 + const stack = new DisposableStack(); 6 + const first = { disposed: false }; 7 + const second = { disposed: false }; 8 + const third = { 9 + disposed: false, 10 + [Symbol.dispose]() { 11 + this.disposed = true; 12 + calls.push("third"); 13 + }, 14 + }; 15 + 16 + assert.strictEqual(stack.adopt(first, (value) => { 17 + value.disposed = true; 18 + calls.push("first"); 19 + }), first); 20 + 21 + assert.strictEqual(stack.defer(() => { 22 + second.disposed = true; 23 + calls.push("second"); 24 + }), undefined); 25 + 26 + const moved = stack.move(); 27 + assert.strictEqual(moved.use(third), third); 28 + let movedStackRejected = false; 29 + try { 30 + stack.defer(() => {}); 31 + } catch (error) { 32 + movedStackRejected = /already disposed/.test(error.message); 33 + } 34 + assert(movedStackRejected); 35 + 36 + assert.strictEqual(moved.dispose(), undefined); 37 + assert.deepStrictEqual(calls, ["third", "second", "first"]); 38 + assert(first.disposed); 39 + assert(second.disposed); 40 + assert(third.disposed); 41 + assert.strictEqual(moved.dispose(), undefined); 42 + } 43 + 44 + { 45 + const stack = new DisposableStack(); 46 + stack.defer(() => { 47 + throw new Error("first"); 48 + }); 49 + stack.defer(() => { 50 + throw new Error("second"); 51 + }); 52 + 53 + assert.throws(() => stack.dispose(), (error) => { 54 + assert(error instanceof SuppressedError); 55 + assert.strictEqual(error.error.message, "first"); 56 + assert.strictEqual(error.suppressed.message, "second"); 57 + return true; 58 + }); 59 + } 60 + 61 + { 62 + const calls = []; 63 + const stack = new AsyncDisposableStack(); 64 + const first = { disposed: false }; 65 + const second = { disposed: false }; 66 + const third = { 67 + disposed: false, 68 + async [Symbol.asyncDispose]() { 69 + this.disposed = true; 70 + calls.push("third"); 71 + }, 72 + }; 73 + 74 + assert.strictEqual(stack.adopt(first, async (value) => { 75 + value.disposed = true; 76 + calls.push("first"); 77 + }), first); 78 + 79 + assert.strictEqual(stack.defer(async () => { 80 + second.disposed = true; 81 + calls.push("second"); 82 + }), undefined); 83 + 84 + const moved = stack.move(); 85 + assert.strictEqual(moved.use(third), third); 86 + 87 + moved.disposeAsync().then(() => { 88 + assert.deepStrictEqual(calls, ["third", "second", "first"]); 89 + assert(first.disposed); 90 + assert(second.disposed); 91 + assert(third.disposed); 92 + }); 93 + } 94 + 95 + { 96 + const stack = new AsyncDisposableStack(); 97 + stack.defer(async () => { 98 + throw new Error("async first"); 99 + }); 100 + stack.defer(async () => { 101 + throw new Error("async second"); 102 + }); 103 + 104 + stack.disposeAsync().then( 105 + () => { 106 + throw new Error("disposeAsync should reject"); 107 + }, 108 + (error) => { 109 + assert(error instanceof SuppressedError); 110 + assert.strictEqual(error.error.message, "async first"); 111 + assert.strictEqual(error.suppressed.message, "async second"); 112 + } 113 + ); 114 + } 115 + 116 + Promise.resolve().then(() => { 117 + console.log("DisposableStack tests completed!"); 118 + });
+28
tests/test_suppressed_error_symbols.cjs
··· 1 + function assert(condition, message) { 2 + if (!condition) throw new Error(message); 3 + } 4 + 5 + assert(typeof Symbol.dispose === "symbol", "Symbol.dispose exists"); 6 + assert(typeof Symbol.asyncDispose === "symbol", "Symbol.asyncDispose exists"); 7 + assert(Symbol.dispose.description === "Symbol.dispose", "Symbol.dispose description"); 8 + assert(Symbol.asyncDispose.description === "Symbol.asyncDispose", "Symbol.asyncDispose description"); 9 + 10 + const error = new Error("inner"); 11 + const suppressed = new TypeError("outer"); 12 + const err = new SuppressedError(error, suppressed, "cleanup failed"); 13 + 14 + assert(err instanceof SuppressedError, "instanceof SuppressedError"); 15 + assert(err instanceof Error, "instanceof Error"); 16 + assert(Error.isError(err), "Error.isError recognizes SuppressedError"); 17 + assert(err.name === "SuppressedError", "name is SuppressedError"); 18 + assert(err.message === "cleanup failed", "message is set"); 19 + assert(err.error === error, "error field is set"); 20 + assert(err.suppressed === suppressed, "suppressed field is set"); 21 + assert(typeof err.stack === "string", "stack is captured"); 22 + 23 + const called = SuppressedError(error, suppressed); 24 + assert(called instanceof SuppressedError, "SuppressedError is callable"); 25 + assert(called.error === error, "callable error field"); 26 + assert(called.suppressed === suppressed, "callable suppressed field"); 27 + 28 + console.log("SuppressedError and dispose symbol tests completed!");
+48
tests/test_throw_expressions.cjs
··· 1 + const assert = require("assert"); 2 + 3 + { 4 + const fn = () => throw 42; 5 + try { 6 + fn(); 7 + assert(false, "arrow throw expression should throw"); 8 + } catch (error) { 9 + assert.strictEqual(error, 42); 10 + } 11 + } 12 + 13 + { 14 + true ? 1 : throw 2; 15 + try { 16 + false ? 1 : throw 2; 17 + assert(false, "conditional throw expression should throw"); 18 + } catch (error) { 19 + assert.strictEqual(error, 2); 20 + } 21 + } 22 + 23 + { 24 + let a; 25 + try { 26 + a = 19 || throw 77; 27 + 88 && throw 23; 28 + assert(false, "logical throw expression should throw"); 29 + } catch (error) { 30 + assert.strictEqual(a + error, 42); 31 + } 32 + } 33 + 34 + { 35 + function fn(arg = throw 42) { 36 + return arg; 37 + } 38 + 39 + assert.strictEqual(fn(21), 21); 40 + try { 41 + fn(); 42 + assert(false, "parameter initializer throw expression should throw"); 43 + } catch (error) { 44 + assert.strictEqual(error, 42); 45 + } 46 + } 47 + 48 + console.log("throw expression tests completed!");