2013-03-10 4 views
13

Я пытаюсь реализовать собственный метод интерполяции строк с помощью макроса, и мне нужно некоторое руководство по использованию API.Строковая интерполяция и макрос: как получить расположение StringContext и выражения

Вот что я хочу сделать:

/** expected 
    * LocatedPieces(List(("\nHello ", Place("world"), Position()), 
         ("\nHow are you, ", Name("Eric"), Position(...))) 
    */ 
val locatedPieces: LocatedPieces = 
    s2""" 
    Hello $place 

    How are you, $name 
    """ 

val place: Piece = Place("world") 
val name: Piece = Name("Eric") 

trait Piece 
case class Place(p: String) extends Piece 
case class Name(n: String) extends Piece 

/** sequence of each interpolated Piece object with: 
    * the preceding text and its location 
    */ 
case class LocatedPieces(located: Seq[(String, Piece, Position)]) 

implicit class s2pieces(sc: StringContext) { 
    def s2(parts: Piece*) = macro s2Impl 
} 

def impl(c: Context)(pieces: c.Expr[Piece]*): c.Expr[LocatedPieces] = { 
    // I want to build a LocatedPieces object with the positions for all 
    // the pieces + the pieces + the (sc: StringContext).parts 
    // with the method createLocatedPieces below 
    // ???  
} 

def createLocatedPieces(parts: Seq[String], pieces: Seq[Piece], positions: Seq[Position]): 
    LocatedPieces = 
    // zip the text parts, pieces and positions together to create a LocatedPieces object 
    ??? 

Мои вопросы:

  1. Как получить доступ к StringContext объект внутри макроса для того, чтобы получить все StringContext.parts строки?

  2. Как я могу захватить позиции каждой части?

  3. Как я могу назвать метод createLocatedPieces выше и подтвердить результат, чтобы получить результат вызова макроса?

+0

Я пробовал различные части API, но я еще не смог собрать полное решение. Любые советы или общее руководство помогут. И полное решение получило бы мою вечную благодарность :-) – Eric

+0

Я не уверен, есть ли у него ответ, но ваш вопрос напомнил мне об этом сообщении: http://hootenannylas.blogspot.com.au/2013/02/syntax -checking-in-scala-string.html –

+0

Я прочитал его уже, и мой пример использования немного больше. Но я знаю Тони, и я попрошу его помочь мне на следующей ScalaSyd на этой неделе, если я не получу ответа тем временем :-). – Eric

ответ

10

Я нашел работоспособное решение после нескольких часов напряженной работы:

object Macros { 

    import scala.reflect.macros.Context 
    import language.experimental.macros 

    sealed trait Piece 
    case class Place(str: String) extends Piece 
    case class Name(str: String) extends Piece 
    case class Pos(column: Int, line: Int) 
    case class LocatedPieces(located: List[(String, Piece, Pos)]) 

    implicit class s2pieces(sc: StringContext) { 
    def s2(pieces: Piece*) = macro s2impl 
    } 

    // pieces contain all the Piece instances passed inside of the string interpolation 
    def s2impl(c: Context)(pieces: c.Expr[Piece]*): c.Expr[LocatedPieces] = { 
    import c.universe.{ Name => _, _ } 

    c.prefix.tree match { 
     // access data of string interpolation 
     case Apply(_, List(Apply(_, rawParts))) => 

     // helper methods 
     def typeIdent[A : TypeTag] = 
      Ident(typeTag[A].tpe.typeSymbol) 

     def companionIdent[A : TypeTag] = 
      Ident(typeTag[A].tpe.typeSymbol.companionSymbol) 

     def identFromString(tpt: String) = 
      Ident(c.mirror.staticModule(tpt)) 

     // We need to translate the data calculated inside of the macro to an AST 
     // in order to write it back to the compiler. 
     def toAST(any: Any) = 
      Literal(Constant(any)) 

     def toPosAST(column: Tree, line: Tree) = 
      Apply(
      Select(companionIdent[Pos], newTermName("apply")), 
      List(column, line)) 

     def toTupleAST(t1: Tree, t2: Tree, t3: Tree) = 
      Apply(
      TypeApply(
       Select(identFromString("scala.Tuple3"), newTermName("apply")), 
       List(typeIdent[String], typeIdent[Piece], typeIdent[Pos])), 
      List(t1, t2, t3)) 

     def toLocatedPiecesAST(located: Tree) = 
      Apply(
      Select(companionIdent[LocatedPieces], newTermName("apply")), 
      List(located)) 

     def toListAST(xs: List[Tree]) = 
      Apply(
      TypeApply(
       Select(identFromString("scala.collection.immutable.List"), newTermName("apply")), 
       List(AppliedTypeTree(
       typeIdent[Tuple3[String, Piece, Pos]], 
       List(typeIdent[String], typeIdent[Piece], typeIdent[Pos])))), 
      xs) 

     // `parts` contain the strings a string interpolation is built of 
     val parts = rawParts map { case Literal(Constant(const: String)) => const } 
     // translate compiler positions to a data structure that can live outside of the compiler 
     val positions = pieces.toList map (_.tree.pos) map (p => Pos(p.column, p.line)) 
     // discard last element of parts, `transpose` does not work otherwise 
     // trim parts to discard unnecessary white space 
     val data = List(parts.init map (_.trim), pieces.toList, positions).transpose 
     // create an AST containing a List[(String, Piece, Pos)] 
     val tupleAST = data map { case List(part: String, piece: c.Expr[_], Pos(column, line)) => 
      toTupleAST(toAST(part), piece.tree, toPosAST(toAST(column), toAST(line))) 
     } 
     // create an AST of `LocatedPieces` 
     val locatedPiecesAST = toLocatedPiecesAST(toListAST(tupleAST)) 
     c.Expr(locatedPiecesAST) 

     case _ => 
     c.abort(c.enclosingPosition, "invalid") 
    } 
    } 
} 

