Class: Expressir::Express::StreamingBuilder

Inherits:
Object
  • Object
show all
Includes:
Parsanol::BuilderCallbacks
Defined in:
lib/expressir/express/streaming_builder.rb

Overview

Streaming builder that receives parse events from Parsanol native parser.

This builder implements the Parsanol::BuilderCallbacks interface and reconstructs the AST from streaming events, then uses the existing Builder to convert to Model objects.

The key insight is that when a value starts (HASH_START, ARRAY_START), we must FIRST add it to the parent with the pending key, THEN push it to the stack so nested values are added to the correct container.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source: nil, include_source: nil) ⇒ StreamingBuilder

Initialize the streaming builder

Parameters:

  • source (String, nil) (defaults to: nil)

    Original source code

  • include_source (Boolean) (defaults to: nil)

    Whether to include source in model elements



31
32
33
34
35
36
37
# File 'lib/expressir/express/streaming_builder.rb', line 31

def initialize(source: nil, include_source: nil)
  @source = source
  @include_source = include_source
  @stack = [] # Stack of containers (Hash or Array)
  @pending_key = nil
  @final_ast = nil
end

Instance Attribute Details

#include_sourceBoolean (readonly)

Returns Whether to include source in model elements.

Returns:

  • (Boolean)

    Whether to include source in model elements



25
26
27
# File 'lib/expressir/express/streaming_builder.rb', line 25

def include_source
  @include_source
end

#sourceString?

Returns Original source code for position tracking.

Returns:

  • (String, nil)

    Original source code for position tracking



22
23
24
# File 'lib/expressir/express/streaming_builder.rb', line 22

def source
  @source
end

Instance Method Details

#finishModel::Repository?

Called when parsing is complete. Returns the built Repository.

Returns:



198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/expressir/express/streaming_builder.rb', line 198

def finish
  return nil unless @final_ast

  # Convert Parsanol native AST format to Builder-compatible format
  # Parsanol native: {"key" => [{:a => ...}, {:b => ...}]}  (Array of single-key hashes)
  # Builder:  {:key => {:a => ..., :b => ...}}       (Hash with multiple keys)
  converted_ast = convert_ast_format(@final_ast)

  # Pass to the existing Builder
  Builder.build_with_remarks(converted_ast, source: @source,
                                            include_source: @include_source)
end

#on_array_element(index) ⇒ Object

Called when an array element is about to be parsed

Parameters:

  • index (Integer)

    The index of the element



162
163
164
# File 'lib/expressir/express/streaming_builder.rb', line 162

def on_array_element(index)
  # Element processed - no action needed
end

#on_array_end(_size) ⇒ Object

Called when finishing parsing an array

Parameters:

  • size (Integer)

    Actual number of elements



169
170
171
172
173
174
175
176
177
178
179
# File 'lib/expressir/express/streaming_builder.rb', line 169

def on_array_end(_size)
  return if @stack.empty?

  finished_array = @stack.pop

  if @stack.empty?
    # Top-level array, save as final AST
    @final_ast = finished_array
  end
  # If there's a parent, the array was already added in on_array_start
end

#on_array_start(_size = nil) ⇒ Object

Called when starting to parse an array

Parameters:

  • size (Integer, nil)

    Expected number of elements



145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/expressir/express/streaming_builder.rb', line 145

def on_array_start(_size = nil)
  new_array = []

  # First add this array to the parent (if any) with the pending key
  if @stack.any?
    add_value_to_current_container(new_array)
  end

  # Then push to stack
  @stack.push(new_array)
  # Clear pending key - we're starting a new array context
  @pending_key = nil
end

#on_bool(value) ⇒ Object

Called when a boolean value is matched

Parameters:

  • value (Boolean)

    The matched boolean value



84
85
86
# File 'lib/expressir/express/streaming_builder.rb', line 84

def on_bool(value)
  add_value_to_current_container(value)
end

#on_error(message) ⇒ Object

Called when parsing fails

Parameters:

  • message (String)

    The error message

Raises:



54
55
56
# File 'lib/expressir/express/streaming_builder.rb', line 54

