Class: Fusion::Parser
Constant Summary collapse
- COMPARISON_MEMBERS =
The comparisons desugar to a compare piped into its reading member:
a < b→[a, b] | @OP.compare | @OP.lt. { lt: "lt", lte: "lte", gt: "gt", gte: "gte" }.freeze
- PRIMARY_STARTERS =
Tokens that can begin a unary operand — used to decide whether
!has an operand or is the bare!null. [:number, :string, :true_kw, :false_kw, :null_kw, :bang, :minus, :slash, :tilde, :lbracket, :lbrace, :lparen, :ident, :at, :atat].freeze
- GUARDEDPAT_STARTERS =
Tokens that can begin a
guardedpat(used to detect whether!is followed by a payload pattern or stands alone). [:number, :string, :true_kw, :false_kw, :null_kw, :minus, :lbracket, :lbrace, :ident].freeze
Constants included from AST
AST::ArrayItem, AST::ArraySpread, AST::Clause, AST::IDENTIFIER, AST::KeyValuePair, AST::ObjectSpread, AST::PatternItem, AST::PatternPair, AST::PatternRest
Class Method Summary collapse
-
.parse_file(src, site:) ⇒ Object
Parse a complete program.
-
.parse_repl(src, site:) ⇒ Object
Parse one REPL entry — a statement (
identifier "=" expr) or a bare expression — returning an AST::Statement::Assignment / AST::Expression, or, like parse_file, a standardized syntax_error value instead of ever raising.
Instance Method Summary collapse
- #advance ⇒ Object
- #at?(type) ⇒ Boolean
- #expect(type) ⇒ Object
-
#fold_operator(operands, member) ⇒ Object
A single-operand run collapses to that operand; otherwise fold n-ary.
-
#initialize(tokens) ⇒ Parser
constructor
A new instance of Parser.
-
#looks_like_function? ⇒ Boolean
Look ahead from current position (just after "(") to decide if this is a function literal: is there a top-level "=>" before the matching ")"?.
-
#name_ref(path) ⇒ Object
--- Desugaring helpers ---------------------------------------------------.
-
#negate(expr) ⇒ Object
-x: a numeric literal folds to a negative literal; anything else negates. -
#op_member(member) ⇒ Object
@OP.member, a shadowable reference. -
#parse_additive ⇒ Object
A run of
+/-folds into one@OP.sum; each-term is negated. - #parse_and ⇒ Object
- #parse_array ⇒ Object
-
#parse_arraypat ⇒ Object
p_array (reference.md §2.5).
- #parse_corepat ⇒ Object
- #parse_equality ⇒ Object
- #parse_errpat ⇒ Object
- #parse_expr ⇒ Object
- #parse_fileref ⇒ Object
-
#parse_function_or_group ⇒ Object
A "(" begins a grouped expression, a function literal, or — when empty — the clause-less function
(). A function is a comma-separated list ofpattern => expr; we detect one by scanning for a top-level=>before the matching). - #parse_guardedpat ⇒ Object
-
#parse_multiplicative ⇒ Object
A run of
*//folds into one@OP.product(each/term inverted);%///are binary and break the run. -
#parse_object ⇒ Object
Fixed keys must be distinct (the ObjLit data rule); a repeat is a clean syntax_error.
-
#parse_objectpat ⇒ Object
p_object (reference.md §2.5).
-
#parse_or ⇒ Object
--- Operator sugar (reference §2.7) -------------------------------------- The ladder below desugars every operator to a pipe into an
@OP.*member (or, for the map-pipes, a stdlib call). -
#parse_ordering ⇒ Object
??(compare) and the comparisons are binary and left-associative — they do not fold, anda < b < cdoes not chain (it compares a boolean). -
#parse_pattern ⇒ Object
---- Patterns ---- ---- Pattern grammar (mirrors reference.md §2.5 EBNF) ------------------ pattern = p_error | p_guarded p_error = "!" | "!" p_guarded p_guarded = p_core [ "?" predicate ] p_core = p_literal | p_bind | p_wildcard | p_array | p_object Note:
p_coredoes NOT include p_error. -
#parse_pattern_pair ⇒ Object
p_pair = string ":" p_guarded.
-
#parse_pattern_rest ⇒ Object
p_rest = "..." [ identifier ] — the single rest binder, shared by array and object patterns.
- #parse_pipe ⇒ Object
- #parse_postfix ⇒ Object
- #parse_primary ⇒ Object
-
#parse_repl_entry ⇒ Object
A leading
identifier =marks a statement; anything else is an expression. -
#parse_statement ⇒ Object
statement = identifier "=" expr (REPL only; files contain one expr).
-
#parse_superref ⇒ Object
@@is super. -
#parse_unary ⇒ Object
Unary prefixes:
!xbuilds an error (bare!is!null);-xnegates,/xinverts,~xis logical not. -
#peek(o = 0) ⇒ Object
---- token helpers ----.
-
#pipe_into(expr, member) ⇒ Object
expr | @OP.member. -
#pipe_operator(operands, member) ⇒ Object
[operands...] | @OP.member.
Constructor Details
#initialize(tokens) ⇒ Parser
Returns a new instance of Parser.
18 19 20 21 |
# File 'lib/fusion/parser.rb', line 18 def initialize(tokens) @toks = tokens @i = 0 end |
Class Method Details
.parse_file(src, site:) ⇒ Object
Parse a complete program. The lexer and parser report failures by raising
ParseError; this single entry point rescues them and returns a standardized
syntax_error value, so no caller ever sees a raw Ruby error. site is the
syntax_error's {origin:, file:} context.
27 28 29 30 31 32 33 34 35 |
# File 'lib/fusion/parser.rb', line 27 def self.parse_file(src, site:) toks = Lexer.new(src).tokens p = new(toks) expr = p.parse_expr p.expect(:eof) expr rescue ParseError => e Interpreter::ErrorVal.from_runtime(kind: "syntax_error", **site, operation: "parsing code", input: src, message: e.) end |
.parse_repl(src, site:) ⇒ Object
Parse one REPL entry — a statement (identifier "=" expr) or a bare
expression — returning an AST::Statement::Assignment / AST::Expression, or, like
parse_file, a standardized syntax_error value instead of ever raising. The
REPL uses the error/non-error distinction to tell "keep editing" (didn't
parse yet) from "evaluate now" (a complete statement or expression).
42 43 44 45 46 47 48 49 50 |
# File 'lib/fusion/parser.rb', line 42 def self.parse_repl(src, site:) toks = Lexer.new(src).tokens p = new(toks) entry = p.parse_repl_entry p.expect(:eof) entry rescue ParseError => e Interpreter::ErrorVal.from_runtime(kind: "syntax_error", **site, operation: "parsing code", input: src, message: e.) end |
Instance Method Details
#advance ⇒ Object
568 569 570 |
# File 'lib/fusion/parser.rb', line 568 def advance @toks[@i].tap { @i += 1 } end |
#at?(type) ⇒ Boolean
564 565 566 |
# File 'lib/fusion/parser.rb', line 564 def at?(type) peek.type == type end |
#expect(type) ⇒ Object
572 573 574 575 576 577 |
# File 'lib/fusion/parser.rb', line 572 def expect(type) t = peek raise ParseError, "Expected #{type} but got #{t.type} (#{t.value.inspect}) at #{t.pos}" unless t.type == type advance end |
#fold_operator(operands, member) ⇒ Object
A single-operand run collapses to that operand; otherwise fold n-ary.
261 262 263 |
# File 'lib/fusion/parser.rb', line 261 def fold_operator(operands, member) operands.length == 1 ? operands.first : pipe_operator(operands, member) end |
#looks_like_function? ⇒ Boolean
Look ahead from current position (just after "(") to decide if this is a function literal: is there a top-level "=>" before the matching ")"?
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 |
# File 'lib/fusion/parser.rb', line 398 def looks_like_function? depth = 0 j = @i while j < @toks.length t = @toks[j] case t.type when :lparen, :lbracket, :lbrace then depth += 1 when :rparen, :rbracket, :rbrace return false if depth == 0 # hit our closing ) first depth -= 1 when :arrow return true if depth == 0 when :eof return false end j += 1 end false end |
#name_ref(path) ⇒ Object
--- Desugaring helpers ---------------------------------------------------
246 |
# File 'lib/fusion/parser.rb', line 246 def name_ref(path) = Expression::FileRef.new(variety: :name, path: path) |
#negate(expr) ⇒ Object
-x: a numeric literal folds to a negative literal; anything else negates.
266 267 268 269 270 271 272 |
# File 'lib/fusion/parser.rb', line 266 def negate(expr) if expr.is_a?(Expression::Lit) && expr.value.is_a?(Numeric) Expression::Lit.new(value: -expr.value) else pipe_into(expr, "negate") end end |
#op_member(member) ⇒ Object
@OP.member, a shadowable reference.
249 |
# File 'lib/fusion/parser.rb', line 249 def op_member(member) = Expression::Member.new(obj: name_ref("OP"), key: member) |
#parse_additive ⇒ Object
A run of +/- folds into one @OP.sum; each - term is negated.
129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/fusion/parser.rb', line 129 def parse_additive terms = [parse_multiplicative] folded = false while at?(:plus) || at?(:minus) negated = at?(:minus) advance term = parse_multiplicative terms << (negated ? negate(term) : term) folded = true end folded ? pipe_operator(terms, "sum") : terms.first end |
#parse_and ⇒ Object
88 89 90 91 92 93 94 95 |
# File 'lib/fusion/parser.rb', line 88 def parse_and operands = [parse_equality] while at?(:andand) advance operands << parse_equality end fold_operator(operands, "and") end |
#parse_array ⇒ Object
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/fusion/parser.rb', line 318 def parse_array expect(:lbracket) items = [] until at?(:rbracket) if at?(:spread) advance items << ArraySpread.new(value: parse_expr) else items << ArrayItem.new(value: parse_expr) end break unless at?(:comma) advance end expect(:rbracket) Expression::ArrLit.new(items: items) end |
#parse_arraypat ⇒ Object
p_array (reference.md §2.5). Items are p_guardeds — never error patterns.
The grammar's two arms (with / without a rest) become two phases: the loop
parses leading items up to an optional single ...rest; once a rest is
consumed, the inner loop parses trailing items only, so a second ... lands
in parse_guardedpat as an unexpected token. There is no seen_rest flag —
"at most one rest" is enforced by the shape of the loop.
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 |
# File 'lib/fusion/parser.rb', line 488 def parse_arraypat expect(:lbracket) items = [] until at?(:rbracket) if at?(:spread) items << parse_pattern_rest while at?(:comma) advance break if at?(:rbracket) # trailing comma raise ParseError, "a pattern may contain at most one `...rest` (at #{peek.pos})" if at?(:spread) items << PatternItem.new(pattern: parse_guardedpat) end break end items << PatternItem.new(pattern: parse_guardedpat) break unless at?(:comma) advance end expect(:rbracket) Pattern::PArr.new(items: items) end |
#parse_corepat ⇒ Object
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 |
# File 'lib/fusion/parser.rb', line 459 def parse_corepat t = peek case t.type when :number, :string, :true_kw, :false_kw, :null_kw advance Pattern::PLit.new(value: t.value) when :minus advance Pattern::PLit.new(value: -expect(:number).value) # negative literal when :lbracket then parse_arraypat when :lbrace then parse_objectpat when :ident advance t.value == "_" ? Pattern::PWild.new(dummy: nil) : Pattern::PBind.new(name: t.value) when :bang # `!pat` is only valid as a clause's top-level pattern, never inside an # array element, object member, or error payload. raise ParseError, "`!pat` may only appear as a clause's top-level pattern (at #{t.pos})" else raise ParseError, "Unexpected token in pattern: #{t.type} at #{t.pos}" end end |
#parse_equality ⇒ Object
97 98 99 100 101 102 103 104 |
# File 'lib/fusion/parser.rb', line 97 def parse_equality operands = [parse_ordering] while at?(:eqeq) advance operands << parse_ordering end fold_operator(operands, "equal") end |
#parse_errpat ⇒ Object
437 438 439 440 441 442 443 444 |
# File 'lib/fusion/parser.rb', line 437 def parse_errpat expect(:bang) if GUARDEDPAT_STARTERS.include?(peek.type) Pattern::PErr.new(inner: parse_guardedpat) # "!" guardedpat else Pattern::PErr.new(inner: Pattern::PWild.new(dummy: nil)) # bare "!" — matches any error, binds nothing end end |
#parse_expr ⇒ Object
69 70 71 |
# File 'lib/fusion/parser.rb', line 69 def parse_expr parse_or end |
#parse_fileref ⇒ Object
307 308 309 310 311 312 313 314 315 316 |
# File 'lib/fusion/parser.rb', line 307 def parse_fileref expect(:at) return Expression::FileRef.new(variety: :self, path: nil) unless at?(:path) # A reference is eligible for builtin/stdlib fallback (:name) iff it has no # "../"; downward paths stay eligible, only "../" forces file-only (:path). # The lexer produced the whole path as one tight token (see Lexer#try_lex_path). path = advance.value Expression::FileRef.new(variety: path.include?("..") ? :path : :name, path: path) end |
#parse_function_or_group ⇒ Object
A "(" begins a grouped expression, a function literal, or — when empty —
the clause-less function (). A function is a comma-separated list of
pattern => expr; we detect one by scanning for a top-level => before the
matching ). () matches nothing (so it yields null for any normal input
and propagates errors).
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 |
# File 'lib/fusion/parser.rb', line 368 def parse_function_or_group expect(:lparen) if at?(:rparen) advance return Expression::FuncLit.new(clauses: []) end if looks_like_function? clauses = [] loop do pat = parse_pattern expect(:arrow) body = parse_expr clauses << Clause.new(pattern: pat, body: body) break if !at?(:comma) advance break if at?(:rparen) # trailing comma end expect(:rparen) Expression::FuncLit.new(clauses: clauses) else e = parse_expr expect(:rparen) e end end |
#parse_guardedpat ⇒ Object
446 447 448 449 450 451 452 453 454 455 456 457 |
# File 'lib/fusion/parser.rb', line 446 def parse_guardedpat inner = parse_corepat if at?(:question) advance # A predicate is a full pipe so it may chain functions: `a ? b | c` tests # `a | b | c`. It stops at `=>`, `,`, `]`, `}`, `)` like any expression. pred = parse_pipe Pattern::PGuard.new(inner: inner, pred_expr: pred) else inner end end |
#parse_multiplicative ⇒ Object
A run of *// folds into one @OP.product (each / term inverted); %///
are binary and break the run. Standard left-to-right: a * b % c is (a * b) % c.
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/fusion/parser.rb', line 144 def parse_multiplicative node = parse_pipe run = nil # accumulating product-run terms, or nil loop do if at?(:star) || at?(:slash) inverted = at?(:slash) advance term = parse_pipe term = pipe_into(term, "invert") if inverted if run run << term else run = [node, term] end elsif at?(:percent) || at?(:slashslash) node = pipe_operator(run, "product") if run run = nil op = at?(:percent) ? "modulo" : "quotient" advance node = pipe_operator([node, parse_pipe], op) else break end end run ? pipe_operator(run, "product") : node end |
#parse_object ⇒ Object
Fixed keys must be distinct (the ObjLit data rule); a repeat is a clean
syntax_error. Keys arriving via ...spread are dynamic and not checked.
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 |
# File 'lib/fusion/parser.rb', line 338 def parse_object expect(:lbrace) pairs = [] keys = [] until at?(:rbrace) if at?(:spread) advance pairs << ObjectSpread.new(value: parse_expr) else key_tok = expect(:string) key = key_tok.value raise ParseError, "duplicate key #{key.inspect} (at #{key_tok.pos})" if keys.include?(key) keys << key expect(:colon) pairs << KeyValuePair.new(key: key, value: parse_expr) end break unless at?(:comma) advance end expect(:rbrace) Expression::ObjLit.new(pairs: pairs) end |
#parse_objectpat ⇒ Object
p_object (reference.md §2.5). Leading pairs up to an optional single
...rest, which must come last — only a trailing comma may follow it. Keys
must be distinct (the PObj data rule); a repeat is a clean syntax_error.
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 |
# File 'lib/fusion/parser.rb', line 515 def parse_objectpat expect(:lbrace) pairs = [] keys = [] until at?(:rbrace) if at?(:spread) pairs << parse_pattern_rest advance if at?(:comma) && peek(1)&.type == :rbrace # trailing comma unless at?(:rbrace) raise ParseError, "in an object pattern, `...rest` must come last (at #{peek.pos})" end break end key_pos = peek.pos pair = parse_pattern_pair raise ParseError, "duplicate key #{pair.key.inspect} (at #{key_pos})" if keys.include?(pair.key) keys << pair.key pairs << pair break unless at?(:comma) advance end expect(:rbrace) Pattern::PObj.new(pairs: pairs) end |
#parse_or ⇒ Object
--- Operator sugar (reference §2.7) --------------------------------------
The ladder below desugars every operator to a pipe into an @OP.* member
(or, for the map-pipes, a stdlib call). Tightest to loosest:
postfix · unary (! - / ~) · pipe (| |: |? |+) · * / % // · + - · ?? < <= > >= · == · && · ||
@OP.* is a shadowable :name reference, so a local @OP reskins the operators.
79 80 81 82 83 84 85 86 |
# File 'lib/fusion/parser.rb', line 79 def parse_or operands = [parse_and] while at?(:oror) advance operands << parse_and end fold_operator(operands, "or") end |
#parse_ordering ⇒ Object
?? (compare) and the comparisons are binary and left-associative — they
do not fold, and a < b < c does not chain (it compares a boolean).
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/fusion/parser.rb', line 112 def parse_ordering node = parse_additive loop do if at?(:qq) advance node = pipe_operator([node, parse_additive], "compare") elsif COMPARISON_MEMBERS.key?(peek.type) member = COMPARISON_MEMBERS[advance.type] node = pipe_into(pipe_operator([node, parse_additive], "compare"), member) else break end end node end |
#parse_pattern ⇒ Object
---- Patterns ----
---- Pattern grammar (mirrors reference.md §2.5 EBNF) ------------------
pattern = p_error | p_guarded
p_error = "!" | "!" p_guarded
p_guarded = p_core [ "?" predicate ]
p_core = p_literal | p_bind | p_wildcard | p_array | p_object
Note: p_core does NOT include p_error. The "no nested !pat" property
falls out of the grammar shape — p_error is only reachable from pattern
(a clause's top level), never from inside arrays, objects, or another
error's payload. No flag-threading is needed.
429 430 431 |
# File 'lib/fusion/parser.rb', line 429 def parse_pattern at?(:bang) ? parse_errpat : parse_guardedpat end |
#parse_pattern_pair ⇒ Object
p_pair = string ":" p_guarded
553 554 555 556 557 |
# File 'lib/fusion/parser.rb', line 553 def parse_pattern_pair key = expect(:string).value expect(:colon) PatternPair.new(key: key, pattern: parse_guardedpat) end |
#parse_pattern_rest ⇒ Object
p_rest = "..." [ identifier ] — the single rest binder, shared by array and object patterns. Callers parse it only at a rest position and then continue with items/pairs only, which is what holds a pattern to one rest.
546 547 548 549 550 |
# File 'lib/fusion/parser.rb', line 546 def parse_pattern_rest expect(:spread) name = at?(:ident) ? advance.value : nil PatternRest.new(name: name) end |
#parse_pipe ⇒ Object
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/fusion/parser.rb', line 171 def parse_pipe node = parse_unary loop do if at?(:pipe) advance node = Expression::Pipe.new(left: node, right: parse_unary) elsif at?(:pipemap) || at?(:pipefilter) || at?(:pipereduce) target = { pipemap: "map", pipefilter: "filter", pipereduce: "reduce" }[peek.type] advance arg = Expression::ObjLit.new( pairs: [ KeyValuePair.new(key: "c", value: node), KeyValuePair.new(key: "f", value: parse_unary), ], ) node = Expression::Pipe.new(left: arg, right: name_ref(target)) else break end end node end |
#parse_postfix ⇒ Object
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/fusion/parser.rb', line 218 def parse_postfix node = parse_primary loop do if at?(:dot) advance key = expect(:ident).value node = Expression::Member.new(obj: node, key: key) elsif at?(:lbracket) advance idx = parse_expr if at?(:equals) advance value = parse_expr expect(:rbracket) node = Expression::IndexSet.new(obj: node, idx: idx, value: value) else expect(:rbracket) node = Expression::Index.new(obj: node, idx: idx) end else break end end node end |
#parse_primary ⇒ Object
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/fusion/parser.rb', line 274 def parse_primary t = peek case t.type when :number, :string, :true_kw, :false_kw, :null_kw advance Expression::Lit.new(value: t.value) when :lbracket then parse_array when :lbrace then parse_object when :lparen then parse_function_or_group when :ident advance Expression::Ident.new(name: t.value) when :at then parse_fileref when :atat then parse_superref else raise ParseError, "Unexpected token #{t.type} (#{t.value.inspect}) at #{t.pos}" end end |
#parse_repl_entry ⇒ Object
A leading identifier = marks a statement; anything else is an expression.
(A bare identifier is itself a valid expression, so the = is the decider.)
54 55 56 57 58 59 60 |
# File 'lib/fusion/parser.rb', line 54 def parse_repl_entry if at?(:ident) && peek(1)&.type == :equals parse_statement else parse_expr end end |
#parse_statement ⇒ Object
statement = identifier "=" expr (REPL only; files contain one expr)
63 64 65 66 67 |
# File 'lib/fusion/parser.rb', line 63 def parse_statement name = expect(:ident).value expect(:equals) AST::Statement::Assignment.new(name: name, expression: parse_expr) end |
#parse_superref ⇒ Object
@@ is super. Bare @@ is super of the current file's own name; @@name
(or a downward @@dir/name) is a stable reference to that name — it skips
the sibling name.fsn, resolving builtin → stdlib, so a user's local shadow
can't intercept it. @@../… is meaningless (super of an upward path) and
falls through to a parse error.
297 298 299 300 301 302 303 304 305 |
# File 'lib/fusion/parser.rb', line 297 def parse_superref expect(:atat) return Expression::FileRef.new(variety: :super, path: nil) unless at?(:path) tok = advance raise ParseError, "`@@` cannot take an upward path (at #{tok.pos})" if tok.value.include?("..") Expression::FileRef.new(variety: :super_name, path: tok.value) end |
#parse_unary ⇒ Object
Unary prefixes: !x builds an error (bare ! is !null); -x negates,
/x inverts, ~x is logical not. All bind tighter than |.
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/fusion/parser.rb', line 200 def parse_unary if at?(:bang) advance PRIMARY_STARTERS.include?(peek.type) ? Expression::ErrLit.new(payload: parse_unary) : Expression::ErrLit.new(payload: nil) elsif at?(:minus) advance negate(parse_unary) elsif at?(:slash) advance pipe_into(parse_unary, "invert") elsif at?(:tilde) advance pipe_into(parse_unary, "not") else parse_postfix end end |
#peek(o = 0) ⇒ Object
---- token helpers ----
560 561 562 |
# File 'lib/fusion/parser.rb', line 560 def peek(o = 0) @toks[@i + o] end |
#pipe_into(expr, member) ⇒ Object
expr | @OP.member
252 |
# File 'lib/fusion/parser.rb', line 252 def pipe_into(expr, member) = Expression::Pipe.new(left: expr, right: op_member(member)) |
#pipe_operator(operands, member) ⇒ Object
[operands...] | @OP.member
255 256 257 258 |
# File 'lib/fusion/parser.rb', line 255 def pipe_operator(operands, member) arr = Expression::ArrLit.new(items: operands.map { |e| ArrayItem.new(value: e) }) Expression::Pipe.new(left: arr, right: op_member(member)) end |