All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.rojoma.json.v3.-impl.interpolation.JsonInterpolatorImpl.scala Maven / Gradle / Ivy

The newest version!
package com.rojoma.json.v3
package `-impl`.interpolation

import java.io.StringReader

import ast._
import io._

import `-impl`.util.MacroCompat
import `-impl`.util.MacroCompat._

object JsonInterpolatorImpl {
  def j(c0: Context)(pieces: c0.Expr[Any]*): c0.Expr[JValue] = {
    val mc = new MacroCompat {
      val c : c0.type = c0
    }
    import mc._
    import c.universe._

    def freshTermName(): TermName = toTermName(c.freshName())

    sealed abstract class Tokenized {
      def position: c.universe.Position
    }
    case class Token(token: JsonToken)(pos: => c.universe.Position) extends Tokenized {
      lazy val position = pos
    }
    case class Unquote(tree: Tree) extends Tokenized {
      def position = tree.pos
    }
    case class UnquoteSplice(tree: Tree) extends Tokenized {
      def position = tree.pos
    }
    case class UnquoteOptional(tree: Tree) extends Tokenized {
      def position = tree.pos
    }
    case class DeferredError(position: c.universe.Position, message: String) extends Tokenized
    case class End(position: c.universe.Position) extends Tokenized

    def stripPos(msg: String): String = {
      val PfxRegex = """-?\d+:-?\d+: (.*)""".r
      msg match {
        case PfxRegex(rest) => rest
        case noPfx => noPfx
      }
    }

    def move(pos: c.universe.Position, offset: Int): c.universe.Position =
      if(isDefined(pos)) {
        if(pos.isRange) pos.withEnd(pos.end + offset).withPoint(pos.point + offset).withStart(pos.start + offset)
        else pos.withPoint(pos.point + offset)
      } else {
        pos
      }

    def offsetOf(s: String, p: io.Position) = {
      val before = s.split("\n", -1).take(p.row - 1).map(_.length + 1).sum
      val result = before + p.column - 1
      result
    }
    def relativize(start: c.universe.Position, s: String, j: io.Position): c.universe.Position =
      if(j == io.Position.Invalid) move(start, s.length)
      else move(start, offsetOf(s, j))

    def abort(msg: String) = c.abort(c.enclosingPosition, msg)
    def tokens(s: String, pos: c.universe.Position) = try {
      new JsonTokenIterator(new StringReader(s)).toList
    } catch {
      case e: JsonLexException =>
       c.abort(relativize(pos, s, e.position), stripPos(e.message))
    }

    case class TokenInfo(tokens: List[JsonToken])(val nextUnquote: Tree => Tokenized, val orig: String, val origPos: c.universe.Position) {
      def pop = TokenInfo(tokens.tail)(nextUnquote, orig, origPos)
      def position = tokens.headOption match {
        case Some(hd) => relativize(origPos, orig, hd.position)
        case None => relativize(origPos, orig, Position.Invalid)
      }
      def positionAtEnd = relativize(origPos, orig, io.Position(row = orig.length - (orig.lastIndexOf('\n') + 1),
                                                                column = orig.split("\n", -1).length - 1))
    }

    sealed trait State {
      def step(thing: Tokenized): Either[State, Tree]
    }

    def finishArray(ctors: List[TermName => Tree]): Tree = {
      if(ctors.isEmpty) q"_root_.com.rojoma.json.v3.ast.JArray.empty"
      else {
        val temp = freshTermName()
        val populations = ctors.reverse.map(_(temp))
        q"""{
          val $temp = _root_.scala.collection.immutable.Vector.newBuilder[_root_.com.rojoma.json.v3.ast.JValue]
          ..$populations
          _root_.com.rojoma.json.v3.ast.JArray($temp.result())
        }"""
      }
    }

    def finishObject(ctors: List[TermName => Tree]): Tree = {
      if(ctors.isEmpty) q"_root_.com.rojoma.json.v3.ast.JObject.empty"
      else {
        val temp = freshTermName()
        val populations = ctors.reverse.map(_(temp))
        q"""{
          val $temp = _root_.scala.collection.immutable.ListMap.newBuilder[_root_.java.lang.String, _root_.com.rojoma.json.v3.ast.JValue]
          ..$populations
          _root_.com.rojoma.json.v3.ast.JObject($temp.result())
        }"""
      }
    }

    def arrayItem(v: Tree): TermName => Tree = { termName =>
      q"$termName += $v"
    }

    def optionalArrayItem(v: Tree): TermName => Tree = { termName =>
      q"$termName ++= _root_.com.rojoma.json.v3.`-impl`.interpolation.Convert.option($v)"
    }

    def arrayItems(v: Tree): TermName => Tree = { termName =>
      q"$termName ++= _root_.com.rojoma.json.v3.`-impl`.interpolation.Convert.array($v)"
    }

    def objectItem(k: Tree, v: Tree): TermName => Tree = { termName =>
      q"$termName += (($k, $v))"
    }

    def optionalObjectItem(k: Tree, v: Tree): TermName => Tree = { termName =>
      val kTemp = freshTermName
      val temp = freshTermName()
      q"""{
            val $kTemp = $k
            _root_.com.rojoma.json.v3.`-impl`.interpolation.Convert.option($v) match {
              case _root_.scala.Some($temp) => $termName += (($kTemp, $temp))
              case _root_.scala.None => {}
            }
          }"""
    }

    def objectItems(v: Tree): TermName => Tree = { termName =>
      q"""$termName ++= _root_.com.rojoma.json.v3.`-impl`.interpolation.Convert.map($v)"""
    }

    def unexpectedTokenized(thing: Tokenized, expecting: String): Nothing =
      thing match {
        case Token(t) =>
          c.abort(thing.position, stripPos(new JsonUnexpectedToken(t, expecting).message))
        case DeferredError(pos, what) =>
          c.abort(pos, what)
        case End(pos) =>
          c.abort(pos, new JsonParserEOF(Position.Invalid).message)
        case UnquoteSplice(t) =>
          c.abort(thing.position, s"Expected $expecting; got splicing unquote")
        case UnquoteOptional(t) =>
          c.abort(thing.position, s"Expected $expecting; got optional unquote")
        case Unquote(t) =>
          c.abort(thing.position, s"Expected $expecting; got unquote")
      }

    def withThing(thing: Tokenized, expecting: String)(f: PartialFunction[Tokenized, Either[State, Tree]]): Either[State, Tree] =
      f.applyOrElse(thing, unexpectedTokenized(_ : Tokenized, expecting))

    class ExpectingArrayDatum(ctors: List[TermName => Tree], returnState: Tree => State, expecting: String) extends State {
      def step(thing: Tokenized) =
        thing match {
          case UnquoteSplice(items) =>
            Left(new ExpectingCommaOrEndOfArray(arrayItems(items) :: ctors, returnState))
          case UnquoteOptional(item) =>
            Left(new ExpectingCommaOrEndOfArray(optionalArrayItem(item) :: ctors, returnState))
          case other =>
            new ExpectingDatum({ v => new ExpectingCommaOrEndOfArray(arrayItem(v) :: ctors, returnState) }, expecting).step(other)
        }
    }


    class ExpectingCommaOrEndOfArray(ctors: List[TermName => Tree], returnState: Tree => State) extends State {
      def step(thing: Tokenized) =
        withThing(thing, "comma or end of array") {
          case Token(TokenCloseBracket()) =>
            Left(returnState(finishArray(ctors)))
          case Token(TokenComma()) =>
            Left(new ExpectingArrayDatum(ctors, returnState, "datum"))
        }
    }

    class ExpectingDatumOrEndOfArray(ctors: List[TermName => Tree], returnState: Tree => State) extends State {
      def step(thing: Tokenized) =
        thing match {
          case Token(TokenCloseBracket()) =>
            Left(returnState(finishArray(ctors)))
          case other =>
            new ExpectingArrayDatum(ctors, returnState, "datum or end of array").step(other)
        }
    }

    class ExpectingCommaOrEndOfObject(ctors: List[TermName => Tree], returnState: Tree => State) extends State {
      def step(thing: Tokenized) =
        withThing(thing, "comma or end of object") {
          case Token(TokenCloseBrace()) =>
            Left(returnState(finishObject(ctors)))
          case Token(TokenComma()) =>
            Left(new ExpectingFieldName(ctors, returnState))
        }
    }

    class ExpectingFieldValue(field: Tree, ctors: List[TermName => Tree], returnState: Tree => State) extends State {
      def step(thing: Tokenized) =
        thing match {
          case UnquoteOptional(item) =>
            Left(new ExpectingCommaOrEndOfObject(optionalObjectItem(field, item) :: ctors, returnState))
          case other =>
            new ExpectingDatum({ v => new ExpectingCommaOrEndOfObject(objectItem(field, v) :: ctors, returnState) }).step(other)
        }
    }

    class ExpectingColon(field: Tree, ctors: List[TermName => Tree], returnState: Tree => State) extends State {
      def step(thing: Tokenized) =
        withThing(thing, "colon") {
          case Token(TokenColon()) =>
            Left(new ExpectingFieldValue(field, ctors, returnState))
        }
    }

    class ExpectingFieldName(ctors: List[TermName => Tree], returnState: Tree => State, expecting: String = "field name") extends State {
      def step(thing: Tokenized) =
        withThing(thing, expecting) {
          case UnquoteSplice(items) =>
            Left(new ExpectingCommaOrEndOfObject(objectItems(items) :: ctors, returnState))
          case Unquote(s) =>
            Left(new ExpectingColon(q"_root_.com.rojoma.json.v3.codec.FieldEncode.toField($s)", ctors, returnState))
          case Token(TokenString(s)) =>
            Left(new ExpectingColon(q"$s", ctors, returnState))
          case Token(TokenIdentifier(s)) =>
            Left(new ExpectingColon(q"$s", ctors, returnState))
        }
    }

    class ExpectingFieldOrEndOfObject(ctors: List[TermName => Tree], returnState: Tree => State) extends State {
      def step(thing: Tokenized) =
        thing match {
          case Token(TokenCloseBrace()) =>
            Left(returnState(finishObject(ctors)))
          case other =>
            new ExpectingFieldName(ctors, returnState, "field name or end of object").step(other)
        }
    }

    class ExpectingDatum(returnState: Tree => State, expecting: String = "datum") extends State {
      def step(thing: Tokenized) =
        withThing(thing, expecting) {
          case Unquote(item) =>
            Left(returnState(q"_root_.com.rojoma.json.v3.codec.JsonEncode.toJValue($item)"))
          case Token(TokenOpenBracket()) =>
            Left(new ExpectingDatumOrEndOfArray(Nil, returnState))
          case Token(TokenOpenBrace()) =>
            Left(new ExpectingFieldOrEndOfObject(Nil, returnState))
          case Token(TokenString(s)) =>
            Left(returnState(q"_root_.com.rojoma.json.v3.ast.JString($s)"))
          case Token(TokenIdentifier("true")) =>
            Left(returnState(q"_root_.com.rojoma.json.v3.ast.JBoolean.canonicalTrue"))
          case Token(TokenIdentifier("false")) =>
            Left(returnState(q"_root_.com.rojoma.json.v3.ast.JBoolean.canonicalFalse"))
          case Token(TokenIdentifier("null")) =>
            Left(returnState(q"_root_.com.rojoma.json.v3.ast.JNull"))
          case Token(TokenNumber(n)) =>
            // Hmm.   We can inspect "n" and generate a more specific JNumber type.
            // But it's most likely we'll just be serializing the result, so there's
            // not much point.
            Left(returnState(q"_root_.com.rojoma.json.v3.ast.JNumber.unsafeFromString($n)"))
        }
    }

    class ExpectingEnd(tree: Tree) extends State {
      def step(thing: Tokenized) =
        withThing(thing, "end of input") {
          case End(_) => Right(tree)
        }
    }

    class TopLevelDatum extends State {
      def step(thing: Tokenized) =
        new ExpectingDatum(new ExpectingEnd(_)).step(thing)
    }


    def walk(initialThings: List[Tokenized]): Tree = {
      var things = initialThings
      var state: State = new TopLevelDatum
      while(!things.isEmpty) {
        state.step(things.head) match {
          case Right(tree) =>
            return tree
          case Left(newState) =>
            state = newState
        }
        things = things.tail
      }
      abort("Internal error: Should have gotten to an end")
    }

    c.prefix.tree match {
      case Apply(_, List(Apply(_, rawParts))) =>
        val parts = rawParts map {
          case node@Literal(Constant(rawPart: String)) =>
            val (part, nextUnquote) =
              if(rawPart.endsWith("..")) (rawPart.dropRight(2), UnquoteSplice)
              else if(rawPart.endsWith("?")) (rawPart.dropRight(1), UnquoteOptional)
              else (rawPart, Unquote)
            try {
              TokenInfo(tokens(part, node.pos))(nextUnquote, part, node.pos)
            } catch {
              case e: JsonReaderException =>
                c.abort(relativize(node.pos, part, e.position), stripPos(e.message))
            }
        }
        if(parts.length != pieces.length + 1) abort("Internal error: there is not one more piece than part")

        val builder = List.newBuilder[Tokenized]
        def interweave(parts: List[TokenInfo], pieces: List[c.Expr[Any]]): Unit = {
          (parts, pieces) match {
            case (Nil, Nil) => // done
            case ((ti: TokenInfo) :: Nil, Nil) =>
              var remaining = ti
              while(!remaining.tokens.isEmpty) {
                val left = remaining
                builder += Token(remaining.tokens.head)(left.position)
                remaining = remaining.pop
              }
              if(remaining.nextUnquote ne Unquote) {
                builder += DeferredError(remaining.positionAtEnd, "Splice at end of input")
              }
              builder += End(ti.positionAtEnd)
            case ((ti: TokenInfo) :: rest, piece :: pieces) =>
              var remaining = ti
              while(!remaining.tokens.isEmpty) {
                val left = remaining
                builder += Token(remaining.tokens.head)(left.position)
                remaining = remaining.pop
              }
              builder += remaining.nextUnquote(piece.tree)
              interweave(rest, pieces)
            case _ =>
              abort("Internal error: pieces and parts didn't line up")
          }
        }
        interweave(parts.toList, pieces.toList)

        val tree = walk(builder.result())
        //println(tree)
        c.Expr[JValue](tree)
      case _ =>
        abort("Not called from interpolation position.")
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy