class Calyx::Registry

Lookup table of all the available rules in the grammar.

Attributes

context[R]
dicts[R]
memos[R]
modifiers[R]
rules[R]
transforms[R]
uniques[R]

Public Class Methods

new() click to toggle source

Construct an empty registry.

# File lib/calyx/registry.rb, line 7
def initialize
  @options = Options.new({})
  @rules = {}
  @dicts = {}
  @transforms = {}
  @modifiers = Modifiers.new
end

Public Instance Methods

combine(registry) click to toggle source

Merges the given registry instance with the target registry.

This is only needed at compile time, so that child classes can easily inherit the set of rules decared by their parent.

@param [Calyx::Registry] registry

# File lib/calyx/registry.rb, line 181
def combine(registry)
  @rules = rules.merge(registry.rules)
end
define_context_rule(name, trace, productions) click to toggle source

Defines a rule in the temporary evaluation context.

@param [Symbol] name @param [Array] productions

# File lib/calyx/registry.rb, line 88
def define_context_rule(name, trace, productions)
  productions = [productions] unless productions.is_a?(Enumerable)
  context[name.to_sym] = Rule.new(name.to_sym, Rule.build_ast(productions, self), trace)
end
define_rule(name, trace, productions) click to toggle source

Defines a static rule in the grammar.

@param [Symbol] name @param [Array] productions

# File lib/calyx/registry.rb, line 70
def define_rule(name, trace, productions)
  symbol = name.to_sym

  # TODO: this could be tidied up by consolidating parsing in a single class
  branch = Rule.build_ast(productions, self)

  # If the static rule is a map of k=>v pairs then add it to the lookup dict
  if branch.is_a?(Production::AffixTable)
    dicts[symbol] = branch
  else
    rules[symbol] = Rule.new(symbol, branch, trace)
  end
end
evaluate(start_symbol=:start, rules_map={}) click to toggle source

Evaluates the grammar defined in this registry, combining it with rules from the passed in context.

Produces a syntax tree of nested list nodes.

@param [Symbol] start_symbol @param [Hash] rules_map @return [Array]

# File lib/calyx/registry.rb, line 193
def evaluate(start_symbol=:start, rules_map={})
  reset_evaluation_context

  rules_map.each do |key, value|
    if rules.key?(key.to_sym)
      raise Errors::DuplicateRule.new(key)
    end

    define_context_rule(key, caller_locations.last, value)
  end

  [start_symbol, expand(start_symbol).evaluate(@options)]
end
expand(symbol) click to toggle source

Expands the given symbol to its rule.

@param [Symbol] symbol @return [Calyx::Rule]

# File lib/calyx/registry.rb, line 97
def expand(symbol)
  expansion = rules[symbol] || context[symbol]

  if expansion.nil?
    if @options.strict?
      raise Errors::UndefinedRule.new(@last_expansion, symbol)
    else
      expansion = Syntax::Terminal.new('')
    end
  end

  @last_expansion = expansion
  expansion
end
expand_filter(name, value) click to toggle source

Applies the given modifier function to the given value to filter it.

@param [Symbol] name @param [String] value @return [String]

# File lib/calyx/registry.rb, line 117
def expand_filter(name, value)
  if transforms.key?(name)
    transforms[name].call(value)
  else
    modifiers.transform(name, value)
  end
end
expand_map(name, value, direction) click to toggle source

Applies a modifier to substitute the value with a bidirectional map lookup.

@param [Symbol] name @param [String] value @param [Symbol] direction :left or :right @return [String]

# File lib/calyx/registry.rb, line 132
def expand_map(name, value, direction)
  map_lookup = dicts[name]

  if direction == :left
    map_lookup.key_for(value)
  else
    map_lookup.value_for(value)
  end
end
filter(name, callable=nil, &block) click to toggle source

Registers the given block as a string filter.

@param [Symbol] name @yield [String] @yieldreturn [String]

# File lib/calyx/registry.rb, line 42
def filter(name, callable=nil, &block)
  if block_given?
    transforms[name.to_sym] = block
  else
    transforms[name.to_sym] = callable
  end
end
mapping(name, pairs) click to toggle source

Registers a paired mapping regex.

@param [Symbol] name @param [Hash<Regex,String>] pairs

# File lib/calyx/registry.rb, line 33
def mapping(name, pairs)
  transforms[name.to_sym] = construct_mapping(pairs)
end
memoize_expansion(symbol) click to toggle source

Expands a memoized rule symbol by evaluating it and storing the result for later.

@param [Symbol] symbol

# File lib/calyx/registry.rb, line 146
def memoize_expansion(symbol)
  memos[symbol] ||= expand(symbol).evaluate(@options)
end
method_missing(name, *productions) click to toggle source

Registers a new grammar rule without explicitly calling the `#rule` method.

@param [Symbol] name @param [Array] productions

# File lib/calyx/registry.rb, line 54
def method_missing(name, *productions)
  define_rule(name, caller_locations.first, productions)
end
modifier(name) click to toggle source

Attaches a modifier module to this instance.

@param [Module] name

# File lib/calyx/registry.rb, line 25
def modifier(name)
  modifiers.extend(name)
end
options(opts) click to toggle source

Applies additional config options to this instance.

@param [Options] opts

# File lib/calyx/registry.rb, line 18
def options(opts)
  @options = @options.merge(opts)
end
rule(name, *productions) click to toggle source

Registers a new grammar rule.

@param [Symbol] name @param [Array] productions

# File lib/calyx/registry.rb, line 62
def rule(name, *productions)
  define_rule(name, caller_locations.first, productions)
end
unique_expansion(symbol) click to toggle source

Expands a unique rule symbol by evaluating it and checking that it hasn't previously been selected.

@param [Symbol] symbol

# File lib/calyx/registry.rb, line 154
def unique_expansion(symbol)
  pending = true
  uniques[symbol] = [] if uniques[symbol].nil?

  while pending
    if uniques[symbol].size == expand(symbol).size
      uniques[symbol] = []
      pending = false
    end

    result = expand(symbol).evaluate(@options)

    unless uniques[symbol].include?(result)
      uniques[symbol] << result
      pending = false
    end
  end

  result
end

Private Instance Methods

construct_mapping(pairs) click to toggle source
# File lib/calyx/registry.rb, line 217
def construct_mapping(pairs)
  mapper = -> (input) {
    match, target = pairs.detect { |match, target| input =~ match }

    if match && target
      input.gsub(match, target)
    else
      input
    end
  }
end
reset_evaluation_context() click to toggle source
# File lib/calyx/registry.rb, line 211
def reset_evaluation_context
  @context = {}
  @memos = {}
  @uniques = {}
end