Использования:

object StringContextTest { 
    val place: Piece = Place("world") 
    val name: Piece = Name("Eric") 
    val pieces = s2""" 
    Hello $place 
    How are you, $name? 
    """ 
    pieces.located foreach println 
} 

Результат:

(Hello,Place(world),Pos(12,9)) 
(How are you,,Name(Eric),Pos(19,10)) 

Я не думал, что это может занять много времени, чтобы все но было приятно провести время. Я надеюсь, что код соответствует вашим требованиям. Если вам нужна дополнительная информация о том, как конкретные вещи работают, то посмотрите на другие вопросы и ответы на SO:

Большое спасибо Трэвис Браун (см комментарии), я получил гораздо более короткое решение для компиляции:

object Macros { 

    import scala.reflect.macros.Context 
    import language.experimental.macros 

    sealed trait Piece 
    case class Place(str: String) extends Piece 
    case class Name(str: String) extends Piece 
    case class Pos(column: Int, line: Int) 
    case class LocatedPieces(located: Seq[(String, Piece, Pos)]) 

    implicit class s2pieces(sc: StringContext) { 
    def s2(pieces: Piece*) = macro s2impl 
    } 

    def s2impl(c: Context)(pieces: c.Expr[Piece]*): c.Expr[LocatedPieces] = { 
    import c.universe.{ Name => _, _ } 

    def toAST[A : TypeTag](xs: Tree*): Tree = 
     Apply(
     Select(Ident(typeOf[A].typeSymbol.companionSymbol), newTermName("apply")), 
     xs.toList) 

    val parts = c.prefix.tree match { 
     case Apply(_, List(Apply(_, rawParts))) => 
     rawParts zip (pieces map (_.tree)) map { 
      case (Literal(Constant(rawPart: String)), piece) => 
      val line = c.literal(piece.pos.line).tree 
      val column = c.literal(piece.pos.column).tree 
      val part = c.literal(rawPart.trim).tree 
      toAST[(_, _, _)](part, piece, toAST[Pos](line, column)) 
     } 
    } 
    c.Expr(toAST[LocatedPieces](toAST[Seq[_]](parts: _*))) 
    } 
} 

Это абстрагируется над сложной конструкцией AST, и ее логика немного отличается, но почти такая же. Если вам трудно понять, как работает код, попробуйте понять первое решение. Он более ясен в том, что он делает.

+3

+1, и я не хочу красть ваш гром, но это можно сделать [_much_ более кратко] (https://gist.github.com/travisbrown/5136824). Я начал эту реализацию этим утром, но не публиковал ее, потому что (как и ваш) она не возвращает полную «позицию». –

+0

Спасибо за вашу тяжелую работу! Я собираюсь адаптировать ваше решение к моему конкретному варианту использования, но похоже, что у меня есть вся необходимая информация о том, как извлечь исходный текст, позиции и пакет в целом. Трэвис, я также посмотрю на ваш смысл, который, на первый взгляд, имеет некоторые интригующие подчеркивания в некоторых позициях. – Eric

+1

@TravisBrown: Большое спасибо, ваше решение потрясающее. Я * знал * должен быть способ абстрагироваться от всего этого дерьма AST, но я не придумал решение. Размышление о нескольких параметрах и кортежах, как и список параметров, проклят. Моя вторая попытка пока короче. – sschaef