summaryrefslogblamecommitdiff
path: root/scalatex/api/src/main/scala/scalatex/stages/Parser.scala
blob: 0b87d97f713be6e5535f304497641406e0fc3789 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
                
              
                   
                       
                              
 





                                                                

                                                          
                                            

   
                                                                                                           
                                    












                                                               
 
                                     

                                                                             

     

                         
                                                                              
   
                     
                               

   
                                            
                                                                                                                 
                                                                                                      


     
                                                          

                                                                
                                                               
                                                                                
                
                    
                                                                                    

     
                         
             
                                       
                                                                
   
                                                             
                     
                                                                                            

                     
                                                                                                                
   
                    
                                                                         
   

                     
                                                                       
   
                     
             
                                                       

                         
                                                                                
             
                  
                                         
   
 
                         
                                                                                           
   
                                              
                                                                                              

                                                                                        
              
   
             


                                                                                     
              
                                                                                                    
       
   
                                                                              
                                                                    
 
                                                                     


                                
                                   
                             
                                           

                                                                                        
                                         
   



                                                                      
                             


                   
                                                               
                             

     
 




                 

     


                                                                                    
                                         

                                                   


                                                                   

                                                                                                                   

                                                                                                  
 




                                                                






                                                                                         
 
package scalatex
package stages
import acyclic.file
import org.parboiled2._
import scalaParser.ScalaSyntax

/**
 * Parses the input text into a roughly-structured AST. This AST
 * is much simpler than the real Scala AST, but serves us well
 * enough until we stuff the code-strings into the real Scala
 * parser later
 */
object Parser extends ((String, Int) => Ast.Block){
  def apply(input: String, offset: Int = 0): Ast.Block = {
    new Parser(input, offset).Body.run().get
  }
}
class Parser(input: ParserInput, indent: Int = 0, offset: Int = 0) extends scalaParser.ScalaSyntax(input) {
  def offsetCursor = offset + cursor
  val txt = input.sliceString(0, input.length)
  val indentTable = txt.split('\n').map{ s =>
    if (s.trim == "") -1
    else s.takeWhile(_ == ' ').length
  }
  val nextIndentTable = (0 until indentTable.length).map { i =>
    val index = indentTable.indexWhere(_ != -1, i + 1)
    if (index == -1) 100000
    else indentTable(index)
  }
  def cursorNextIndent() = {
    nextIndentTable(txt.take(cursor).count(_ == '\n'))
  }

  def TextNot(chars: String) = rule {
    push(offsetCursor) ~ capture(oneOrMore(noneOf(chars + "\n") | "@@")) ~> {
      (i, x) => Ast.Block.Text(x.replace("@@", "@"), i)
    }
  }
  def Text = TextNot("@")
  def Code = rule {
    "@" ~ capture(Identifiers.Id | BlockExpr2 | ('(' ~ optional(Exprs) ~ ')'))
  }
  def Header = rule {
    "@" ~ capture(Def | Import)
  }

  def HeaderBlock: Rule1[Ast.Header] = rule{
    Header ~ zeroOrMore(capture(WL) ~ Header ~> (_ + _)) ~ runSubParser{new Parser(_, indent, cursor).Body0} ~> {
      (start: String, heads: Seq[String], body: Ast.Block) => Ast.Header(start + heads.mkString, body)
    }
  }

  def BlankLine = rule{ '\n' ~ zeroOrMore(' ') ~ &('\n') }
  def IndentSpaces = rule{ indent.times(' ') ~ zeroOrMore(' ') }
  def Indent = rule{ '\n' ~ IndentSpaces }
  def LoneScalaChain: Rule2[Ast.Block.Text, Ast.Chain] = rule {
    (push(offsetCursor) ~ capture(Indent) ~> ((i, x) => Ast.Block.Text(x, i))) ~
    ScalaChain ~
    IndentBlock ~> {
      (chain: Ast.Chain, body: Ast.Block) => chain.copy(parts = chain.parts :+ body)
    }
  }
  def IndentBlock = rule{
    &("\n") ~
    test(cursorNextIndent() > indent) ~
    runSubParser(new Parser(_, cursorNextIndent(), cursor).Body)
  }
  def IfHead = rule{ "@" ~ capture("if" ~ "(" ~ Expr ~ ")") }
  def IfElse1 = rule{
    push(offsetCursor) ~ IfHead ~ BraceBlock ~ optional("else" ~ (BraceBlock | IndentBlock))
  }
  def IfElse2 = rule{
    Indent ~ push(offsetCursor) ~ IfHead ~ IndentBlock ~ optional(Indent ~ "@else" ~ (BraceBlock | IndentBlock))
  }
  def IfElse = rule{
    (IfElse1 | IfElse2) ~> ((a, b, c, d) => Ast.Block.IfElse(b, c, d, a))
  }

