///|
priv struct Parser {
  tokens : Array[Token]
  mut pos : Int
}

///|
fn Parser::new(tokens : Array[Token]) -> Parser {
  { tokens, pos: 0 }
}

///|
fn Parser::peek(self : Parser) -> Token {
  if self.pos < self.tokens.length() {
    self.tokens[self.pos]
  } else {
    Eof
  }
}

///|
fn Parser::advance(self : Parser) -> Token {
  let tok = self.peek()
  self.pos += 1
  tok
}

///|
fn Parser::expect(self : Parser, expected : Token) -> Unit raise JqError {
  let tok = self.advance()
  if tok != expected {
    raise JqError(
      "expected " + expected.to_string() + " but got " + tok.to_string(),
    )
  }
}

///|
fn parse(tokens : Array[Token]) -> Filter raise JqError {
  let parser = Parser::new(tokens)
  let filter = parser.parse_pipe()
  let tok = parser.peek()
  if tok != Eof {
    raise JqError("unexpected token: " + tok.to_string())
  }
  filter
}

///|
fn Parser::parse_pipe(self : Parser) -> Filter raise JqError {
  let left = self.parse_comma()
  match self.peek() {
    Pipe => {
      ignore(self.advance())
      let right = self.parse_pipe()
      Pipe(left, right)
    }
    _ => left
  }
}

///|
fn Parser::parse_comma(self : Parser) -> Filter raise JqError {
  let mut left = self.parse_assign()
  while self.peek() == Comma {
    ignore(self.advance())
    let right = self.parse_assign()
    left = Comma(left, right)
  }
  left
}

///|
fn Parser::parse_assign(self : Parser) -> Filter raise JqError {
  let left = self.parse_binding()
  match self.peek() {
    Eq => {
      ignore(self.advance())
      let right = self.parse_assign()
      Assign(left, right)
    }
    PipeEq => {
      ignore(self.advance())
      let right = self.parse_assign()
      Update(left, right)
    }
    PlusEq => {
      ignore(self.advance())
      let right = self.parse_assign()
      // X += Y  =>  (Y) as $__rhs__ | X |= (. + $__rhs__)
      Binding(
        PatVar("$__rhs__"),
        right,
        Update(left, Arith(ArithOp::Add, Identity, FuncCall("$__rhs__", []))),
      )
    }
    MinusEq => {
      ignore(self.advance())
      let right = self.parse_assign()
      Binding(
        PatVar("$__rhs__"),
        right,
        Update(left, Arith(ArithOp::Sub, Identity, FuncCall("$__rhs__", []))),
      )
    }
    StarEq => {
      ignore(self.advance())
      let right = self.parse_assign()
      Binding(
        PatVar("$__rhs__"),
        right,
        Update(left, Arith(ArithOp::Mul, Identity, FuncCall("$__rhs__", []))),
      )
    }
    SlashEq => {
      ignore(self.advance())
      let right = self.parse_assign()
      Binding(
        PatVar("$__rhs__"),
        right,
        Update(left, Arith(ArithOp::Div, Identity, FuncCall("$__rhs__", []))),
      )
    }
    PercentEq => {
      ignore(self.advance())
      let right = self.parse_assign()
      Binding(
        PatVar("$__rhs__"),
        right,
        Update(left, Arith(ArithOp::Mod, Identity, FuncCall("$__rhs__", []))),
      )
    }
    SlashSlashEq => {
      ignore(self.advance())
      let right = self.parse_assign()
      UpdateAlt(left, right)
    }
    _ => left
  }
}

///|
/// Evaluate a constant string expression at parse time (for pattern keys).
fn eval_const_string(filter : Filter) -> String raise JqError {
  match filter {
    Literal(String(s)) => s
    Arith(ArithOp::Add, left, right) =>
      eval_const_string(left) + eval_const_string(right)
    _ =>
      raise JqError("pattern object key must be a constant string expression")
  }
}

///|
fn Parser::parse_pattern(self : Parser) -> Pattern raise JqError {
  match self.peek() {
    Variable(name) => {
      ignore(self.advance())
      PatVar("$" + name)
    }
    LBracket => {
      ignore(self.advance())
      let pats : Array[Pattern] = []
      if self.peek() != RBracket {
        pats.push(self.parse_pattern())
        while self.peek() == Comma {
          ignore(self.advance())
          pats.push(self.parse_pattern())
        }
      }
      self.expect(RBracket)
      PatArray(pats)
    }
    LBrace => {
      ignore(self.advance())
      let fields : Array[(String, Pattern)] = []
      if self.peek() != RBrace {
        fields.push(self.parse_pattern_field())
        while self.peek() == Comma {
          ignore(self.advance())
          fields.push(self.parse_pattern_field())
        }
      }
      self.expect(RBrace)
      PatObject(fields)
    }
    _ => raise JqError("expected pattern (variable, [ ], or { })")
  }
}

