summaryrefslogtreecommitdiff
path: root/scalatex/api/src/main/scala/scalatex/stages/Parser.scala
blob: 0b87d97f713be6e5535f304497641406e0fc3789 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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
  }
}