import { Reduction_Contract, FPR_Contract } from './contracts.mjs'; import * as CF from '@efforting.tech/schema/field-configuration-factories'; export const Reduction_Order = new CF.symbol_set({ RULE_MAJOR: 'For each rule, scan the full sequence', POSITION_MAJOR: 'For each item in the sequence, try all rules', }, 'Reduction ordering'); export const Reduction_Settings = new CF.Schema({ reduction_order: CF.symbol(Reduction_Order, 'RULE_MAJOR'), reduction_contract: CF.instance(Reduction_Contract, 'The reduction contract defines the implementation of the reduction scanner'), rules: CF.factory(() => []), }, 'Reduction settings'); //TODO: Consider whether we can implement some hierarchical sub schema, like classes - we could of course reuse things by defining and object and spread it in here export const FP_Reduction_Settings = new CF.Schema({ reduction_order: CF.symbol(Reduction_Order, 'RULE_MAJOR'), reduction_contract: CF.instance(FPR_Contract, 'The reduction contract defines the implementation of the reduction scanner'), rules: CF.factory(() => []), }, 'Fixed point reduction settings'); export class Reduction_Scanner { static settings_schema = Reduction_Settings; constructor(settings, use_lifecycle_cb=true) { settings = this.constructor.settings_schema.load(settings); Object.assign(this, { settings }); this.clear_transform_state(); if (use_lifecycle_cb) { settings.reduction_contract.on_create_scanner(this); } } perform_reduction(sequence) { const { settings } = this; switch (settings.reduction_order) { case Reduction_Order.symbols.RULE_MAJOR: for (const rule of settings.rules) { const match = rule.match(sequence); if (match) { //console.log('RULE_MAJOR', match); rule.action(this, sequence, match); //TODO: should rule.action be able to add additional checks? Though that probably dillutes responsibility and blurs interfaces return true; } } return false; case Reduction_Order.symbols.POSITION_MAJOR: let best_match, best_rule; for (const rule of settings.rules) { const match = rule.match(sequence); if (match) { if (!best_match || best_match.match_start > match.match_start) { //TODO - early return if start of sequence best_match = match; best_rule = rule; } } } if (best_match) { //console.log('POSITION_MAJOR', best_match) best_rule.action(this, sequence, best_match); //TODO: should rule.action be able to add additional checks? Though that probably dillutes responsibility and blurs interfaces return true; } return false; default: throw new Error(`Unknown reduction order: ${this.reduction_order}`); //TODO: Force invalid configuration error } } clear_transform_state() { this.last_reduction_status = null; this.reductions_attempted = 0; this.reductions_made = 0; } transform(sequence) { const { settings } = this; const reduction_contract = settings.reduction_contract; this.clear_transform_state(); reduction_contract.on_start_transform(this, sequence); while (!reduction_contract.check_if_done(this, sequence)) { reduction_contract.assert_readiness(this, sequence); this.last_reduction_status = this.perform_reduction(sequence); this.reductions_attempted++; if (this.last_reduction_status) { this.reductions_made++; reduction_contract.on_reduction_made(this, sequence); } reduction_contract.on_reduction_attempted(this, sequence); } reduction_contract.on_end_transform(this, sequence); return sequence; } }