///|
fn Parser::parse_pattern_field(
  self : Parser,
) -> (String, Pattern) raise JqError {
  match self.peek() {
    Variable(name) => {
      ignore(self.advance())
      (name, PatVar("$" + name))
    }
    Ident(name) => {
      ignore(self.advance())
      if self.peek() == Colon {
        ignore(self.advance())
        let pat = self.parse_pattern()
        (name, pat)
      } else {
        (name, PatVar("$" + name))
      }
    }
    Str(s) => {
      ignore(self.advance())
      self.expect(Colon)
      let pat = self.parse_pattern()
      (s, pat)
    }
    LParen => {
      // Dynamic key pattern: ("expr"): $var
      // We evaluate the key expression at bind time
      // For now, parse the expression but store it as a special marker
      // Actually jq evaluates the key at bind time, which requires
      // storing the expression. We'll use a special pattern variant.
      // For simplicity, eval the expr during parse if it's a literal concatenation
      // Actually, we need to handle this at eval time.
      // Store the expression source in a special way.
      ignore(self.advance())
      let key_expr = self.parse_pipe()
      self.expect(RParen)
      self.expect(Colon)
      let pat = self.parse_pattern()
      // We encode dynamic key patterns by storing a sentinel key
      // and the expression in PatObject. But Pattern doesn't support this.
      // Alternative: evaluate literal expressions at parse time.
      // For now, try to evaluate constant string concatenations:
      let key = eval_const_string(key_expr)
      (key, pat)
    }
    _ => {
      // Handle keywords used as field names (e.g., "as", "if", "then", etc.)
      let tok = self.peek()
      let name = match tok {
        KwAs => "as"
        KwAnd => "and"
        KwOr => "or"
        KwNot => "not"
        KwIf => "if"
        KwThen => "then"
        KwElif => "elif"
        KwElse => "else"
        KwEnd => "end"
        KwDef => "def"
        KwTry => "try"
        KwCatch => "catch"
        KwReduce => "reduce"
        KwForeach => "foreach"
        KwTrue => "true"
        KwFalse => "false"
        KwNull => "null"
        KwLabel => "label"
        KwBreak => "break"
        _ => raise JqError("expected pattern field")
      }
      ignore(self.advance())
      if self.peek() == Colon {
        ignore(self.advance())
        let pat = self.parse_pattern()
        (name, pat)
      } else {
        (name, PatVar("$" + name))
      }
    }
  }
}

///|
fn Parser::parse_binding(self : Parser) -> Filter raise JqError {
  let expr = self.parse_alternative()
  if self.peek() == KwAs {
    ignore(self.advance())
    let pat = self.parse_pattern()
    // Check for ?// alternative destructuring
    let patterns : Array[Pattern] = [pat]
    while self.peek() == Question {
      let saved = self.pos
      ignore(self.advance())
      if self.peek() == SlashSlash {
        ignore(self.advance())
        patterns.push(self.parse_pattern())
      } else {
        self.pos = saved
        break
      }
    }
    self.expect(Pipe)
    let body = self.parse_pipe()
    if patterns.length() == 1 {
      Binding(pat, expr, body)
    } else {
      BindingAlt(patterns, expr, body)
    }
  } else {
    expr
  }
}

///|
fn Parser::parse_alternative(self : Parser) -> Filter raise JqError {
  let left = self.parse_or()
  if self.peek() == SlashSlash {
    ignore(self.advance())
    let right = self.parse_alternative()
    Alternative(left, right)
  } else {
    left
  }
}

///|
fn Parser::parse_or(self : Parser) -> Filter raise JqError {
  let mut left = self.parse_and()
  while self.peek() == KwOr {
    ignore(self.advance())
    let right = self.parse_and()
    left = LogicOr(left, right)
  }
  left
}

///|
fn Parser::parse_and(self : Parser) -> Filter raise JqError {
  let mut left = self.parse_compare()
  while self.peek() == KwAnd {
    ignore(self.advance())
    let right = self.parse_compare()
    left = LogicAnd(left, right)
  }
  left
}

