<commit_before>package scala.tools.reflect

import scala.reflect.macros.contexts.Context
import scala.collection.mutable.{ ListBuffer, Stack }
import scala.reflect.internal.util.Position
import scala.PartialFunction.cond
import scala.util.matching.Regex.Match

import java.util.{ Formatter, Formattable, IllegalFormatException }

abstract class FormatInterpolator {
  val c: Context

  import c.universe.{ Match => _, _ }
  import definitions._

  @inline private def truly(body: => Unit): Boolean = { body ; true }
  @inline private def falsely(body: => Unit): Boolean = { body ; false }

  private def fail(msg: String) = c.abort(c.enclosingPosition, msg)

  def interpolate: Tree = c.macroApplication match {
    case q"$_(..$parts).f(..$args)" =>
      def badlyInvoked = (parts.length != args.length + 1) && truly {
        def because(s: String) = s"too $s arguments for interpolated string"
        val (p, msg) =
          if (parts.length == 0) (c.prefix.tree.pos, "there are no parts")
          else if (args.length + 1 < parts.length)
            (if (args.isEmpty) c.enclosingPosition else args.last.pos, because("few"))
          else (args(parts.length-1).pos, because("many"))
        c.abort(p, msg)
      }
      if (badlyInvoked) c.macroApplication else interpolated(parts, args)
    case other =>
      fail(s"Unexpected application ${showRaw(other)}")
      other
  }

  /** Every part except the first must begin with a conversion for
   *  the arg that preceded it. If the conversion is missing, "%s"
   *  is inserted.
   *
   *  In any other position, the only permissible conversions are
   *  the literals (%% and %n) or an index reference (%1$ or %<).
   *
   *  A conversion specifier has the form:
   *
   *  [index$][flags][width][.precision]conversion
   *
   *  1) "...${smth}" => okay, equivalent to "...${smth}%s"
   *  2) "...${smth}blahblah" => okay, equivalent to "...${smth}%sblahblah"
   *  3) "...${smth}%" => error
   *  4) "...${smth}%n" => okay, equivalent to "...${smth}%s%n"
   *  5) "...${smth}%%" => okay, equivalent to "...${smth}%s%%"
   *  6) "...${smth}[%legalJavaConversion]" => okay*
   *  7) "...${smth}[%illegalJavaConversion]" => error
   *  *Legal according to [[http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Formatter.html]]
   */
  def interpolated(parts: List[Tree], args: List[Tree]) = {
    val fstring  = new StringBuilder
    val evals    = ListBuffer[ValDef]()
    val ids      = ListBuffer[Ident]()
    val argStack = Stack(args: _*)

    // create a tmp val and add it to the ids passed to format
    def defval(value: Tree, tpe: Type): Unit = {
      val freshName = TermName(c.freshName("arg$"))
      evals += ValDef(Modifiers(), freshName, TypeTree(tpe) setPos value.pos.focus, value) setPos value.pos
      ids += Ident(freshName)
    }
    // Append the nth part to the string builder, possibly prepending an omitted %s first.
    // Sanity-check the % fields in this part.
    def copyPart(part: Tree, n: Int): Unit = {
      import SpecifierGroups.{ Spec, Index }
      val s0 = part match {
        case Literal(Constant(x: String)) => x
        case _ => throw new IllegalArgumentException("internal error: argument parts must be a list of string literals")
      }
      val s  = StringContext.treatEscapes(s0)
      val ms = fpat findAllMatchIn s

      def errorLeading(op: Conversion) = op.errorAt(Spec, s"conversions must follow a splice; ${Conversion.literalHelp}")

      def first = n == 0
      // a conversion for the arg is required
      if (!first) {
        val arg = argStack.pop()
        def s_%() = {
          fstring append "%s"
          defval(arg, AnyTpe)
        }
        def accept(op: Conversion) = {
          if (!op.isLeading) errorLeading(op)
          op.accepts(arg) match {
            case Some(tpe) => defval(arg, tpe)
            case None      =>
          }
        }
        if (ms.hasNext) {
          Conversion(ms.next, part.pos, args.size) match {
            case Some(op) if op.isLiteral => s_%()
            case Some(op) if op.indexed =>
              if (op.index map (_ == n) getOrElse true) accept(op)
              else {
                // either some other arg num, or '<'
                c.warning(op.groupPos(Index), "Index is not this arg")
                s_%()
              }
            case Some(op) => accept(op)
            case None     =>
          }
        } else s_%()
      }
      // any remaining conversions must be either literals or indexed
      while (ms.hasNext) {
        Conversion(ms.next, part.pos, args.size) match {
          case Some(op) if first && op.hasFlag('<')   => op.badFlag('<', "No last arg")
          case Some(op) if op.isLiteral || op.indexed => // OK
          case Some(op) => errorLeading(op)
          case None     =>
        }
      }
      fstring append s
    }

    parts.zipWithIndex foreach {
      case (part, n) => copyPart(part, n)
    }

    q"{..$evals; ${fstring.toString}.format(..$ids)}"
  }

