diff options
Diffstat (limited to 'scalatex/api/src/main/scala/scalatex/stages/Parser.scala')
-rw-r--r-- | scalatex/api/src/main/scala/scalatex/stages/Parser.scala | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/scalatex/api/src/main/scala/scalatex/stages/Parser.scala b/scalatex/api/src/main/scala/scalatex/stages/Parser.scala new file mode 100644 index 0000000..0b87d97 --- /dev/null +++ b/scalatex/api/src/main/scala/scalatex/stages/Parser.scala @@ -0,0 +1,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 + } +} |