///|
fn Parser::parse_compare(self : Parser) -> Filter raise JqError {
  let left = self.parse_add()
  match self.peek() {
    EqEq => {
      ignore(self.advance())
      let right = self.parse_add()
      Compare(CmpOp::Eq, left, right)
    }
    Ne => {
      ignore(self.advance())
      let right = self.parse_add()
      Compare(CmpOp::Ne, left, right)
    }
    Lt => {
      ignore(self.advance())
      let right = self.parse_add()
      Compare(CmpOp::Lt, left, right)
    }
    Gt => {
      ignore(self.advance())
      let right = self.parse_add()
      Compare(CmpOp::Gt, left, right)
    }
    Le => {
      ignore(self.advance())
      let right = self.parse_add()
      Compare(CmpOp::Le, left, right)
    }
    Ge => {
      ignore(self.advance())
      let right = self.parse_add()
      Compare(CmpOp::Ge, left, right)
    }
    _ => left
  }
}

///|
fn Parser::parse_add(self : Parser) -> Filter raise JqError {
  let mut left = self.parse_mul()
  while (match self.peek() {
          Plus | Minus => true
          _ => false
        }) {
    let op = self.advance()
    let right = self.parse_mul()
    left = match op {
      Plus => Arith(ArithOp::Add, left, right)
      _ => Arith(ArithOp::Sub, left, right)
    }
  }
  left
}

///|
fn Parser::parse_mul(self : Parser) -> Filter raise JqError {
  let mut left = self.parse_unary()
  while (match self.peek() {
          Star | Slash | Percent => true
          _ => false
        }) {
    let op = self.advance()
    let right = self.parse_unary()
    left = match op {
      Star => Arith(ArithOp::Mul, left, right)
      Slash => Arith(ArithOp::Div, left, right)
      _ => Arith(ArithOp::Mod, left, right)
    }
  }
  left
}

///|
fn Parser::parse_unary(self : Parser) -> Filter raise JqError {
  if self.peek() == Minus {
    ignore(self.advance())
    let expr = self.parse_postfix()
    Neg(expr)
  } else {
    self.parse_postfix()
  }
}

///|
fn Parser::parse_postfix(self : Parser) -> Filter raise JqError {
  let mut expr = self.parse_primary()
  while (match self.peek() {
          Dot | LBracket | Question => true
          _ => false
        }) {
    match self.peek() {
      Dot => {
        ignore(self.advance())
        match self.peek() {
          Ident(name) => {
            ignore(self.advance())
            expr = Pipe(expr, Field(name))
          }
          Str(name) => {
            ignore(self.advance())
            expr = Pipe(expr, Field(name))
          }
          _ => raise JqError("expected field name after '.'")
        }
      }
      LBracket => {
        ignore(self.advance())
        let suffix = self.parse_bracket_suffix()
        match suffix {
          DynIndex(idx_expr) => expr = PostfixDynIndex(expr, idx_expr)
          DynSlice(_, start_expr, end_expr) =>
            expr = DynSlice(expr, start_expr, end_expr)
          _ => expr = Pipe(expr, suffix)
        }
      }
      Question => {
        ignore(self.advance())
        expr = Try(expr)
      }
      _ => break
    }
  }
  expr
}

///|
fn Parser::parse_signed_int(self : Parser) -> Int raise JqError {
  if self.peek() == Minus {
    ignore(self.advance())
    match self.peek() {
      Num(n) => {
        ignore(self.advance())
        -n.to_int()
      }
      _ => raise JqError("expected number after '-'")
    }
  } else {
    match self.peek() {
      Num(n) => {
        ignore(self.advance())
        n.to_int()
      }
      _ => raise JqError("expected number")
    }
  }
}

///|
/// Parse a signed number as a Double, preserving float precision.
fn Parser::parse_signed_num(self : Parser) -> Double raise JqError {
  if self.peek() == Minus {
    ignore(self.advance())
    match self.peek() {
      Num(n) => {
        ignore(self.advance())
        -n
      }
      _ => raise JqError("expected number after '-'")
    }
  } else {
    match self.peek() {
      Num(n) => {
        ignore(self.advance())
        n
      }
      _ => raise JqError("expected number")
    }
  }
}

