From bf371268cfb16ece0e413f0aaa93de469a273554 Mon Sep 17 00:00:00 2001 From: cha0s Date: Mon, 22 Jun 2020 14:47:11 -0500 Subject: [PATCH] feat: some traversal mutation --- src/client/types/literal.type-renderer.jsx | 2 +- src/client/types/traversal.type-renderer.jsx | 53 ++++++- src/client/types/typing.js | 148 ++++++++++++++----- 3 files changed, 155 insertions(+), 48 deletions(-) diff --git a/src/client/types/literal.type-renderer.jsx b/src/client/types/literal.type-renderer.jsx index b1d72e1..3fd21cb 100644 --- a/src/client/types/literal.type-renderer.jsx +++ b/src/client/types/literal.type-renderer.jsx @@ -19,7 +19,7 @@ const Literal = ({ const typeRenderers = useTypeRenderers(); const type = typeFromLiteral(value.value); const Component = typeRenderers[type]; - const [options] = stepsOptions(context, [{type: 'key'}], type); + const [options] = stepsOptions(context, [], type); options.push(''); return (
diff --git a/src/client/types/traversal.type-renderer.jsx b/src/client/types/traversal.type-renderer.jsx index 3345331..a246c81 100644 --- a/src/client/types/traversal.type-renderer.jsx +++ b/src/client/types/traversal.type-renderer.jsx @@ -6,6 +6,7 @@ import Value from '~/client/value'; import propTypes from './prop-types'; import { + defaultSteps, descriptionFromSteps, stepsOptions, toBehaviorItem, @@ -38,7 +39,23 @@ const Traversal = (props) => { const options = optionsList[i]; return ( - { + const newSteps = [ + ...steps.slice(0, i), + { + ...steps[i], + key: event.target.value, + }, + ]; + return onChange({ + ...value, + steps: defaultSteps(context, stepsType, newSteps), + value: assignValue, + }); + }} + value={step.key} + > { options .sort((l, r) => (l.toLowerCase() < r.toLowerCase() ? -1 : 1)) @@ -59,7 +76,27 @@ const Traversal = (props) => { {arg.label} { + return onChange({ + ...value, + steps: [ + ...steps.slice(0, i), + { + ...steps[i], + args: [ + ...steps[i].args.slice(0, j), + { + ...steps[i].args[j], + ...toBehaviorItem(argValue), + }, + ...steps[i].args.slice(j + 1), + ], + }, + ...steps.slice(i + 1), + ], + value: assignValue, + }, event); + }} type={description.args[j][1].type} value={arg} /> @@ -83,11 +120,13 @@ const Traversal = (props) => { = onChange({ - ...value, - steps, - value: toBehaviorItem(valueValue), - })} + onChange={(valueValue, event) => ( + onChange({ + ...value, + steps, + value: toBehaviorItem(valueValue), + }, event) + )} type={stepsType} value={assignValue} /> diff --git a/src/client/types/typing.js b/src/client/types/typing.js index e0432c2..4012c4b 100644 --- a/src/client/types/typing.js +++ b/src/client/types/typing.js @@ -1,4 +1,5 @@ import {Context} from '@avocado/behavior'; +import {Traversal} from '@avocado/behavior/item/traversal'; let digraph; // eslint-disable-next-line no-return-assign @@ -17,7 +18,7 @@ export function isBehaviorItem(valueOrItem) { } export function toBehaviorItem(valueOrItem) { - return isBehaviorItem(valueOrItem) ? isBehaviorItem : {type: 'literal', value: valueOrItem}; + return isBehaviorItem(valueOrItem) ? valueOrItem : {type: 'literal', value: valueOrItem}; } export function typeFromLiteral(value) { @@ -42,33 +43,36 @@ export function typeFromLiteral(value) { return 'object'; } -export function typeFromValue(v) { - return isBehaviorItem(v) ? v.type : undefined; -} - const fakeContextDescription = (context) => ( Object.entries(context.all()) .reduce((r, [key, tuple]) => ({...r, [key]: {type: tuple[1]}}), {}) ); +const stepsValue = (context, steps) => (new Traversal({steps})).get(context); + export function descriptionFromSteps(context, steps) { + let description = fakeContextDescription(context); + if (0 === steps.length) { + return description; + } const isInvocation = 'invoke' === steps[steps.length - 1].type; const keyStepsCount = steps.length - (isInvocation ? 1 : 0); - let description = fakeContextDescription(context); let variable; for (let i = 0; i < keyStepsCount; ++i) { const {key} = steps[i]; - if (0 === i) { - [variable] = context.get(key); + variable = stepsValue(context, steps.slice(0, i + 1)); + if ('function' === typeof variable && description[key].args) { + description = description[key]; } else { - variable = 'object' === typeof variable ? variable[key] : undefined; + const candidateDescription = Context.typeDescription( + description[key].type, + 'function' === description[key].type ? variable : undefined, + ); + description = 0 !== Object.keys(candidateDescription).length + ? candidateDescription + : description[key]; } - const lookupDesc = Context.typeDescription( - description[key].type, - (isInvocation && keyStepsCount - 1 === i) ? undefined : variable, - ); - description = 0 !== Object.keys(lookupDesc).length ? lookupDesc : description[key]; } return description; } @@ -77,34 +81,36 @@ export function typeFits(reference, candidate) { return 'any' === reference || -1 !== (reference.split('|') || []).indexOf(candidate); } -export function stepsOptions(context, steps, type) { - const isInvocation = 'invoke' === steps[steps.length - 1].type; - const keyStepsCount = steps.length - (isInvocation ? 1 : 0); - const optionsList = []; +const descriptionCandidates = (description, type) => { const inverted = cachedDigraph(); const candidates = (inverted[type] || []).concat(type); - for (let i = 0; i < keyStepsCount; ++i) { - let description = fakeContextDescription(context); - let variable; - for (let j = 0; j < i; ++j) { - const {key} = steps[j]; - if (0 === i) { - [variable] = context.get(key); - } - else { - variable = 'object' === typeof variable ? variable[key] : undefined; - } - description = Context.typeDescription(description[key].type, variable); - } - const options = 'any' === type - ? Object.keys(description) - : Object.entries(description) - .reduce((r, [key, spec]) => ( - candidates.find((candidate) => typeFits(candidate, spec.type)) - ? r.concat(key) - : r - ), []); - optionsList.push(options); + return 'any' === type + ? Object.keys(description) + : Object.entries(description) + .reduce((r, [key, spec]) => ( + candidates.find((candidate) => typeFits(candidate, spec.type)) + ? r.concat(key) + : r + ), []); +}; + +export function stepsOptions(context, steps, type) { + const optionsList = [ + descriptionCandidates( + fakeContextDescription(context), + type, + ), + ]; + if (0 === steps.length) { + return optionsList; + } + const isInvocation = 'invoke' === steps[steps.length - 1].type; + const keyStepsCount = steps.length - (isInvocation ? 1 : 0); + for (let i = 1; i < keyStepsCount; ++i) { + optionsList.push(descriptionCandidates( + descriptionFromSteps(context, steps.slice(0, i)), + type, + )); } return optionsList; } @@ -128,3 +134,65 @@ export function typeFromSteps(context, steps) { const [, type] = steps.slice(1).reduce(stepTo, context.get(steps[0].key)); return type; } + +export const defaultSteps = (context, type, originalSteps = []) => { + const steps = [...originalSteps]; + let stepsType = typeFromSteps(context, originalSteps); + const typesVisited = {[stepsType]: true}; + while (!typeFits(type, stepsType) || 0 === steps.length) { + let variable = steps.length > 0 ? (new Traversal({steps})).get(context) : undefined; + if ('function' === typeof variable) { + if ('function' === type) { + return steps; + } + const funcDescription = descriptionFromSteps(context, steps); + if (typeFits(type, funcDescription.type)) { + steps.push({ + type: 'invoke', + args: funcDescription.args.map(() => ({ + type: 'literal', + value: '', + })), + }); + return steps; + } + stepsType = typeFromSteps(context, originalSteps); + variable = stepsValue(context, steps); + } + const description = steps.length > 0 + ? Context.typeDescription(stepsType, variable) + : fakeContextDescription(context); + const candidates = descriptionCandidates(description, type); + const key = candidates.find((candidate) => { + const {type: candidateType} = description[candidate]; + if (typesVisited[candidateType]) { + return false; + } + if (typeFits(type, candidateType)) { + return true; + } + typesVisited[candidateType] = true; + return true; + }); + steps.push({type: 'key', key}); + stepsType = typeFromSteps(context, originalSteps); + variable = stepsValue(context, steps); + if ('function' === typeof variable) { + const funcDescription = descriptionFromSteps(context, steps); + steps.push({ + type: 'invoke', + args: funcDescription.args.map(() => ({ + type: 'literal', + value: '', + })), + }); + return steps; + } + stepsType = typeFromSteps(context, steps); + } + return steps; +}; + +export function typeFromValue(v) { + return isBehaviorItem(v) ? v.type : undefined; +}