110 lines
3.1 KiB
JavaScript
110 lines
3.1 KiB
JavaScript
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]));
|