/** * Note: all here is strictly based on duck typing. * We don't exepect the parent rule to explain the type of the contained formula, for example. */ import R from 'ramda' import { ParsedRule, ParsedRules } from './types' // type GraphNode = { // name: string // children?: Array // } type ASTNode = { [index: string]: {} | undefined } // [XXX] - Vaudrait-il mieux utiliser les DottedNames directement ici? // A priori non car on peut imaginer cette lib comme étant indépendante des règles existantes et // fonctionnant par ex en "mode serveur". type RuleNode = /* ASTNode & */ ParsedRule type Value = ASTNode & { nodeValue: any constant: true } export function isValue(node: ASTNode): node is Value { return ( (node as Value).nodeValue !== undefined && (node as Value).constant === true ) } type Operation = ASTNode & { operationType: 'comparison' | 'calculation' explanation: Array } export function isOperation(node: ASTNode): node is Operation { return ( (node as Operation).operationType === 'comparison' || (node as Operation).operationType === 'calculation' ) // return R.includes((node as Operation).operationType, [ // 'comparison', // 'calculation' // ]) } type Possibilities = ASTNode & { possibilités: Array 'choix obligatoire'?: 'oui' | 'non' // [XXX] - This should already be a defined type. 'une possibilité': 'oui' | 'non' } export function isPossibilities(node: ASTNode): node is Possibilities { const possibilities = node as Possibilities return ( possibilities.possibilités instanceof Array && possibilities.possibilités.every(it => typeof it === 'string') && (possibilities['choix obligatoire'] === undefined || possibilities['choix obligatoire'] === 'oui' || possibilities['choix obligatoire'] === 'non') && (possibilities['une possibilité'] === 'oui' || possibilities['une possibilité'] === 'non') // R.includes(possibilities['choix obligatoire'], [undefined, 'oui', 'non']) && // R.includes(possibilities['une possibilité'], ['oui', 'non']) ) } type Reference = Omit, 'category'> & { // [XXX] - a priori non pour le omit, il n'y a pas du tout autant de choses que dans RuleNode à l'intérieur d'une reference category: 'reference' partialReference: Name dottedName: Name } export function isReference( node: ASTNode ): node is Reference { return ( (node as Reference).category === 'reference' && (node as Reference).partialReference !== undefined && (node as Reference).dottedName !== undefined ) } type Recalcul = ASTNode & { explanation: { recalcul: Reference amendedSituation: Record> } } export function isRecalcul( node: ASTNode ): node is Recalcul { const recalcul = node as Recalcul return ( typeof recalcul.explanation === 'object' && typeof recalcul.explanation.recalcul === 'object' && isReference(recalcul.explanation.recalcul as ASTNode) && typeof recalcul.explanation.amendedSituation === 'object' && R.values(recalcul.explanation.amendedSituation).every(v => v !== undefined) // [XXX] - maybe a bit useless ) } type Mechanism = ASTNode & { category: 'mecanism' } export function isMechanism(node: ASTNode): node is Mechanism { return (node as Mechanism).category === 'mecanism' } type FormuleNode = | Value | Operation | Possibilities | Reference | Recalcul | Mechanism export function isFormuleNode( node: ASTNode ): node is FormuleNode { return ( isValue(node) || isOperation(node) || isReference(node) || isPossibilities(node) || isRecalcul(node) || isMechanism(node) ) } type EncadrementMech = Mechanism & { valeur: { explanation: ASTNode [s: string]: {} } } export function isEncadrementMech( mechanism: Mechanism ): mechanism is EncadrementMech { return (mechanism as EncadrementMech).explanation.name === 'encadrement' } type SommeMech = any // extends Mechanism export function isSommeMech(mechanism: Mechanism): mechanism is SommeMech { return (mechanism as EncadrementMech).explanation.name === 'somme' } function logVisit(depth: number, typeName: string, repr: string): void { console.log(' '.repeat(depth) + `visiting ${typeName} node ${repr}`) } export function ruleDependenciesOfNode( depth: number, node: ASTNode ): Array { function ruleDependenciesOfValue(depth: number, value: Value): Array { logVisit(depth, 'value', value.nodeValue) return [] } function ruleDependenciesOfOperation( depth: number, operation: Operation ): Array { logVisit(depth, 'operation', operation.operationType) return R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]) )(operation.explanation) } function ruleDependenciesOfPossibilities( depth: number, possibilities: Possibilities ): Array { logVisit(depth, 'possibilities', possibilities.possibilités.join(', ')) return [] } function ruleDependenciesOfReference( depth: number, reference: Reference ): Array { logVisit(depth, 'reference', reference.dottedName) return [reference.dottedName] } function ruleDependenciesOfRecalcul( depth: number, recalcul: Recalcul ): Array { logVisit( depth, 'recalcul', recalcul.explanation.recalcul.partialReference as string ) return [recalcul.explanation.recalcul.partialReference] } function ruleDependenciesOfMechanism( depth: number, mechanism: Mechanism ): Array { logVisit(depth, 'mechanism', '') debugger // [XXX] - flatten this out in the main function, like the other types // if (isEncadrementMech(mechanism)) { // chain(partial(ruleDependenciesOfNode, [depth + 1]))( // mechanism.valeur.explanation // ) // } // if (isSommeMech(mechanism)) { // chain(partial(ruleDependenciesOfNode, [depth + 1]))(mechanism.explanation) // } return [] // [XXX] } if (isValue(node)) { return ruleDependenciesOfValue(depth, node as Value) } else if (isOperation(node)) { return ruleDependenciesOfOperation(depth, node as Operation) } else if (isReference(node)) { return ruleDependenciesOfReference(depth, node as Reference) } else if (isPossibilities(node)) { return ruleDependenciesOfPossibilities(depth, node as Possibilities) } else if (isRecalcul(node)) { return ruleDependenciesOfRecalcul(depth, node as Recalcul) } else if (isMechanism(node)) { return ruleDependenciesOfMechanism(depth, node as Mechanism) } return [] // [XXX] } function ruleDependenciesOfRule( depth: number, rule: RuleNode ): Array { logVisit(depth, 'rule', rule.dottedName as string) if (rule.formule) { const formuleNode: FormuleNode = rule.formule.explanation // This is for comfort, as the responsibility over structure isn't owned by this piece of code: if (!isFormuleNode(formuleNode)) { debugger // throw Error( // `This rule's formule is not of a known type: ${rule.dottedName}` // ) } return ruleDependenciesOfNode(depth + 1, formuleNode) } else return [rule.dottedName] } export function buildRulesDependencies( parsedRules: ParsedRules ): Array<[Name, Array]> { // This stringPairs thing is necessary because `toPairs` is strictly considering that // object keys are strings (same for `Object.entries`). Maybe we should build our own // `toPairs`? const stringPairs: Array<[string, RuleNode]> = R.toPairs(parsedRules) const pairs: Array<[Name, RuleNode]> = stringPairs as Array< [Name, RuleNode] > const pairsResults: Array> = R.map( ([_, ruleNode]: [Name, RuleNode]): Array => ruleDependenciesOfRule(0, ruleNode), pairs ) console.log(pairsResults) return R.map( ([dottedName, ruleNode]: [Name, RuleNode]): [Name, Array] => [ dottedName, ruleDependenciesOfRule(0, ruleNode) ], pairs ) }