diff options
Diffstat (limited to 'src/data/composite.js')
-rw-r--r-- | src/data/composite.js | 300 |
1 files changed, 233 insertions, 67 deletions
diff --git a/src/data/composite.js b/src/data/composite.js index 7a98c424..f31c4069 100644 --- a/src/data/composite.js +++ b/src/data/composite.js @@ -29,6 +29,7 @@ input.value = _valueIntoToken('input.value'); input.dependency = _valueIntoToken('input.dependency'); input.myself = () => Symbol.for(`hsmusic.composite.input.myself`); +input.thisProperty = () => Symbol.for('hsmusic.composite.input.thisProperty'); input.updateValue = _valueIntoToken('input.updateValue'); @@ -71,30 +72,22 @@ function getInputTokenValue(token) { } } -function getStaticInputMetadata(inputOptions) { +function getStaticInputMetadata(inputMapping) { const metadata = {}; - for (const [name, token] of Object.entries(inputOptions)) { - if (typeof token === 'string') { - metadata[input.staticDependency(name)] = token; - metadata[input.staticValue(name)] = null; - } else if (isInputToken(token)) { - const tokenShape = getInputTokenShape(token); - const tokenValue = getInputTokenValue(token); - - metadata[input.staticDependency(name)] = - (tokenShape === 'input.dependency' - ? tokenValue - : null); - - metadata[input.staticValue(name)] = - (tokenShape === 'input.value' - ? tokenValue - : null); - } else { - metadata[input.staticDependency(name)] = null; - metadata[input.staticValue(name)] = null; - } + for (const [name, token] of Object.entries(inputMapping)) { + const tokenShape = getInputTokenShape(token); + const tokenValue = getInputTokenValue(token); + + metadata[input.staticDependency(name)] = + (tokenShape === 'input.dependency' + ? tokenValue + : null); + + metadata[input.staticValue(name)] = + (tokenShape === 'input.value' + ? tokenValue + : null); } return metadata; @@ -284,6 +277,7 @@ export function templateCompositeFrom(description) { 'input.value', 'input.dependency', 'input.myself', + 'input.thisProperty', 'input.updateValue', ].includes(tokenShape)) { expectedValueProvidingTokenInputNames.push(name); @@ -340,7 +334,29 @@ export function templateCompositeFrom(description) { } }); - const inputMetadata = getStaticInputMetadata(inputOptions); + const inputMapping = {}; + if ('inputs' in description) { + for (const [name, token] of Object.entries(description.inputs)) { + const tokenValue = getInputTokenValue(token); + if (name in inputOptions) { + if (typeof inputOptions[name] === 'string') { + inputMapping[name] = input.dependency(inputOptions[name]); + } else { + // This is always an input token, since only a string or + // an input token is a valid input option (asserted above). + inputMapping[name] = inputOptions[name]; + } + } else if (tokenValue.defaultValue) { + inputMapping[name] = input.value(tokenValue.defaultValue); + } else if (tokenValue.defaultDependency) { + inputMapping[name] = input.dependency(tokenValue.defaultDependency); + } else { + inputMapping[name] = input.value(null); + } + } + } + + const inputMetadata = getStaticInputMetadata(inputMapping); const expectedOutputNames = (Array.isArray(description.outputs) @@ -412,25 +428,6 @@ export function templateCompositeFrom(description) { } if ('inputs' in description) { - const inputMapping = {}; - - for (const [name, token] of Object.entries(description.inputs)) { - const tokenValue = getInputTokenValue(token); - if (name in inputOptions) { - if (typeof inputOptions[name] === 'string') { - inputMapping[name] = input.dependency(inputOptions[name]); - } else { - inputMapping[name] = inputOptions[name]; - } - } else if (tokenValue.defaultValue) { - inputMapping[name] = input.value(tokenValue.defaultValue); - } else if (tokenValue.defaultDependency) { - inputMapping[name] = input.dependency(tokenValue.defaultDependency); - } else { - inputMapping[name] = input.value(null); - } - } - finalDescription.inputMapping = inputMapping; finalDescription.inputDescriptions = description.inputs; } @@ -529,7 +526,10 @@ export function compositeFrom(description) { ? compositeFrom(step.toResolvedComposition()) : step)); - const inputMetadata = getStaticInputMetadata(description.inputMapping ?? {}); + const inputMetadata = + (description.inputMapping + ? getStaticInputMetadata(description.inputMapping) + : {}); function _mapDependenciesToOutputs(providedDependencies) { if (!description.outputs) { @@ -567,6 +567,8 @@ export function compositeFrom(description) { return token; case 'input.myself': return 'this'; + case 'input.thisProperty': + return 'thisProperty'; default: return null; } @@ -721,6 +723,8 @@ export function compositeFrom(description) { return (tokenValue.startsWith('#') ? null : tokenValue); case 'input.myself': return 'this'; + case 'input.thisProperty': + return 'thisProperty'; default: return null; } @@ -752,6 +756,9 @@ export function compositeFrom(description) { anyStepsUseUpdateValue || anyStepsUpdate; + const stepsFirstTimeCalling = + Array.from({length: steps.length}).fill(true); + const stepEntries = stitchArrays({ step: steps, stepComposes: stepsCompose, @@ -774,16 +781,9 @@ export function compositeFrom(description) { (step.annotation ? ` (${step.annotation})` : ``); aggregate.nest({message}, ({push}) => { - if (isBase && stepComposes !== compositionNests) { - return push(new TypeError( - (compositionNests - ? `Base must compose, this composition is nestable` - : `Base must not compose, this composition isn't nestable`))); - } else if (!isBase && !stepComposes) { + if (!isBase && !stepComposes) { return push(new TypeError( - (compositionNests - ? `All steps must compose` - : `All steps (except base) must compose`))); + `All steps leading up to base must compose`)); } if ( @@ -877,6 +877,8 @@ export function compositeFrom(description) { return valueSoFar; case 'input.myself': return initialDependencies['this']; + case 'input.thisProperty': + return initialDependencies['thisProperty']; case 'input': return initialDependencies[token]; default: @@ -907,8 +909,16 @@ export function compositeFrom(description) { debug(() => colors.bright(`begin composition - not transforming`)); } - for (let i = 0; i < steps.length; i++) { - const step = steps[i]; + for ( + const [i, { + step, + stepComposes, + }] of + stitchArrays({ + step: steps, + stepComposes: stepsCompose, + }).entries() + ) { const isBase = i === steps.length - 1; debug(() => [ @@ -968,7 +978,16 @@ export function compositeFrom(description) { (expectingTransform ? {[input.updateValue()]: valueSoFar} : {}), - [input.myself()]: initialDependencies?.['this'] ?? null, + + [input.myself()]: + (initialDependencies && Object.hasOwn(initialDependencies, 'this') + ? initialDependencies.this + : null), + + [input.thisProperty()]: + (initialDependencies && Object.hasOwn(initialDependencies, 'thisProperty') + ? initialDependencies.thisProperty + : null), }; const selectDependencies = @@ -983,6 +1002,8 @@ export function compositeFrom(description) { return dependency; case 'input.myself': return input.myself(); + case 'input.thisProperty': + return input.thisProperty(); case 'input.dependency': return tokenValue; case 'input.updateValue': @@ -1016,26 +1037,175 @@ export function compositeFrom(description) { const naturalEvaluate = () => { const [name, ...argsLayout] = getExpectedEvaluation(); - let args; + let args = argsLayout; - if (isBase && !compositionNests) { - args = - argsLayout.filter(arg => arg !== continuationSymbol); + let effectiveDependencies; + let reviewAccessedDependencies; + + if (stepsFirstTimeCalling[i]) { + const expressedDependencies = + selectDependencies; + + const remainingDependencies = + new Set(expressedDependencies); + + const unavailableDependencies = []; + const accessedDependencies = []; + + effectiveDependencies = + new Proxy(filteredDependencies, { + get(target, key) { + accessedDependencies.push(key); + remainingDependencies.delete(key); + + const value = target[key]; + + if (value === undefined) { + unavailableDependencies.push(key); + } + + return value; + }, + }); + + reviewAccessedDependencies = () => { + const topAggregate = + openAggregate({ + message: `Errors in accessed dependencies`, + }); + + const showDependency = dependency => + (isInputToken(dependency) + ? getInputTokenShape(dependency) + + `(` + + inspect(getInputTokenValue(dependency), {compact: true}) + + ')' + : dependency.toString()); + + let anyErrors = false; + + for (const dependency of remainingDependencies) { + topAggregate.push(new Error( + `Expected to access ${showDependency(dependency)}`)); + + anyErrors = true; + } + + for (const dependency of unavailableDependencies) { + const subAggregate = + openAggregate({ + message: + `Accessed ${showDependency(dependency)}, which is unavailable`, + }); + + let reason = false; + + if (!expressedDependencies.includes(dependency)) { + subAggregate.push(new Error( + `Missing from step's expressed dependencies`)); + reason = true; + } + + if (filterableDependencies[dependency] === undefined) { + subAggregate.push( + new Error( + `Not available` + + (isInputToken(dependency) + ? ` in input()-type dependencies` + : dependency.startsWith('#') + ? ` in local dependencies` + : ` on object dependencies`))); + reason = true; + } + + if (!reason) { + subAggregate.push(new Error( + `Not sure why this is unavailable, sorry!`)); + } + + topAggregate.call(subAggregate.close); + + anyErrors = true; + } + + if (anyErrors) { + topAggregate.push(new Error( + `These dependencies, in total, were accessed:` + + (empty(accessedDependencies) + ? ` (none)` + : accessedDependencies.length === 1 + ? showDependency(accessedDependencies[0]) + : `\n` + + accessedDependencies + .map(showDependency) + .map(line => ` - ${line}`) + .join('\n')))); + } + + topAggregate.close(); + }; } else { + effectiveDependencies = filteredDependencies; + reviewAccessedDependencies = null; + } + + args = + args.map(arg => + (arg === filteredDependencies + ? effectiveDependencies + : arg)); + + if (stepComposes) { let continuation; ({continuation, continuationStorage} = _prepareContinuation(callingTransformForThisStep)); args = - argsLayout.map(arg => + args.map(arg => (arg === continuationSymbol ? continuation : arg)); + } else { + args = + args.filter(arg => arg !== continuationSymbol); } - return expose[name](...args); - } + let stepError; + try { + return expose[name](...args); + } catch (error) { + stepError = error; + } finally { + stepsFirstTimeCalling[i] = false; + + let reviewError; + if (reviewAccessedDependencies) { + try { + reviewAccessedDependencies(); + } catch (error) { + reviewError = error; + } + } + + const stepPart = + `step ${i+1}` + + (isBase + ? ` (base)` + : ` of ${steps.length}`) + + (step.annotation ? `, ${step.annotation}` : ``); + + if (stepError && reviewError) { + throw new AggregateError( + [stepError, reviewError], + `Errors in ${stepPart}`); + } else if (stepError || reviewError) { + throw new Error( + `Error in ${stepPart}`, + {cause: stepError || reviewError}); + } + } + }; switch (step.cache) { // Warning! Highly WIP! @@ -1091,11 +1261,6 @@ export function compositeFrom(description) { if (result !== continuationSymbol) { debug(() => [`step #${i+1} - result: exit (inferred) ->`, result]); - - if (compositionNests) { - throw new TypeError(`Inferred early-exit is disallowed in nested compositions`); - } - debug(() => colors.bright(`end composition - exit (inferred)`)); return result; @@ -1216,6 +1381,7 @@ export function compositeFrom(description) { `Error computing composition` + (annotation ? ` ${annotation}` : '')); error.cause = thrownError; + error[Symbol.for('hsmusic.aggregate.translucent')] = true; throw error; } }; |