require 'wlang/rule'
module WLang
#
# This class allows grouping matching rules together to build a given dialect.
# Rules are always added with add_rule, which also allows creating simple rules
# on the fly (that is, without subclassing Rule).
#
# Examples:
# # we will create a simple dialect with a special tag:
# # +{...} which will uppercase its contents
# upcaser = RuleSet.new
# upcaser.add_rule '+' do |parser,offset|
# parsed, offset = parser.parse(offset)
# [parsed.upcase, offset]
# end
#
# == Detailed API
class RuleSet
# Which modules are reused
attr_reader :reuse
#
# Creates an new dialect rule set.
#
def initialize()
@rules = {}
@reuse = []
@patterns = Hash.new{|h, k| h[k] = build_pattern(k)}
end
# Yields the block with name, rule pairs
def each
@rules.each_pair{|name,rule| yield(name, rule)}
end
#
# Adds a tag matching rule to this rule set. _tag_ must be a String with the
# tag associated to the rule (without the '{', that is '$' for the tag ${...}
# for example. If rule is ommited and a block is given, a new Rule instance is
# created on the fly with _block_ as implementation (see Rule#new).
# Otherwise rule is expected to be a Rule instance. This method check its
# arguments, raising an ArgumentError if incorrect.
#
def add_rule(tag, rule=nil, &block)
if rule.nil?
raise(ArgumentError,"Block required") unless block_given?
rule = Rule.new(&block)
end
raise(ArgumentError, "Rule expected") unless Rule===rule
@rules[tag] = rule
@patterns.clear
end
#
# Add rules defined in a given RuleSet module.
#
def add_rules(mod, pairs=nil)
raise(ArgumentError,"Module expected") unless Module===mod
reuse << mod
pairs = mod::DEFAULT_RULESET if pairs.nil?
pairs.each_pair do |symbol,method|
meth = mod.method(method)
raise(ArgumentError,"No such method: #{method}") if meth.nil?
add_rule(symbol, &meth.to_proc)
end
end
#
# Returns a Regexp instance with recognizes all tags installed in the rule set.
# The returned Regexp is backslashing aware (it matches \${ for example)
# as well as '{' and '}' aware. This pattern is used by WLang::Parser and is
# not intended to be used by users themselve.
#
def pattern(block_symbols)
@patterns[block_symbols]
end
#
# Returns the Rule associated with a given tag, _nil_ if no such rule.
#
def [](tag) @rules[tag]; end
### protected section ######################################################
protected
# Internal implementation of pattern.
def build_pattern(block_symbols)
start, stop = WLang::Template::BLOCK_SYMBOLS[block_symbols]
start, stop = Regexp.escape(start), Regexp.escape(stop)
s = '([\\\\]{0,2}('
i=0
@rules.each_key do |tag|
s << '|' if i>0
s << '(' << Regexp.escape(tag) << ')'
i += 1
end
s << ")#{start})|[\\\\]{0,2}#{start}|[\\\\]{0,2}#{stop}"
Regexp.new(s)
end
end # class RuleSet
end # module WLang