Skip to content
This repository was archived by the owner on Jan 17, 2025. It is now read-only.

Commit ceb6129

Browse files
authored
Add mask combinator (#52)
* Add mask combinator * Add composer.mask tests
1 parent 56fc898 commit ceb6129

File tree

2 files changed

+66
-4
lines changed

2 files changed

+66
-4
lines changed

composer.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ class Composer {
283283

284284
repeat(count) { // varargs, no options
285285
if (typeof count !== 'number') throw new ComposerError('Invalid argument', count)
286-
return this.let({ count }, this.while(this.function(() => count-- > 0, { helper: 'repeat_1' }), this.seq(...Array.prototype.slice.call(arguments, 1))))
286+
return this.let({ count }, this.while(this.function(() => count-- > 0, { helper: 'repeat_1' }), this.mask(this.seq(...Array.prototype.slice.call(arguments, 1)))))
287287
}
288288

289289
retry(count) { // varargs, no options
@@ -292,10 +292,15 @@ class Composer {
292292
return this.let({ count },
293293
this.function(params => ({ params }), { helper: 'retry_1' }),
294294
this.dowhile(
295-
this.finally(this.function(({ params }) => params, { helper: 'retry_2' }), attempt),
295+
this.finally(this.function(({ params }) => params, { helper: 'retry_2' }), this.mask(attempt)),
296296
this.function(({ result }) => typeof result.error !== 'undefined' && count-- > 0, { helper: 'retry_3' })),
297297
this.function(({ result }) => result, { helper: 'retry_4' }))
298298
}
299+
300+
mask(body, options) {
301+
if (arguments.length > 2) throw new ComposerError('Too many arguments')
302+
return new Composition({ type: 'mask', body: this.task(body) }, options)
303+
}
299304
}
300305

301306
module.exports = new Composer()
@@ -333,6 +338,9 @@ function init(__eval__, composition) {
333338
case 'let':
334339
var body = compile(json.body, path + '.body')
335340
return [[{ type: 'let', let: json.declarations, path }], body, [{ type: 'exit', path }]].reduce(chain)
341+
case 'mask':
342+
var body = compile(json.body, path + '.body')
343+
return [[{ type: 'mask', path }], body, [{ type: 'exit', path }]].reduce(chain)
336344
case 'retain':
337345
var body = compile(json.body, path + '.body')
338346
var fsm = [[{ type: 'push', path }], body, [{ type: 'pop', collect: true, path }]].reduce(chain)
@@ -433,14 +441,29 @@ function init(__eval__, composition) {
433441

434442
// run function f on current stack
435443
function run(f) {
444+
// handle let/mask pairs
445+
const view = []
446+
let n = 0
447+
for (let i in stack) {
448+
if (typeof stack[i].mask !== 'undefined') {
449+
n++
450+
} else if (typeof stack[i].let !== 'undefined') {
451+
if (n === 0) {
452+
view.push(stack[i])
453+
} else {
454+
n--
455+
}
456+
}
457+
}
458+
436459
// update value of topmost matching symbol on stack if any
437460
function set(symbol, value) {
438-
const element = stack.find(element => typeof element.let !== 'undefined' && typeof element.let[symbol] !== 'undefined')
461+
const element = view.find(element => typeof element.let !== 'undefined' && typeof element.let[symbol] !== 'undefined')
439462
if (typeof element !== 'undefined') element.let[symbol] = JSON.parse(JSON.stringify(value))
440463
}
441464

442465
// collapse stack for invocation
443-
const env = stack.reduceRight((acc, cur) => typeof cur.let === 'object' ? Object.assign(acc, cur.let) : acc, {})
466+
const env = view.reduceRight((acc, cur) => typeof cur.let === 'object' ? Object.assign(acc, cur.let) : acc, {})
444467
let main = '(function(){try{'
445468
for (const name in env) main += `var ${name}=arguments[1]['${name}'];`
446469
main += `return eval((${f}))(arguments[0])}finally{`
@@ -476,6 +499,9 @@ function init(__eval__, composition) {
476499
case 'let':
477500
stack.unshift({ let: JSON.parse(JSON.stringify(json.let)) })
478501
break
502+
case 'mask':
503+
stack.unshift({ mask: true })
504+
break
479505
case 'exit':
480506
if (stack.length === 0) return internalError(`State ${current} attempted to pop from an empty stack`)
481507
stack.shift()

test/test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,42 @@ describe('composer', function () {
517517
})
518518
})
519519

520+
describe('mask', function () {
521+
it('let/let/mask', function () {
522+
return invoke(composer.let({ x: 42 }, composer.let({ x: 69 }, composer.mask(() => x))))
523+
.then(activation => assert.deepEqual(activation.response.result, { value: 42 }))
524+
})
525+
526+
it('let/mask/let', function () {
527+
return invoke(composer.let({ x: 42 }, composer.mask(composer.let({ x: 69 }, () => x))))
528+
.then(activation => assert.deepEqual(activation.response.result, { value: 69 }))
529+
})
530+
531+
it('let/let/try/mask', function () {
532+
return invoke(composer.let({ x: 42 }, composer.let({ x: 69 },
533+
composer.try(composer.mask(() => x)), () => { })))
534+
.then(activation => assert.deepEqual(activation.response.result, { value: 42 }))
535+
})
536+
537+
it('let/let/let/mask', function () {
538+
return invoke(composer.let({ x: 42 }, composer.let({ x: 69 },
539+
composer.let({ x: -1 }, composer.mask(() => x)))))
540+
.then(activation => assert.deepEqual(activation.response.result, { value: 69 }))
541+
})
542+
543+
it('let/let/let/mask/mask', function () {
544+
return invoke(composer.let({ x: 42 }, composer.let({ x: 69 },
545+
composer.let({ x: -1 }, composer.mask(composer.mask(() => x))))))
546+
.then(activation => assert.deepEqual(activation.response.result, { value: 42 }))
547+
})
548+
549+
it('let/let/mask/let/mask', function () {
550+
return invoke(composer.let({ x: 42 }, composer.let({ x: 69 },
551+
composer.mask(composer.let({ x: -1 }, composer.mask(() => x))))))
552+
.then(activation => assert.deepEqual(activation.response.result, { value: 42 }))
553+
})
554+
})
555+
520556
describe('retain', function () {
521557
it('base case', function () {
522558
return invoke(composer.retain('TripleAndIncrement'), { n: 3 })

0 commit comments

Comments
 (0)