def on_error(message)
  raise Error::SchemaParseFailure.new("(streaming)", message)
end

#on_float(value) ⇒ Object

Called when a float value is matched

Parameters:

  • value (Float)

    The matched float value



77
78
79
# File 'lib/expressir/express/streaming_builder.rb', line 77

def on_float(value)
  add_value_to_current_container(value)
end

#on_hash_end(_size) ⇒ Object

Called when finishing parsing a hash/object

Parameters:

  • size (Integer)

    Actual number of entries



130
131
132
133
134
135
136
137
138
139
140
# File 'lib/expressir/express/streaming_builder.rb', line 130

def on_hash_end(_size)
  return if @stack.empty?

  finished_hash = @stack.pop

  if @stack.empty?
    # This is the top-level hash, save it as final AST
    @final_ast = finished_hash
  end
  # If there's a parent, the hash was already added in on_hash_start
end

#on_hash_key(key) ⇒ Object

Called when a hash key is encountered

Parameters:

  • key (String)

    The hash key name



113
114
115
116
117
# File 'lib/expressir/express/streaming_builder.rb', line 113

def on_hash_key(key)
  # Convert string key to symbol to match existing parser AST format
  puts "DEBUG on_hash_key: #{key.inspect} (#{key.class})" if ENV["DEBUG_STREAMING"]
  @pending_key = key.to_sym
end

#on_hash_start(_size = nil) ⇒ Object

Called when starting to parse a hash/object

Parameters:

  • size (Integer, nil)

    Expected number of entries



96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/expressir/express/streaming_builder.rb', line 96

def on_hash_start(_size = nil)
  new_hash = {}

  # First add this hash to the parent (if any) with the pending key
  if @stack.any?
    add_value_to_current_container(new_hash)
  end

  # Then push to stack so nested values go into this hash
  @stack.push(new_hash)
  # Clear pending key - we're starting a new hash context
  @pending_key = nil
end

#on_hash_value(key) ⇒ Object

Called when a hash value is about to be parsed

Parameters:

  • key (String)

    The hash key name



122
123
124
125
# File 'lib/expressir/express/streaming_builder.rb', line 122

def on_hash_value(key)
  puts "DEBUG on_hash_value: #{key.inspect} (#{key.class})" if ENV["DEBUG_STREAMING"]
  @pending_key = nil
end

#on_int(value) ⇒ Object

Called when an integer value is matched

Parameters:

  • value (Integer)

    The matched integer value



70
71
72
# File 'lib/expressir/express/streaming_builder.rb', line 70

def on_int(value)
  add_value_to_current_container(value)
end

#on_named_end(name) ⇒ Object

Called when finishing parsing a named rule

Parameters:

  • name (String)

    The rule name



191
192
193
# File 'lib/expressir/express/streaming_builder.rb', line 191

def on_named_end(name)
  # Named captures are handled via hash wrapping - no action needed
end

#on_named_start(name) ⇒ Object

Called when starting to parse a named rule

Parameters:

  • name (String)

    The rule name



184
185
186
# File 'lib/expressir/express/streaming_builder.rb', line 184

def on_named_start(name)
  # Named captures are handled via hash wrapping - no action needed
end

#on_nilObject

Called when a nil/null value is matched



89
90
91
# File 'lib/expressir/express/streaming_builder.rb', line 89

def on_nil
  add_value_to_current_container(nil)
end

#on_start(input) ⇒ Object

Called when parsing starts

Parameters:

  • input (String)

    The input being parsed



42
43
44
# File 'lib/expressir/express/streaming_builder.rb', line 42

def on_start(input)
  @on_start ||= input
end

#on_string(value, _offset, _length) ⇒ Object

Called when a string value is matched

Parameters:

  • value (String)

    The matched string value

  • offset (Integer)

    Byte offset

  • length (Integer)

    Length



63
64
65
# File 'lib/expressir/express/streaming_builder.rb', line 63

def on_string(value, _offset, _length)
  add_value_to_current_container(value)
end

#on_successObject

Called when parsing succeeds



47
48
49
# File 'lib/expressir/express/streaming_builder.rb', line 47

def on_success
  # Parsing completed
end