Module: Expressir::Express::Builder
- Defined in:
- lib/expressir/express/builder.rb
Overview
Builder registry for AST node type handlers. Each builder is a callable object that transforms AST data into Model objects. This is the ONLY way to build models from AST - no Transformer fallback.
Constant Summary collapse
- SNAKE_CASE_CACHE =
Cache for snake_case conversions
{}
- OPERATOR_TOKENS =
Build a Model object from AST data. Operator tokens that return nil (separators, punctuation) When these appear as the first key in a multi-key hash, they should be skipped in favor of the content key. This handles grammar patterns like ‘element >> (op_comma >> element).repeat` which produce => …, :element => {…}.
Set.new(%i[ op_comma op_colon op_decl op_delim op_leftparen op_rightparen op_leftbracket op_rightbracket op_left_curly_brace op_right_curly_brace op_period op_pipe op_double_backslash op_double_pipe op_double_asterisk op_asterisk op_slash op_plus op_minus op_less_equal op_greater_equal op_less_greater op_less_than op_greater_than op_equals op_colon_less_greater_colon op_colon_equals_colon op_query_begin op_query_end op_question_mark ]).freeze
Class Attribute Summary collapse
-
.include_source ⇒ Object
readonly
Returns the value of attribute include_source.
-
.source ⇒ Object
readonly
Returns the value of attribute source.
Class Method Summary collapse
- .build(ast, source: nil, include_source: nil) ⇒ Object
-
.build_children(ast_array) ⇒ Object
Build children (array of AST nodes) Optimized to avoid intermediate array allocations.
-
.build_expression(data) ⇒ Object
Fast path for expression nodes.
-
.build_factor(data) ⇒ Object
Fast path for factor nodes.
-
.build_node(node_type, data) ⇒ Object
Call a registered builder directly with data (avoids hash wrapper allocation).
-
.build_optional(ast) ⇒ Object
Build optional (returns nil if ast is nil).
-
.build_primary(data) ⇒ Object
Fast path for primary nodes.
-
.build_simple_expression(data) ⇒ Object
Fast path for simple_expression nodes.
-
.build_simple_factor(data) ⇒ Object
Fast path for simple_factor nodes.
-
.build_term(data) ⇒ Object
Fast path for term nodes.
-
.build_with_remarks(ast, source: nil, include_source: nil) ⇒ Object
Build with remark attachment.
-
.ensure_array(value) ⇒ Object
Normalize a value to an Array for iteration.
-
.register(node_type, builder = nil) { ... } ⇒ Object
Register a builder for a node type.
-
.registered?(node_type) ⇒ Boolean
Check if a builder is registered for a node type.
-
.registered_types ⇒ Object
Get all registered node types.
Class Attribute Details
.include_source ⇒ Object (readonly)
Returns the value of attribute include_source.
10 11 12 |
# File 'lib/expressir/express/builder.rb', line 10 def include_source @include_source end |
.source ⇒ Object (readonly)
Returns the value of attribute source.
10 11 12 |
# File 'lib/expressir/express/builder.rb', line 10 def source @source end |
Class Method Details
.build(ast, source: nil, include_source: nil) ⇒ Object
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 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 |
# File 'lib/expressir/express/builder.rb', line 44 def build(ast, source: nil, include_source: nil) return nil unless ast # Only set instance variables on first call (when they're provided) # Recursive calls pass nil which shouldn't override the saved values @source = source unless source.nil? @include_source = include_source unless include_source.nil? # Optimized: Hash is 90%+ of cases, check it first case ast when Hash node_type = ast.keys.first node_data = ast[node_type] handler_key = cached_snake_case(node_type) snake_data = fast_convert_keys(node_data) builder = @register[handler_key] if builder result = builder.call(snake_data) # Fast path: single-key hash or non-nil result if !result.nil? || ast.keys.length <= 1 attach_source_info(result, node_data) return result end # Slow path: operator token returned nil in multi-key hash. # Try other keys for actual content. This handles # {:op_comma => ..., :element => {...}} where the first key # is an operator separator rather than a content key. if OPERATOR_TOKENS.include?(handler_key) ast.each_key do |key| next if key == node_type h_key = cached_snake_case(key) h_builder = @register[h_key] next unless h_builder n_data = ast[key] s_data = fast_convert_keys(n_data) result = h_builder.call(s_data) unless result.nil? attach_source_info(result, n_data) return result end end end else raise Error::UnknownNodeTypeError, node_type end nil when Array ast.map do |item| build(item) end when Parsanol::Slice ast.to_s else ast end end |
.build_children(ast_array) ⇒ Object
Build children (array of AST nodes) Optimized to avoid intermediate array allocations
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/expressir/express/builder.rb', line 155 def build_children(ast_array) return [] if ast_array.nil? # Handle Parsanol::Slice (empty Slices from optional rules) # Convert to empty Array if ast_array.is_a?(Parsanol::Slice) return [] end # Handle single element (common case) unless ast_array.is_a?(Array) return [build(ast_array)].compact end # Build result in single pass, avoiding flatten/compact/map chain result = [] ast_array.each do |item| next if item.nil? # Empty Slices from optional rules should be treated as empty arrays if item.is_a?(Parsanol::Slice) next end case item when Array item.each do |sub| result << build(sub) unless sub.nil? end else built = build(item) result << built if built end end result end |
.build_expression(data) ⇒ Object
Fast path for expression nodes
227 228 229 |
# File 'lib/expressir/express/builder.rb', line 227 def build_expression(data) build_node(:expression, data) end |
.build_factor(data) ⇒ Object
Fast path for factor nodes
212 213 214 |
# File 'lib/expressir/express/builder.rb', line 212 def build_factor(data) build_node(:factor, data) end |
.build_node(node_type, data) ⇒ Object
Call a registered builder directly with data (avoids hash wrapper allocation)
199 200 201 202 203 204 |
# File 'lib/expressir/express/builder.rb', line 199 def build_node(node_type, data) builder = @register&.[](node_type) raise Error::UnknownNodeTypeError, node_type unless builder builder.call(data) end |
.build_optional(ast) ⇒ Object
Build optional (returns nil if ast is nil)
138 139 140 141 142 |
# File 'lib/expressir/express/builder.rb', line 138 def build_optional(ast) return nil unless ast build(ast) end |
.build_primary(data) ⇒ Object
Fast path for primary nodes
222 223 224 |
# File 'lib/expressir/express/builder.rb', line 222 def build_primary(data) build_node(:primary, data) end |
.build_simple_expression(data) ⇒ Object
Fast path for simple_expression nodes
232 233 234 |
# File 'lib/expressir/express/builder.rb', line 232 def build_simple_expression(data) build_node(:simple_expression, data) end |
.build_simple_factor(data) ⇒ Object
Fast path for simple_factor nodes
217 218 219 |
# File 'lib/expressir/express/builder.rb', line 217 def build_simple_factor(data) build_node(:simple_factor, data) end |
.build_term(data) ⇒ Object
Fast path for term nodes
207 208 209 |
# File 'lib/expressir/express/builder.rb', line 207 def build_term(data) build_node(:term, data) end |
.build_with_remarks(ast, source: nil, include_source: nil) ⇒ Object
Build with remark attachment
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/expressir/express/builder.rb', line 109 def build_with_remarks(ast, source: nil, include_source: nil) # Reset instance variables at the start of a top-level build # This ensures state from previous parses is cleared @source = source @include_source = include_source result = build(ast) # Only attach remarks if include_source is explicitly true # (nil means use default behavior - attach remarks) if source && result && include_source != false attacher = RemarkAttacher.new(source) attacher.attach(result) end result end |
.ensure_array(value) ⇒ Object
Normalize a value to an Array for iteration. Handles: nil → [], Parsanol::Slice → [], Array → Array, other → [other]
146 147 148 149 150 151 |
# File 'lib/expressir/express/builder.rb', line 146 def ensure_array(value) return [] if value.nil? return [] if value.is_a?(Parsanol::Slice) value.is_a?(Array) ? value : [value] end |
.register(node_type, builder = nil) { ... } ⇒ Object
Register a builder for a node type.
19 20 21 22 |
# File 'lib/expressir/express/builder.rb', line 19 def register(node_type, builder = nil, &block) @register ||= {} @register[node_type] = builder || block end |
.registered?(node_type) ⇒ Boolean
Check if a builder is registered for a node type.
128 129 130 |
# File 'lib/expressir/express/builder.rb', line 128 def registered?(node_type) @register&.key?(node_type) end |
.registered_types ⇒ Object
Get all registered node types.
133 134 135 |
# File 'lib/expressir/express/builder.rb', line 133 def registered_types @register&.keys || [] end |