# 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
class RulesModule
def initialize
@verbs = Set.new
- @tools = []
+ @rules = {}
end
def types(hash)
def tool(name, &block)
builder = ToolBuilder.new(name)
builder.instance_eval(&block)
- @tools << builder.build
+ register(builder.build)
end
def verbs(list)
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)