Module: Stupidedi::Parser::Generation
- Included in:
- StateMachine
- Defined in:
- lib/stupidedi/parser/generation.rb
Instance Method Summary collapse
-
#execute(op, zipper, reader, segment_tok) ⇒ AbstractCursor<StateMachine>
Three things change together when executing an Instruction:.
- #insert(segment_tok, strict, reader) ⇒ (StateMachine, Reader::TokenReader)
-
#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. - #update_reader(op, reader, successor)
Instance Method Details
#execute(op, zipper, reader, segment_tok) ⇒ AbstractCursor<StateMachine>
Three things change together when executing an Instruction:
The stack of instruction tables that indicates where a segment would be located if it existed, or was added to the parse tree
The parse tree, to which we add the new syntax nodes using a zipper.
The corresponding tree of states, which tie together the first two and are also updated using a zipper
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, = {}) limit = .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 |