///|
/// Expression printing - declaring function for recursion
fn print_expr(expr : @syntax.Expr, logger : StringBuilder) -> Unit {
  match expr {
    Constant(c~, ..) => print_constant(c, logger)
    Ident(id~, ..) => print_var(id, logger)
    Infix(op~, lhs~, rhs~, ..) => {
      print_expr(lhs, logger)
      logger.write_string(" ")
      print_var(op, logger)
      logger.write_string(" ")
      print_expr(rhs, logger)
    }
    Let(pattern~, expr~, body~, ..) => {
      logger.write_string("let ")
      print_pattern(pattern, logger)
      logger.write_string(" = ")
      print_expr(expr, logger)
      logger.write_string("; ")
      print_expr(body, logger)
    }
    Apply(func~, args~, attr~, ..) => {
      print_expr(func, logger)
      match attr {
        Question => logger.write_string("?")
        Exclamation => logger.write_string("!")
        NoAttr => ()
      }
      // Special case: if we have try operator (? or !) with empty args on an identifier,
      // don't print empty parentheses since this is likely a try operator on a value
      let should_print_parens = match (func, attr, args.is_empty()) {
        (Ident(..), Question, true) | (Ident(..), Exclamation, true) => false
        _ => true
      }
      if should_print_parens {
        logger.write_string("(")
        print_list(args, print_argument, ", ", logger)
        logger.write_string(")")
      }
    }
    Unary(op~, expr~, ..) => {
      print_var(op, logger)
      print_expr(expr, logger)
    }
    Array(exprs~, ..) => {
      logger.write_string("[")
      print_list(exprs, print_expr, ", ", logger)
      logger.write_string("]")
    }
    ArraySpread(elems~, ..) => {
      logger.write_string("[")
      print_list(elems, print_spreadable_elem, ", ", logger)
      logger.write_string("]")
    }
    ArrayGet(array~, index~, ..) => {
      print_expr(array, logger)
      logger.write_string("[")
      print_expr(index, logger)
      logger.write_string("]")
    }
    ArrayGetSlice(array~, start_index~, end_index~, ..) => {
      print_expr(array, logger)
      logger.write_string("[")
      match start_index {
        Some(start) => print_expr(start, logger)
        None => ()
      }
      logger.write_string(":")
      match end_index {
        Some(end) => print_expr(end, logger)
        None => ()
      }
      logger.write_string("]")
    }
    ArraySet(array~, index~, value~, ..) => {
      print_expr(array, logger)
      logger.write_string("[")
      print_expr(index, logger)
      logger.write_string("] = ")
      print_expr(value, logger)
    }
    Tuple(exprs~, ..) => {
      logger.write_string("(")
      print_list(exprs, print_expr, ", ", logger)
      logger.write_string(")")
    }
    Record(type_name~, fields~, trailing~, ..) => {
      match type_name {
        Some(tn) => {
          print_type_name(tn, logger)
          logger.write_string("::")
        }
        None => ()
      }
      logger.write_string("{")
      print_list(fields, print_field_def, ", ", logger)
      match trailing {
        Comma => logger.write_string(",")
        Semi => logger.write_string(";")
        None => ()
      }
      logger.write_string("}")
    }
    Field(record~, accessor~, ..) => {
      print_expr(record, logger)
      print_accessor(accessor, logger)
    }
    If(cond~, ifso~, ifnot~, ..) => {
      logger.write_string("if ")
      print_expr(cond, logger)
      logger.write_string(" { ")
      print_expr(ifso, logger)
      logger.write_string(" }")
      match ifnot {
        Some(else_expr) => {
          logger.write_string(" else { ")
          print_expr(else_expr, logger)
          logger.write_string(" }")
        }
        None => ()
      }
    }
    Match(expr~, cases~, ..) => {
      logger.write_string("match ")
      print_expr(expr, logger)
      logger.write_string(" { ")
      for case in cases {
        print_pattern(case.pattern, logger)
        match case.guard_ {
          Some(guard_expr) => {
            logger.write_string(" if ")
            print_expr(guard_expr, logger)
          }
          None => ()
        }
        logger.write_string(" => ")
        print_expr(case.body, logger)
        logger.write_string("; ")
      }
      logger.write_string("}")
    }
    Sequence(exprs~, last_expr~, ..) => {
      for expr_item in exprs {
        print_expr(expr_item, logger)
        logger.write_string("; ")
      }
      print_expr(last_expr, logger)
    }
    Unit(faked~, ..) => if not(faked) { logger.write_string("()") }
    Hole(kind=@syntax.Hole::Synthesized | Incomplete, ..) =>
      logger.write_string("_")
    Hole(kind=@syntax.Hole::Todo, ..) => logger.write_string("...")
    Group(expr~, group~, ..) =>
      match group {
        Brace => {
          logger.write_string("{ ")
          print_expr(expr, logger)
          logger.write_string(" }")
        }
        Paren => {
          logger.write_string("(")
          print_expr(expr, logger)
          logger.write_string(")")
        }
      }
    ArrayAugmentedSet(op~, array~, index~, value~, ..) => {
      print_expr(array, logger)
      logger.write_string("[")
      print_expr(index, logger)
      logger.write_string("] ")
      print_var(op, logger)
      logger.write_string("= ")
      print_expr(value, logger)
    }
    MultilineString(elems~, ..) => print_multiline_string(elems, logger)
    Interp(elems~, ..) => {
      logger.write_string("\"")
      for elem in elems {
        match elem {
          Literal(repr~, ..) => logger.write_string(repr)
          Expr(expr~, ..) => {
            logger.write_string("\\{")
            print_expr(expr, logger)
            logger.write_string("}")
          }
          Source(_) => logger.write_string("\\{...}")
        }
      }
      logger.write_string("\"")
    }
    Constraint(expr~, ty~, ..) => {
      logger.write_string("(")
      print_expr(expr, logger)
      logger.write_string(" : ")
      print_type(ty, logger)
      logger.write_string(")")
    }
    Constr(constr~, ..) => print_constructor(constr, logger)
    While(loop_cond~, loop_body~, while_else~, label~, ..) => {
      match label {
        Some(l) => {
          print_label(l, logger)
          logger.write_string("~: ")
        }
        None => ()
      }
      logger.write_string("while ")
      print_expr(loop_cond, logger)
      logger.write_string(" { ")
      print_expr(loop_body, logger)
      logger.write_string(" }")
      match while_else {
        Some(else_expr) => {
          logger.write_string(" else { ")
          print_expr(else_expr, logger)
          logger.write_string(" }")
        }
        None => ()
      }
    }
    Function(func~, ..) => print_func(func, logger)
    Guard(cond~, otherwise~, body~, ..) => {
      logger.write_string("guard ")
      print_expr(cond, logger)
      match otherwise {
        Some(else_expr) => {
          logger.write_string(" else { ")
          print_expr(else_expr, logger)
          logger.write_string(" }; ")
          print_expr(body, logger)
        }
        None => {
          // Guard statement without else clause - needs semicolon for statement separation
          logger.write_string("; ")
          print_expr(body, logger)
        }
      }
    }
    Is(expr~, pat~, ..) => {
      print_expr(expr, logger)
      logger.write_string(" is ")
      logger.write_string("(")
      print_pattern(pat, logger)
      logger.write_string(")")
    }
    Defer(expr~, body~, ..) => {
      logger.write_string("defer ")
      print_expr(expr, logger)
      logger.write_string("; ")
      print_expr(body, logger)
    }
    LetFn(name~, func~, body~, ..) =>
      // Print function declaration syntax: fn name(params) { ... }
      match func {
        Lambda(parameters~, body=func_body, return_type~, error_type~, ..) => {
          logger.write_string("fn ")
          print_binder(name, logger)
          logger.write_string("(")
          print_list(parameters, print_parameter, ", ", logger)
          logger.write_string(")")
          match return_type {
            Some(ret_ty) => {
              logger.write_string(" -> ")
              print_type(ret_ty, logger)
            }
            None => ()
          }
          print_error_type(error_type, logger)
          logger.write_string(" { ")
          print_expr(func_body, logger)
          logger.write_string(" }; ")
          print_expr(body, logger)
        }
        Match(cases~, ..) => {
          logger.write_string("fn ")
          print_binder(name, logger)
          logger.write_string(" { ")
          for case in cases {
            print_list(case.patterns, print_pattern, ", ", logger)
            match case.guard_ {
              Some(guard_expr) => {
                logger.write_string(" if ")
                print_expr(guard_expr, logger)
              }
              None => ()
            }
            logger.write_string(" => ")
            print_expr(case.body, logger)
            logger.write_string("; ")
          }
          logger.write_string("}; ")
          print_expr(body, logger)
        }
      }
    LetRec(bindings~, body~, ..) => {
      logger.write_string("letrec ")
      let mut first = true
      for binding in bindings {
        if not(first) {
          logger.write_string(" and ")
        }
        first = false
        print_binder(binding.0, logger)
        logger.write_string(" = ")
        print_func(binding.1, logger)
      }
      logger.write_string("; ")
      print_expr(body, logger)
    }
    LetAnd(bindings~, body~, ..) => {
      logger.write_string("letrec ")
      let mut first = true
      for binding in bindings {
        if not(first) {
          logger.write_string(" and ")
        }
        first = false
        print_binder(binding.0, logger)
        match binding.1 {
          Some(t) => {
            logger.write_string(" : ")
            print_type(t, logger)
          }
          None => ()
        }
        logger.write_string(" = ")
        print_func(binding.2, logger)
      }
      logger.write_string("; ")
      print_expr(body, logger)
    }
    RecordUpdate(type_name~, record~, fields~, ..) => {
      match type_name {
        Some(tn) => {
          print_type_name(tn, logger)
          logger.write_string("::")
        }
        None => ()
      }
      logger.write_string("{ ")
      logger.write_string("..")
      print_expr(record, logger)
      logger.write_string(", ")
      print_list(fields, print_field_def, ", ", logger)
      logger.write_string(" }")
    }
    Method(type_name~, method_name~, ..) => {
      print_type_name(type_name, logger)
      logger.write_string("::")
      print_label(method_name, logger)
    }
    DotApply(self~, method_name~, args~, return_self~, attr~, ..) => {
      print_expr(self, logger)
      if return_self {
        logger.write_string("..")
      } else {
        logger.write_string(".")
      }
      print_label(method_name, logger)
      match attr {
        Question => logger.write_string("?")
        Exclamation => logger.write_string("!")
        NoAttr => ()
      }
      logger.write_string("(")
      print_list(args, print_argument, ", ", logger)
      logger.write_string(")")
    }
    As(expr~, trait_~, ..) => {
      print_expr(expr, logger)
      logger.write_string(" as ")
      print_type_name(trait_, logger)
    }
    Mutate(record~, accessor~, field~, augmented_by~, ..) => {
      print_expr(record, logger)
      print_accessor(accessor, logger)
      logger.write_string(" ")
      match augmented_by {
        Some(op) => {
          print_var(op, logger)
          logger.write_string("= ")
        }
        None => logger.write_string("= ")
      }
      print_expr(field, logger)
    }
    LetMut(binder~, ty~, expr~, body~, ..) => {
      logger.write_string("let mut ")
      print_binder(binder, logger)
      match ty {
        Some(t) => {
          logger.write_string(" : ")
          print_type(t, logger)
        }
        None => ()
      }
      logger.write_string(" = ")
      print_expr(expr, logger)
      logger.write_string("; ")
      print_expr(body, logger)
    }
    Pipe(lhs~, rhs~, ..) => {
      print_expr(lhs, logger)
      logger.write_string(" |> ")
      print_expr(rhs, logger)
    }
    Assign(var_~, expr~, augmented_by~, ..) => {
      print_var(var_, logger)
      logger.write_string(" ")
      match augmented_by {
        Some(op) => {
          print_var(op, logger)
          logger.write_string("= ")
        }
        None => logger.write_string("= ")
      }
      print_expr(expr, logger)
    }
    Return(return_value~, ..) => {
      logger.write_string("return")
      match return_value {
        Some(value) => {
          logger.write_string(" ")
          print_expr(value, logger)
        }
        None => ()
      }
    }
    Raise(err_value~, ..) => {
      logger.write_string("raise ")
      print_expr(err_value, logger)
    }
    Break(arg~, label~, ..) => {
      logger.write_string("break")
      match label {
        Some(l) => {
          logger.write_string(" ")
          print_label(l, logger)
          logger.write_string("~")
        }
        None => ()
      }
      match arg {
        Some(a) => {
          logger.write_string(" ")
          print_expr(a, logger)
        }
        None => ()
      }
    }
    Continue(args~, label~, ..) => {
      logger.write_string("continue")
      match label {
        Some(l) => {
          logger.write_string(" ")
          print_label(l, logger)
          logger.write_string("~")
        }
        None => ()
      }
      if not(args.is_empty()) {
        logger.write_string(" ")
        print_list(args, print_expr, ", ", logger)
      }
    }
    Loop(args~, body~, label~, ..) => {
      match label {
        Some(l) => {
          print_label(l, logger)
          logger.write_string("~: ")
        }
        None => ()
      }
      logger.write_string("loop ")
      if not(args.is_empty()) {
        if args.length() == 1 {
          // Single argument: "loop arg { ... }"
          print_expr(args.unsafe_head(), logger)
          logger.write_string(" ")
        } else {
          // Multiple arguments: "loop (arg1, arg2) { ... }"
          logger.write_string("(")
          print_list(args, print_expr, ", ", logger)
          logger.write_string(") ")
        }
      }
      logger.write_string("{ ")
      for case in body {
        print_list(case.patterns, print_pattern, ", ", logger)
        match case.guard_ {
          Some(guard_expr) => {
            logger.write_string(" if ")
            print_expr(guard_expr, logger)
          }
          None => ()
        }
        logger.write_string(" => ")
        print_expr(case.body, logger)
        logger.write_string("; ")
      }
      logger.write_string("}")
    }
    For(binders~, condition~, continue_block~, body~, for_else~, label~, ..) => {
      match label {
        Some(l) => {
          print_label(l, logger)
          logger.write_string("~: ")
        }
        None => ()
      }
      logger.write_string("for ")
      let mut first = true
      for binder_pair in binders {
        if not(first) {
          logger.write_string(", ")
        }
        first = false
        print_binder(binder_pair.0, logger)
        logger.write_string(" = ")
        print_expr(binder_pair.1, logger)
      }
      match condition {
        Some(cond) => {
          logger.write_string("; ")
          print_expr(cond, logger)
        }
        None => ()
      }
      if not(continue_block.is_empty()) {
        logger.write_string("; ")
        first = true
        for update_pair in continue_block {
          if not(first) {
            logger.write_string(", ")
          }
          first = false
          print_binder(update_pair.0, logger)
          logger.write_string(" = ")
          print_expr(update_pair.1, logger)
        }
      }
      logger.write_string(" { ")
      print_expr(body, logger)
      logger.write_string(" }")
      match for_else {
        Some(else_expr) => {
          logger.write_string(" else { ")
          print_expr(else_expr, logger)
          logger.write_string(" }")
        }
        None => ()
      }
    }
    ForEach(binders~, expr~, body~, else_block~, label~, ..) => {
      match label {
        Some(l) => {
          print_label(l, logger)
          logger.write_string("~: ")
        }
        None => ()
      }
      logger.write_string("for ")
      let mut first = true
      for binder in binders {
        if not(first) {
          logger.write_string(", ")
        }
        first = false
        match binder {
          Some(b) => print_binder(b, logger)
          None => logger.write_string("_")
        }
      }
      logger.write_string(" in ")
      print_expr(expr, logger)
      logger.write_string(" { ")
      print_expr(body, logger)
      logger.write_string(" }")
      match else_block {
        Some(else_expr) => {
          logger.write_string(" else { ")
          print_expr(else_expr, logger)
          logger.write_string(" }")
        }
        None => ()
      }
    }
    Try(body~, catch_~, catch_all~, try_else~, has_try~, ..) => {
      if has_try {
        logger.write_string("try ")
      }
      loop body {
        Group(expr~, ..) => continue expr
        _ as body => {
          logger.write_string("{ ")
          print_expr(body, logger)
          logger.write_string(" }")
        }
      }
      if not(catch_.is_empty()) || catch_all {
        logger.write_string(" catch { ")
        for case in catch_ {
          print_pattern(case.pattern, logger)
          match case.guard_ {
            Some(guard_expr) => {
              logger.write_string(" if ")
              print_expr(guard_expr, logger)
            }
            None => ()
          }
          logger.write_string(" => ")
          print_expr(case.body, logger)
          logger.write_string("; ")
        }
        logger.write_string("}")
      }
      match try_else {
        Some(else_cases) => {
          logger.write_string(" else { ")
          for case in else_cases {
            print_pattern(case.pattern, logger)
            match case.guard_ {
              Some(guard_expr) => {
                logger.write_string(" if ")
                print_expr(guard_expr, logger)
              }
              None => ()
            }
            logger.write_string(" => ")
            print_expr(case.body, logger)
            logger.write_string("; ")
          }
          logger.write_string("}")
        }
        None => ()
      }
    }
    TryOperator(body~, kind~, ..) => {
      logger.write_string("try")
      match kind {
        Question => logger.write_string("? ")
        Exclamation => logger.write_string("! ")
      }
      print_expr(body, logger)
    }
    Map(elems~, ..) => {
      logger.write_string("{")
      let mut first = true
      for elem in elems {
        if not(first) {
          logger.write_string(", ")
        }
        first = false
        print_constant(elem.key, logger)
        logger.write_string(": ")
        print_expr(elem.expr, logger)
      }
      logger.write_string("}")
    }
    StaticAssert(asserts~, body~) => {
      logger.write_string("assert ")
      let mut first = true
      for assertion in asserts {
        if not(first) {
          logger.write_string(", ")
        }
        first = false
        print_type(assertion.ty, logger)
        logger.write_string(" : ")
        print_long_ident(assertion.trait_, logger)
        if not(assertion.msg.is_empty()) {
          logger.write_string(" \"")
          logger.write_string(assertion.msg)
          logger.write_string("\"")
        }
      }
      logger.write_string("; ")
      print_expr(body, logger)
    }
  }
}

