import { Reduction_Scanner, Reduction_Settings } from '@efforting.tech/rule-processing/reduction-scanner'; import * as R from '@efforting.tech/rule-processing/rules'; import { inspect } from 'node:util'; /* Here's what needs addressing: **`perform_reduction` refactor** - Remove the `start_index` loop — scanning is the condition's responsibility - RULE_MAJOR: iterate rules, call `rule.match(sequence, context)`, apply first that returns a match - POSITION_MAJOR: collect matches from all rules, apply the one with lowest `start_index` in result **`Sequence_Condition.match` implementation** - Iterate positions internally - Return match result with `start_index`, `end_index`, captures - Return null if no match found anywhere **Rule interface** - `rule.match(sequence, context)` → match result or null - `rule.action(scanner, sequence, match)` → performs the transformation - Decide: forwarding getters, bind in constructor, or scanner calls `rule.condition.match` directly **Match result shape** - `{ rule, sequence, start_index, end_index, captures, ...extra_info }` - `captures` lazily evaluated via getter **Normalization** - Decide when rules get normalized/compiled (construction, first transform, explicit `prepare()`) - Normalize bare functions to condition objects at that point **`context` shape** - What does the scanner inject into context beyond `start_index`/`end_index`? - How does `extra_info` from resolver flow through to condition match? */ class Rule { //NOTE: This is somewhat of a place holder because we may want to declare specific transformations later rather than always having an opaque handler function constructor(condition, handler) { Object.assign(this, { condition, handler }); } get match() { return this.condition.match.bind(this.condition); } get action() { return this.handler; } } const N = new R.Predicate((i) => typeof i === 'number' || i.type == 'BINOP' ); const ASTERISK = new R.Strict_Equality('*'); const PLUS = new R.Strict_Equality('+'); const rss = Reduction_Settings.load({ // Switching this on or off affects whether add comes before mul or not //reduction_order: 'POSITION_MAJOR', }); const rs = new Reduction_Scanner(rss); rss.rules.push( new Rule( new R.Sequence_Condition([N, ASTERISK, N]), (rs, sequence, match) => { const MS = match.match_start; const ME = match.match_end; sequence.splice(MS, ME - MS + 1, { type: 'BINOP', op: 'MUL', operands: [sequence[MS], sequence[ME]]}); } ), new Rule( new R.Sequence_Condition([N, PLUS, N]), (rs, sequence, match) => { const MS = match.match_start; const ME = match.match_end; sequence.splice(MS, ME - MS + 1, { type: 'BINOP', op: 'ADD', operands: [sequence[MS], sequence[ME]]}); }, ), ); const arr = [10, '+', 20, '*', 30]; console.log(inspect(rs.transform(arr), { colors: true, depth: null })); /* OUTPUT [ { type: 'BINOP', op: 'ADD', operands: [ 10, { type: 'BINOP', op: 'MUL', operands: [ 20, 30 ] } ] } ] */ // These are for testing conditions without reduction // const sc = new R.Sequence_Condition([N, PLUS, N]); // console.log(sc.match([10, '+', 20, '*', 30]));