Class: Whittle::Rule

Inherits:
Object
  • Object
show all
Defined in:
lib/whittle/rule.rb

Overview

Represents an individual Rule, forming part of an overall RuleSet.

Direct Known Subclasses

NonTerminal, Terminal

Constant Summary collapse

NULL_ACTION =
Proc.new { }
DUMP_ACTION =
Proc.new { |input| input }

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, *components) ⇒ Rule

Create a new Rule for the RuleSet named name.

The components can either be names of other Rules, or for a terminal Rule, a single pattern to match in the input string.



27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/whittle/rule.rb', line 27

def initialize(name, *components)
  @components = components
  @action     = DUMP_ACTION
  @name       = name
  @assoc      = :right
  @prec       = 0

  @components.each do |c|
    unless Regexp === c || String === c || Symbol === c
      raise ArgumentError, "Unsupported rule component #{c.class}"
    end
  end
end

Instance Attribute Details

#actionObject (readonly)

Returns the value of attribute action.



12
13
14
# File 'lib/whittle/rule.rb', line 12

def action
  @action
end

#assocObject (readonly)

Returns the value of attribute assoc.



14
15
16
# File 'lib/whittle/rule.rb', line 14

def assoc
  @assoc
end

#componentsObject (readonly)

Returns the value of attribute components.



13
14
15
# File 'lib/whittle/rule.rb', line 13

def components
  @components
end

#nameObject (readonly)

Returns the value of attribute name.



11
12
13
# File 'lib/whittle/rule.rb', line 11

def name
  @name
end

#precObject (readonly)

Returns the value of attribute prec.



15
16
17
# File 'lib/whittle/rule.rb', line 15

def prec
  @prec
end

Instance Method Details

#%(assoc) ⇒ Rule

Set the associativity of this Rule.

Accepts values of :left, :right (default) or :nonassoc.

Raises:

  • (ArgumentError)


186
187
188
189
190
191
# File 'lib/whittle/rule.rb', line 186

def %(assoc)
  raise ArgumentError, "Invalid associativity #{assoc.inspect}" \
    unless [:left, :right, :nonassoc].include?(assoc)

  tap { @assoc = assoc }
end

#^(prec) ⇒ Object

Set the precedence of this Rule, as an Integer.

The higher the number, the higher the precedence.

Raises:

  • (ArgumentError)


199
200
201
202
203
204
# File 'lib/whittle/rule.rb', line 199

def ^(prec)
  raise ArgumentError, "Invalid precedence level #{prec.inspect}" \
    unless prec.respond_to?(:to_i)

  tap { @prec = prec.to_i }
end

#as(preset = nil, &block) ⇒ Rule

Specify how this Rule should be reduced.

Given a block, the Rule will be reduced by passing the result of reducing all inputs as arguments to the block.

The default action is to return the leftmost input unchanged.

Given the Symbol :value, the matched input will be returned verbatim. Given the Symbol :nothing, nil will be returned; you can use this to skip whitesapce and comments, for example.



155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/whittle/rule.rb', line 155

def as(preset = nil, &block)
  tap do
    case preset
      when :value   then @action = DUMP_ACTION
      when :nothing then @action = NULL_ACTION
      when nil
        raise ArgumentError, "Rule#as expected a block, not none given" unless block_given?
        @action = block
      else
        raise ArgumentError, "Invalid preset #{preset.inspect} to Rule#as"
    end
  end
end

#build_parse_table(table, parser, context) ⇒ Object

Walks all possible branches from the given rule, building a parse table.

The parse table is a list of instructions (transitions) that can be looked up, given the current parser state and the current lookahead token.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/whittle/rule.rb', line 65

def build_parse_table(table, parser, context)
  state      = table[context[:state]] ||= {}
  sym        = components[context[:offset]]
  rule       = parser.rules[sym]
  new_offset = context[:offset] + 1
  new_state  = if state.key?(sym)
    state[sym][:state]
  end || [self, new_offset].hash

  if sym.nil?
    assert_reducible!(state, sym)

    state[sym] = {
      :action => :reduce,
      :rule   => self,
      :prec   => context[:prec]
    }

    if context[:initial]
      state[:$end] = {
        :action => :accept,
        :rule   => self
      }
    end
  else
    raise GrammarError, "Unreferenced rule #{sym.inspect}" if rule.nil?

    new_prec = if rule.terminal?
      rule.prec
    else
      context[:prec]
    end

    if rule.terminal?
      state[sym] = {
        :action => :shift,
        :state  => new_state,
        :prec   => new_prec,
        :assoc  => rule.assoc
      }
    else
      state[sym] = {
        :action => :goto,
        :state  => new_state
      }

      rule.build_parse_table(
        table,
        parser,
        {
          :state  => context[:state],
          :seen   => context[:seen],
          :offset => 0,
          :prec   => 0
        }
      )
    end

    build_parse_table(
      table,
      parser,
      {
        :initial => context[:initial],
        :state   => new_state,
        :seen    => context[:seen],
        :offset  => new_offset,
        :prec    => new_prec
      }
    )
  end

  resolve_conflicts(state)
end

#skip!Rule

Alias for as(:nothing).



173
174
175
# File 'lib/whittle/rule.rb', line 173

def skip!
  as(:nothing)
end

#terminal?Boolean

Predicate check for whether or not the Rule represents a terminal symbol.

A terminal symbol is effectively any rule that directly matches some pattern in the input string and references no other rules.



48
49
50
# File 'lib/whittle/rule.rb', line 48

def terminal?
  raise "Must be implemented by subclass"
end