  def ForHead = rule{
    push(offsetCursor) ~ "@" ~ capture("for" ~ '(' ~ Enumerators ~ ')')
  }
  def ForLoop = rule{
    ForHead ~
    BraceBlock ~> ((a, b, c) => Ast.Block.For(b, c, a))
  }
  def LoneForLoop = rule{
    (push(offsetCursor) ~ capture(Indent) ~> ((i, t) => Ast.Block.Text(t, i))) ~
    ForHead ~
    IndentBlock ~>
    ((a, b, c) => Ast.Block.For(b, c, a))
  }

  def ScalaChain = rule {
    push(offsetCursor) ~ Code ~ zeroOrMore(Extension) ~> { (a, b, c) => Ast.Chain(b, c, a)}
  }
  def Extension: Rule1[Ast.Chain.Sub] = rule {
    (push(offsetCursor) ~ '.' ~ capture(Identifiers.Id) ~> ((x, y) => Ast.Chain.Prop(y, x))) |
    (push(offsetCursor) ~ capture(TypeArgs2) ~> ((x, y) => Ast.Chain.TypeArgs(y, x))) |
    (push(offsetCursor) ~ capture(ArgumentExprs2) ~> ((x, y) => Ast.Chain.Args(y, x))) |
    BraceBlock
  }
  def Ws = WL
  // clones of the version in ScalaSyntax, but without tailing whitespace or newlines
  def TypeArgs2 = rule { '[' ~ Ws ~ Types ~ ']' }
  def ArgumentExprs2 = rule {
    '(' ~ Ws ~
    (optional(Exprs ~ ',' ~ Ws) ~ PostfixExpr ~ ':' ~ Ws ~ '_' ~ Ws ~ '*' ~ Ws | optional(Exprs) ) ~
    ')'
  }
  def BlockExpr2: Rule0 = rule { '{' ~ Ws ~ (CaseClauses | Block) ~ Ws ~ '}' }
  def BraceBlock: Rule1[Ast.Block] = rule{ '{' ~ BodyNoBrace ~ '}' }

  def BodyItem(exclusions: String): Rule1[Seq[Ast.Block.Sub]] = rule{
    ForLoop ~> (Seq(_)) |
    LoneForLoop ~> (Seq(_, _)) |
    IfElse ~> (Seq(_)) |
    LoneScalaChain ~> (Seq(_, _)) |
    HeaderBlock ~> (Seq(_)) |
    TextNot("@" + exclusions) ~> (Seq(_)) |
    (push(offsetCursor) ~ capture(Indent) ~> ((i, x) => Seq(Ast.Block.Text(x, i)))) |
    (push(offsetCursor) ~ capture(BlankLine) ~> ((i, x) => Seq(Ast.Block.Text(x, i)))) |
    ScalaChain ~> (Seq(_: Ast.Block.Sub))
  }
  def Body = rule{ BodyEx() }
  def BodyNoBrace = rule{ BodyEx("}") }
  def BodyEx(exclusions: String = "") = rule{
    push(offsetCursor) ~ oneOrMore(BodyItem(exclusions)) ~> {(i, x) =>
      Ast.Block(x.flatten, i)
    }
  }
  def Body0 = rule{
    push(offsetCursor) ~ zeroOrMore(BodyItem("")) ~> {(i, x) =>
      Ast.Block(x.flatten, i)
    }
  }
}

trait Ast{
  def offset: Int
}
object Ast{

  /**
   * @param parts The various bits of text and other things which make up this block
   * @param offset
   */
  case class Block(parts: Seq[Block.Sub],
                   offset: Int = 0)
                   extends Chain.Sub with Block.Sub
  object Block{
    trait Sub extends Ast
    case class Text(txt: String, offset: Int = 0) extends Block.Sub
    case class For(generators: String, block: Block, offset: Int = 0) extends Block.Sub
    case class IfElse(condition: String, block: Block, elseBlock: Option[Block], offset: Int = 0) extends Block.Sub
  }
  case class Header(front: String, block: Block, offset: Int = 0) extends Block.Sub with Chain.Sub

  /**
   * @param lhs The first expression in this method-chain
   * @param parts A list of follow-on items chained to the first
   * @param offset
   */
  case class Chain(lhs: String, parts: Seq[Chain.Sub], offset: Int = 0) extends Block.Sub
  object Chain{
    trait Sub extends Ast
    case class Prop(str: String, offset: Int = 0) extends Sub
    case class TypeArgs(str: String, offset: Int = 0) extends Sub
    case class Args(str: String, offset: Int = 0) extends Sub
  }
}