⚙️ 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
parent
00e920d00f
commit
9b4a3b9e57
|
@ -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'
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(' . ')
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue