///|
priv struct WgslIrExprTokenDepth {
  mut paren : Int32
  mut angle : Int32
  mut bracket : Int32
  mut brace : Int32
}

///|
fn WgslIrExprTokenDepth::WgslIrExprTokenDepth() -> WgslIrExprTokenDepth {
  { paren: 0, angle: 0, bracket: 0, brace: 0, }
}

///|
fn WgslIrExprTokenDepth::at_top(self : WgslIrExprTokenDepth) -> Bool {
  self.paren == 0 && self.angle == 0 && self.bracket == 0 && self.brace == 0
}

///|
fn WgslIrExprTokenDepth::observe(
  self : WgslIrExprTokenDepth,
  token : @ast.WgslExprToken,
) -> Unit {
  match token.kind() {
    Operator(ShiftRight) => {
      if self.angle >= 2 {
        self.angle = self.angle - 2
      } else {
        self.angle = 0
      }
      return
    }
    _ => ()
  }
  match token.kind() {
    Punctuation(40) => self.paren = self.paren + 1
    Punctuation(41) => self.paren = self.paren - 1
    Punctuation(60) => self.angle = self.angle + 1
    Punctuation(62) => self.angle = self.angle - 1
    Punctuation(91) => self.bracket = self.bracket + 1
    Punctuation(93) => self.bracket = self.bracket - 1
    Punctuation(123) => self.brace = self.brace + 1
    Punctuation(125) => self.brace = self.brace - 1
    _ => ()
  }
}

///|
fn wgsl_ir_expr_tokens_assignment(
  tokens : Array[@ast.WgslExprToken],
) -> (String, Array[@ast.WgslExprToken], Array[@ast.WgslExprToken])? {
  let depth = WgslIrExprTokenDepth()
  for i in 0..
          return Some(
            (
              op,
              wgsl_ir_expr_token_slice(tokens, 0, i),
              wgsl_ir_expr_token_slice(tokens, i + consumed, tokens.length()),
            ),
          )
        None => ()
      }
    }
    depth.observe(tokens[i])
  }
  None
}

///|
fn wgsl_ir_expr_assignment_at(
  tokens : Array[@ast.WgslExprToken],
  index : Int32,
) -> (String, Int32)? {
  guard index >= 0 && index < tokens.length() else { return None }
  match tokens[index].kind() {
    Operator(Unknown(op)) if wgsl_ir_is_compound_assignment_operator(op) =>
      Some((op, 1))
    Operator(ShiftLeft) if index + 1 < tokens.length() &&
      wgsl_ir_expr_token_is_punctuation(tokens[index + 1], 61) =>
      Some(("<<=", 2))
    Operator(ShiftRight) if index + 1 < tokens.length() &&
      wgsl_ir_expr_token_is_punctuation(tokens[index + 1], 61) =>
      Some((">>=", 2))
    Punctuation(61) => Some(("=", 1))
    _ => None
  }
}

///|
fn wgsl_ir_is_compound_assignment_operator(op : String) -> Bool {
  op == "+=" ||
  op == "-=" ||
  op == "*=" ||
  op == "/=" ||
  op == "%=" ||
  op == "&=" ||
  op == "|=" ||
  op == "^=" ||
  op == "<<=" ||
  op == ">>="
}

///|
fn wgsl_ir_expr_tokens_update_operator(
  tokens : Array[@ast.WgslExprToken],
) -> (Array[@ast.WgslExprToken], Bool)? {
  guard tokens.length() > 1 else { return None }
  let last = tokens[tokens.length() - 1]
  match last.kind() {
    Operator(Unknown("++")) =>
      Some((wgsl_ir_expr_token_slice(tokens, 0, tokens.length() - 1), true))
    Operator(Unknown("--")) =>
      Some((wgsl_ir_expr_token_slice(tokens, 0, tokens.length() - 1), false))
    _ => None
  }
}

