diff --git a/package.json b/package.json index e2933ad9e..82349508c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "install": "^0.8.2", "js-yaml": "^3.7.0", "marked": "^0.3.6", + "nearley": "^2.7.14", "npm": "^4.0.3", "ramda": "^0.23.0", "react": "^15.0.1", @@ -52,6 +53,7 @@ "html-loader": "^0.4.2", "img-loader": "^1.2.2", "json-loader": "^0.5.4", + "nearley-loader": "0.0.2", "postcss-loader": "^1.2.2", "redux-devtools": "^3.2.0", "redux-devtools-dock-monitor": "^1.1.1", diff --git a/règles/rémunération-travail/cdd/CIF.yaml b/règles/rémunération-travail/cdd/CIF.yaml index 862b4b4b3..15ef5b0e7 100644 --- a/règles/rémunération-travail/cdd/CIF.yaml +++ b/règles/rémunération-travail/cdd/CIF.yaml @@ -5,13 +5,13 @@ collecteur: OPCA références: Code du travail - Article L6322-37 : https://www.legifrance.gouv.fr/affichCodeArticle.do?idArticle=LEGIARTI000022234996&cidTexte=LEGITEXT000006072050 - - non applicable si: - l'une de ces conditions: - - événements . CDD poursuivi en CDI - - motif . saisonnier - - motif . jeune vacances - - contrat aidé + # + # non applicable si: + # l'une de ces conditions: + # - événements . CDD poursuivi en CDI + # - motif . saisonnier + # - motif . jeune vacances + # - contrat aidé # Données de test # # non applicable si: diff --git a/source/engine/expressions-tests.yaml b/source/engine/expressions-tests.yaml index 2695da6e6..f301be410 100644 --- a/source/engine/expressions-tests.yaml +++ b/source/engine/expressions-tests.yaml @@ -1,5 +1,6 @@ # CalcExpression - salaire de base * 3 + - Salariat . salaire de base * 3 - congés non pris / 25 diff --git a/source/engine/grammar.ne b/source/engine/grammar.ne index 60afb96d0..3028daa45 100644 --- a/source/engine/grammar.ne +++ b/source/engine/grammar.ne @@ -1,83 +1,48 @@ -main -> CalcExpression {% d => (['CalcExpression', ...d]) %} - | BooleanVariableExpression {% d => (['BooleanVariableExpression', ...d]) %} - | ModifiedVariable {% d => (['ModifiedVariable', ...d]) %} - | Comparison {% d => (['Comparison', ...d]) %} +@{% function buildNode(type, d){return ({nodeType: type, explanation: d})} %} -Comparison -> Comparable _ ComparisonOperator _ Comparable +main -> + CalcExpression {% id %} + | Variable {% id %} + | ModifiedVariable {% id %} + | Comparison {% id %} -Comparable -> (int | CalcExpression | Variable) +Comparison -> Comparable _ ComparisonOperator _ Comparable {% d => ({nodeType: 'Comparison', operator: d[2][0], explanation: [d[0], d[4]]}) %} + +Comparable -> (int | CalcExpression | Variable) {% d => d[0][0] %} ComparisonOperator -> ">" | "<" | ">=" | "<=" | "=" +ModifiedVariable -> Variable _ Modifier {% d => ({nodeType: 'ModifiedVariable', modifier: d[2], variable: d[0] }) %} -ModifiedVariable -> Variable _ Modifier +Modifier -> "[" TemporalModifier "]" {% d =>d[1][0] %} -Modifier -> "[" TemporalModifier "]" +TemporalModifier -> "annuel" | "mensuel" | "jour ouvré" {% id %} -TemporalModifier -> "annuel" | "mensuel" | "jour ouvré" +CalcExpression -> Term _ ArithmeticOperator _ Term {% d => ({nodeType: 'CalcExpression', operator: d[2], explanation: [d[0], d[4]]}) %} -CalcExpression -> Term _ ArithmeticOperator _ Term +Term -> Variable {% id %} + | int {% id %} -Term -> Variable - | int - -ArithmeticOperator -> "+" | "-" | "*" | "/" - -BooleanVariableExpression -> ("!" _):? Variable {% d => (['BooleanVariableExpression', ...d]) %} +ArithmeticOperator -> "+" {% id %} + | "-" {% id %} + | "*" {% id %} + | "/" {% id %} -VariableWord -> [a-zA-Z\u00C0-\u017F]:+ {% d => (['VariableWord', ...d]) %} - -Variable -> VariableFragment (_ Dot _ VariableFragment):* {% d => (['Variable', ...d]) %} - -VariableFragment -> VariableWord (_ VariableWord):* {% d => (['VariableFragment', ...d]) %} - -Dot -> [\.] {% d => (['Dot', ...d]) %} - -_ -> [\s] {% function(d) {return null } %} +# BooleanVariableExpression -> ("!" _):? Variable {% d => (['BooleanVariableExpression', ...d]) %} +Variable -> VariableFragment (_ Dot _ VariableFragment {% d => d[3] %}):* {% d => ({nodeType: 'Variable', fragments: [d[0], ...d[1]] }) %} -# PEMDAS! -# We define each level of precedence as a nonterminal. +VariableFragment -> VariableWord (_ VariableWord {% d=> ' ' + d[1] %}):* {% d => d[0] + ' ' + d[1].join('') %} -# Parentheses -P -> "(" _ AS _ ")" {% function(d) {return {type:'P', d:d, v:d[2].v}} %} - | N {% id %} -# Exponents -E -> P _ "^" _ E {% function(d) {return {type:'E', d:d, v:Math.pow(d[0].v, d[4].v)}} %} - | P {% id %} +VariableWord -> [a-zA-Z\u00C0-\u017F]:+ {% d => d[0].join('') %} -# Multiplication and division -MD -> MD _ "*" _ E {% function(d) {return {type: 'M', d:d, v:d[0].v*d[4].v}} %} - | MD _ "/" _ E {% function(d) {return {type: 'D', d:d, v:d[0].v/d[4].v}} %} - | E {% id %} +Dot -> [\.] {% d => null %} -# Addition and subtraction -AS -> AS _ "+" _ MD {% function(d) {return {type:'A', d:d, v:d[0].v+d[4].v}} %} - | AS _ "-" _ MD {% function(d) {return {type:'S', d:d, v:d[0].v-d[4].v}} %} - | MD {% id %} +_ -> [\s] {% d => null %} -# A number or a function of a number -N -> float {% id %} - | "sin" _ P {% function(d) {return {type:'sin', d:d, v:Math.sin(d[2].v)}} %} - | "cos" _ P {% function(d) {return {type:'cos', d:d, v:Math.cos(d[2].v)}} %} - | "tan" _ P {% function(d) {return {type:'tan', d:d, v:Math.tan(d[2].v)}} %} - | "asin" _ P {% function(d) {return {type:'asin', d:d, v:Math.asin(d[2].v)}} %} - | "acos" _ P {% function(d) {return {type:'acos', d:d, v:Math.acos(d[2].v)}} %} - | "atan" _ P {% function(d) {return {type:'atan', d:d, v:Math.atan(d[2].v)}} %} - - | "pi" {% function(d) {return {type:'pi', d:d, v:Math.PI}} %} - | "e" {% function(d) {return {type:'e', d:d, v:Math.E}} %} - | "sqrt" _ P {% function(d) {return {type:'sqrt', d:d, v:Math.sqrt(d[2].v)}} %} - | "ln" _ P {% function(d) {return {type:'ln', d:d, v:Math.log(d[2].v)}} %} - -# I use `float` to basically mean a number with a decimal point in it -float -> - int "." int {% function(d) {return {v:parseFloat(d[0].v + d[1].v + d[2].v)}} %} - | int {% function(d) {return {v:parseInt(d[0].v)}} %} - -int -> [0-9]:+ {% function(d) {return {v:d[0].join("")}} %} +int -> [0-9]:+ {% d => ({nodeType: 'value', value: d[0].join("")}) %} diff --git a/source/engine/traverse.js b/source/engine/traverse.js index 6387362b7..505af81e6 100644 --- a/source/engine/traverse.js +++ b/source/engine/traverse.js @@ -2,6 +2,10 @@ import {rules, findRuleByName, parentName} from './rules' import {recognizeExpression} from './expressions' import R from 'ramda' import knownMecanisms from './known-mecanisms.yaml' +import { Parser } from 'nearley' +import Grammar from './grammar.ne' + +let nearley = new Parser(Grammar.ParserRules, Grammar.ParserStart) /* Dans ce fichier, les règles YAML sont parsées. @@ -234,6 +238,8 @@ let treat = (situationGate, rule) => rawNode => { if (k === 'multiplication') { let base = v['assiette'], + parsed = nearley.feed(base), + yaya = console.log('parsed', parsed), [baseVariableName] = recognizeExpression(rule, base), baseValue = situationGate(baseVariableName), rateNode = treat(situationGate, rule)({taux: v['taux']}), @@ -255,7 +261,7 @@ let treat = (situationGate, rule) => rawNode => { missingVariables: baseValue == null ? [baseVariableName] : [] }, rate: rateNode, - prorata: + // prorata: //TODO limit: 'plafond' //TODO multiplier: 'multiplicateur' } @@ -328,99 +334,6 @@ let treatRuleRoot = (situationGate, rule) => R.evolve({ // -> Voilà les attribu })(rule) -let deriveRuleOld = (situationGate, rule) => pipe( // eslint-disable-line no-unused-vars - toPairs, - reduce(({missingVariables, computedValue}, [key, value]) => { - if (key === 'concerne') { - let [variableName, evaluation] = recognizeExpression(rule, value) - // Si cette variable a été renseignée - if (knownVariable(situationGate, variableName)) { - // Si l'expression n'est pas vraie... - if (!evaluation(situationGate)) { - // On court-circuite toute la variable, et on n'a besoin d'aucune information ! - return reduced({missingVariables: []}) - } else { - // Sinon, on continue - return {missingVariables} - } - // sinon on demande la valeur de cette variable - } else return { missingVariables: [...missingVariables, variableName] } - } - - if (key === 'non applicable si') { - let conditions = value['l\'une de ces conditions'] - let [subVariableNames, reduced] = reduce(([variableNames], expression) => { - let [variableName, evaluation] = recognizeExpression(rule, expression) - if (knownVariable(situationGate, variableName)) { - if (evaluation(situationGate)) { - return reduced([[], true]) - } else { - return [variableNames] - } - } - return [[...variableNames, variableName]] - }, [[], null])(conditions) - - if (reduced) return reduced({missingVariables: []}) - else return {missingVariables: [...missingVariables, ...subVariableNames]} - } - - if (key === 'formule') { - if (value['multiplication']) { - let {assiette, taux} = value['multiplication'] - - // A propos de l'assiette - let [assietteVariableName] = recognizeExpression(rule, assiette), - assietteValue = situationGate(assietteVariableName), - unknownAssiette = assietteValue == undefined - - // Arrivés là, cette formule devrait être calculable ! - let {missingVariables: tauxMissingVariables = [], computedValue} = typeof taux !== 'string' ? - do { - let numericalLogic = taux['logique numérique'] - if (!numericalLogic) throw 'On ne sait pas pour l\'instant traiter ce mécanisme de taux' - - let treatNumericalLogic = numericalLogic => { - if (typeof numericalLogic == 'string') { - return new Object({computedValue: assietteValue * transformPercentage(numericalLogic)}) - } else { - return pipe( - toPairs(), - reduce(({missingVariables}, [expression, subLogic]) => { - let [variableName, evaluation] = recognizeExpression(rule, expression) - if (knownVariable(situationGate, variableName)) { - if (evaluation(situationGate)) { - return reduced(treatNumericalLogic(subLogic)) - } else { - return {missingVariables} - } - } else return {missingVariables: [...missingVariables, variableName]} - }, {missingVariables: []}) - )(numericalLogic) - }} - treatNumericalLogic(numericalLogic) - } : ({computedValue: assietteValue * transformPercentage(taux)}) - - let formulaResult = { - missingVariables: [ - ...missingVariables, - ...(unknownAssiette ? [assietteVariableName] : []), - ...tauxMissingVariables - ], - computedValue - } - - return computedValue != null ? reduced(formulaResult) : formulaResult - - } - } - - return {missingVariables} - }, {missingVariables: []} - ) -) - - /* Analyse the set of selected rules, and add derived information to them : - do they need variables that are not present in the user situation ? - if not, do they have a computed value or are they non applicable ? diff --git a/source/webpack.config.js b/source/webpack.config.js index 776fd2b18..8ab1a8e27 100644 --- a/source/webpack.config.js +++ b/source/webpack.config.js @@ -35,6 +35,9 @@ module.exports = { { test: /\.(jpe?g|png|gif|svg)$/i, loader: 'url?limit=10000!img?progressive=true' + }, { + test: /\.ne$/, + loader: 'nearley' }] }, postcss: [