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.

Parameters:

  • name (String)

    the name of the RuleSet to which this Rule belongs

  • components... (Object...)

    a variable list of components that make up the Rule



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.

Parameters:

  • assoc (Symbol)

    one of :left, :right or :nonassoc

Returns:

  • (Rule)

    returns self

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.

Parameters:

  • prec (Fixnum)

    the precedence (default is zero)

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.

Parameters:

  • preset (Symbol) (defaults to: nil)

    one of the preset actions, :value or :nothing; optional

Returns:

  • (Rule)

    returns self



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.

Parameters:

  • table (Hash<Fixnum,Hash>)

    the table to construct for

  • parser (Parser)

    the Parser containing all the Rules in the grammar

  • context (Hash)

    a Hash used to track state as the grammar is analyzed



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).

Returns:

  • (Rule)

    returns self



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.

Returns:

  • (Boolean)

    true if this rule represents a terminal symbol



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

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