///|
fn wgsl_ir_expr_token_slice(
  tokens : Array[@ast.WgslExprToken],
  start : Int32,
  end_ : Int32,
) -> Array[@ast.WgslExprToken] {
  let out : Array[@ast.WgslExprToken] = []
  for i in start.. Bool {
  match token.kind() {
    Punctuation(value) => value == code
    _ => false
  }
}

///|
fn wgsl_ir_expr_node_from_tokens(
  tokens : Array[@ast.WgslExprToken],
) -> @ast.WgslExprNode raise WgslIrLowerError {
  guard tokens.length() > 0 else {
    raise UnsupportedExpression("empty expression token list")
  }
  let parser = WgslIrExprTokenParser::{ tokens, index: 0, failed: false, }
  match parser.parse_expression(0) {
    Some(node) =>
      if parser.failed || !parser.at_end() {
        raise UnsupportedExpression("failed to parse expression token list")
      } else {
        node
      }
    None => raise UnsupportedExpression("failed to parse expression token list")
  }
}

///|
fn WgslIrExprTokenParser::at_end(self : WgslIrExprTokenParser) -> Bool {
  self.index >= self.tokens.length()
}

///|
fn WgslIrExprTokenParser::peek(
  self : WgslIrExprTokenParser,
) -> @ast.WgslExprToken? {
  self.tokens.get(self.index)
}

///|
fn WgslIrExprTokenParser::consume_punctuation(
  self : WgslIrExprTokenParser,
  code : Int32,
) -> Bool {
  match self.peek() {
    Some(token) if wgsl_ir_expr_token_is_punctuation(token, code) => {
      self.index = self.index + 1
      true
    }
    _ => false
  }
}

///|
fn wgsl_ir_expr_make_node(
  kind : @ast.WgslExprNodeKind,
  start : Int32,
  end_ : Int32,
) -> @ast.WgslExprNode {
  WgslExprNode(kind, start, end_)
}

///|
fn WgslIrExprTokenParser::parse_expression(
  self : WgslIrExprTokenParser,
  min_precedence : Int32,
) -> @ast.WgslExprNode? {
  let prefix = match self.parse_prefix() {
    Some(value) => value
    None => return None
  }
  let mut lhs = self.parse_postfix(prefix)
  while true {
    let (op, consumed) = match self.binary_operator_at() {
      Some(value) => value
      None => break
    }
    let precedence = op.binary_precedence()
    if precedence < min_precedence {
      break
    }
    self.index = self.index + consumed
    let rhs = match self.parse_expression(precedence + 1) {
      Some(value) => value
      None => {
        self.failed = true
        break
      }
    }
    lhs = wgsl_ir_expr_make_node(Binary(op, lhs, rhs), lhs.start(), rhs.end())
  }
  Some(lhs)
}

///|
fn WgslIrExprTokenParser::parse_prefix(
  self : WgslIrExprTokenParser,
) -> @ast.WgslExprNode? {
  match self.peek() {
    Some(token) =>
      match token.kind() {
        Identifier => {
          self.index = self.index + 1
          Some(
            wgsl_ir_expr_make_node(
              Identifier(WgslIdent(token.text(), token.start(), token.end())),
              token.start(),
              token.end(),
            ),
          )
        }
        BoolLiteral(_) | NumericLiteral(_) => {
          self.index = self.index + 1
          Some(
            wgsl_ir_expr_make_node(Literal(token), token.start(), token.end()),
          )
        }
        Punctuation(33)
        | Punctuation(45)
        | Punctuation(43)
        | Punctuation(42)
        | Punctuation(38)
        | Punctuation(126) => {
          self.index = self.index + 1
          let rhs = match self.parse_prefix() {
            Some(node) => node
            None => {
              self.failed = true
              return None
            }
          }
          Some(
            wgsl_ir_expr_make_node(
              Unary(@ast.WgslExprOperator::from_text(token.text()), rhs),
              token.start(),
              rhs.end(),
            ),
          )
        }
        Punctuation(40) => {
          let start = token.start()
          self.index = self.index + 1
          let inner = self.parse_sequence_until_any([41])
          let end_ = match self.peek() {
            Some(close) if wgsl_ir_expr_token_is_punctuation(close, 41) => {
              self.index = self.index + 1
              close.end()
            }
            _ => inner.end()
          }
          Some(wgsl_ir_expr_make_node(Group(inner), start, end_))
        }
        _ => {
          self.index = self.index + 1
          self.failed = true
          None
        }
      }
    None => None
  }
}

///|
fn WgslIrExprTokenParser::parse_postfix(
  self : WgslIrExprTokenParser,
  base : @ast.WgslExprNode,
) -> @ast.WgslExprNode {
  let mut node = base
  while true {
    match self.peek() {
      Some(token) if wgsl_ir_expr_token_is_punctuation(token, 40) => {
        self.index = self.index + 1
        let args : Array[@ast.WgslExprNode] = []
        while !self.at_end() {
          match self.peek() {
            Some(close) if wgsl_ir_expr_token_is_punctuation(close, 41) => {
              self.index = self.index + 1
              node = wgsl_ir_expr_make_node(
                Call(node, args),
                node.start(),
                close.end(),
              )
              break
            }
            _ => {
              args.push(self.parse_sequence_until_any([44, 41]))
              if self.consume_punctuation(44) {
                continue
              }
            }
          }
        }
        continue
      }
      Some(token) if wgsl_ir_expr_token_is_punctuation(token, 46) => {
        self.index = self.index + 1
        match self.peek() {
          Some(member_token) if member_token.kind() is Identifier => {
            self.index = self.index + 1
            node = wgsl_ir_expr_make_node(
              Member(
                node,
                WgslIdent(
                  member_token.text(),
                  member_token.start(),
                  member_token.end(),
                ),
              ),
              node.start(),
              member_token.end(),
            )
            continue
          }
          _ => return node
        }
      }
      Some(token) if wgsl_ir_expr_token_is_punctuation(token, 91) => {
        self.index = self.index + 1
        let index = self.parse_sequence_until_any([93])
        let end_ = match self.peek() {
          Some(close) if wgsl_ir_expr_token_is_punctuation(close, 93) => {
            self.index = self.index + 1
            close.end()
          }
          _ => index.end()
        }
        node = wgsl_ir_expr_make_node(Index(node, index), node.start(), end_)
        continue
      }
      _ => return node
    }
  }
  node
}

///|
fn WgslIrExprTokenParser::parse_sequence_until_any(
  self : WgslIrExprTokenParser,
  stop_codes : Array[Int32],
) -> @ast.WgslExprNode {
  let nodes : Array[@ast.WgslExprNode] = []
  while !self.at_end() {
    match self.peek() {
      Some(token) if token.kind() is Punctuation(code) &&
        stop_codes.contains(code) => break
      _ => ()
    }
    match self.parse_expression(0) {
      Some(node) => nodes.push(node)
      None => {
        self.failed = true
        break
      }
    }
    if self.consume_punctuation(44) {
      continue
    }
    match self.peek() {
      Some(token) if token.kind() is Punctuation(code) &&
        stop_codes.contains(code) => break
      _ => ()
    }
  }
  match nodes {
    [single] => single
    [] => wgsl_ir_expr_make_node(Sequence([]), 0, 0)
    _ =>
      wgsl_ir_expr_make_node(
        Sequence(nodes),
        nodes[0].start(),
        nodes[nodes.length() - 1].end(),
      )
  }
}

///|
fn WgslIrExprTokenParser::binary_operator_at(
  self : WgslIrExprTokenParser,
) -> (@ast.WgslExprOperator, Int32)? {
  guard self.index < self.tokens.length() else { return None }
  match self.tokens[self.index].kind() {
    Operator(op) => Some((op, 1))
    Punctuation(124) => Some((BitOr, 1))
    Punctuation(38) => Some((BitAnd, 1))
    Punctuation(60) => Some((Less, 1))
    Punctuation(62) => Some((Greater, 1))
    Punctuation(43) => Some((Add, 1))
    Punctuation(45) => Some((Subtract, 1))
    Punctuation(42) => Some((Multiply, 1))
    Punctuation(47) => Some((Divide, 1))
    Punctuation(37) => Some((Modulo, 1))
    Punctuation(94) => Some((BitXor, 1))
    _ => None
  }
}