  val fpat = """%(?:(\d+)\$)?([-#+ 0,(\<]+)?(\d+)?(\.\d+)?([tT]?[%a-zA-Z])?""".r
  object SpecifierGroups extends Enumeration { val Spec, Index, Flags, Width, Precision, CC = Value }

  val stdContextTags = new { val tc: c.type = c } with StdContextTags
  import stdContextTags._
  val tagOfFormattable = typeTag[Formattable]

  /** A conversion specifier matched by `m` in the string part at `pos`,
   *  with `argc` arguments to interpolate.
   */<commit_msg>SI-8092 Review cleanup, no qq<commit_after>package scala.tools.reflect

import scala.reflect.macros.runtime.Context
import scala.collection.mutable.{ ListBuffer, Stack }
import scala.reflect.internal.util.Position
import scala.PartialFunction.cond
import scala.util.matching.Regex.Match

import java.util.{ Formatter, Formattable, IllegalFormatException }

abstract class FormatInterpolator {
  val c: Context
  val global: c.universe.type = c.universe

  import c.universe.{ Match => _, _ }
  import definitions._
  import treeInfo.Applied

  @inline private def truly(body: => Unit): Boolean = { body ; true }
  @inline private def falsely(body: => Unit): Boolean = { body ; false }

  private def fail(msg: String) = c.abort(c.enclosingPosition, msg)
  private def bail(msg: String) = global.abort(msg)

  def interpolate: Tree = c.macroApplication match {
    //case q"$_(..$parts).f(..$args)" =>
    case Applied(Select(Apply(_, parts), _), _, argss) =>
      val args = argss.flatten
      def badlyInvoked = (parts.length != args.length + 1) && truly {
        def because(s: String) = s"too $s arguments for interpolated string"
        val (p, msg) =
          if (parts.length == 0) (c.prefix.tree.pos, "there are no parts")
          else if (args.length + 1 < parts.length)
            (if (args.isEmpty) c.enclosingPosition else args.last.pos, because("few"))
          else (args(parts.length-1).pos, because("many"))
        c.abort(p, msg)
      }
      if (badlyInvoked) c.macroApplication else interpolated(parts, args)
    case other =>
      bail(s"Unexpected application ${showRaw(other)}")
      other
  }

