Module: Stupidedi::Parser::Generation

Included in:
StateMachine
Defined in:
lib/stupidedi/parser/generation.rb

Instance Method Summary collapse

Instance Method Details

#execute(op, zipper, reader, segment_tok) ⇒ AbstractCursor<StateMachine>

Three things change together when executing an Instruction:

  1. The stack of instruction tables that indicates where a segment would be located if it existed, or was added to the parse tree

  2. The parse tree, to which we add the new syntax nodes using a zipper.

  3. The corresponding tree of states, which tie together the first two and are also updated using a zipper

Returns:


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
# File 'lib/stupidedi/parser/generation.rb', line 88

def execute(op, zipper, reader, segment_tok)
  table = zipper.node.instructions  # 1.
  value = zipper.node.zipper        # 2.
  state = zipper                    # 3.

  op.pop_count.times do
    value = value.up
    state = state.up
  end

  if op.push.nil?
    # This instruction doesn't create a child node in the parse tree,
    # but it might move us forward to a sibling or upward to an uncle
    segment = AbstractState.mksegment(segment_tok, op.segment_use)
    value   = value.append(segment)

    # If we're moving upward, pop off the current table(s). If we're
    # moving forward, shift off the previous instructions. Important
    # that these are done in order.
    instructions = table.pop(op.pop_count).drop(op.drop_count)

    # Create a new AbstractState node that has a new InstructionTable
    # and also points to a new AbstractVal tree (with the new segment)
    state.append(state.node.copy(
      :zipper       => value,
      :instructions => instructions))
  else
    # Make a new sibling or uncle that will be the parent to the child
    parent = state.node.copy \
      :zipper       => value,
      :children     => [],
      :separators   => reader.try(&:separators),
      :segment_dict => reader.try(&:segment_dict),
      :instructions => table.pop(op.pop_count).drop(op.drop_count)

    # Note, `state` is a cursor pointing at a state, while `parent`
    # is an actual state
    state = state.append(parent) unless state.root?

    # Note, eg, op.push == TableState; op.push.push == TableState.push
    op.push.push(state, parent, segment_tok, op.segment_use, @config)
  end
end

#insert(segment_tok, strict, reader) ⇒ (StateMachine, Reader::TokenReader)


57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/stupidedi/parser/generation.rb', line 57

def insert(segment_tok, strict, reader)
  active = @active.flat_map do |zipper|
    state        = zipper.node
    instructions = state.instructions.matches(segment_tok, strict, :insert)

    if instructions.empty?
      zipper.append(FailureState.mksegment(segment_tok, state)).cons
    else
      instructions.map do |op|
        successor = execute(op, zipper, reader, segment_tok)
        reader    = update_reader(op, reader, successor)
        successor
      end
    end
  end

  return StateMachine.new(@config, active), reader
end

#read(reader, options = {}) ⇒ (StateMachine, Reader::Result)

Consumes all input from reader and returns the updated StateMachine along with the result of the last attempt to read a segment.

The nondeterminism argument specifies a limit on how many parse trees can be built simultaneously due to ambiguity in the input and/or specification. This prevents runaway memory CPU consumption (see GH-129), and will return a Reader::Result.failure once exceeded.

The default value is 1, resulting in an error if any input is ambiguous.

NOTE: The error is detected after the resources are already been consumed. The extra parse trees are returned (in memory) via the StateMachine to aide diagnosis.


25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/stupidedi/parser/generation.rb', line 25

def read(reader, options = {})
  limit    = options.fetch(:nondeterminism, 1)
  machine  = self
  reader_e = reader.read_segment

  while reader_e.defined?
    reader_e = reader_e.flatmap do |segment_tok, reader_|
      machine, reader__ =
        machine.insert(segment_tok, false, reader_)

      if machine.active.length <= limit
        reader__.read_segment
      else
        matches = machine.active.map do |m|
          if segment_use = m.node.zipper.node.usage
            "SegmentUse(#{segment_use.position}, #{segment_use.id},
            #{segment_use.requirement.inspect}, #{segment_use.repeat_count.inspect})".join
          else
            m.node.zipper.node.inspect
          end
        end.join(", ")

        return machine,
          Reader::Result.failure("too much non-determinism: #{matches}", reader__.input, true)
      end
    end
  end

  return machine, reader_e
end

#update_reader(op, reader, successor)


132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/stupidedi/parser/generation.rb', line 132

def update_reader(op, reader, successor)
  # We might be moving up or down past the interchange or functional
  # group envelope, which control the set of separators and the set
  # of all possible segments
  unless op.push.nil? and (op.pop_count.zero? or reader.stream?)
    unless reader.separators.eql?(successor.node.separators) \
      and reader.segment_dict.eql?(successor.node.segment_dict)
      reader.copy \
        :separators   => successor.node.separators,
        :segment_dict => successor.node.segment_dict
    end
  end || reader
end