mon-entreprise/publicodes/source/parse.tsx

258 lines
6.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// This should be the new way to implement mecanisms
// In a specific file
// TODO import them automatically
// TODO convert the legacy functions to new files
import mecanismRound, { unchainRoundMecanism } from './mecanisms/arrondi'
import barème from './mecanisms/barème'
import durée from './mecanisms/durée'
import encadrement from './mecanisms/encadrement'
import grille from './mecanisms/grille'
import operation from './mecanisms/operation'
import régularisation from './mecanisms/régularisation'
import tauxProgressif from './mecanisms/tauxProgressif'
import variableTemporelle from './mecanisms/variableTemporelle'
import variations from './mecanisms/variations'
import { Grammar, Parser } from 'nearley'
import {
add,
difference,
divide,
equals,
fromPairs,
gt,
gte,
lt,
lte,
multiply,
subtract
} from 'ramda'
import React from 'react'
import { EngineError, syntaxError } from './error'
import { formatValue } from './format'
import grammar from './grammar.ne'
import {
mecanismAllOf,
mecanismInversion,
mecanismMax,
mecanismMin,
mecanismOneOf,
mecanismOnePossibility,
mecanismProduct,
mecanismRecalcul,
mecanismReduction,
mecanismSum,
mecanismSynchronisation
} from './mecanisms'
import { parseReferenceTransforms } from './parseReference'
import { EvaluatedRule } from './types'
export const parse = (rules, rule, parsedRules) => rawNode => {
if (rawNode == null) {
syntaxError(
rule.dottedName,
`
Une des valeurs de la formule est vide.
Vérifiez que tous les champs à droite des deux points sont remplis`
)
}
if (typeof rawNode === 'boolean') {
syntaxError(
rule.dottedName,
`
Les valeure booléenes true / false ne sont acceptée.
Utilisez leur contrepartie française : 'oui' / 'non'`
)
}
const node =
typeof rawNode === 'object' ? rawNode : parseExpression(rule, '' + rawNode)
const parsedNode = parseMecanism(rules, rule, parsedRules)(node)
parsedNode.evaluate = parsedNode.evaluate ?? ((_, __, ___, node) => node)
return parsedNode
}
const compiledGrammar = Grammar.fromCompiled(grammar)
const parseExpression = (rule, 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. */
try {
const [parseResult] = new Parser(compiledGrammar).feed(rawNode).results
return parseResult
} catch (e) {
syntaxError(
rule.dottedName,
`\`${rawNode}\` n'est pas une expression valide`,
e
)
}
}
const parseMecanism = (rules, rule, parsedRules) => rawNode => {
if (Array.isArray(rawNode)) {
syntaxError(
rule.dottedName,
`
Il manque le nom du mécanisme pour le tableau : [${rawNode
.map(x => `'${x}'`)
.join(', ')}]
Les mécanisme possibles sont : 'somme', 'le maximum de', 'le minimum de', 'toutes ces conditions', 'une de ces conditions'.
`
)
}
const keys = Object.keys(rawNode)
const unchainableMecanisms = difference(keys, chainableMecanisms)
if (keys.length > 1) {
if (unchainableMecanisms.length > 1) {
syntaxError(
rule.dottedName,
`
Les mécanismes suivants se situent au même niveau : ${unchainableMecanisms
.map(x => `'${x}'`)
.join(', ')}
Cela vient probablement d'une erreur dans l'indentation
`
)
}
return parseChainedMecanisms(rules, rule, parsedRules, rawNode)
}
const mecanismName = Object.keys(rawNode)[0]
const values = rawNode[mecanismName]
const parseFunctions = {
...statelessParseFunction,
'une possibilité': mecanismOnePossibility(rule.dottedName),
'inversion numérique': mecanismInversion(rule.dottedName),
recalcul: mecanismRecalcul(rule.dottedName),
filter: () =>
parseReferenceTransforms(
rules,
rule,
parsedRules
)({
filter: values.filter,
variable: values.explanation
}),
variable: () =>
parseReferenceTransforms(rules, rule, parsedRules)({ variable: values }),
unitConversion: () =>
parseReferenceTransforms(
rules,
rule,
parsedRules
)({
variable: values.explanation,
unit: values.unit
})
}
const parseFn = parseFunctions[mecanismName]
if (!parseFn) {
syntaxError(
rule.dottedName,
`
Le mécanisme ${mecanismName} est inconnu.
Vérifiez qu'il n'y ait pas d'erreur dans l'orthographe du nom.`
)
}
try {
return parseFn(parse(rules, rule, parsedRules), mecanismName, values)
} catch (e) {
if (e instanceof EngineError) {
throw e
}
syntaxError(rule.dottedName, e.message)
}
}
const chainableMecanisms = ['arrondi', 'plancher', 'plafond']
function parseChainedMecanisms(rules, rule, parsedRules, rawNode) {
const keys = Object.keys(rawNode)
const recurse = parseMecanism(rules, rule, parsedRules)
if (keys.includes('arrondi')) {
return recurse(
unchainRoundMecanism(parse(rules, rule, parsedRules), rawNode)
)
} else if (keys.includes('plancher')) {
const { plancher, ...valeur } = rawNode
return recurse({
encadrement: {
valeur,
plancher
}
})
} else if (keys.includes('plafond')) {
const { plafond, ...valeur } = rawNode
return recurse({
encadrement: {
valeur,
plafond
}
})
}
}
const knownOperations = {
'*': [multiply, '×'],
'/': [divide, ''],
'+': [add],
'-': [subtract, ''],
'<': [lt],
'<=': [lte, '≤'],
'>': [gt],
'>=': [gte, '≥'],
'=': [equals],
'!=': [(a, b) => !equals(a, b), '≠']
}
const operationDispatch = fromPairs(
Object.entries(knownOperations).map(([k, [f, symbol]]) => [
k,
operation(k, f, symbol)
])
)
const statelessParseFunction = {
...operationDispatch,
'une de ces conditions': mecanismOneOf,
'toutes ces conditions': mecanismAllOf,
somme: mecanismSum,
régularisation,
multiplication: mecanismProduct,
produit: mecanismProduct,
temporalValue: variableTemporelle,
arrondi: mecanismRound,
barème,
grille,
'taux progressif': tauxProgressif,
encadrement,
durée,
'le maximum de': mecanismMax,
'le minimum de': mecanismMin,
allègement: mecanismReduction,
variations,
synchronisation: mecanismSynchronisation,
valeur: (recurse, __, v) => recurse(v),
constant: (_, __, v) => ({
type: v.type,
constant: true,
nodeValue: v.nodeValue,
unit: v.unit,
// eslint-disable-next-line
jsx: ({ nodeValue, unit }: EvaluatedRule) => (
<span className={v.type}>
{formatValue({
unit,
nodeValue,
language: 'fr',
// We want to display constants with full precision,
// espacilly for percentages like APEC 0,036 %
precision: 5
})}
</span>
)
})
}