  /** Every part except the first must begin with a conversion for
   *  the arg that preceded it. If the conversion is missing, "%s"
   *  is inserted.
   *
   *  In any other position, the only permissible conversions are
   *  the literals (%% and %n) or an index reference (%1$ or %<).
   *
   *  A conversion specifier has the form:
   *
   *  [index$][flags][width][.precision]conversion
   *
   *  1) "...${smth}" => okay, equivalent to "...${smth}%s"
   *  2) "...${smth}blahblah" => okay, equivalent to "...${smth}%sblahblah"
   *  3) "...${smth}%" => error
   *  4) "...${smth}%n" => okay, equivalent to "...${smth}%s%n"
   *  5) "...${smth}%%" => okay, equivalent to "...${smth}%s%%"
   *  6) "...${smth}[%legalJavaConversion]" => okay*
   *  7) "...${smth}[%illegalJavaConversion]" => error
   *  *Legal according to [[http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Formatter.html]]
   */
  def interpolated(parts: List[Tree], args: List[Tree]) = {
    val fstring  = new StringBuilder
    val evals    = ListBuffer[ValDef]()
    val ids      = ListBuffer[Ident]()
    val argStack = Stack(args: _*)

    // create a tmp val and add it to the ids passed to format
    def defval(value: Tree, tpe: Type): Unit = {
      val freshName = TermName(c.freshName("arg$"))
      evals += ValDef(Modifiers(), freshName, TypeTree(tpe) setPos value.pos.focus, value) setPos value.pos
      ids += Ident(freshName)
    }
    // Append the nth part to the string builder, possibly prepending an omitted %s first.
    // Sanity-check the % fields in this part.
    def copyPart(part: Tree, n: Int): Unit = {
      import SpecifierGroups.{ Spec, Index }
      val s0 = part match {
        case Literal(Constant(x: String)) => x
        case _ => throw new IllegalArgumentException("internal error: argument parts must be a list of string literals")
      }
      val s  = StringContext.treatEscapes(s0)
      val ms = fpat findAllMatchIn s

      def errorLeading(op: Conversion) = op.errorAt(Spec, s"conversions must follow a splice; ${Conversion.literalHelp}")

      def first = n == 0
      // a conversion for the arg is required
      if (!first) {
        val arg = argStack.pop()
        def s_%() = {
          fstring append "%s"
          defval(arg, AnyTpe)
        }
        def accept(op: Conversion) = {
          if (!op.isLeading) errorLeading(op)
          op.accepts(arg) match {
            case Some(tpe) => defval(arg, tpe)
            case None      =>
          }
        }
        if (ms.hasNext) {
          Conversion(ms.next, part.pos, args.size) match {
            case Some(op) if op.isLiteral => s_%()
            case Some(op) if op.indexed =>
              if (op.index map (_ == n) getOrElse true) accept(op)
              else {
                // either some other arg num, or '<'
                c.warning(op.groupPos(Index), "Index is not this arg")
                s_%()
              }
            case Some(op) => accept(op)
            case None     =>
          }
        } else s_%()
      }
      // any remaining conversions must be either literals or indexed
      while (ms.hasNext) {
        Conversion(ms.next, part.pos, args.size) match {
          case Some(op) if first && op.hasFlag('<')   => op.badFlag('<', "No last arg")
          case Some(op) if op.isLiteral || op.indexed => // OK
          case Some(op) => errorLeading(op)
          case None     =>
        }
      }
      fstring append s
    }

    parts.zipWithIndex foreach {
      case (part, n) => copyPart(part, n)
    }

    //q"{..$evals; ${fstring.toString}.format(..$ids)}"
    locally {
      val expr =
        Apply(
          Select(
            Literal(Constant(fstring.toString)),
            newTermName("format")),
          ids.toList
        )
      val p = c.macroApplication.pos
      Block(evals.toList, atPos(p.focus)(expr)) setPos p.makeTransparent
    }
  }

  val fpat = """%(?:(\d+)\$)?([-#+ 0,(\<]+)?(\d+)?(\.\d+)?([tT]?[%a-zA-Z])?""".r
  object SpecifierGroups extends Enumeration { val Spec, Index, Flags, Width, Precision, CC = Value }

  val stdContextTags = new { val tc: c.type = c } with StdContextTags
  import stdContextTags._
  val tagOfFormattable = typeTag[Formattable]

  /** A conversion specifier matched by `m` in the string part at `pos`,
   *  with `argc` arguments to interpolate.
   */
{'fraction_non_alphanumeric': 0.1458533601328323, 'fraction_numerical': 0.0036703661627195665, 'mean_word_length': 2.4049390062481404, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 54, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '977825', 'n_tokens_mistral': 3748, 'n_tokens_neox': 3612, 'n_words': 1027}