import React from 'react' import { Parser } from 'nearley' import Grammar from './grammar.ne' import { contains, propEq, curry, cond, equals, divide, multiply, map, intersection, keys, propOr, always, head, gte, lte, lt, gt, add, subtract } from 'ramda' import { evaluateNode, rewriteNode, makeJsx, mergeMissing } from './evaluation' import { Node } from './mecanismViews/common' import { treatVariable, treatNegatedVariable, treatVariableTransforms } from './treatVariable' import { treat } from './traverse' import knownMecanisms from './known-mecanisms.yaml' import { mecanismOneOf, mecanismAllOf, mecanismNumericalSwitch, mecanismSum, mecanismProduct, mecanismScale, mecanismLinearScale, mecanismContinuousScale, mecanismMax, mecanismMin, mecanismError, mecanismComplement, mecanismSelection, mecanismInversion, mecanismReduction, mecanismVariations, mecanismSynchronisation } from './mecanisms' let nearley = () => new Parser(Grammar.ParserRules, Grammar.ParserStart) export let treatString = (rules, rule) => rawNode => { /* On a affaire à un string, donc à une expression infixe. Elle sera traité avec le parser obtenu grâce à NearleyJs et notre grammaire `grammar.ne`. On obtient un objet de type Variable (avec potentiellement un 'modifier', par exemple temporel), CalcExpression ou Comparison. Cet objet est alors rebalancé à 'treat'. */ let [parseResult, ...additionnalResults] = nearley().feed(rawNode).results if ( additionnalResults && additionnalResults.length > 0 && parseResult.category !== 'boolean' ) { // booleans, 'oui' and 'non', have an exceptional resolving precedence throw new Error( "Attention ! L'expression <" + rawNode + '> ne peut être traitée de façon univoque' ) } if ( !contains(parseResult.category)([ 'variable', 'calcExpression', 'filteredVariable', 'comparison', 'negatedVariable', 'percentage', 'boolean' ]) ) throw new Error( "Attention ! Erreur de traitement de l'expression : " + rawNode ) if (parseResult.category == 'variable') return treatVariableTransforms(rules, rule)(parseResult) if (parseResult.category == 'negatedVariable') return treatNegatedVariable( treatVariable(rules, rule)(parseResult.variable) ) if (parseResult.category == 'boolean') { return { nodeValue: parseResult.nodeValue, // eslint-disable-next-line jsx: () => {rawNode} } } // We don't need to handle category == 'value' because YAML then returns it as // numerical value, not a String: it goes to treatNumber if (parseResult.category == 'percentage') return { nodeValue: parseResult.nodeValue, category: 'percentage', // eslint-disable-next-line jsx: () => {rawNode.split('%')[0]} % //on ajoute l'espace nécessaire en français avant le pourcentage } if ( parseResult.category == 'calcExpression' || parseResult.category == 'comparison' ) { let evaluate = (cache, situation, parsedRules, node) => { let operatorFunction = { '*': multiply, '/': divide, '+': add, '-': subtract, '<': lt, '<=': lte, '>': gt, '>=': gte, '=': equals, '!=': (a, b) => !equals(a, b) }[node.operator], explanation = map( curry(evaluateNode)(cache, situation, parsedRules), node.explanation ), value1 = explanation[0].nodeValue, value2 = explanation[1].nodeValue, nodeValue = value1 == null || value2 == null ? null : operatorFunction(value1, value2), missingVariables = mergeMissing( explanation[0].missingVariables, explanation[1].missingVariables ) return rewriteNode(node, nodeValue, explanation, missingVariables) } let explanation = parseResult.explanation.map( cond([ [ propEq('category', 'variable'), treatVariableTransforms(rules, rule) ], [ propEq('category', 'value'), node => ({ nodeValue: node.nodeValue, // eslint-disable-next-line jsx: nodeValue => {nodeValue} }) ], [ propEq('category', 'percentage'), node => ({ nodeValue: node.nodeValue, // eslint-disable-next-line jsx: nodeValue => ( {nodeValue * 100}% ) //the best would be to display the original text before parsing, but nearley does'nt let us access it }) ] ]) ), operator = parseResult.operator let operatorToUnicode = operator => ({ '>=': '≥', '<=': '≤', '!=': '≠', '*': '∗', '/': '∕', '-': '−' }[operator] || operator) let jsx = (nodeValue, explanation) => ( {makeJsx(explanation[0])} {operatorToUnicode(parseResult.operator)} {makeJsx(explanation[1])} } /> ) return { evaluate, jsx, operator, text: rawNode, category: parseResult.category, type: parseResult.category == 'calcExpression' ? 'numeric' : 'boolean', explanation } } } export let treatNumber = rawNode => ({ text: '' + rawNode, category: 'number', nodeValue: rawNode, type: 'numeric', jsx: {rawNode} }) export let treatOther = rawNode => { throw new Error( 'Cette donnée : ' + rawNode + ' doit être un Number, String ou Object' ) } export let treatObject = (rules, rule, treatOptions) => rawNode => { let mecanisms = intersection(keys(rawNode), keys(knownMecanisms)) if (mecanisms.length != 1) { throw new Error(`OUPS : On ne devrait reconnaître que un et un seul mécanisme dans cet objet Objet YAML : ${JSON.stringify(rawNode)} Mécanismes implémentés correspondants : ${JSON.stringify(mecanisms)} Cette liste doit avoir un et un seul élément. Vérifier que le mécanisme est dans l'objet 'dispatch' et dans les'knownMecanisms.yaml' `) } let k = head(mecanisms), v = rawNode[k] let dispatch = { 'une de ces conditions': mecanismOneOf, 'toutes ces conditions': mecanismAllOf, 'aiguillage numérique': mecanismNumericalSwitch, somme: mecanismSum, multiplication: mecanismProduct, barème: mecanismScale, 'barème linéaire': mecanismLinearScale, 'barème continu': mecanismContinuousScale, 'le maximum de': mecanismMax, 'le minimum de': mecanismMin, complément: mecanismComplement, sélection: mecanismSelection, 'une possibilité': always({ ...v, 'une possibilité': 'oui', missingVariables: { [rule.dottedName]: 1 } }), inversion: mecanismInversion(rule.dottedName), allègement: mecanismReduction, variations: mecanismVariations, synchronisation: mecanismSynchronisation }, action = propOr(mecanismError, k, dispatch) return action(treat(rules, rule, treatOptions), k, v) }