lib/wlang/dialect.rb in wlang-2.0.0.beta vs lib/wlang/dialect.rb in wlang-2.0.0
- old
+ new
@@ -1,53 +1,193 @@
-require 'wlang/dialect/dispatching'
-require 'wlang/dialect/evaluation'
-require 'wlang/dialect/tags'
module WLang
class Dialect
- include Dialect::Dispatching
- include Dialect::Evaluation
- include Dialect::Tags
class << self
- def factor(options = {})
- new(default_options.merge(options))
- end
+ # facade
def default_options(options = {})
@default_options ||= (superclass.default_options.dup rescue {})
@default_options.merge!(options)
end
- def compiler(options = {})
- factor(options).compiler
+ def compile(source, options = {})
+ Template.new source, :dialect => self
end
- def compile(source, options = {})
- compiler(options).compile(source)
+ def render(source, scope = {}, buffer = "")
+ compile(source).call(scope, buffer)
end
- def to_ruby_code(source, options = {})
- compiler(options).to_ruby_code(source)
+ # tag installation and dispatching
+
+ # Install a new tag on the dialect for `symbols`.
+ #
+ # Optional `dialects` can be passed if dialect modulation needs to occur for some
+ # blocks. The source code of the tag can either be passed as a `method` Symbol or
+ # as a block.
+ #
+ # Examples:
+ #
+ # # A simple tag with explicit code as a block
+ # tag('$') do |buf,fn| ... end
+ #
+ # # A simple tag, reusing a method (better for testing the method easily)
+ # tag('$', :some_existing_method)
+ #
+ # # Specifying that the first block of #{...}{...} has to be parsed in the same
+ # # dialect whereas the second has to be parsed in the dummy one.
+ # tag('#', [self, WLang::Dummy], ::some_existing_method)
+ #
+ def tag(symbols, dialects = nil, method = nil, &block)
+ if block
+ tag(symbols, dialects, block)
+ else
+ dialects, method = nil, dialects if method.nil?
+ define_tag_method(symbols, dialects, method)
+ end
end
- def render(source, scope = {}, buffer = "")
- compile(source).call(scope, buffer)
+ # Returns the dispatching method name for a given tag symbol and optional prefix
+ # (defaults to '_tag').
+ #
+ # Example:
+ #
+ # Dialect.tag_dispatching_name('!$')
+ # # => :_tag_33_36
+ #
+ # Dialect.tag_dispatching_name('!$', "my_prefix")
+ # # => :my_prefix_33_36
+ #
+ def tag_dispatching_name(symbols, prefix = "_tag")
+ symbols = symbols.chars unless symbols.is_a?(Array)
+ chars = symbols.map{|s| s.ord}.join("_")
+ "#{prefix}_#{chars}".to_sym
end
- end
+ # Binds two methods for the given `symbols`:
+ #
+ # 1) _tag_xx_yy that executes `code`
+ # 2) _diatag_xx_yy that returns the dialect information of the tag blocks.
+ #
+ # `code` can either be a Symbol (existing method) or a Proc (some explicit code).
+ #
+ def define_tag_method(symbols, dialects, code)
+ rulename = tag_dispatching_name(symbols, "_tag")
+ case code
+ when Symbol
+ module_eval %Q{ alias :#{rulename} #{code} }
+ when Proc
+ define_method(rulename, code)
+ else
+ raise "Unable to use #{code} for a tag"
+ end
+ dialects_info_name = tag_dispatching_name(symbols, "_diatag")
+ define_method(dialects_info_name) do dialects end
+ end
+ end # class methods
+
+ # Set the default options, '{' and '}' for braces and no autospacing
default_options :braces => WLang::BRACES,
:autospacing => false
+ # All dialect options
attr_reader :options
- def braces; options[:braces]; end
- attr_reader :compiler
+ # The template that uses this dialect instance to render
+ attr_reader :template
- def initialize(options = {})
- @options = options
- @compiler = WLang::Compiler.new(self)
+ # Creates a dialect instance with options
+ def initialize(*args)
+ template, options = nil, {}
+ args.each do |arg|
+ options = arg.to_hash if arg.respond_to?(:to_hash)
+ template = arg if Template===arg
+ end
+ @template = template
+ @options = self.class.default_options.merge(options)
+ end
+
+ # meta information
+
+ # Returns the braces to use, as set in the options
+ def braces
+ options[:braces]
+ end
+
+ # Returns the dialects used to parse the blocks associated with `symbols`, as
+ # previously installed by `define_tag_method`.
+ def dialects_for(symbols)
+ info = self.class.tag_dispatching_name(symbols, "_diatag")
+ raise ArgumentError, "No tag for #{symbols}" unless respond_to?(info)
+ send(info)
+ end
+
+ # rendering
+
+ # Renders `fn` to a `buffer`, optionally extending the current scope.
+ #
+ # `fn` can either be a String (immediately pushed on the buffer), a Proc (taken as a
+ # tag block to render further), or a Template (taken as a partial to render in the
+ # current scope).
+ #
+ # Is `scope` is specified, the current scope is first branched to use to new one in
+ # priority, then rendering applies and the newly created scope if then poped.
+ #
+ # Returns the buffer.
+ #
+ def render(fn, scope = nil, buffer = "")
+ if scope.nil?
+ case fn
+ when String
+ buffer << fn
+ when Proc
+ fn.call(self, buffer)
+ when Template
+ fn.call(@scope, buffer)
+ else
+ raise ArgumentError, "Unable to render `#{fn}`"
+ end
+ buffer
+ else
+ with_scope(scope){ render(fn, nil, buffer) }
+ end
+ end
+
+ # evaluation
+
+ # Returns the current rendering scope.
+ def scope
+ @scope ||= Scope.root
+ end
+
+ # Yields the block with a scope branched with a sub-scope `x`.
+ def with_scope(x)
+ @scope = scope.push(x)
+ res = yield
+ @scope = scope.pop
+ res
+ end
+
+ # Evaluates `expr` in the current scope. `expr` can be either
+ #
+ # * a Symbol or a String, taken as a dot expression to evaluate
+ # * a Proc or a Template, first rendered and then evaluated
+ #
+ # Evaluation is delegated to the scope (@see Scope#evaluate) and the result returned
+ # by this method.
+ #
+ def evaluate(expr, *default)
+ case expr
+ when Symbol, String
+ catch(:fail) do
+ return scope.evaluate(expr, *default)
+ end
+ raise NameError, "Unable to find `#{expr}`"
+ else
+ evaluate(render(expr), *default)
+ end
end
end # class Dialect
end # module WLang