///|
fn Parser::parse_bracket_suffix(self : Parser) -> Filter raise JqError {
  match self.peek() {
    RBracket => {
      ignore(self.advance())
      Iterate
    }
    Str(s) => {
      ignore(self.advance())
      self.expect(RBracket)
      Field(s)
    }
    Variable(_) => {
      let expr = self.parse_pipe()
      self.expect(RBracket)
      DynIndex(expr)
    }
    Colon => {
      // .[:-2] or .[:expr]
      ignore(self.advance())
      match self.peek() {
        Num(_) | Minus => {
          let saved = self.pos
          let num = self.parse_signed_num()
          let end_is_float = num != num.floor()
          if self.peek() == RBracket {
            self.expect(RBracket)
            if end_is_float {
              DynSlice(Identity, None, Some(Literal(Json::number(num))))
            } else {
              Slice(None, Some(num.to_int()))
            }
          } else {
            // Not a simple number, reparse as expression
            self.pos = saved
            let end_expr = self.parse_pipe()
            self.expect(RBracket)
            DynSlice(Identity, None, Some(end_expr))
          }
        }
        _ => {
          let end_expr = self.parse_pipe()
          self.expect(RBracket)
          DynSlice(Identity, None, Some(end_expr))
        }
      }
    }
    Num(_) | Minus => {
      let saved = self.pos
      let num = self.parse_signed_num()
      let is_float = num != num.floor()
      match self.peek() {
        Colon => {
          // .[n:end] or .[n:] or .[n:expr]
          ignore(self.advance())
          match self.peek() {
            RBracket => {
              ignore(self.advance())
              if is_float {
                DynSlice(Identity, Some(Literal(Json::number(num))), None)
              } else {
                Slice(Some(num.to_int()), None)
              }
            }
            Num(_) | Minus => {
              let saved2 = self.pos
              let end_num = self.parse_signed_num()
              let end_is_float = end_num != end_num.floor()
              if self.peek() == RBracket {
                self.expect(RBracket)
                if is_float || end_is_float {
                  DynSlice(
                    Identity,
                    Some(Literal(Json::number(num))),
                    Some(Literal(Json::number(end_num))),
                  )
                } else {
                  Slice(Some(num.to_int()), Some(end_num.to_int()))
                }
              } else {
                self.pos = saved2
                let end_expr = self.parse_pipe()
                self.expect(RBracket)
                DynSlice(
                  Identity,
                  Some(Literal(Json::number(num))),
                  Some(end_expr),
                )
              }
            }
            _ => {
              let end_expr = self.parse_pipe()
              self.expect(RBracket)
              DynSlice(
                Identity,
                Some(Literal(Json::number(num))),
                Some(end_expr),
              )
            }
          }
        }
        Comma =>
          if is_float {
            // Float index — fall through to expression parsing
            self.pos = saved
            let expr = self.parse_pipe()
            self.expect(RBracket)
            DynIndex(expr)
          } else {
            // .[n,m,...] multi-index
            let n = num.to_int()
            let indices : Array[Int] = [n]
            while self.peek() == Comma {
              ignore(self.advance())
              indices.push(self.parse_signed_int())
            }
            self.expect(RBracket)
            let mut result : Filter = Index(indices[0])
            for i = 1; i < indices.length(); i = i + 1 {
              result = Comma(result, Index(indices[i]))
            }
            result
          }
        _ => {
          // .[n]
          self.expect(RBracket)
          if is_float {
            DynIndex(Literal(Json::number(num)))
          } else {
            Index(num.to_int())
          }
        }
      }
    }
    _ => {
      let expr = self.parse_pipe()
      if self.peek() == Colon {
        // .[expr:end] dynamic slice
        ignore(self.advance())
        if self.peek() == RBracket {
          ignore(self.advance())
          DynSlice(Identity, Some(expr), None)
        } else {
          let end_expr = self.parse_pipe()
          self.expect(RBracket)
          DynSlice(Identity, Some(expr), Some(end_expr))
        }
      } else {
        self.expect(RBracket)
        DynIndex(expr)
      }
    }
  }
}

