mon-entreprise/publicodes/source/index.ts

189 lines
4.7 KiB
TypeScript

/* eslint-disable @typescript-eslint/ban-types */
import { compose, map, mapObjIndexed } from 'ramda'
import { ASTNode, EvaluatedNode, NodeKind } from './AST/types'
import { evaluationFunctions } from './evaluationFunctions'
import { simplifyNodeUnit } from './nodeUnits'
import parse from './parse'
import parsePublicodes, { disambiguateReference } from './parsePublicodes'
import {
getReplacements,
inlineReplacements,
ReplacementNode,
} from './replacement'
import { Rule, RuleNode } from './rule'
import * as utils from './ruleUtils'
const emptyCache = () => ({
_meta: { contextRule: [] },
})
type Cache = {
_meta: {
contextRule: Array<string>
parentEvaluationStack?: Array<string>
inversionFail?:
| {
given: string
estimated: string
}
| true
inRecalcul?: boolean
filter?: string
}
}
export type EvaluationOptions = Partial<{
unit: string
}>
export * as cyclesLib from './AST/graph'
export { reduceAST, updateAST } from './AST'
export * from './components'
export { formatValue } from './format'
export { default as translateRules } from './translateRules'
export { ASTNode, EvaluatedNode }
export { Unit, Evaluation } from './AST/types'
export { parsePublicodes }
export { utils }
export { Rule }
export type evaluationFunction<Kind extends NodeKind = NodeKind> = (
this: Engine,
node: ASTNode & { nodeKind: Kind }
) => ASTNode & { nodeKind: Kind } & EvaluatedNode
export type ParsedRules<Name extends string> = Record<
Name,
RuleNode & { dottedName: Name }
>
export default class Engine<Name extends string = string> {
parsedRules: ParsedRules<Name>
parsedSituation: Record<string, ASTNode> = {}
replacements: Record<string, Array<ReplacementNode>> = {}
cache: Cache
private warnings: Array<string> = []
constructor(rules: string | Record<string, Rule> | ParsedRules<Name>) {
this.cache = emptyCache()
this.resetCache()
if (typeof rules === 'string') {
this.parsedRules = parsePublicodes(rules) as ParsedRules<Name>
}
const firstRuleObject = Object.values(rules)[0] as Rule | RuleNode
if (
typeof firstRuleObject === 'object' &&
firstRuleObject != null &&
'nodeKind' in firstRuleObject
) {
this.parsedRules = rules as ParsedRules<Name>
return
}
this.parsedRules = parsePublicodes(
rules as Record<string, Rule>
) as ParsedRules<Name>
this.replacements = getReplacements(this.parsedRules)
}
private resetCache() {
this.cache = emptyCache()
}
setSituation(
situation: Partial<Record<Name, string | number | object | ASTNode>> = {}
) {
this.resetCache()
this.parsedSituation = mapObjIndexed((value, key) => {
if (value && typeof value === 'object' && 'nodeKind' in value) {
return value as ASTNode
}
return compose(
inlineReplacements(this.replacements),
disambiguateReference(this.parsedRules)
)(
parse(value, {
dottedName: `situation [${key}]`,
parsedRules: {},
})
)
}, situation)
return this
}
evaluate(expression: string | Object): EvaluatedNode {
/*
TODO
EN ATTENDANT d'AVOIR une meilleure gestion d'erreur, on va mocker console.warn
*/
const originalWarn = console.warn
console.warn = (warning: string) => {
this.warnings.push(warning)
originalWarn(warning)
}
const result = this.evaluateNode(
compose(
inlineReplacements(this.replacements),
disambiguateReference(this.parsedRules)
)(
parse(expression, {
dottedName: "evaluation'''",
parsedRules: {},
})
)
)
console.warn = originalWarn
return result
}
getWarnings() {
return this.warnings
}
inversionFail(): boolean {
return !!this.cache._meta.inversionFail
}
getParsedRules(): ParsedRules<Name> {
return this.parsedRules
}
evaluateNode<N extends ASTNode = ASTNode>(node: N): N & EvaluatedNode {
if (!node.nodeKind) {
throw Error('The provided node must have a "nodeKind" attribute')
} else if (!evaluationFunctions[node.nodeKind]) {
throw Error(`Unknown "nodeKind": ${node.nodeKind}`)
}
return evaluationFunctions[node.nodeKind].call(this, node)
}
}
// This function is an util for allowing smother migration to the new Engine API
export function evaluateRule<DottedName extends string = string>(
engine: Engine<DottedName>,
dottedName: DottedName,
modifiers: Object = {}
): EvaluatedRule<DottedName> {
const evaluation = simplifyNodeUnit(
engine.evaluate({ valeur: dottedName, ...modifiers })
)
const rule = engine.getParsedRules()[dottedName] as RuleNode & {
dottedName: DottedName
}
return {
...rule.rawNode,
...rule,
...evaluation,
} as EvaluatedRule<DottedName>
}
export type EvaluatedRule<Name extends string = string> = EvaluatedNode &
Omit<
(ASTNode & {
nodeKind: 'rule'
}) &
(ASTNode & {
nodeKind: 'rule'
})['rawNode'] & { dottedName: Name },
'nodeKind'
>