end
end
+# Stage 0 result (spec SS5.7). Everything downstream (Tier 1/2/3, executor)
+# operates on this, never on raw text or loose token/clause arrays.
+module Utterance
+ Clause = Struct.new(:tokens, :kind)
+ Result = Struct.new(:raw, :clauses)
+
+ # SS5.8 limits.
+ MAX_CHARS = 500
+ MAX_CLAUSES = 10
+
+ # Raised with a user-facing message; caller should print it and move on.
+ Rejected = Class.new(StandardError)
+
+ # Returns a Result, or nil for empty/punctuation-only input (a no-op).
+ def self.build(raw)
+ raise Rejected, "that's too long, please split it into smaller requests" if raw.length > MAX_CHARS
+
+ tokens = Lexer.tokenize(raw)
+ raise Rejected, "unmatched quote, please close it and try again" if Lexer.unbalanced_quote?(tokens)
+
+ tokens = Lexer.downcase_words(tokens)
+ sentences = Lexer.sentences(tokens)
+ return nil if sentences.empty?
+
+ clauses = sentences.flat_map { |sentence| Lexer.clauses(sentence) }
+ raise Rejected, "that's too many clauses in one request, please split it up" if clauses.length > MAX_CLAUSES
+
+ Result.new(raw, clauses.map { |tokens| Clause.new(tokens, Classifier.classify(tokens)) })
+ end
+end
module Ruleset
class RulesModule
text = gets
exit 0 if text.nil?
- # Tokenize the request
- tokens = Lexer.tokenize(text.chomp)
- if Lexer.unbalanced_quote?(tokens)
- puts "unmatched quote, please close it and try again"
+ begin
+ utterance = Utterance.build(text.chomp)
+ rescue Utterance::Rejected => e
+ puts e.message
next
end
- tokens = Lexer.downcase_words(tokens)
+ next if utterance.nil? # empty/punctuation-only input: no-op
- # Debug only: print the sentences found
- Lexer.sentences(tokens).each_with_index do |sentence, i|
- puts "#{i}: #{sentence.map(&:to_s).join(' ')}"
+ if utterance.clauses.any? { |clause| clause.kind.nil? }
+ puts "couldn't tell what kind of request this is, please rephrase"
+ next
+ end
- clauses = Lexer.clauses(sentence)
- kinds = clauses.map { |clause| Classifier.classify(clause) }
- if kinds.any?(&:nil?)
- puts " couldn't tell what kind of request this is, please rephrase"
- next
- end
+ utterance.clauses.each_with_index do |clause, j|
+ puts "clause #{j} [#{clause.kind}]: #{clause.tokens.map(&:to_s).join(' ')}"
+ next unless clause.kind == :action
- clauses.each_with_index do |clause, j|
- puts " clause #{j} [#{kinds[j]}]: #{clause.map(&:to_s).join(' ')}"
- if kinds[j] == :action
- node = Tier1.lookup(clause)
- puts " #{node ? "tier1 hit: #{node}" : 'tier1 miss'}"
- end
- end
- puts " match: #{Lexer.match_text(sentence)}"
+ node = Tier1.lookup(clause.tokens)
+ puts " #{node ? "tier1 hit: #{node}" : 'tier1 miss'}"
end
+ puts "match: #{Lexer.match_text(utterance.clauses.flat_map(&:tokens))}"
- # Later stages (clause splitting, classification, tiers) take a sentence's
- # token array. The executor reads literal.text directly; no restore step.
+ # Later stages (Tier 2/3, executor) take a clause's token array. The
+ # executor reads literal.text directly; no restore step.
end