///|
fn Parser::parse_primary(self : Parser) -> Filter raise JqError {
  match self.peek() {
    Dot => {
      ignore(self.advance())
      match self.peek() {
        Ident(name) => {
          ignore(self.advance())
          Field(name)
        }
        Str(name) => {
          ignore(self.advance())
          Field(name)
        }
        LBracket => {
          ignore(self.advance())
          self.parse_bracket_suffix()
        }
        _ => Identity
      }
    }
    DotDot => {
      ignore(self.advance())
      RecurseOp
    }
    Num(n) => {
      ignore(self.advance())
      Literal(Json::number(n))
    }
    Str(s) => {
      ignore(self.advance())
      Literal(Json::string(s))
    }
    StrInterp(parts) => {
      ignore(self.advance())
      let interp_parts : Array[(String, Filter?)] = []
      for part in parts {
        match part {
          Lit(s) => interp_parts.push((s, None))
          ExprSource(src) => {
            let expr_tokens = tokenize(src)
            let expr_filter = parse(expr_tokens)
            interp_parts.push(("", Some(expr_filter)))
          }
        }
      }
      StringInterp(interp_parts)
    }
    KwTrue => {
      ignore(self.advance())
      Literal(Json::boolean(true))
    }
    KwFalse => {
      ignore(self.advance())
      Literal(Json::boolean(false))
    }
    KwNull => {
      ignore(self.advance())
      Literal(Json::null())
    }
    Variable(name) => {
      ignore(self.advance())
      FuncCall("$" + name, [])
    }
    LParen => {
      ignore(self.advance())
      let expr = self.parse_pipe()
      self.expect(RParen)
      Paren(expr)
    }
    LBracket => {
      ignore(self.advance())
      if self.peek() == RBracket {
        ignore(self.advance())
        ArrayConstruct(None)
      } else {
        let expr = self.parse_pipe()
        self.expect(RBracket)
        ArrayConstruct(Some(expr))
      }
    }
    LBrace => {
      ignore(self.advance())
      self.parse_object_construct()
    }
    KwIf => {
      ignore(self.advance())
      self.parse_if()
    }
    KwTry => {
      ignore(self.advance())
      let expr = self.parse_postfix()
      if self.peek() == KwCatch {
        ignore(self.advance())
        let handler = self.parse_postfix()
        TryCatch(expr, handler)
      } else {
        Try(expr)
      }
    }
    KwReduce => {
      ignore(self.advance())
      self.parse_reduce()
    }
    KwForeach => {
      ignore(self.advance())
      self.parse_foreach()
    }
    KwDef => {
      ignore(self.advance())
      self.parse_def()
    }
    KwLabel => {
      ignore(self.advance())
      let var_tok = self.advance()
      let var_name = match var_tok {
        Variable(n) => n
        _ => raise JqError("expected variable after 'label'")
      }
      self.expect(Pipe)
      let body = self.parse_pipe()
      Label("$" + var_name, body)
    }
    KwBreak => {
      ignore(self.advance())
      let var_tok = self.advance()
      let var_name = match var_tok {
        Variable(n) => n
        _ => raise JqError("expected variable after 'break'")
      }
      Break("$" + var_name)
    }
    KwNot => {
      ignore(self.advance())
      FuncCall("not", [])
    }
    Ident(name) => {
      ignore(self.advance())
      if self.peek() == LParen {
        ignore(self.advance())
        let args = self.parse_func_args()
        FuncCall(name, args)
      } else {
        FuncCall(name, [])
      }
    }
    Format(name) => {
      ignore(self.advance())
      FuncCall("@" + name, [])
    }
    Minus => {
      ignore(self.advance())
      let expr = self.parse_postfix()
      Neg(expr)
    }
    _ => {
      let tok = self.peek()
      raise JqError("unexpected token: " + tok.to_string())
    }
  }
}

///|
fn Parser::parse_func_args(self : Parser) -> Array[Filter] raise JqError {
  let args : Array[Filter] = []
  if self.peek() == RParen {
    ignore(self.advance())
    return args
  }
  args.push(self.parse_pipe())
  while self.peek() == Semicolon {
    ignore(self.advance())
    args.push(self.parse_pipe())
  }
  self.expect(RParen)
  args
}

///|
fn Parser::parse_object_construct(self : Parser) -> Filter raise JqError {
  let fields : Array[(ObjKey, Filter)] = []
  if self.peek() == RBrace {
    ignore(self.advance())
    return ObjectConstruct(fields)
  }
  while true {
    let (key, value) = self.parse_object_field()
    fields.push((key, value))
    match self.peek() {
      Comma => ignore(self.advance())
      RBrace => {
        ignore(self.advance())
        break
      }
      _ => raise JqError("expected ',' or '}' in object")
    }
  }
  ObjectConstruct(fields)
}

///|
/// Parse pipe-separated bindings without consuming commas.
/// Used for object values where `{x: a | b}` means `{x: (a | b)}`.
fn Parser::parse_object_value(self : Parser) -> Filter raise JqError {
  let mut left = self.parse_binding()
  while self.peek() == Pipe {
    ignore(self.advance())
    let right = self.parse_binding()
    left = Pipe(left, right)
  }
  left
}