///|
fn print_multiline_string_inner(s : String, logger : StringBuilder) -> Unit {
  fn flush_segment(seg : Int, i : Int) {
    if i > seg {
      logger.write_substring(s, seg, i - seg)
    }
  }
  // The loop keeps two pieces of state:
  //   i   : the current scanning position
  //   seg : the beginning index of the current "plain" segment that has
  //         no escaping requirements. Whenever we meet a character that
  //         needs escaping, we flush the segment [seg, i) and reset seg.
  let len = s.length()
  for i = 0, seg = 0 {
    if i >= len {
      // If we reached the end of the string, flush any remaining segment
      // and break out of the loop.
      flush_segment(seg, i)
      break
    }
    let code = s.unsafe_charcode_at(i)
    match code {
      '"' | '\\' as c => {
        flush_segment(seg, i)
        logger..write_char('\\')..write_char(c.unsafe_to_char())
        // Advance both pointers: continue with next index, new segment starts after current char
        continue i + 1, i + 1
      }
      '\n' => {
        flush_segment(seg, i)
        logger.write_string("\\n")
        continue i + 1, i + 1
      }
      '\r' => {
        flush_segment(seg, i)
        logger.write_string("\\r")
        continue i + 1, i + 1
      }
      '\b' => {
        flush_segment(seg, i)
        logger.write_string("\\b")
        continue i + 1, i + 1
      }
      '\t' => {
        flush_segment(seg, i)
        logger.write_string("\\t")
        continue i + 1, i + 1
      }
      code =>
        if code < ' ' {
          flush_segment(seg, i)
          logger
          ..write_string("\\u{")
          ..write_char(to_hex_digit(code / 16))
          ..write_char(to_hex_digit(code % 16))
          ..write_char('}')
          continue i + 1, i + 1
        } else {
          // Normal character, keep scanning; only advance index.
          continue i + 1, seg
        }
    }
  }
}

///|
fn to_hex_digit(i : Int) -> Char {
  if i < 10 {
    (i + '0').unsafe_to_char()
  } else {
    (i + 'a' - 10).unsafe_to_char()
  }
}

///|
fn print_multiline_string(
  elems : @list.List[@syntax.MultilineStringElem],
  logger : StringBuilder,
) -> Unit {
  logger.write_string("\"")
  for elem in elems {
    match elem {
      String(s) => print_multiline_string_inner(s, logger)
      Interp(interp_elems) =>
        for interp_elem in interp_elems {
          match interp_elem {
            Literal(repr~, ..) => print_multiline_string_inner(repr, logger)
            Expr(expr~, ..) => {
              logger.write_string("\\{")
              print_expr(expr, logger)
              logger.write_string("}")
            }
            Source(elem) => logger.write_string(elem.source)
          }
        }
    }
  }
  logger.write_string("\"")
}