⚙️ parsedRules passe d'une liste à un object

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 ?
pull/481/head
Mael 2019-06-13 18:17:22 +02:00
parent 00e920d00f
commit 9b4a3b9e57
7 changed files with 89 additions and 103 deletions

View File

@ -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'

View File

@ -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',

View File

@ -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) => {

View File

@ -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
)

View File

@ -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) => (
<ShowValuesConsumer>
@ -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) => (
<Node

View File

@ -25,7 +25,8 @@ import {
toPairs,
trim,
when,
groupBy
groupBy,
dissoc
} from 'ramda'
import rawRules from 'Règles/base.yaml'
import translations from 'Règles/externalized.yaml'
@ -42,7 +43,7 @@ export let enrichRule = rule => {
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(' . ')

View File

@ -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)