///|
fn Parser::parse_object_field(self : Parser) -> (ObjKey, Filter) raise JqError {
  match self.peek() {
    Ident(name) => {
      ignore(self.advance())
      if self.peek() == Colon {
        ignore(self.advance())
        let value = self.parse_object_value()
        (StrKey(name), value)
      } else {
        (StrKey(name), Field(name))
      }
    }
    Str(s) => {
      ignore(self.advance())
      if self.peek() == Colon {
        ignore(self.advance())
        let value = self.parse_object_value()
        (StrKey(s), value)
      } else {
        // Shorthand: {"key"} means {"key": .["key"]}
        (StrKey(s), DynIndex(Literal(Json::string(s))))
      }
    }
    StrInterp(parts) => {
      ignore(self.advance())
      // Build the StringInterp filter for the key
      let interp_parts : Array[(String, Filter?)] = []
      for part in parts {
        match part {
          Lit(s) => interp_parts.push((s, None))
          ExprSource(src) => {
            let expr_tokens = tokenize(src)
            let expr_filter = parse(expr_tokens)
            interp_parts.push(("", Some(expr_filter)))
          }
        }
      }
      let key_filter = StringInterp(interp_parts)
      if self.peek() == Colon {
        ignore(self.advance())
        let value = self.parse_object_value()
        (ExprKey(key_filter), value)
      } else {
        // Shorthand: {"interp_key"} means {"interp_key": .["interp_key"]}
        (ExprKey(key_filter), DynIndex(key_filter))
      }
    }
    LParen => {
      ignore(self.advance())
      let key_expr = self.parse_pipe()
      self.expect(RParen)
      self.expect(Colon)
      let value = self.parse_object_value()
      (ExprKey(key_expr), value)
    }
    Variable(name) => {
      ignore(self.advance())
      if self.peek() == Colon {
        ignore(self.advance())
        let value = self.parse_object_value()
        (StrKey(name), value)
      } else {
        (StrKey(name), FuncCall("$" + name, []))
      }
    }
    _ => raise JqError("expected object key")
  }
}

///|
fn Parser::parse_if(self : Parser) -> Filter raise JqError {
  let cond = self.parse_pipe()
  self.expect(KwThen)
  let then_branch = self.parse_pipe()
  let else_branch = match self.peek() {
    KwElif => {
      ignore(self.advance())
      self.parse_if()
    }
    KwElse => {
      ignore(self.advance())
      let e = self.parse_pipe()
      self.expect(KwEnd)
      e
    }
    KwEnd => {
      ignore(self.advance())
      Identity
    }
    _ => raise JqError("expected 'elif', 'else', or 'end' in if expression")
  }
  IfThenElse(cond, then_branch, else_branch)
}

///|
fn Parser::parse_reduce(self : Parser) -> Filter raise JqError {
  let iter_expr = self.parse_or()
  self.expect(KwAs)
  let pat = self.parse_pattern()
  self.expect(LParen)
  let init = self.parse_pipe()
  self.expect(Semicolon)
  let update = self.parse_pipe()
  self.expect(RParen)
  Reduce(iter_expr, pat, init, update)
}

///|
fn Parser::parse_foreach(self : Parser) -> Filter raise JqError {
  let iter_expr = self.parse_mul()
  self.expect(KwAs)
  let pat = self.parse_pattern()
  self.expect(LParen)
  let init = self.parse_pipe()
  self.expect(Semicolon)
  let update = self.parse_pipe()
  let extract : Filter? = if self.peek() == Semicolon {
    ignore(self.advance())
    Some(self.parse_pipe())
  } else {
    None
  }
  self.expect(RParen)
  Foreach(iter_expr, pat, init, update, extract)
}

///|
fn Parser::parse_def(self : Parser) -> Filter raise JqError {
  let name_tok = self.advance()
  let name = match name_tok {
    Ident(n) => n
    _ => raise JqError("expected function name after 'def'")
  }
  let params : Array[String] = []
  if self.peek() == LParen {
    ignore(self.advance())
    if self.peek() != RParen {
      while true {
        let p = self.advance()
        match p {
          Ident(pname) => params.push(pname)
          _ => raise JqError("expected parameter name")
        }
        match self.peek() {
          Semicolon => ignore(self.advance())
          RParen => break
          _ => raise JqError("expected ';' or ')' in def params")
        }
      }
    }
    self.expect(RParen)
  }
  self.expect(Colon)
  let body = self.parse_pipe()
  self.expect(Semicolon)
  let rest = self.parse_pipe()
  FuncDef(name, params, body, rest)
}