From c99812501ea140e3992c787e103e6f6db1b168e2 Mon Sep 17 00:00:00 2001 From: Mike Lowis Date: Wed, 30 Sep 2026 23:20:18 -0400 Subject: [PATCH] added collision detection for tool rule definitions --- nlshell.rb | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/nlshell.rb b/nlshell.rb index 9dff994..e0c5330 100755 --- a/nlshell.rb +++ b/nlshell.rb @@ -194,20 +194,18 @@ end # word-for-word against a clause (same length, no fuzzing - that's Tier 2). module Tier1 class << self - attr_accessor :tools + attr_accessor :rules end def self.configure(ruleset) - self.tools = ruleset.tool_patterns + self.rules = ruleset.rule_index end # Returns an action node (Hash) on an exact match, or nil on a miss. def self.lookup(clause) - tools.each do |tool| - tool.rules.each do |pattern| - args = match(pattern.split, clause) - return { type: "action", name: tool.name, args: args, tool: tool } if args - end + rules.each do |pattern, tool| + args = match(pattern.split, clause) + return { type: "action", name: tool.name, args: args, tool: tool } if args end nil end @@ -328,7 +326,7 @@ module Ruleset class RulesModule def initialize @verbs = Set.new - @tools = [] + @rules = {} end def types(hash) @@ -346,7 +344,7 @@ module Ruleset def tool(name, &block) builder = ToolBuilder.new(name) builder.instance_eval(&block) - @tools << builder.build + register(builder.build) end def verbs(list) @@ -355,18 +353,31 @@ module Ruleset def literal_patterns = @types def connective_patterns = @connectives - def tool_patterns = @tools + def rule_index = @rules # Every rule's leading word is a verb by construction (ToolBuilder # rejects a rule that opens on a :slot), plus any verbs() declared for # words not yet backed by a tool - so chaining still recognizes them. def verb_patterns - @verbs | @tools.flat_map { |t| t.rules.map { |r| r.split.first } } + @verbs | @rules.keys.map { |pattern| pattern.split.first } end # :action phrases come from verbs, not the classifiers() call, so the # two lists can't drift apart. def classifier_patterns = @classifiers.merge(action: verb_patterns.to_a.map { |v| [v] }) + + private + + # Last tool to claim a rule pattern wins; an earlier claimant is + # overwritten with a warning so shadowing is never silent. + def register(tool) + tool.rules.each do |pattern| + prior = @rules[pattern] + warn "warning: rule #{pattern.inspect} reassigned from tool #{prior.name.inspect} to #{tool.name.inspect}" if prior && prior != tool + + @rules[pattern] = tool + end + end end def self.create(name, &block) -- 2.55.0