124 lines
3.6 KiB
JavaScript
124 lines
3.6 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');
|
|
|
|
|
|
|
|
//TODO - we should probably have a pre-defined record shape as argument for actions and such rather than using an ever growing list of positionals or an anonymous Object()
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
find_reduction_candidate(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) {
|
|
return { sequence, rule, match };
|
|
}
|
|
}
|
|
return;
|
|
|
|
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) {
|
|
return { sequence, rule: best_rule, match: best_match };
|
|
}
|
|
|
|
|
|
return;
|
|
default:
|
|
throw new Error(`Unknown reduction order: ${this.reduction_order}`); //TODO: Force invalid configuration error
|
|
|
|
}
|
|
}
|
|
|
|
|
|
perform_reduction(sequence) {
|
|
const candidate = this.find_reduction_candidate(sequence);
|
|
if (candidate) {
|
|
const { sequence, rule, match } = candidate;
|
|
//console.log('ACT', match.match)
|
|
match.action({ reduction_system: this, rule, sequence, match: match.match });
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
}
|
|
|