summaryrefslogtreecommitdiff
path: root/scalatexApi
diff options
context:
space:
mode:
authorLi Haoyi <haoyi@dropbox.com>2014-11-03 21:20:37 -0800
committerLi Haoyi <haoyi@dropbox.com>2014-11-03 21:20:37 -0800
commit2ca177afb55232ae762051d9a2cc205629000e70 (patch)
treef4f1a2e134e77340a3542c86912a59ab1fbdf7c6 /scalatexApi
parentd229249c8805fdb0eb3536500a6ac8e42e15bd40 (diff)
downloadhands-on-scala-js-2ca177afb55232ae762051d9a2cc205629000e70.tar.gz
hands-on-scala-js-2ca177afb55232ae762051d9a2cc205629000e70.tar.bz2
hands-on-scala-js-2ca177afb55232ae762051d9a2cc205629000e70.zip
It compiles with the old stuff stripped out
Diffstat (limited to 'scalatexApi')
-rw-r--r--scalatexApi/src/main/scala/scalatex/ScalatexParser.scala15
-rw-r--r--scalatexApi/src/main/scala/scalatex/package.scala16
-rw-r--r--scalatexApi/src/main/scala/scalatex/stages/Compiler.scala306
-rw-r--r--scalatexApi/src/main/scala/scalatex/stages/IndentHandler.scala111
-rw-r--r--scalatexApi/src/main/scala/scalatex/stages/Parser.scala790
-rw-r--r--scalatexApi/src/test/scala/scalatex/ParserTests.scala52
6 files changed, 197 insertions, 1093 deletions
diff --git a/scalatexApi/src/main/scala/scalatex/ScalatexParser.scala b/scalatexApi/src/main/scala/scalatex/ScalatexParser.scala
index 02f1cf5..a36d2d8 100644
--- a/scalatexApi/src/main/scala/scalatex/ScalatexParser.scala
+++ b/scalatexApi/src/main/scala/scalatex/ScalatexParser.scala
@@ -9,14 +9,15 @@ trait Ast{
object Ast{
case class Block(parts: Seq[Block.Sub], offset: Int = 0) extends Chain.Sub
object Block{
- trait Sub
+ trait Sub extends Ast
case class Text(txt: String, offset: Int = 0) extends Block.Sub
}
- case class Code(code: String, offset: Int = 0)
- case class Chain(lhs: Code, parts: Seq[Chain.Sub], offset: Int = 0) extends Block.Sub
+
+ case class Chain(lhs: String, parts: Seq[Chain.Sub], offset: Int = 0) extends Block.Sub
object Chain{
- trait Sub
+ 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
}
}
@@ -42,7 +43,7 @@ class ScalatexParser(input: ParserInput, indent: Int = 0) extends ScalaSyntax(in
}
def Text = TextNot("@")
def Code = rule {
- "@" ~ capture(Id | BlockExpr | ('(' ~ optional(Exprs) ~ ')')) ~> (Ast.Code(_))
+ "@" ~ capture(Id | BlockExpr2 | ('(' ~ optional(Exprs) ~ ')'))
}
def BlankLine = rule{ '\n' ~ zeroOrMore(' ') ~ &('\n') }
def Indent = rule{ '\n' ~ indent.times(' ') ~ zeroOrMore(' ') }
@@ -62,7 +63,8 @@ class ScalatexParser(input: ParserInput, indent: Int = 0) extends ScalaSyntax(in
Code ~ zeroOrMore(Extension) ~> {Ast.Chain(_, _)}
}
def Extension: Rule1[Ast.Chain.Sub] = rule {
- (capture(('.' ~ Id) ~ optional(TypeArgs2)) ~> (Ast.Chain.Prop(_))) |
+ (capture('.' ~ Id) ~> (Ast.Chain.Prop(_))) |
+ (capture(TypeArgs2) ~> (Ast.Chain.TypeArgs(_))) |
(capture(ArgumentExprs2) ~> (Ast.Chain.Args(_))) |
TBlock
}
@@ -72,6 +74,7 @@ class ScalatexParser(input: ParserInput, indent: Int = 0) extends ScalaSyntax(in
def ArgumentExprs2 = rule {
'(' ~ Ws ~ (optional(Exprs ~ ',' ~ Ws) ~ PostfixExpr ~ ':' ~ Ws ~ '_' ~ Ws ~ '*' ~ Ws | optional(Exprs)) ~ ')'
}
+ def BlockExpr2: Rule0 = rule { '{' ~ Ws ~ (CaseClauses | Block) ~ '}' }
def TBlock = rule{ '{' ~ Body ~ '}' }
def Body = rule{
diff --git a/scalatexApi/src/main/scala/scalatex/package.scala b/scalatexApi/src/main/scala/scalatex/package.scala
index d86976e..a55f3e3 100644
--- a/scalatexApi/src/main/scala/scalatex/package.scala
+++ b/scalatexApi/src/main/scala/scalatex/package.scala
@@ -25,14 +25,14 @@ package object scalatex {
def applyMacroFile(c: Context)(filename: c.Expr[String]): c.Expr[Frag] = {
import c.universe._
- val s = filename.tree
+ val fileName = filename.tree
.asInstanceOf[Literal]
.value
.value
.asInstanceOf[String]
- val txt = io.Source.fromFile(s).mkString |> stages.IndentHandler
+ val txt = io.Source.fromFile(fileName).mkString
val sourceFile = new BatchSourceFile(
- new PlainFile(s),
+ new PlainFile(fileName),
txt.toCharArray
)
@@ -47,7 +47,7 @@ package object scalatex {
debug: Boolean)
: c.Expr[Frag] = {
import c.universe._
- val s = expr.tree
+ val scalatexFragment = expr.tree
.asInstanceOf[Literal]
.value
.value
@@ -58,10 +58,8 @@ package object scalatex {
.lineContent
.drop(expr.tree.pos.column)
.take(2)
- val indented = s |> stages.IndentHandler
- if (debug) println(indented)
compileThing(c)(
- indented,
+ scalatexFragment,
expr.tree.pos.source,
expr.tree.pos.point + (if (stringStart == "\"\"") 1 else -1),
runtimeErrors,
@@ -80,7 +78,7 @@ package object scalatex {
def compile(s: String): c.Tree = {
val realPos = new OffsetPosition(source, point).asInstanceOf[c.universe.Position]
- Compiler(c)(realPos, s |> stages.Parser)
+ Compiler(c)(realPos, new ScalatexParser(s).Body.run().get)
}
import c.Position
@@ -96,7 +94,5 @@ package object scalatex {
c.Expr( q"""throw scalatex.Internals.DebugFailure($msg, $posMsg)""")
}
}
-
}
-
}
diff --git a/scalatexApi/src/main/scala/scalatex/stages/Compiler.scala b/scalatexApi/src/main/scala/scalatex/stages/Compiler.scala
index 5fdf998..5127945 100644
--- a/scalatexApi/src/main/scala/scalatex/stages/Compiler.scala
+++ b/scalatexApi/src/main/scala/scalatex/stages/Compiler.scala
@@ -1,4 +1,5 @@
-package scalatex.stages
+package scalatex
+package stages
import acyclic.file
import scala.reflect.macros.Context
@@ -10,167 +11,166 @@ import scala.reflect.internal.util.{Position, OffsetPosition}
* given Frag at runtime.
*/
object Compiler{
- val WN = TwistNodes
- def apply(c: Context)(literalPos: c.Position, template: WN.Template): c.Tree = {
+
+ def apply(c: Context)(fragPos: c.Position, template: Ast.Block): c.Tree = {
import c.universe._
def fragType = tq"scalatags.Text.all.Frag"
- def compileTree(frag: WN.TemplateTree): Tree = {
-// println(frag)
- object fragPos{
- def posFor(offset: Int) = {
- new OffsetPosition(
- literalPos.source,
- math.min(offset, literalPos.source.length - 1)
- ).asInstanceOf[c.universe.Position]
- }
- private val fragPos = posFor(literalPos.point + frag.offset)
- private def fragPosFor(offset: Int) = {
- posFor(fragPos.point + offset)
- }
- def at(t: Tree, offset: Int) = {
- val res = atPos(fragPosFor(offset))(t)
- res
- }
- def set(t: Tree, offset: Int) = {
- c.internal.setPos(t, fragPosFor(offset))
- t
- }
+ def compileChain(code: String, parts: Seq[Ast.Chain.Sub], offset: Int): c.Tree = {
+ parts.foldLeft(c.parse(code)){
+ case (curr, Ast.Chain.Prop(str, offset2)) => q"$curr.${TermName(str)}"
+ case (curr, Ast.Chain.Args(str, offset2)) =>
+ val Apply(fun, args) = c.parse(s"omg$str")
+ Apply(curr, args)
+ case (curr, Ast.Chain.TypeArgs(str, offset2)) =>
+ val TypeApply(fun, args) = c.parse(s"omg$str")
+ TypeApply(curr, args)
}
-
-
-// println(s"${frag.offset}\n${literalPos.point}\n${pos.point}\n$frag\n")
-
- val f: Tree = frag match {
- case WN.Plain(text, offset) => fragPos.at(q"$text", 0)
- case WN.Display(exp, offset) => compileTree(exp)
- case WN.Comment(msg, offset) => q""
- case WN.ScalaExp(Seq(WN.Simple(first, _), WN.Block(ws, args, content, _)), offset)
- if first.startsWith("for(") =>
- val fresh = c.fresh()
- val skeleton: Tree = c.parse(first + s"{$fresh}").asInstanceOf[Apply]
-// println("FIRST " + first)
- skeleton.foreach{x =>
- x
- if (x.pos != NoPosition) fragPos.set(x, x.pos.point + 1)
- }
- val b = content.map(compileTree(_))
- def rec(t: Tree): Tree = t match {
- case a @ Apply(fun, List(f @ Function(vparams, body))) =>
- val f2 = Function(vparams, rec(body))
- val a2 = Apply(fun, List(f2))
- atPos(a.pos)(a2)
- atPos(f.pos)(f2)
- a2
- case Ident(x: TermName) if x.decoded == fresh =>
- q"Seq[$fragType](..$b)"
- }
- rec(skeleton)
-
- case WN.ScalaExp(WN.Simple(first, _) +: WN.Block(_, None, content1, _) +: rest, offset)
- if first.startsWith("if(") =>
-
- val b1 = content1.map(compileTree(_))
- val tree = c.parse(first + "{}").asInstanceOf[If]
- tree.foreach{x =>
- fragPos.set(x, x.pos.point + 1)
- }
- val If(cond, _, _) = tree
- val b2 = rest match{
- case Seq(WN.Simple(next, _), WN.Block(_, None, content2, _)) =>
- content2.map(compileTree(_))
- case Seq() => Nil
- }
- q"if($cond){ Seq[$fragType](..$b1): $fragType } else { Seq[$fragType](..$b2): $fragType }"
-
- case xx @ WN.ScalaExp(WN.Simple(first, _) +: rest, offset) =>
-
- val firstTree = c.parse(first)
-
- firstTree.foreach{x =>
- fragPos.set(x, x.pos.point)
- }
-
- val s = rest.foldLeft[Tree](firstTree) {
- case (l, WN.Simple(code, _)) =>
-
- val fresh = c.fresh()
-
- val snippet = s"$fresh$code"
- val skeleton = c.parse(snippet)
-
- def rec(t: Tree): Tree = {
-
- val res = t match {
- case Apply(fun, args) =>
- for(arg <- args; tree <- arg if tree.pos != NoPosition){
- fragPos.set(tree, tree.pos.point + first.length - fresh.length)
- }
-
- Apply(rec(fun), args)
- case Select(qualifier, name) => Select(rec(qualifier), name)
- case Ident(x: TermName) if x.decoded == fresh => l
- }
- fragPos.at(res, t.pos.point + first.length - fresh.length)
-// println(Position.formatMessage(newPos.asInstanceOf[scala.reflect.internal.util.Position], "", true))
- res
- }
- rec(skeleton)
-
- case (l, WN.Block(ws, None, content, offset)) =>
- val contentTrees = content.map(compileTree(_))
- fragPos.at(q"$l(..$contentTrees)", offset)
- case (l, WN.Block(ws, Some(args), content, offset)) =>
-
- val snippet = s"{$args ()}"
- val skeleton = c.parse(snippet)
-
- val Function(vparams, body) = skeleton
-
- vparams.map(_.foreach { t =>
- if (t.pos != NoPosition)
- fragPos.set(t, t.pos.point)
- })
- val contentTrees = content.map{compileTree(_)}
-
- val func = Function(vparams, q"Seq[$fragType](..$contentTrees)")
- fragPos.at(func, skeleton.pos.point)
- val res = q"$l($func)"
- fragPos.at(res, offset)
- res
- }
-
- s
- }
- f
}
-
- def compileTemplate(tmpl: WN.Template): Tree = {
- val WN.Template(name, comment, params, topImports, imports, subs, content, offset) = tmpl
- val fullName = if (name.toString == "") c.fresh("outer") else name
-
- val DefDef(mods, realName, tparams, vparamss, tpt, rhs) = {
- val snippet = s"def $fullName$params = {}"
- val z = c.parse(snippet).asInstanceOf[DefDef]
- z
+ def compileBlock(parts: Seq[Ast.Block.Sub], offset: Int): c.Tree = {
+ val compiledParts = parts.map{
+ case Ast.Block.Text(str, offset2) => q"$str"
+ case Ast.Chain(code, parts, offset) => compileChain(code, parts, offset)
}
-
- val innerDefs = subs.map(compileTemplate(_))
-
- val body = atPos(literalPos)(q"""{
- ..${topImports.map(i => c.parse(i.code))}
- ..$innerDefs
-
- Seq[scalatags.Text.all.Frag](..${content.map(compileTree(_))})
- }""")
-
- if (name.toString == "") body
- else DefDef(mods, realName, tparams, vparamss, tpt, body)
+ q"Seq[$fragType](..$compiledParts)"
}
-
- atPos(literalPos)(q"${compileTemplate(template)}")
+ ???
+// def compileTree(frag: Ast): Tree = frag match{
+// case Ast.Block(parts, offset)
+//
+//// println(s"${frag.offset}\n${literalPos.point}\n${pos.point}\n$frag\n")
+//
+// val f: Tree = frag match {
+// case Ast.Block.Text(text, offset) => fragPos.at(q"$text", 0)
+// case WN.Display(exp, offset) => compileTree(exp)
+// case WN.Comment(msg, offset) => q""
+// case WN.ScalaExp(Seq(WN.Simple(first, _), WN.Block(ws, args, content, _)), offset)
+// if first.startsWith("for(") =>
+// val fresh = c.fresh()
+// val skeleton: Tree = c.parse(first + s"{$fresh}").asInstanceOf[Apply]
+//// println("FIRST " + first)
+// skeleton.foreach{x =>
+// x
+// if (x.pos != NoPosition) fragPos.set(x, x.pos.point + 1)
+// }
+// val b = content.map(compileTree(_))
+// def rec(t: Tree): Tree = t match {
+// case a @ Apply(fun, List(f @ Function(vparams, body))) =>
+// val f2 = Function(vparams, rec(body))
+// val a2 = Apply(fun, List(f2))
+// atPos(a.pos)(a2)
+// atPos(f.pos)(f2)
+// a2
+// case Ident(x: TermName) if x.decoded == fresh =>
+// q"Seq[$fragType](..$b)"
+// }
+// rec(skeleton)
+//
+// case WN.ScalaExp(WN.Simple(first, _) +: WN.Block(_, None, content1, _) +: rest, offset)
+// if first.startsWith("if(") =>
+//
+// val b1 = content1.map(compileTree(_))
+// val tree = c.parse(first + "{}").asInstanceOf[If]
+// tree.foreach{x =>
+// fragPos.set(x, x.pos.point + 1)
+// }
+// val If(cond, _, _) = tree
+// val b2 = rest match{
+// case Seq(WN.Simple(next, _), WN.Block(_, None, content2, _)) =>
+// content2.map(compileTree(_))
+// case Seq() => Nil
+// }
+// q"if($cond){ Seq[$fragType](..$b1): $fragType } else { Seq[$fragType](..$b2): $fragType }"
+//
+// case xx @ WN.ScalaExp(WN.Simple(first, _) +: rest, offset) =>
+//
+// val firstTree = c.parse(first)
+//
+// firstTree.foreach{x =>
+// fragPos.set(x, x.pos.point)
+// }
+//
+// val s = rest.foldLeft[Tree](firstTree) {
+// case (l, WN.Simple(code, _)) =>
+//
+// val fresh = c.fresh()
+//
+// val snippet = s"$fresh$code"
+// val skeleton = c.parse(snippet)
+//
+// def rec(t: Tree): Tree = {
+//
+// val res = t match {
+// case Apply(fun, args) =>
+// for(arg <- args; tree <- arg if tree.pos != NoPosition){
+// fragPos.set(tree, tree.pos.point + first.length - fresh.length)
+// }
+//
+// Apply(rec(fun), args)
+// case Select(qualifier, name) => Select(rec(qualifier), name)
+// case Ident(x: TermName) if x.decoded == fresh => l
+// }
+// fragPos.at(res, t.pos.point + first.length - fresh.length)
+//// println(Position.formatMessage(newPos.asInstanceOf[scala.reflect.internal.util.Position], "", true))
+// res
+// }
+// rec(skeleton)
+//
+// case (l, WN.Block(ws, None, content, offset)) =>
+// val contentTrees = content.map(compileTree(_))
+// fragPos.at(q"$l(..$contentTrees)", offset)
+//
+// case (l, WN.Block(ws, Some(args), content, offset)) =>
+//
+// val snippet = s"{$args ()}"
+// val skeleton = c.parse(snippet)
+//
+// val Function(vparams, body) = skeleton
+//
+// vparams.map(_.foreach { t =>
+// if (t.pos != NoPosition)
+// fragPos.set(t, t.pos.point)
+// })
+// val contentTrees = content.map{compileTree(_)}
+//
+// val func = Function(vparams, q"Seq[$fragType](..$contentTrees)")
+// fragPos.at(func, skeleton.pos.point)
+// val res = q"$l($func)"
+// fragPos.at(res, offset)
+// res
+// }
+//
+// s
+// }
+// f
+// }
+//
+// def compileTemplate(tmpl: WN.Template): Tree = {
+// val WN.Template(name, comment, params, topImports, imports, subs, content, offset) = tmpl
+// val fullName = if (name.toString == "") c.fresh("outer") else name
+//
+// val DefDef(mods, realName, tparams, vparamss, tpt, rhs) = {
+// val snippet = s"def $fullName$params = {}"
+// val z = c.parse(snippet).asInstanceOf[DefDef]
+// z
+// }
+//
+// val innerDefs = subs.map(compileTemplate(_))
+//
+// val body = atPos(literalPos)(q"""{
+// ..${topImports.map(i => c.parse(i.code))}
+// ..$innerDefs
+//
+// Seq[scalatags.Text.all.Frag](..${content.map(compileTree(_))})
+// }""")
+//
+// if (name.toString == "") body
+// else DefDef(mods, realName, tparams, vparamss, tpt, body)
+// }
+//
+// atPos(literalPos)(q"${compileTemplate(template)}")
}
} \ No newline at end of file
diff --git a/scalatexApi/src/main/scala/scalatex/stages/IndentHandler.scala b/scalatexApi/src/main/scala/scalatex/stages/IndentHandler.scala
deleted file mode 100644
index a39b498..0000000
--- a/scalatexApi/src/main/scala/scalatex/stages/IndentHandler.scala
+++ /dev/null
@@ -1,111 +0,0 @@
-package scalatex.stages
-
-import acyclic.file
-/**
- * Implements the offside-rule for Twirlite fragments.
- *
- * This stage walks over the whitespace before each line of the Twirlite,
- * augmenting it with the corresponding open-{ and close-} such that an
- * un-modified Twirl parser can then parse it.
- *
- * The rule is simple: any line which starts with an expression potentially
- * opens a new block. Any text after the end of the expression is inside the
- * block, as well as any text on subsequent lines which are indented more
- * deeply.
- */
-object IndentHandler extends (String => String){
- def noBraceLine(remainder: String) = {
- !remainder.trim.headOption.exists("{(".toSeq.contains)
- }
-
- def successRemainder(r: Parser.ParseResult[_]) = r match {
- case Parser.Error(_, _) => None
- case Parser.Success(_, input) => Some(input.source().take(input.offset()))
- }
-
- def apply(input: String): String = {
-
- val lines = "" :: input.lines.toList ::: List("")
- val tails = lines.tails.takeWhile(_ != Nil)
-
- def getIndent(rest: List[String]): (Int, String) = rest match {
- case Nil => (0, "")
- case x if x.head.trim() != "" => (x.head.takeWhile(_ == ' ').length, x.head)
- case _ => getIndent(rest.tail)
- }
- val min = input.lines
- .map(_.indexWhere(_ != ' '))
- .filter(_ != -1)
- .min
-
- val linesOut = tails.foldLeft((min :: Nil, Seq[String]())){ (x, tail) =>
- val (stack, text) = x
- val spacedCurrent :: next = tail
- val (spaces, current) = spacedCurrent.splitAt(
- math.max(spacedCurrent.indexWhere(_ != ' '), 0)
- )
-
-// println("index " + math.max(spacedCurrent.indexWhere(_ != ' '), 0))
-// println(spaces, current)
-
- val declRemainder = successRemainder(Parser.parse(current.trim, _.templateDeclaration()))
-// println("::::::" + current.trim)
-// println(successRemainder(Parser.parse(current.trim, _.expression())))
- val exprRemainder = successRemainder(Parser.parse(current.trim, _.expression())).filter {
- // Either it takes up the entire line or the line ends with a =>, in which case
- // we assume the guy wanted to pass a lambda
- x =>
-// println(x)
- x == current.trim || current.trim.endsWith("=>")
- }
-
-
- /**
- * Whether or not the line starts with some sort of indent-start-marker, and
- * if it does what the remainder of the line contains
- */
- val headerSplit: Option[(String, String)] = {
- if (current.startsWith("@import ")) None
- else
- declRemainder.orElse(exprRemainder)
- .map(r => (r, current.trim.drop(r.length)))
- }
-
- val (nextIndent, nextLine) = getIndent(next)
- val indent = spaces.length
- val nextIsElse = nextLine.drop(nextIndent).take("@else ".length).trim.startsWith("@else")
-
- val elseCorrection = if (nextIsElse) 1 else 0
-
- val delta =
- if (nextIndent > indent && headerSplit.map(_._2).exists(noBraceLine)) 1
- else -stack.takeWhile(_ > nextIndent).length
-
- val baseLine: String = headerSplit match {
- case None => current
- case Some((before, after)) =>
-
- val newFirst =
- if (!before.startsWith("@else")) before
- else before.dropRight("@else".length)+ "} else"
-// println(before, after, delta)
- if (delta > 0 && noBraceLine(after)) newFirst + "{" + after
- else if (delta <= 0 && noBraceLine(after) && after.trim != "") newFirst + "{" + after + "}" * (1 - elseCorrection)
- else current
- }
-
-
- val closing = "}" * (-delta - elseCorrection)
-
- val newStack =
- if (delta > 0) nextIndent :: stack
- else stack.drop(-delta)
-// println(stack.toString.padTo(15, ' ') + current.padTo(15, ' ') + indent + "\t" + nextIndent + "\t" + delta + "\t" + headerSplit.toString.padTo(15, ' ') + (baseLine + closing))
- (newStack, text :+ (spaces + baseLine + closing))
- }
-
- val res = linesOut._2.mkString("\n")
-// println(res)
- res
- }
-}
diff --git a/scalatexApi/src/main/scala/scalatex/stages/Parser.scala b/scalatexApi/src/main/scala/scalatex/stages/Parser.scala
deleted file mode 100644
index c83197d..0000000
--- a/scalatexApi/src/main/scala/scalatex/stages/Parser.scala
+++ /dev/null
@@ -1,790 +0,0 @@
-package scalatex.stages
-
-import acyclic.file
-import scala.annotation.elidable
-import scala.annotation.elidable._
-import scala.Some
-
-import scala.collection.mutable.{ArrayBuffer, Buffer, ListBuffer}
-
-object TwistNodes{
- trait Positioned{
- def offset: Int
- }
- abstract class TemplateTree extends Positioned
- abstract class ScalaExpPart extends Positioned
-
- case class Params(code: String, offset: Int) extends Positioned
- case class Template(name: PosString, comment: Option[Comment], params: PosString, topImports: Seq[Simple], imports: Seq[Simple], sub: Seq[Template], content: Seq[TemplateTree], offset: Int) extends Positioned
- case class PosString(str: String, offset: Int) extends Positioned {
- override def toString = str
- }
- case class Plain(text: String, offset: Int) extends TemplateTree with Positioned
- case class Display(exp: ScalaExp, offset: Int) extends TemplateTree with Positioned
- case class Comment(msg: String, offset: Int) extends TemplateTree with Positioned
- case class ScalaExp(parts: Seq[ScalaExpPart], offset: Int) extends TemplateTree with Positioned
- case class Simple(code: String, offset: Int) extends ScalaExpPart with Positioned
- case class Block(whitespace: String, args: Option[PosString], content: Seq[TemplateTree], offset: Int) extends ScalaExpPart with Positioned
-}
-
-import TwistNodes._
-
-object Parser extends (String => Template){
- def apply(source: String) = {
- Parser.parse(source, _.template()) match{
- case Parser.Success(tmpl: Template, input) => tmpl
- case Parser.Error(input, errors) => throw new Exception("Parsing Failed " + errors.mkString("\n"))
- }
- }
-
- def parse[T](source: String, f: Parser => T): ParseResult[T] = {
- val p = new Parser
-
- // Initialize mutable state
-
- p.input.reset(source)
- p.errorStack.clear()
- val res = f(p)
-
- if (p.errorStack.length == 0 && res != null) Success(res, p.input)
- else Error(p.input, p.errorStack.toList)
- }
-
- sealed abstract class ParseResult[+T]{
- def toOption: Option[T]
- }
- case class Success[+T](t: T, input: Input) extends ParseResult[T]{
- def toOption = Some(t)
- }
- case class Error(input: Input, errors: List[PosString]) extends ParseResult[Nothing]{
- def toOption = None
- }
-
- case class Input() {
- private var offset_ = 0
- private var source_ = ""
- private var length_ = 1
- val regressionStatistics = new collection.mutable.HashMap[String, (Int, Int)]
-
- /** Peek at the current input. Does not check for EOF. */
- def apply(): Char = source_.charAt(offset_)
-
- /**
- * Peek `length` characters ahead. Does not check for EOF.
- * @return string from current offset upto current offset + `length`
- */
- def apply(length: Int): String = source_.substring(offset_, (offset_ + length))
-
- /** Equivalent to `input(str.length) == str`. Does not check for EOF. */
- def matches(str: String): Boolean = {
- var i = 0;
- val l = str.length
- while (i < l) {
- if (source_.charAt(offset_ + i) != str.charAt(i))
- return false
- i += 1
- }
- true
- }
-
- /** Advance input by one character */
- def advance(): Unit = offset_ += 1
-
- /** Advance input by `increment` number of characters */
- def advance(increment: Int): Unit = offset_ += increment
-
- /** Backtrack by `decrement` numner of characters */
- def regress(decrement: Int): Unit = offset_ -= decrement
-
- /** Backtrack to a known offset */
- def regressTo(offset: Int): Unit = {
- @noinline @elidable(INFO)
- def updateRegressionStatistics() = {
- val distance = offset_ - offset
- val methodName = Thread.currentThread().getStackTrace()(2).getMethodName()
- val (count, charAccum) = regressionStatistics.get(methodName) getOrElse ((0, 0))
- regressionStatistics(methodName) = (count + 1, charAccum + distance)
- }
-
- offset_ = offset
- }
-
- def isPastEOF(len: Int): Boolean = (offset_ + len-1) >= length_
-
- def isEOF() = isPastEOF(1)
-
- def atEnd() = isEOF()
-
- def offset() = offset_
-
- def source() = source_
-
- /** Reset the input to have the given contents */
- def reset(source: String) {
- offset_ = 0
- source_ = source
- length_ = source.length()
- regressionStatistics.clear()
- }
- }
-}
-class Parser{
- import Parser._
-
- val input: Input = new Input
- val errorStack: ListBuffer[PosString] = ListBuffer()
-
- /**
- * Try to match `str` and advance `str.length` characters.
- *
- * Reports an error if the input does not match `str` or if `str.length` goes past the EOF.
- */
- def accept(str: String): Unit = {
- val len = str.length
- if (!input.isPastEOF(len) && input.matches(str))
- input.advance(len)
- else
- error("Expected '" + str + "' but found '" + (if (input.isPastEOF(len)) "EOF" else input(len)) + "'")
- }
-
- /**
- * Does `f` applied to the current peek return true or false? If true, advance one character.
- *
- * Will not advance if at EOF.
- *
- * @return true if advancing, false otherwise.
- */
- def check(f: Char => Boolean): Boolean = {
- if (!input.isEOF() && f(input())) {
- input.advance()
- true
- } else false
- }
-
- /**
- * Does the current input match `str`? If so, advance `str.length`.
- *
- * Will not advance if `str.length` surpasses EOF
- *
- * @return true if advancing, false otherwise.
- */
- def check(str: String): Boolean = {
- val len = str.length
- if (!input.isPastEOF(len) && input.matches(str)){
- input.advance(len)
- true
- } else false
- }
-
- def error(message: String, offset: Int = input.offset): Unit = {
- errorStack += PosString(message, offset)
- }
-
- /** Consume/Advance `length` characters, and return the consumed characters. Returns "" if at EOF. */
- def any(length: Int = 1): String = {
- if (input.isEOF()) {
- error("Expected more input but found 'EOF'")
- ""
- } else {
- val s = input(length)
- input.advance(length)
- s
- }
- }
-
- /**
- * Consume characters until input matches `stop`
- *
- * @param inclusive - should stop be included in the consumed characters?
- * @return the consumed characters
- */
- def anyUntil(stop: String, inclusive: Boolean): String = {
- var sb = new StringBuilder
- while (!input.isPastEOF(stop.length) && !input.matches(stop))
- sb.append(any())
- if (inclusive && !input.isPastEOF(stop.length))
- sb.append(any(stop.length))
- sb.toString()
- }
-
- /**
- * Consume characters until `f` returns false on the peek of input.
- *
- * @param inclusive - should the stopped character be included in the consumed characters?
- * @return the consumed characters
- */
- def anyUntil(f: Char => Boolean, inclusive: Boolean): String = {
- var sb = new StringBuilder
- while (!input.isEOF() && f(input()) == false)
- sb.append(any())
- if (inclusive && !input.isEOF())
- sb.append(any())
- sb.toString
- }
-
- /** Recursively match pairs of prefixes and suffixes and return the consumed characters
- *
- * Terminates at EOF.
- */
- def recursiveTag(prefix: String, suffix: String, allowStringLiterals: Boolean = false): String = {
- if (check(prefix)) {
- var stack = 1
- val sb = new StringBuffer
- sb.append(prefix)
- while (stack > 0) {
- if (check(prefix)) {
- stack += 1
- sb.append(prefix)
- } else if (check(suffix)) {
- stack -= 1
- sb.append(suffix)
- } else if (input.isEOF()) {
- error("Expected '" + suffix + "' but found 'EOF'")
- stack = 0
- } else if (allowStringLiterals) {
- stringLiteral("\"", "\\") match {
- case null => sb.append(any())
- case s => sb.append(s)
- }
- } else {
- sb.append(any())
- }
- }
- sb.toString()
- } else null
- }
-
- /**
- * Match a string literal, allowing for escaped quotes.
- * Terminates at EOF.
- */
- def stringLiteral(quote: String, escape: String): String = {
- if (check(quote)) {
- var within = true
- val sb = new StringBuffer
- sb.append(quote)
- while (within) {
- if (check(quote)) { // end of string literal
- sb.append(quote)
- within = false
- } else if (check(escape)) {
- sb.append(escape)
- if (check(quote)) { // escaped quote
- sb.append(quote)
- } else if (check(escape)) { // escaped escape
- sb.append(escape)
- }
- } else if (input.isEOF()) {
- error("Expected '" + quote + "' but found 'EOF'")
- within = false
- } else {
- sb.append(any())
- }
- }
- sb.toString()
- } else null
- }
-
- /** Match zero or more `parser` */
- def several[T, BufferType <: Buffer[T]](parser: () => T, provided: BufferType = null)(implicit manifest: Manifest[BufferType]): BufferType = {
-
- val ab = if (provided != null) provided else manifest.runtimeClass.newInstance().asInstanceOf[BufferType]
- var parsed = parser()
- while (parsed != null) {
- ab += parsed
- parsed = parser()
- }
- ab
- }
-
- def parentheses(): String = recursiveTag("(", ")", allowStringLiterals = true)
-
- def squareBrackets(): String = recursiveTag("[", "]")
-
- def whitespace(): String = anyUntil(_ > '\u0020', inclusive = false)
-
- // not completely faithful to original because it allows for zero whitespace
- def whitespaceNoBreak(): String = anyUntil(c => c != ' ' && c != '\t', inclusive = false)
-
- def identifier(): String = {
- var result: String = null
- // TODO: should I be checking for EOF here?
- if (!input.isEOF() && Character.isJavaIdentifierStart(input())) {
- result = anyUntil(Character.isJavaIdentifierPart(_) == false, false)
- }
- result
- }
-
- def comment(): Comment = {
- val pos = input.offset
- if (check("@*")) {
- val text = anyUntil("*@", inclusive = false)
- accept("*@")
- Comment(text, pos)
- } else null
- }
-
- def startArgs(): String = {
- val result = several[String, ArrayBuffer[String]](parentheses)
- if (result.length > 0)
- result.mkString
- else
- null
- }
-
- def importExpression(): Simple = {
- val p = input.offset
- if (check("@import "))
- Simple("import " + anyUntil("\n", inclusive = true).trim, p+1) // don't include position of @
- else null
- }
-
- def scalaBlock(): Simple = {
- if (check("@{")) {
- input.regress(1); // we need to parse the '{' via 'brackets()'
- val p = input.offset
- brackets() match {
- case null => null
- case b => Simple(b, p)
- }
- } else null
- }
-
- def brackets(): String = {
- var result = recursiveTag("{", "}")
- // mimicking how the original parser handled EOF for this rule
- if (result != null && result.last != '}')
- result = null
- result
- }
-
- def mixed(): ListBuffer[TemplateTree] = {
- // parses: comment | scalaBlockDisplayed | forExpression | matchExpOrSafeExprOrExpr | caseExpression | plain
- def opt1(): ListBuffer[TemplateTree] = {
- val t =
- comment() match {
- case null => scalaBlockDisplayed() match {
- case null => forExpression match {
- case null => matchExpOrSafeExpOrExpr() match {
- case null => caseExpression() match {
- case null => plain()
- case x => x
- }
- case x => x
- }
- case x => x
- }
- case x => x
- }
- case x => x
- }
- if (t != null) ListBuffer(t)
- else null
- }
-
- // parses: '{' mixed* '}'
- def opt2(): ListBuffer[TemplateTree] = {
- val lbracepos = input.offset()
- if (check("{")) {
- var buffer = new ListBuffer[TemplateTree]
- buffer += Plain("{", lbracepos)
- for (m <- several[ListBuffer[TemplateTree], ListBuffer[ListBuffer[TemplateTree]]](mixed))
- buffer = buffer ++ m // creates a new object, but is constant in time, as opposed to buffer ++= m which is linear (proportional to size of m)
- val rbracepos = input.offset
- if (check("}"))
- buffer += Plain("}", rbracepos)
- else
- error("Expected ending '}'")
- buffer
- } else null
- }
-
- opt1() match {
- case null => opt2()
- case x => x
- }
- }
-
- def scalaBlockDisplayed(): Display = {
- val sb = scalaBlock()
-
- if (sb != null)
- Display(ScalaExp(sb :: Nil, sb.offset), sb.offset)
- else
- null
- }
-
- def blockArgs(): PosString = {
- val p = input.offset
- val result = anyUntil("=>", true)
- if (result.endsWith("=>") && !result.contains("\n"))
- PosString(result, p)
- else {
- input.regress(result.length())
- null
- }
- }
-
- def block(): Block = {
- var result: Block = null
- val p = input.offset
- val ws = whitespaceNoBreak()
- if (check("{")) {
- val blkArgs = Option(blockArgs())
- val mixeds = several[ListBuffer[TemplateTree], ListBuffer[ListBuffer[TemplateTree]]](mixed)
- accept("}")
- // TODO - not use flatten here (if it's a performance problem)
- result = Block(ws, blkArgs, mixeds.flatten, p)
- } else {
- input.regressTo(p)
- }
-
- result
- }
-
- def caseExpression(): TemplateTree = {
- var result: TemplateTree = null
-
- val wspos = input.offset
- val ws = whitespace()
- val p = input.offset()
- if (check("case ")) {
- val pattern = Simple("case " + anyUntil("=>", inclusive = true), p)
- val blk = block()
- if (blk != null) {
- result = ScalaExp(ListBuffer(pattern, blk), blk.offset)
- whitespace()
- } else {
- //error("Expected block after 'case'")
- input.regressTo(wspos)
- }
- } else if (ws.length > 0) {
- // We could regress here and not return something for the ws, because the plain production rule
- // would parse this, but this would actually be a hotspot for backtracking, so let's return it
- // here seeing as it's been parsed all ready.
- result = Plain(ws, wspos)
- }
-
- result
- }
-
- def matchExpOrSafeExpOrExpr(): Display = {
- val resetPosition = input.offset
- val result =
- expression() match {
- case null => safeExpression()
- case x => x
- }
-
- if (result != null) {
- val exprs = result.exp.parts.asInstanceOf[ListBuffer[ScalaExpPart]]
- val mpos = input.offset
- val ws = whitespaceNoBreak()
- if (check("match")) {
- val m = Simple(ws + "match", mpos)
- val blk = block()
- if (blk != null) {
- exprs.append(m)
- exprs.append(blk)
- } else {
- // error("expected block after match")
- input.regressTo(mpos)
- }
- } else {
- input.regressTo(mpos)
- }
- }
-
- result
- }
-
- def forExpression(): Display = {
- var result: Display = null
- val p = input.offset
- if (check("@for")) {
- val parens = parentheses()
- if (parens != null) {
- val blk = block()
- if (blk != null) {
- result = Display(ScalaExp(ListBuffer(Simple("for" + parens + " yield ", p+1), blk), p+1), p+1) // don't include pos of @
- }
- }
- }
-
- if (result == null)
- input.regressTo(p)
-
- result
- }
-
- def safeExpression(): Display = {
- if (check("@(")) {
- input.regress(1)
- val p = input.offset
- Display(ScalaExp(ListBuffer(Simple(parentheses(), p)), p), p)
- } else null
- }
-
- def plain(): Plain = {
- def single(): String = {
- if (check("@@")) "@"
- else if (!input.isEOF() && input() != '@' && input() != '}' && input() != '{') any()
- else null
- }
- val p = input.offset
- var result: Plain = null
- var part = single()
- if (part != null) {
- val sb = new StringBuffer
- while (part != null) {
- sb.append(part)
- part = single()
- }
- result = Plain(sb.toString(), p)
- }
-
- result
- }
-
- def expression(): Display = {
- var result: Display = null
- if (check("@")) {
- val pos = input.offset
- val code = methodCall()
- if (code != null) {
- val parts = several[ScalaExpPart, ListBuffer[ScalaExpPart]](expressionPart)
- parts.prepend(Simple(code, pos))
- result = Display(ScalaExp(parts, pos-1), pos-1)
- } else {
- input.regressTo(pos - 1) // don't consume the @ if we fail
- }
- }
-
- result
- }
-
- def methodCall(): String = {
- val name = identifier()
- if (name != null) {
- val sb = new StringBuffer(name)
- sb.append(Option(squareBrackets) getOrElse "")
- sb.append(Option(parentheses) getOrElse "")
- sb.toString()
- } else null
- }
-
- def expressionPart(): ScalaExpPart = {
- def simpleParens() = {
- val p = input.offset
- val parens = parentheses()
- if (parens != null) Simple(parens, p)
- else null
- }
-
- def wsThenScalaBlockChained() = {
- val reset = input.offset
- val ws = whitespaceNoBreak()
- val chained = scalaBlockChained()
- if (chained eq null) input.regressTo(reset)
- chained
- }
-
- chainedMethods() match {
- case null => block() match {
- case null => wsThenScalaBlockChained() match {
- case null => elseCall() match {
- case null => simpleParens()
- case x => x
- }
- case x => x
- }
- case x => x
- }
- case x => x
- }
- }
-
- def scalaBlockChained(): Block = {
- val blk = scalaBlock()
- if (blk != null)
- Block("", None, ListBuffer(ScalaExp(ListBuffer(blk), blk.offset)), blk.offset)
- else null
- }
-
- def chainedMethods(): Simple = {
- val p = input.offset
- var result: Simple = null
- if (check(".")) {
- val firstMethodCall = methodCall()
- if (firstMethodCall != null) {
- val sb = new StringBuffer("." + firstMethodCall)
- var done = false
- while (!done) {
- val reset = input.offset
- var nextLink: String = null
- if (check(".")) {
- methodCall() match {
- case m: String => nextLink = m
- case _ =>
- }
- }
-
- nextLink match {
- case null => {
- done = true
- input.regressTo(reset)
- }
- case _ => {
- sb.append(".")
- sb.append(nextLink)
- }
- }
- }
-
- result = Simple(sb.toString(), p)
- } else input.regressTo(p)
- }
-
- result
- }
-
- def elseCall(): Simple = {
- val reset = input.offset
- whitespaceNoBreak()
- val p = input.offset
- if (check("else")) {
- whitespaceNoBreak()
- Simple("else", p)
- } else {
- input.regressTo(reset)
- null
- }
- }
-
- def template(): Template = {
- val topImports = extraImports()
- whitespace()
- val commentpos = input.offset
- val cm = Option(comment()).map(_.copy(offset = commentpos))
- whitespace()
- val args =
- if (check("@(")) {
- input.regress(1)
- val p = input.offset
- val args = startArgs()
- if (args != null) Some(PosString(args, p))
- else None
- } else None
- val (imports, templates, mixeds) = templateContent()
-
- Template(PosString("", 0), cm, args.getOrElse(PosString("()", 0)), topImports, imports, templates, mixeds, 0)
- }
- def subTemplate(): Template = {
- var result: Template = null
- val resetPosition = input.offset
- val templDecl = templateDeclaration()
- if (templDecl != null) {
- anyUntil(c => c != ' ' && c != '\t', inclusive = false)
- if (check("{")) {
- val (imports, templates, mixeds) = templateContent()
- if (check("}"))
- result = Template(templDecl._1, None, templDecl._2, Nil, imports, templates, mixeds, templDecl._1.offset)
- }
- }
-
- if (result == null)
- input.regressTo(resetPosition)
- result
- }
-
- def templateDeclaration(): (PosString, PosString) = {
- if (check("@")) {
- val namepos = input.offset
- val name = identifier() match {
- case null => null
- case id => PosString(id, namepos)
- }
-
- if (name != null) {
- val paramspos = input.offset
- val types = Option(squareBrackets) getOrElse ""
- val args = several[String, ArrayBuffer[String]](parentheses)
- val params = PosString(types + args.mkString, paramspos)
- if (params != null)
-
- anyUntil(c => c != ' ' && c != '\t', inclusive = false)
- if (check("=")) {
- return (name, params)
- }
- } else input.regress(1) // don't consume @
- }
-
- null
- }
-
- def templateContent(): (Seq[Simple], Seq[Template], Seq[TemplateTree]) = {
- val imports = new ArrayBuffer[Simple]
-
- val templates = new ArrayBuffer[Template]
- val mixeds = new ArrayBuffer[TemplateTree]
-
- var done = false
- while (!done) {
- val impExp = importExpression()
- if (impExp != null) imports += impExp
- else {
-
- val templ = subTemplate()
- if (templ != null) templates += templ
- else {
- val mix = mixed()
- if (mix != null) mixeds ++= mix
- else {
- // check for an invalid '@' symbol, and just skip it so we can continue the parse
- val pos = input.offset
- if (check("@")) error("Invalid '@' symbol", pos)
- else done = true
- }
- }
- }
- }
-
- (imports, templates, mixeds)
- }
-
- def extraImports(): Seq[Simple] = {
- val resetPosition = input.offset
- val imports = new ArrayBuffer[Simple]
-
- while (whitespace().nonEmpty || (comment() ne null)) {} // ignore
-
- var done = false
- while (!done) {
- val importExp = importExpression()
- if (importExp ne null) {
- imports += importExp
- whitespace()
- } else {
- done = true
- }
- }
-
- if (imports.isEmpty) {
- input.regressTo(resetPosition)
- }
-
- imports
- }
-
-
-
- def mkRegressionStatisticsString() {
- val a = input.regressionStatistics.toArray.sortBy { case (m, (c, a)) => c }
- a.mkString("\n")
- }
-
- // TODO - only for debugging purposes, remove before release
- def setSource(source: String) {
- input.reset(source)
- }
-} \ No newline at end of file
diff --git a/scalatexApi/src/test/scala/scalatex/ParserTests.scala b/scalatexApi/src/test/scala/scalatex/ParserTests.scala
index 35e7dbc..c28cdbd 100644
--- a/scalatexApi/src/test/scala/scalatex/ParserTests.scala
+++ b/scalatexApi/src/test/scala/scalatex/ParserTests.scala
@@ -26,38 +26,44 @@ object ParserTests extends utest.TestSuite{
}
'Code{
- * - check("@(1 + 1)", _.Code.run(), Code("(1 + 1)"))
- * - check("@{{1} + (1)}", _.Code.run(), Code("{{1} + (1)}"))
- * - check("@{val x = 1; 1}", _.Code.run(), Code("{val x = 1; 1}"))
- * - check("@{`{}}{()@`}", _.Code.run(), Code("{`{}}{()@`}"))
- }
+ * - check("@(1 + 1)lolsss\n", _.Code.run(), "(1 + 1)")
+ * - check("@{{1} + (1)} ", _.Code.run(), "{{1} + (1)}")
+ * - check("@{val x = 1; 1} ", _.Code.run(), "{val x = 1; 1}")
+ * - check("@{`{}}{()@`}\n", _.Code.run(), "{`{}}{()@`}")
+ * - check("@gggg ", _.Code.run(), "gggg")
+ }
'Block{
* - check("{i am a cow}", _.TBlock.run(), Block(Seq(Block.Text("i am a cow"))))
* - check("{i @am a @cow}", _.TBlock.run(),
Block(Seq(
Block.Text("i "),
- Chain(Code("am"),Seq()),
+ Chain("am",Seq()),
Block.Text(" a "),
- Chain(Code("cow"),Seq())
+ Chain("cow",Seq())
))
)
}
'Chain{
- * - check("@omg.bbq[omg].fff[fff](123)", _.ScalaChain.run(),
- Chain(Code("omg"),Seq(Chain.Prop(".bbq[omg]"), Chain.Prop(".fff[fff]"), Chain.Args("(123)")))
+ * - check("@omg.bbq[omg].fff[fff](123) ", _.ScalaChain.run(),
+ Chain("omg",Seq(
+ Chain.Prop(".bbq"),
+ Chain.TypeArgs("[omg]"),
+ Chain.Prop(".fff"),
+ Chain.TypeArgs("[fff]"),
+ Chain.Args("(123)")
+ ))
)
- * - check("@omg{bbq}.cow(moo){a @b}", _.ScalaChain.run(),
- Chain(Code("omg"),Seq(
+ * - check("@omg{bbq}.cow(moo){a @b}\n", _.ScalaChain.run(),
+ Chain("omg",Seq(
Block(Seq(Block.Text("bbq"))),
Chain.Prop(".cow"),
Chain.Args("(moo)"),
- Block(Seq(Block.Text("a "), Chain(Code("b"), Nil)))
+ Block(Seq(Block.Text("a "), Chain("b", Nil)))
))
)
}
'Body{
-
* - check(
"""
|@omg
@@ -66,10 +72,10 @@ object ParserTests extends utest.TestSuite{
| @lol""".stripMargin,
_.Body.run(),
Block(Seq(
- Chain(Code("omg"),Seq(Block(Seq(
- Chain(Code("wtf"),Seq(Block(Seq(
- Chain(Code("bbq"),Seq(Block(Seq(
- Chain(Code("lol"),Seq(Block(Seq(
+ Chain("omg",Seq(Block(Seq(
+ Chain("wtf",Seq(Block(Seq(
+ Chain("bbq",Seq(Block(Seq(
+ Chain("lol",Seq(Block(Seq(
))))
))))
))))
@@ -83,12 +89,12 @@ object ParserTests extends utest.TestSuite{
|@bbq""".stripMargin,
_.Body.run(),
Block(Seq(
- Chain(Code("omg"),Seq(Block(
+ Chain("omg",Seq(Block(
Seq(
Text("\n "),
- Chain(Code("wtf"),Seq()))
+ Chain("wtf",Seq()))
))),
- Chain(Code("bbq"),
+ Chain("bbq",
Seq(Block(Seq()))
)
))
@@ -100,11 +106,11 @@ object ParserTests extends utest.TestSuite{
|bbq""".stripMargin,
_.Body.run(),
Block(Seq(
- Chain(Code("omg"),Seq(
+ Chain("omg",Seq(
Args("""("lol", 1, 2)"""),
Block(Seq(
Text("\n "),
- Chain(Code("wtf"),Seq())))
+ Chain("wtf",Seq())))
)),
Text("\n"),
Text("bbq")
@@ -120,7 +126,7 @@ object ParserTests extends utest.TestSuite{
|bbq""".stripMargin,
_.Body.run(),
Block(Seq(
- Chain(Code("omg"),Seq(
+ Chain("omg",Seq(
Args("(\"lol\",\n1,\n 2\n )"),
Block(Seq(
Text("\n "), Text("wtf")