diff --git a/experiments/res1.mjs b/experiments/res1.mjs index c99a48a..763c37e 100644 --- a/experiments/res1.mjs +++ b/experiments/res1.mjs @@ -1,6 +1,6 @@ import { Mapping_Resolver, Chained_Resolver } from '@efforting.tech/rule-processing/resolvers'; - +import { inspect } from 'node:util'; const vr = new Mapping_Resolver(); @@ -9,9 +9,55 @@ const tr = new Mapping_Resolver(new Map(), item => typeof item); const cr = new Chained_Resolver([vr, tr]); -vr.rules.set('HELLO', () => 'WORLD'); -tr.rules.set('string', () => 'World'); +vr.rules.set('HELLO', {handler: () => 'WORLD'}); +tr.rules.set('string', {extra_stuff: 'Yo!', handler: (c) => `World with context: ${inspect(c)}`}); console.log(cr.resolve('HELLO')); console.log(cr.resolve('hello')); console.log(cr.resolve(123)); + + +/* OUTPUT + +WORLD +World with context: { + resolver: Chained_Resolver { + chain_links: [ [Mapping_Resolver], [Mapping_Resolver] ] + }, + item: 'hello', + extra_stuff: 'Yo!', + handler: [Function: handler] +} +file:///srv/Projekt/efforting.tech/nodejs.esm-library/build/packages/rule-processing/resolv + throw new Item_Unresolvable({ resolver: this, item }); + ^ + +Item_Unresolvable [Error]: Cannot resolve item 123 of type "number" using resolver Chained_ + at Chained_Resolver.resolve (file:///srv/Projekt/efforting.tech/nodejs.esm-library/buil + at file:///srv/Projekt/efforting.tech/nodejs.esm-library/experiments/res1.mjs:17:16 + at ModuleJob.run (node:internal/modules/esm/module_job:430:25) + at async node:internal/modules/esm/loader:639:26 + at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) { + data: { + resolver: Chained_Resolver { + chain_links: [ + Mapping_Resolver { + rules: Map(1) { 'HELLO' => [Object] }, + key_function: null + }, + Mapping_Resolver { + rules: Map(1) { 'string' => [Object] }, + key_function: [Function (anonymous)] + } + ] + }, + item: 123 + } +} + +Node.js v25.8.2 + + + + +*/ \ No newline at end of file diff --git a/experiments/text-nodes-dispatch.mjs b/experiments/text-nodes-dispatch.mjs new file mode 100644 index 0000000..f5ba849 --- /dev/null +++ b/experiments/text-nodes-dispatch.mjs @@ -0,0 +1,36 @@ +import * as CF from '@efforting.tech/data/field-configuration-factories'; + +import { inspect } from 'node:util'; +import { Text_Tree_Node, Text_Tree_Settings } from '@efforting.tech/text/basic-tree'; +import { RegExp_Resolver } from '@efforting.tech/rule-processing/resolvers'; + + + +const ts = Text_Tree_Settings.load({ + text: { + indention_mode: 'TABULATORS', + }, + trim_lines: true, +}); + +const example_string = +` +animals: dog, cat +trees: birch, pine + +`; + + +const root = Text_Tree_Node.from_string(ts, example_string); + + +const d = new RegExp_Resolver([ + [/^animals:\s*(.*)$/, (c) => console.log("ANIMAL", c)], + [/^trees:\s*(.*)$/, () => console.log("TREE")], +]); + +for (const child of root.children) { + if (child.has_line) { + console.log(child.line, d.resolve(child.line)); + } +} diff --git a/experiments/text-nodes.mjs b/experiments/text-nodes.mjs index 36ce7ac..c94a3d7 100644 --- a/experiments/text-nodes.mjs +++ b/experiments/text-nodes.mjs @@ -1,13 +1,13 @@ import * as CF from '@efforting.tech/data/field-configuration-factories'; import { inspect } from 'node:util'; - -import { Indention_Mode, Text_Settings, string_has_contents, indented_line_iterator } from '@efforting.tech/data/string-utilities'; -import { Text_Node } from '@efforting.tech/text/basic-tree'; +import { Text_Tree_Node, Text_Tree_Settings } from '@efforting.tech/text/basic-tree'; -const ts = Text_Settings.load({ - indention_mode: 'TABULATORS', +const ts = Text_Tree_Settings.load({ + text: { + indention_mode: 'TABULATORS', + }, }); const example_string = @@ -30,7 +30,7 @@ branch3 `; -const root = Text_Node.from_string(ts, example_string); +const root = Text_Tree_Node.from_string(ts, example_string); diff --git a/source/data/field-configuration-factories.mjs b/source/data/field-configuration-factories.mjs index c2b5d3b..3d29cc2 100644 --- a/source/data/field-configuration-factories.mjs +++ b/source/data/field-configuration-factories.mjs @@ -15,6 +15,12 @@ export function typed_value(coercion_function, default_value, description) { return new Field_Configuration(null, coercion_function, () => default_value, description); } +export function boolean(default_value, description) { + //BUG: Text representations such as "false" is still truthy here - we should have a more capable coearcing function + return new Field_Configuration(null, Boolean, () => default_value, description); +} + + export function typed_factory(coercion_function, factory_function, description) { return new Field_Configuration(null, coercion_function, factory_function, description); } diff --git a/source/data/string-utilities.mjs b/source/data/string-utilities.mjs index cd0696f..f424585 100644 --- a/source/data/string-utilities.mjs +++ b/source/data/string-utilities.mjs @@ -46,6 +46,6 @@ export function *indented_line_iterator(settings, text) { break; } default: - throw new Error('Unsupported indention mode'); //TODO - proper error + throw new Error(`Unsupported indention mode: ${settings.indention_mode}`); //TODO - proper error } } diff --git a/source/errors.mjs b/source/errors.mjs index 18a9e63..7ef6e37 100644 --- a/source/errors.mjs +++ b/source/errors.mjs @@ -50,7 +50,7 @@ export class Item_Unresolvable extends Error { constructor(data) { const { resolver, item } = data; const type = item === null ? 'null' : typeof item; - super(`Cannot resolve item ${inspect(item)} of type "${type}" using resolver ${resolver}`); + super(`Cannot resolve item ${inspect(item)} of type "${type}" using resolver ${resolver.constructor.name}`); this.data = data; } } diff --git a/source/rule-processing/resolvers.mjs b/source/rule-processing/resolvers.mjs index f958458..2d9addd 100644 --- a/source/rule-processing/resolvers.mjs +++ b/source/rule-processing/resolvers.mjs @@ -2,11 +2,12 @@ import { Item_Unresolvable } from '@efforting.tech/errors'; export class Abstract_Resolver { resolve(item) { - const handler = this.resolve_handler(item); - if (!handler) { + const result = this.resolve_handler(item); + if (!result?.handler) { throw new Item_Unresolvable({ resolver: this, item }); } - return handler({ resolver: this, item }); + // TO DOC: Spreading result into the resulting context means there are some reserved keys we need to be mindful of to avoid clobbering them + return result.handler({ resolver: this, item, ...result }); } } @@ -19,14 +20,47 @@ export class Chained_Resolver extends Abstract_Resolver { resolve_handler(item) { const { chain_links } = this; for (const link of chain_links) { - const handler = link.resolve_handler(item); - if (handler) { - return handler; + const result = link.resolve_handler(item); + if (result?.handler) { + return result; } } } } +export class Predicate_Resolver extends Abstract_Resolver { + constructor(rules=[]) { + // NOTE: Rules should be iterable as [predicate, handler] pairs + super(); + Object.assign(this, { rules }); + } + + resolve_handler(item) { + const { rules } = this; + for (const [predicate, handler] of rules) { + const predicate_result = predicate(item); + if (predicate_result !== undefined) { + return { handler, predicate_result }; + } + } + } +} + +export class RegExp_Resolver extends Predicate_Resolver { + constructor(rules=[]) { + // NOTE: Rules should be iterable as [predicate, handler] pairs + super(); + Object.assign(this, { rules: rules.map(([pattern, handler]) => { + + const wrapped_handler = handler; //TODO + const predicate = ((str) => str.match(pattern)); + + return [predicate, wrapped_handler]; + })}); + } + + +} export class Mapping_Resolver extends Abstract_Resolver { constructor(rules=new Map(), key_function=null) { diff --git a/source/text/basic-tree.mjs b/source/text/basic-tree.mjs index c9d6c81..aee95d9 100644 --- a/source/text/basic-tree.mjs +++ b/source/text/basic-tree.mjs @@ -1,19 +1,36 @@ import { string_has_contents, indented_line_iterator } from '@efforting.tech/data/string-utilities'; +import * as CF from '@efforting.tech/data/field-configuration-factories'; +import { Text_Settings } from '@efforting.tech/data/string-utilities'; -export class Text_Node { - constructor(text_settings, line=undefined, indent=0, line_no=undefined, index=undefined, raw=undefined, parent=undefined) { - Object.assign(this, { text_settings, line, indent, line_no, index, raw, parent, children: [] }); +export const Text_Tree_Settings = new CF.Schema({ + //BUG - there is currently no way (I think) to put defaults into a sub schema - this should be fixed + text: Text_Settings, + trim_head: CF.boolean(false, 'Trim the empty lines from the head of a node'), + trim_tail: CF.boolean(false, 'Trim the empty lines from the tail of a node'), + trim_lines: CF.boolean(false, 'Trim lines'), +}, 'Text tree settings'); + + +export class Text_Tree_Node { + constructor(text_tree_settings, line=undefined, indent=0, line_no=undefined, index=undefined, raw=undefined, parent=undefined) { + Object.assign(this, { text_tree_settings, line, indent, line_no, index, raw, parent, children: [] }); } - static from_string(text_settings, str) { + get has_line() { + return string_has_contents(this.line); + } - const root = new this(text_settings); + + static from_string(text_tree_settings, str) { + + const root = new this(text_tree_settings); + const { trim_head, trim_tail, trim_lines } = text_tree_settings; // NOTE: This first Text_Node is not added to the tree, it serves as an initial cursor only. - let current = new this(root.text_settings, undefined, 0, undefined, undefined, undefined, root); + let current = new this(root.text_tree_settings, undefined, 0, undefined, undefined, undefined, root); - for (const line_info of indented_line_iterator(text_settings, str)) { + for (const line_info of indented_line_iterator(text_tree_settings.text, str)) { // TODO: Implement other variants than inherit-from-previous const indent = string_has_contents(line_info.line) ? line_info.indent : current.indent; @@ -21,14 +38,14 @@ export class Text_Node { const delta_indent = indent - current.indent; if (delta_indent == 0) { - const pending = new Text_Node(current.text_settings, undefined, current.indent, undefined, undefined, undefined, current.parent); // Partial insertion - same level + const pending = new this(current.text_tree_settings, undefined, current.indent, undefined, undefined, undefined, current.parent); // Partial insertion - same level if (current.parent) { current.parent.children.push(pending); } current = pending; } else if (delta_indent > 0) { for (let i=0; i