feat: some traversal mutation

This commit is contained in:
cha0s 2020-06-22 14:47:11 -05:00
parent f2071507b9
commit bf371268cf
3 changed files with 155 additions and 48 deletions

View File

@ -19,7 +19,7 @@ const Literal = ({
const typeRenderers = useTypeRenderers(); const typeRenderers = useTypeRenderers();
const type = typeFromLiteral(value.value); const type = typeFromLiteral(value.value);
const Component = typeRenderers[type]; const Component = typeRenderers[type];
const [options] = stepsOptions(context, [{type: 'key'}], type); const [options] = stepsOptions(context, [], type);
options.push('<literal>'); options.push('<literal>');
return ( return (
<div className="literal"> <div className="literal">

View File

@ -6,6 +6,7 @@ import Value from '~/client/value';
import propTypes from './prop-types'; import propTypes from './prop-types';
import { import {
defaultSteps,
descriptionFromSteps, descriptionFromSteps,
stepsOptions, stepsOptions,
toBehaviorItem, toBehaviorItem,
@ -38,7 +39,23 @@ const Traversal = (props) => {
const options = optionsList[i]; const options = optionsList[i];
return ( return (
<span className="key" data-step-key={step.key}> <span className="key" data-step-key={step.key}>
<select readOnly value={step.key}> <select
onChange={(event) => {
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 options
.sort((l, r) => (l.toLowerCase() < r.toLowerCase() ? -1 : 1)) .sort((l, r) => (l.toLowerCase() < r.toLowerCase() ? -1 : 1))
@ -59,7 +76,27 @@ const Traversal = (props) => {
{arg.label} {arg.label}
<Value.Component <Value.Component
context={context} context={context}
onChange={onChange} onChange={(argValue, event) => {
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} type={description.args[j][1].type}
value={arg} value={arg}
/> />
@ -83,11 +120,13 @@ const Traversal = (props) => {
<span className="op">=</span> <span className="op">=</span>
<Value.Component <Value.Component
context={context} context={context}
onChange={(valueValue) => onChange({ onChange={(valueValue, event) => (
onChange({
...value, ...value,
steps, steps,
value: toBehaviorItem(valueValue), value: toBehaviorItem(valueValue),
})} }, event)
)}
type={stepsType} type={stepsType}
value={assignValue} value={assignValue}
/> />

View File

@ -1,4 +1,5 @@
import {Context} from '@avocado/behavior'; import {Context} from '@avocado/behavior';
import {Traversal} from '@avocado/behavior/item/traversal';
let digraph; let digraph;
// eslint-disable-next-line no-return-assign // eslint-disable-next-line no-return-assign
@ -17,7 +18,7 @@ export function isBehaviorItem(valueOrItem) {
} }
export function toBehaviorItem(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) { export function typeFromLiteral(value) {
@ -42,33 +43,36 @@ export function typeFromLiteral(value) {
return 'object'; return 'object';
} }
export function typeFromValue(v) {
return isBehaviorItem(v) ? v.type : undefined;
}
const fakeContextDescription = (context) => ( const fakeContextDescription = (context) => (
Object.entries(context.all()) Object.entries(context.all())
.reduce((r, [key, tuple]) => ({...r, [key]: {type: tuple[1]}}), {}) .reduce((r, [key, tuple]) => ({...r, [key]: {type: tuple[1]}}), {})
); );
const stepsValue = (context, steps) => (new Traversal({steps})).get(context);
export function descriptionFromSteps(context, steps) { export function descriptionFromSteps(context, steps) {
let description = fakeContextDescription(context);
if (0 === steps.length) {
return description;
}
const isInvocation = 'invoke' === steps[steps.length - 1].type; const isInvocation = 'invoke' === steps[steps.length - 1].type;
const keyStepsCount = steps.length - (isInvocation ? 1 : 0); const keyStepsCount = steps.length - (isInvocation ? 1 : 0);
let description = fakeContextDescription(context);
let variable; let variable;
for (let i = 0; i < keyStepsCount; ++i) { for (let i = 0; i < keyStepsCount; ++i) {
const {key} = steps[i]; const {key} = steps[i];
if (0 === i) { variable = stepsValue(context, steps.slice(0, i + 1));
[variable] = context.get(key); if ('function' === typeof variable && description[key].args) {
description = description[key];
} }
else { else {
variable = 'object' === typeof variable ? variable[key] : undefined; const candidateDescription = Context.typeDescription(
}
const lookupDesc = Context.typeDescription(
description[key].type, description[key].type,
(isInvocation && keyStepsCount - 1 === i) ? undefined : variable, 'function' === description[key].type ? variable : undefined,
); );
description = 0 !== Object.keys(lookupDesc).length ? lookupDesc : description[key]; description = 0 !== Object.keys(candidateDescription).length
? candidateDescription
: description[key];
}
} }
return description; return description;
} }
@ -77,26 +81,10 @@ export function typeFits(reference, candidate) {
return 'any' === reference || -1 !== (reference.split('|') || []).indexOf(candidate); return 'any' === reference || -1 !== (reference.split('|') || []).indexOf(candidate);
} }
export function stepsOptions(context, steps, type) { const descriptionCandidates = (description, type) => {
const isInvocation = 'invoke' === steps[steps.length - 1].type;
const keyStepsCount = steps.length - (isInvocation ? 1 : 0);
const optionsList = [];
const inverted = cachedDigraph(); const inverted = cachedDigraph();
const candidates = (inverted[type] || []).concat(type); const candidates = (inverted[type] || []).concat(type);
for (let i = 0; i < keyStepsCount; ++i) { return 'any' === type
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.keys(description)
: Object.entries(description) : Object.entries(description)
.reduce((r, [key, spec]) => ( .reduce((r, [key, spec]) => (
@ -104,7 +92,25 @@ export function stepsOptions(context, steps, type) {
? r.concat(key) ? r.concat(key)
: r : r
), []); ), []);
optionsList.push(options); };
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; return optionsList;
} }
@ -128,3 +134,65 @@ export function typeFromSteps(context, steps) {
const [, type] = steps.slice(1).reduce(stepTo, context.get(steps[0].key)); const [, type] = steps.slice(1).reduce(stepTo, context.get(steps[0].key));
return type; 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;
}