From 9b4a3b9e5736ab03fda12592132700cbd27dbe9c Mon Sep 17 00:00:00 2001 From: Mael Date: Thu, 13 Jun 2019 18:17:22 +0200 Subject: [PATCH] =?UTF-8?q?:gear:=20parsedRules=20passe=20d'une=20liste=20?= =?UTF-8?q?=C3=A0=20un=20object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comme pendant l'évaluation et son objet cache, parsedRules est construit au fur et à mesure du parsing sous la forme [dottedName]: parsedRule Cela nous permet pendant le parsing de faire l'annotation de type et de faire moins de boulot lors de l'évaluation Problème : - (presque fixé) dans l'inversion on produisait des références de variables pour le JSX => boucle infinie - dans chiffre d'affaire, notre implé un peu bizarre fait une référence de variables a priori circulaire, mais gérée par les variations. Or pendant le parsing on parcourt évidemment toutes les branches sans les évaluer. Sachant qu'on implémente ce cache parsedRules surtout pour les unités, peut on garder la formule ainsi et simplement stocker 'chiffre d'affaires': 'currently being parsed' pour éviter la boucle infinie ? --- source/engine/evaluateRule.js | 9 +------- source/engine/mecanisms.js | 39 ++++++--------------------------- source/engine/parse.js | 37 +++++++++++++++++-------------- source/engine/parseReference.js | 26 +++++++++++++++------- source/engine/parseRule.js | 31 ++++++++++++++++++-------- source/engine/rules.js | 32 +++++++++++---------------- source/engine/traverse.js | 18 +++++++-------- 7 files changed, 89 insertions(+), 103 deletions(-) diff --git a/source/engine/evaluateRule.js b/source/engine/evaluateRule.js index 08fa74cef..9ab7b4bf8 100644 --- a/source/engine/evaluateRule.js +++ b/source/engine/evaluateRule.js @@ -1,11 +1,4 @@ -import { - - keys, - map, - pick, - pipe, - -} from 'ramda' +import { mergeAll, keys, map, pick, pipe } from 'ramda' import { bonus, mergeMissing, evaluateNode } from 'Engine/evaluation' import { anyNull, undefOrTrue, val } from './traverse-common-functions' diff --git a/source/engine/mecanisms.js b/source/engine/mecanisms.js index 8adf377a4..ef0767f24 100644 --- a/source/engine/mecanisms.js +++ b/source/engine/mecanisms.js @@ -324,30 +324,19 @@ export let findInversion = (situationGate, parsedRules, v, dottedName) => { let candidates = inversions .map(i => disambiguateRuleReference( - parsedRules, - parsedRules.find(propEq('dottedName', dottedName)), + Object.values(parsedRules), + parsedRules[dottedName], i ) ) .map(name => { let userInput = situationGate(name) != undefined let rule = findRuleByDottedName(parsedRules, name) - /* When the fixedObjectiveValue is null, the inversion can't be done : the user needs to set the target's value - * But the objectiveRule can also have an 'alternative' property, - * which must point to a rule whose value either is set by the user, - * or is calculated according to a formula that does not depend on the rule being inversed. - * This alternative's value will be used as a target. - * */ - let alternativeRule = - !userInput && - rule.alternative && - findRuleByDottedName(parsedRules, rule.alternative) - if (!userInput && !alternativeRule) return null + if (!userInput) return null return { fixedObjectiveRule: rule, userInput, - fixedObjectiveValue: situationGate(name), - alternativeRule + fixedObjectiveValue: situationGate(name) } }), candidateWithUserInput = candidates.find(c => c && c.userInput) @@ -365,20 +354,7 @@ let doInversion = (oldCache, situationGate, parsedRules, v, dottedName) => { missingVariables: { [dottedName]: 1 }, nodeValue: null } - let { fixedObjectiveValue, fixedObjectiveRule, alternativeRule } = inversion - - let evaluatedAlternative = - alternativeRule && - evaluateNode(oldCache, situationGate, parsedRules, alternativeRule) - if (evaluatedAlternative && evaluatedAlternative.nodeValue == null) - return { - missingVariables: evaluatedAlternative.missingVariables, - nodeValue: null - } - - let objectiveValue = evaluatedAlternative - ? evaluatedAlternative.nodeValue - : fixedObjectiveValue + let { fixedObjectiveValue, fixedObjectiveRule } = inversion let inversionCache = {} let fx = x => { @@ -411,7 +387,7 @@ let doInversion = (oldCache, situationGate, parsedRules, v, dottedName) => { nodeValue = uniroot( x => { let y = fx(x) - return y.nodeValue - objectiveValue + return y.nodeValue - fixedObjectiveValue }, 0.1, 1000000000, @@ -451,7 +427,6 @@ export let mecanismInversion = dottedName => (recurse, k, v) => { node, nodeValue, { - ...evolve({ avec: map(recurse) }, v), inversedWith: inversion?.inversedWith }, missingVariables @@ -465,7 +440,7 @@ export let mecanismInversion = dottedName => (recurse, k, v) => { return { ...v, evaluate, - explanation: evolve({ avec: map(recurse) }, v), + explanation: v, jsx: InversionNumérique, category: 'mecanism', name: 'inversion numérique', diff --git a/source/engine/parse.js b/source/engine/parse.js index 06feb4119..90e5ee45c 100644 --- a/source/engine/parse.js +++ b/source/engine/parse.js @@ -21,8 +21,8 @@ import { subtract, fromPairs, is, - cond - , T + cond, + T } from 'ramda' import React from 'react' import { evaluateNode, makeJsx, mergeMissing, rewriteNode } from './evaluation' @@ -47,16 +47,16 @@ import { } from './mecanisms' import { Node } from './mecanismViews/common' import { - parseNegatedVariable, - parseVariable, - parseVariableTransforms -} from './parseVariable' + parseNegatedReference, + parseReference, + parseReferenceTransforms +} from './parseReference' -export let parse = (rules, rule) => rawNode => { +export let parse = (rules, rule, parsedRules) => rawNode => { let onNodeType = cond([ - [is(String), parseString(rules, rule)], + [is(String), parseString(rules, rule, parsedRules)], [is(Number), parseNumber], - [is(Object), parseObject(rules, rule)], + [is(Object), parseObject(rules, rule, parsedRules)], [T, parseOther] ]) @@ -70,14 +70,14 @@ export let parse = (rules, rule) => rawNode => { export let nearley = () => new Parser(Grammar.ParserRules, Grammar.ParserStart) -export let parseString = (rules, rule) => rawNode => { +export let parseString = (rules, rule, parsedRules) => rawNode => { /* Strings correspond to infix expressions. * Indeed, a subset of expressions like simple arithmetic operations `3 + (quantity * 2)` or like `salary [month]` are more explicit that their prefixed counterparts. * This function makes them prefixed operations. */ let [parseResult] = nearley().feed(rawNode).results - return parseObject(rules, rule)(parseResult) + return parseObject(rules, rule, parsedRules)(parseResult) } export let parseNumber = rawNode => ({ @@ -94,7 +94,7 @@ export let parseOther = rawNode => { ) } -export let parseObject = (rules, rule, parseOptions) => rawNode => { +export let parseObject = (rules, rule, parsedRules) => rawNode => { /* TODO instead of describing mecanisms in knownMecanisms.yaml, externalize the mecanisms themselves in an individual file and describe it let mecanisms = intersection(keys(rawNode), keys(knownMecanisms)) @@ -152,15 +152,18 @@ export let parseObject = (rules, rule, parseOptions) => rawNode => { synchronisation: mecanismSynchronisation, ...operationDispatch, '≠': () => - parseNegatedVariable(parseVariable(rules, rule)(v.explanation)), + parseNegatedReference( + parseReference(rules, rule, parsedRules)(v.explanation) + ), filter: () => - parseVariableTransforms(rules, rule)({ + parseReferenceTransforms(rules, rule, parsedRules)({ filter: v.filter, variable: v.explanation }), - variable: () => parseVariableTransforms(rules, rule)({ variable: v }), + variable: () => + parseReferenceTransforms(rules, rule, parsedRules)({ variable: v }), temporalTransform: () => - parseVariableTransforms(rules, rule)({ + parseReferenceTransforms(rules, rule, parsedRules)({ variable: v.explanation, temporalTransform: v.temporalTransform }), @@ -173,7 +176,7 @@ export let parseObject = (rules, rule, parseOptions) => rawNode => { }, action = propOr(mecanismError, k, dispatch) - return action(parse(rules, rule, parseOptions), k, v) + return action(parse(rules, rule, parsedRules), k, v) } let mecanismOperation = (k, operatorFunction, symbol) => (recurse, k, v) => { diff --git a/source/engine/parseReference.js b/source/engine/parseReference.js index b07448717..d23ba7894 100644 --- a/source/engine/parseReference.js +++ b/source/engine/parseReference.js @@ -8,11 +8,18 @@ import { findRuleByDottedName } from './rules' import { getSituationValue } from './variables' +import parseRule from 'Engine/parseRule' -export let parseReference = (rules, rule, filter) => ({ fragments }) => { - let variablePartialName = fragments.join(' . '), - dottedName = disambiguateRuleReference(rules, rule, variablePartialName) +export let parseReference = (rules, rule, parsedRules, filter) => ({ + fragments +}) => { + let partialReference = fragments.join(' . '), + dottedName = disambiguateRuleReference(rules, rule, partialReference) + let variable = + parsedRules[dottedName] || + (console.log('uncached : from `', rule.dottedName, '` to `', dottedName) || + parseRule(rules, findRuleByDottedName(rules, dottedName), parsedRules)) let evaluate = (cache, situation, parsedRules, node) => { let dottedName = node.dottedName, // On va vérifier dans le cache courant, dict, si la variable n'a pas été déjà évaluée @@ -21,8 +28,7 @@ export let parseReference = (rules, rule, filter) => ({ fragments }) => { cached = cache[cacheName] if (cached) return cached - let variable = findRuleByDottedName(parsedRules, dottedName), - variableHasFormula = variable.formule != null, + let variableHasFormula = variable.formule != null, variableHasCond = variable['applicable si'] != null || variable['non applicable si'] != null || @@ -86,7 +92,7 @@ export let parseReference = (rules, rule, filter) => ({ fragments }) => { /> ), - name: variablePartialName, + name: partialReference, category: 'variable', fragments, dottedName @@ -98,7 +104,11 @@ export let parseReference = (rules, rule, filter) => ({ fragments }) => { // See the période.yaml test suite for details // - filters on the variable to select one part of the variable's 'composantes' -export let parseReferenceTransforms = (rules, rule) => parseResult => { +export let parseReferenceTransforms = ( + rules, + rule, + parsedRules +) => parseResult => { let evaluateTransforms = originalEval => ( cache, situation, @@ -176,7 +186,7 @@ export let parseReferenceTransforms = (rules, rule) => parseResult => { return result } - let node = parseReference(rules, rule, parseResult.filter)( + let node = parseReference(rules, rule, parsedRules, parseResult.filter)( parseResult.variable ) diff --git a/source/engine/parseRule.js b/source/engine/parseRule.js index 25d268064..faed7ff34 100644 --- a/source/engine/parseRule.js +++ b/source/engine/parseRule.js @@ -1,6 +1,13 @@ import evaluate from 'Engine/evaluateRule' +import { findParentDependency } from './rules' +import { evolve, map } from 'ramda' +import React from 'react' +import { ShowValuesConsumer } from 'Components/rule/ShowValuesContext' +import { Node } from './mecanismViews/common' +import { evaluateNode, makeJsx, rewriteNode } from './evaluation' +import { parse } from 'Engine/parse' -export default (rules, rule) => { +export default (rules, rule, parsedRules) => { if (parsedRules[rule.dottedName]) return parsedRules[rule.dottedName] /* The parseRule function will traverse the tree of the `rule` and produce an AST, an object containing other objects containing other objects... @@ -19,7 +26,7 @@ export default (rules, rule) => { // condition d'applicabilité de la règle parentDependency: parent => { - let node = parse(rules, rule)(parent.dottedName) + let node = parse(rules, rule, parsedRules)(parent.dottedName) let jsx = (nodeValue, explanation) => ( @@ -46,8 +53,13 @@ export default (rules, rule) => { explanation: node } }, - 'non applicable si': evolveCond('non applicable si', rule, rules), - 'applicable si': evolveCond('applicable si', rule, rules), + 'non applicable si': evolveCond( + 'non applicable si', + rule, + rules, + parsedRules + ), + 'applicable si': evolveCond('applicable si', rule, rules, parsedRules), // formule de calcul formule: value => { let evaluate = (cache, situationGate, parsedRules, node) => { @@ -63,7 +75,7 @@ export default (rules, rule) => { return rewriteNode(node, nodeValue, explanation, missingVariables) } - let child = parse(rules, rule)(value) + let child = parse(rules, rule, parsedRules)(value) let jsx = (nodeValue, explanation) => makeJsx(explanation) @@ -78,7 +90,7 @@ export default (rules, rule) => { } }, contrôles: map(control => { - let testExpression = parse(rules, rule)(control.si) + let testExpression = parse(rules, rule, parsedRules)(control.si) if ( !testExpression.explanation && !(testExpression.category === 'variable') @@ -98,15 +110,16 @@ export default (rules, rule) => { }) })(root) - return { + parsedRules[rule.dottedName] = { // Pas de propriété explanation et jsx ici car on est parti du (mauvais) principe que 'non applicable si' et 'formule' sont particuliers, alors qu'ils pourraient être rangé avec les autres mécanismes ...parsedRoot, evaluate, parsed: true } + return parsedRules[rule.dottedName] } -let evolveCond = (name, rule, rules) => value => { +let evolveCond = (name, rule, rules, parsedRules) => value => { let evaluate = (cache, situationGate, parsedRules, node) => { let explanation = evaluateNode( cache, @@ -119,7 +132,7 @@ let evolveCond = (name, rule, rules) => value => { return rewriteNode(node, nodeValue, explanation, missingVariables) } - let child = parse(rules, rule)(value) + let child = parse(rules, rule, parsedRules)(value) let jsx = (nodeValue, explanation) => ( { try { let unit = rule.unité && parseUnit(rule.unité) return { - ...rule, + ...dissoc('contrôles', rule), type: possibleVariableTypes.find(t => has(t, rule) || rule.type === t), name: rule['nom'], title: capitalise0(rule['titre'] || rule['nom']), @@ -143,26 +144,19 @@ export let collectDefaults = pipe( Méthodes de recherche d'une règle */ export let findRuleByName = (allRules, query) => - allRules.find(({ name }) => name === query) + (Array.isArray(allRules) ? allRules : Object.values(allRules)).find( + ({ name }) => name === query + ) export let findRulesByName = (allRules, query) => - allRules.filter(({ name }) => name === query) + (Array.isArray(allRules) ? allRules : Object.values(allRules)).filter( + ({ name }) => name === query + ) -export let searchRules = searchInput => - rules - .filter( - rule => - rule && - hasKnownRuleType(rule) && - JSON.stringify(rule) - .toLowerCase() - .indexOf(searchInput) > -1 - ) - .map(enrichRule) - -export let findRuleByDottedName = (allRules, dottedName) => { - return allRules.find(rule => rule.dottedName == dottedName) -} +export let findRuleByDottedName = (allRules, dottedName) => + Array.isArray(allRules) + ? allRules.find(rule => rule.dottedName == dottedName) + : allRules[dottedName] export let findRule = (rules, nameOrDottedName) => nameOrDottedName.includes(' . ') diff --git a/source/engine/traverse.js b/source/engine/traverse.js index 1996d78d4..39b878ab0 100644 --- a/source/engine/traverse.js +++ b/source/engine/traverse.js @@ -1,16 +1,11 @@ -import { ShowValuesConsumer } from 'Components/rule/ShowValuesContext' import { evaluateControls } from 'Engine/controls' -import { chain, cond, evolve, is, map, path, T } from 'ramda' -import React from 'react' -import { evaluateNode, makeJsx, rewriteNode } from './evaluation' -import { Node } from './mecanismViews/common' +import { chain, map, path } from 'ramda' +import { evaluateNode } from './evaluation' import { disambiguateRuleReference, - findParentDependency, findRule, findRuleByDottedName } from './rules' -import { anyNull, undefOrTrue, val } from './traverse-common-functions' import parseRule from 'Engine/parseRule' /* @@ -51,8 +46,10 @@ par exemple ainsi : https://github.com/Engelberg/instaparse#transforming-the-tre export let parseAll = flatRules => { /* First we parse each rule one by one. When a mechanism is encountered, it is recursively parsed. When a reference to a variable is encountered, a 'variable' node is created, we don't parse variables recursively. */ - let parseOne = rule => parseRule(flatRules, rule) - let parsed = map(parseOne, flatRules) + + let parsedRules = {} + let parseOne = rule => parseRule(flatRules, rule, parsedRules) + map(parseOne, flatRules) /* Then we need to infer units. Since only references to variables have been created, we need to wait for the latter map to complete before starting this job. Consider this example : A = B * C B = D / E @@ -65,7 +62,7 @@ export let parseAll = flatRules => { * * */ - return parsed + return parsedRules } export let getTargets = (target, rules) => { @@ -85,6 +82,7 @@ export let analyseMany = (parsedRules, targetNames) => situationGate => { // TODO: we should really make use of namespaces at this level, in particular // setRule in Rule.js needs to get smarter and pass dottedName let cache = { parseLevel: 0 } + console.log('orang', parsedRules) let parsedTargets = targetNames.map(t => { let parsedTarget = findRule(parsedRules, t)