]> git.mdlowis.com Git - proto/nlshell.git/commitdiff
added collision detection for tool rule definitions
authorMike Lowis <mike@mdlowis.com>
Thu, 1 Oct 2026 03:20:18 +0000 (23:20 -0400)
committerMike Lowis <mike@mdlowis.com>
Thu, 1 Oct 2026 03:20:18 +0000 (23:20 -0400)
nlshell.rb

index 9dff9946fdafe034fa60d6f167adca5f5393504e..e0c533052b0c242809dfbe4fd420f3ea845807bc 100755 (executable)
@@ -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)