Files
nodejs.esm-library/source/rule-processing/reduction-scanner.mjs

114 lines
3.5 KiB
JavaScript

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;
}
}