summaryrefslogtreecommitdiff
path: root/scalatex
diff options
context:
space:
mode:
Diffstat (limited to 'scalatex')
-rw-r--r--scalatex/api/src/main/scala/scalatex/package.scala98
-rw-r--r--scalatex/api/src/main/scala/scalatex/stages/Compiler.scala103
-rw-r--r--scalatex/api/src/main/scala/scalatex/stages/Omg/scala.scala8
-rw-r--r--scalatex/api/src/main/scala/scalatex/stages/Parser.scala170
-rw-r--r--scalatex/api/src/main/scala/scalatex/stages/Trim.scala29
-rw-r--r--scalatex/api/src/test/scala/scalatex/BasicTests.scala468
-rw-r--r--scalatex/api/src/test/scala/scalatex/ErrorTests.scala373
-rw-r--r--scalatex/api/src/test/scala/scalatex/ParserTests.scala424
-rw-r--r--scalatex/api/src/test/scala/scalatex/TestUtil.scala16
-rw-r--r--scalatex/build.sbt44
-rwxr-xr-xscalatex/compilerPlugin/src/main/resources/scalac-plugin.xml4
-rwxr-xr-xscalatex/compilerPlugin/src/main/scala/scalatex/CompilerPlugin.scala79
-rw-r--r--scalatex/project/build.properties1
-rw-r--r--scalatex/project/build.sbt1
-rw-r--r--scalatex/scalaParser/src/main/scala/scalaParser/ScalaSyntax.scala416
-rw-r--r--scalatex/scalaParser/src/main/scala/scalaParser/syntax/Basic.scala51
-rw-r--r--scalatex/scalaParser/src/main/scala/scalaParser/syntax/Identifiers.scala35
-rw-r--r--scalatex/scalaParser/src/main/scala/scalaParser/syntax/Literals.scala58
-rw-r--r--scalatex/scalaParser/src/test/scala/scalaParser/SyntaxTest.scala448
-rw-r--r--scalatex/scalatexSbtPlugin/src/main/scala/SbtPlugin.scala16
20 files changed, 2842 insertions, 0 deletions
diff --git a/scalatex/api/src/main/scala/scalatex/package.scala b/scalatex/api/src/main/scala/scalatex/package.scala
new file mode 100644
index 0000000..1f13e63
--- /dev/null
+++ b/scalatex/api/src/main/scala/scalatex/package.scala
@@ -0,0 +1,98 @@
+import scala.reflect.internal.util.{BatchSourceFile, SourceFile, OffsetPosition}
+import scala.reflect.io.{PlainFile, AbstractFile}
+import scala.reflect.macros.{TypecheckException, Context}
+import scalatags.Text.all._
+import scalatex.stages.{Parser, Compiler}
+import scala.language.experimental.macros
+import acyclic.file
+
+package object scalatex {
+ /**
+ * Wraps the given string as a twist fragment.
+ */
+ def tw(expr: String): Frag = macro Internals.applyMacro
+ def twf(filename: String): Frag = macro Internals.applyMacroFile
+ object Internals {
+
+ def twRuntimeErrors(expr: String): Frag = macro applyMacroRuntimeErrors
+ def twDebug(expr: String): Frag = macro applyMacroDebug
+
+ def applyMacro(c: Context)(expr: c.Expr[String]): c.Expr[Frag] = applyMacroFull(c)(expr, false, false)
+ def applyMacroDebug(c: Context)(expr: c.Expr[String]): c.Expr[Frag] = applyMacroFull(c)(expr, false, true)
+
+ def applyMacroRuntimeErrors(c: Context)(expr: c.Expr[String]): c.Expr[Frag] = applyMacroFull(c)(expr, true, false)
+
+ def applyMacroFile(c: Context)(filename: c.Expr[String]): c.Expr[Frag] = {
+ import c.universe._
+ val fileName = filename.tree
+ .asInstanceOf[Literal]
+ .value
+ .value
+ .asInstanceOf[String]
+ val txt = io.Source.fromFile(fileName).mkString
+ val sourceFile = new BatchSourceFile(
+ new PlainFile(fileName),
+ txt.toCharArray
+ )
+
+ compileThing(c)(txt, sourceFile, 0, false, false)
+ }
+
+ case class DebugFailure(msg: String, pos: String) extends Exception(msg)
+
+ private[this] def applyMacroFull(c: Context)
+ (expr: c.Expr[String],
+ runtimeErrors: Boolean,
+ debug: Boolean)
+ : c.Expr[Frag] = {
+ import c.universe._
+ val scalatexFragment = expr.tree
+ .asInstanceOf[Literal]
+ .value
+ .value
+ .asInstanceOf[String]
+ val stringStart =
+ expr.tree
+ .pos
+ .lineContent
+ .drop(expr.tree.pos.column)
+ .take(2)
+ compileThing(c)(
+ scalatexFragment,
+ expr.tree.pos.source,
+ expr.tree.pos.point + (if (stringStart == "\"\"") 1 else -1),
+ runtimeErrors,
+ debug
+ )
+ }
+ }
+
+ def compileThing(c: Context)
+ (scalatexSource: String,
+ source: SourceFile,
+ point: Int,
+ runtimeErrors: Boolean,
+ debug: Boolean) = {
+ import c.universe._
+ def compile(s: String): c.Tree = {
+ val realPos = new OffsetPosition(source, point).asInstanceOf[c.universe.Position]
+
+ Compiler(c)(realPos, Parser.tupled(stages.Trim(s)))
+ }
+
+
+ import c.Position
+ try {
+ val compiled = compile(scalatexSource)
+ if (debug) println(compiled)
+ c.Expr[Frag](c.typeCheck(compiled))
+ } catch {
+ case e@TypecheckException(pos: Position, msg) =>
+ if (!runtimeErrors) c.abort(pos, msg)
+ else {
+ val posMsg = pos.lineContent + "\n" + (" " * pos.column) + "^"
+ c.Expr( q"""throw scalatex.Internals.DebugFailure($msg, $posMsg)""")
+ }
+ }
+ }
+}
diff --git a/scalatex/api/src/main/scala/scalatex/stages/Compiler.scala b/scalatex/api/src/main/scala/scalatex/stages/Compiler.scala
new file mode 100644
index 0000000..3df8da7
--- /dev/null
+++ b/scalatex/api/src/main/scala/scalatex/stages/Compiler.scala
@@ -0,0 +1,103 @@
+package scalatex
+package stages
+
+import acyclic.file
+
+import scala.reflect.macros.whitebox.Context
+import scala.reflect.internal.util.{Position, OffsetPosition}
+
+/**
+ * Walks the parsed AST, converting it into a structured Scala c.Tree
+ */
+object Compiler{
+
+ def apply(c: Context)(fragPos: c.Position, template: Ast.Block): c.Tree = {
+
+ import c.universe._
+ def fragType = tq"scalatags.Text.all.Frag"
+
+ def incPosRec(trees: c.Tree, offset: Int): trees.type = {
+
+ trees.foreach(incPos(_, offset))
+ trees
+ }
+ def incPos(tree: c.Tree, offset: Int): tree.type = {
+
+ val current = if (tree.pos == NoPosition) 0 else tree.pos.point
+ c.internal.setPos(tree,
+ new OffsetPosition(
+ fragPos.source,
+ offset + current + fragPos.point
+ ).asInstanceOf[c.universe.Position]
+ )
+ tree
+ }
+
+ def compileChain(code: String, parts: Seq[Ast.Chain.Sub], offset: Int): c.Tree = {
+
+ val out = parts.foldLeft(incPosRec(c.parse(code), offset + 1)){
+ case (curr, Ast.Chain.Prop(str, offset2)) =>
+ incPos(q"$curr.${TermName(str)}", offset2 + 1)
+ case (curr, Ast.Chain.Args(str, offset2)) =>
+ val Apply(fun, args) = c.parse(s"omg$str")
+ incPos(Apply(curr, args.map(incPosRec(_, offset2 - 2))), offset2)
+ case (curr, Ast.Chain.TypeArgs(str, offset2)) =>
+ val TypeApply(fun, args) = c.parse(s"omg$str")
+ incPos(TypeApply(curr, args.map(incPosRec(_, offset2 - 2))), offset2)
+ case (curr, Ast.Block(parts, offset1)) =>
+ incPos(q"$curr(..${compileBlock(parts, offset1)})", offset1)
+ case (curr, Ast.Header(header, block, offset1)) =>
+ incPos(q"$curr(${compileHeader(header, block, offset1)})", offset1)
+ }
+
+ out
+ }
+ def compileBlock(parts: Seq[Ast.Block.Sub], offset: Int): Seq[c.Tree] = {
+ val res = parts.map{
+ case Ast.Block.Text(str, offset1) =>
+ incPos(q"$str", offset1)
+ case Ast.Chain(code, parts, offset1) =>
+ compileChain(code, parts, offset1)
+ case Ast.Header(header, block, offset1) =>
+ compileHeader(header, block, offset1)
+ case Ast.Block.IfElse(condString, Ast.Block(parts2, offset2), elseBlock, offset1) =>
+ val If(cond, _, _) = c.parse(condString + "{}")
+ val elseCompiled = elseBlock match{
+ case Some(Ast.Block(parts3, offset3)) => compileBlockWrapped(parts3, offset3)
+ case None => EmptyTree
+ }
+
+ val res = If(incPosRec(cond, offset1 + 2), compileBlockWrapped(parts2, offset2), elseCompiled)
+
+ incPos(res, offset1)
+ res
+ case Ast.Block.For(generators, Ast.Block(parts2, offset2), offset1) =>
+ val fresh = c.fresh()
+
+ val tree = incPosRec(c.parse(s"$generators yield $fresh"), offset1 + 2)
+
+ 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))
+ a2
+ case Ident(x: TermName) if x.decoded == fresh =>
+ compileBlockWrapped(parts2, offset2)
+ }
+
+ rec(tree)
+ }
+ res
+ }
+ def compileBlockWrapped(parts: Seq[Ast.Block.Sub], offset: Int): c.Tree = {
+ incPos(q"Seq[$fragType](..${compileBlock(parts, offset)})", offset)
+ }
+ def compileHeader(header: String, block: Ast.Block, offset: Int): c.Tree = {
+ val Block(stmts, expr) = c.parse(s"{$header\n ()}")
+ Block(stmts, compileBlockWrapped(block.parts, block.offset))
+ }
+
+ val res = compileBlockWrapped(template.parts, template.offset)
+ res
+ }
+} \ No newline at end of file
diff --git a/scalatex/api/src/main/scala/scalatex/stages/Omg/scala.scala b/scalatex/api/src/main/scala/scalatex/stages/Omg/scala.scala
new file mode 100644
index 0000000..81fa1f9
--- /dev/null
+++ b/scalatex/api/src/main/scala/scalatex/stages/Omg/scala.scala
@@ -0,0 +1,8 @@
+package scalatex.stages.Omg
+
+/**
+ * Created by haoyi on 12/3/14.
+ */
+class scala {
+
+}
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
+ }
+}
diff --git a/scalatex/api/src/main/scala/scalatex/stages/Trim.scala b/scalatex/api/src/main/scala/scalatex/stages/Trim.scala
new file mode 100644
index 0000000..8993734
--- /dev/null
+++ b/scalatex/api/src/main/scala/scalatex/stages/Trim.scala
@@ -0,0 +1,29 @@
+package scalatex.stages
+import acyclic.file
+
+/**
+ * Preprocesses the input string to normalize things related to whitespace
+ *
+ * Find the "first" non-whitespace-line of the text and remove the front
+ * of every line to align that first line with the left margin.
+ *
+ * Remove all trailing whitespace from each line.
+ */
+object Trim extends (String => (String, Int)){
+ def apply(str: String) = {
+ val lines = str.split("\n", -1)
+ val offset = lines.iterator
+ .filter(_.length > 0)
+ .next()
+ .takeWhile(_ == ' ')
+ .length
+ val res = lines.iterator
+ .map(_.replaceFirst("\\s+$", ""))
+ .mkString("\n")
+ (res, offset)
+ }
+ def old(str: String) = {
+ val (res, offset) = this.apply(str)
+ res.split("\n", -1).map(_.drop(offset)).mkString("\n")
+ }
+}
diff --git a/scalatex/api/src/test/scala/scalatex/BasicTests.scala b/scalatex/api/src/test/scala/scalatex/BasicTests.scala
new file mode 100644
index 0000000..4bc362c
--- /dev/null
+++ b/scalatex/api/src/test/scala/scalatex/BasicTests.scala
@@ -0,0 +1,468 @@
+package scalatex
+import utest._
+import scala.collection.mutable.ArrayBuffer
+import scalatex.stages._
+import scalatags.Text.all._
+
+
+/**
+* Created by haoyi on 7/14/14.
+*/
+object BasicTests extends TestSuite{
+ import TestUtil._
+
+ val tests = TestSuite{
+
+ 'helloWorld{
+ object omg {
+ def wtf(s: Frag*): Frag = Seq[Frag]("|", s, "|")
+ }
+ def str = "hear me moo"
+ check(
+ tw("""
+ @omg.wtf
+ i @b{am} cow @str
+ """),
+ "|i<b>am</b>cowhearmemoo|"
+ )
+ }
+ 'interpolation{
+ 'chained-check(
+ tw("omg @scala.math.pow(0.5, 3) wtf"),
+ "omg 0.125 wtf"
+ )
+ 'parens-check(
+ tw("omg @(1 + 2 + 3 + 4) wtf"),
+ "omg 10 wtf"
+ )
+ 'block-check(
+ tw("""
+ @{"lol" * 3}
+ @{
+ val omg = "omg"
+ omg * 2
+ }
+ """),
+ """
+ lollollol
+ omgomg
+ """
+ )
+ }
+ 'definitions{
+ 'imports{
+ object Whee{
+ def func(x: Int) = x * 2
+ }
+ check(
+ tw("""
+ @import math._
+ @import Whee.func
+ @abs(-10)
+ @p
+ @max(1, 2)
+ @func(2)
+ """),
+ """
+ 10
+ <p>
+ 2
+ 4
+ </p>
+ """
+ )
+ }
+ 'valDefVar{
+ check(
+ tw("""
+ Hello
+ @val x = 1
+ World @x
+ @def y = "omg"
+ mooo
+ @y
+ """),
+ """
+ Hello
+ World 1
+ mooo
+ omg
+ """
+ )
+ }
+ 'classObjectTrait{
+ check(
+ tw("""
+ @trait Trait{
+ def tt = 2
+ }
+ Hello
+ @case object moo extends Trait{
+ val omg = "wtf"
+ }
+
+ @moo.toString
+ @moo.omg
+ @case class Foo(i: Int, s: String, b: Boolean)
+ TT is @moo.tt
+ @Foo(10, "10", true).toString
+ """),
+ """
+ Hello
+ moo
+ wtf
+ TT is 2
+ Foo(10, 10, true)
+ """
+ )
+ }
+ }
+ 'parenArgumentLists{
+ 'attributes{
+ check(
+ tw("""
+ @div(id:="my-id"){ omg }
+ @div(id:="my-id")
+ omg
+ """),
+ """
+ <divid="my-id">omg</div>
+ <divid="my-id">omg</div>
+ """
+ )
+ }
+// 'multiline{
+//
+// check(
+// tw("""
+// @div(
+// h1("Hello World"),
+// p("I am a ", b{"cow"})
+// )
+// """),
+// """
+// <div>
+// <h1>Hello World</h1>
+// <p>I am a <b>cow</b></p>
+// </div>
+// """
+// )
+// }
+ }
+ 'grouping{
+ 'negative{
+ // The indentation for "normal" text is ignored; we only
+ // create blocks from the indentation following a scala
+ // @xxx expression
+ check(
+ tw("""
+ I am cow hear me moo
+ I weigh twice as much as you
+ And I look good on the barbecue
+ Yoghurt curds cream cheese and butter
+ Comes from liquids from my udder
+ I am cow I am cow hear me moooooo
+ """),
+ """
+ I am cow hear me moo
+ I weigh twice as much as you
+ And I look good on the barbecue
+ Yoghurt curds cream cheese and butter
+ Comes from liquids from my udder
+ I am cow I am cow hear me moooooo
+ """
+ )
+ }
+ 'indentation{
+ 'simple{
+ val world = "World2"
+
+ check(
+ tw("""
+ @h1
+ Hello World
+ @h2
+ hello @world
+ @h3
+ Cow
+ """),
+ """
+ <h1>HelloWorld</h1>
+ <h2>helloWorld2</h2>
+ <h3>Cow</h3>
+ """
+ )
+ }
+ 'linearNested{
+ check(
+ tw("""
+ @h1 @span @a Hello World
+ @h2 @span @a hello
+ @b world
+ @h3 @i
+ @div Cow
+ """),
+ """
+ <h1></h1><span></span><a></a>HelloWorld
+ <h2></h2><span></span><a></a>hello<b></b>world
+ <h3></h3><i></i><div></div>Cow
+ """
+ )
+ }
+ 'crasher{
+ tw("""
+@html
+ @head
+ @meta
+ @div
+ @a
+ @span
+ """)
+ }
+ }
+ 'curlies{
+ 'simple{
+ val world = "World2"
+
+ check(
+ tw("""@div{Hello World}"""),
+ """<div>HelloWorld</div>"""
+ )
+ }
+ 'multiline{
+ check(
+ tw("""
+ @div{
+ Hello
+ }
+ """),
+ """
+ <div>Hello</div>
+ """
+ )
+ }
+ }
+ 'mixed{
+ check(
+ tw("""
+ @div{
+ Hello
+ @div
+ @h1
+ WORLD @b{!!!}
+ lol
+ @p{
+ @h2{Header 2}
+ }
+ }
+ """),
+ """
+ <div>
+ Hello
+ <div>
+ <h1>WORLD<b>!!!</b>lol</h1>
+ <p><h2>Header2</h2></p>
+ </div>
+ </div>
+ """
+ )
+ }
+//
+// 'args{
+// val things = Seq(1, 2, 3)
+// check(
+// tw("""
+// @ul
+// @things.map { x =>
+// @li
+// @x
+// }
+// """),
+// tw("""
+// @ul
+// @things.map x =>
+// @li
+// @x
+//
+// """),
+// """
+// <ul>
+// <li>1</li>
+// <li>2</li>
+// <li>3</li>
+// </ul>
+// """
+// )
+// }
+ }
+//
+ 'loops {
+//
+ * - check(
+ tw("""
+ @for(x <- 0 until 3)
+ lol
+ """),
+ tw("""
+ @for(x <- 0 until 3){
+ lol
+ }
+ """),
+ "lollollol"
+ )
+
+
+ * - check(
+ tw("""
+ @p
+ @for(x <- 0 until 2)
+ @for(y <- 0 until 2)
+ lol@x@y
+ """),
+ tw( """
+ @p
+ @for(x <- 0 until 2){
+ @for(y <- 0 until 2)
+ lol@x@y
+ }
+ """),
+ tw("""
+ @p
+ @for(x <- 0 until 2)
+ @for(y <- 0 until 2){
+ lol@x@y
+ }
+ """),
+ "<p>lol00lol01lol10lol11</p>"
+ )
+ check(
+ tw("""
+ @p
+ @for(x <- 0 until 2)
+ @for(y <- 0 until 2)
+ lol@x@y
+ """),
+ "<p>lol00lol01lol10lol11</p>"
+ )
+
+ * - check(
+ tw(
+ """
+ @for(x <- 0 until 2; y <- 0 until 2)
+ @div{@x@y}
+
+ """),
+ """<div>00</div><div>01</div><div>10</div><div>11</div>"""
+ )
+ }
+
+ 'ifElse{
+ 'basicExamples{
+ * - check(
+ tw("""
+ @if(false)
+ Hello
+ @else
+ lols
+ @p
+ """),
+ "lols<p></p>"
+ )
+
+ * - check(
+ tw("""
+ @div
+ @if(true)
+ Hello
+ @else
+ lols
+ """),
+ "<div>Hello</div>"
+ )
+
+ * - check(
+ tw("""
+ @div
+ @if(true)
+ Hello
+ @else
+ lols
+ """),
+ "<div>Hello</div>"
+ )
+ * - check(
+ tw("""
+ @if(false)
+ Hello
+ @else
+ lols
+ """),
+ "lols"
+ )
+ * - check(
+ tw("""
+ @if(false)
+ Hello
+ @else
+ lols
+ @img
+ """),
+ "lols<img/>"
+ )
+ * - check(
+ tw("""
+ @p
+ @if(true)
+ Hello
+ @else
+ lols
+ """),
+ tw("""
+ @p
+ @if(true){
+ Hello
+ }else{
+ lols
+ }
+ """),
+ "<p>Hello</p>"
+ )
+ }
+// 'funkyExpressions{
+// * - check(
+// tw("""
+// @p
+// @if(true == false == (true.==(false)))
+// @if(true == false == (true.==(false)))
+// Hello1
+// @else
+// lols1
+// @else
+// @if(true == false == (true.==(false)))
+// Hello2
+// @else
+// lols2
+// """),
+// "<p>Hello1</p>"
+// )
+// * - check(
+// tw("""
+// @p
+// @if(true == false != (true.==(false)))
+// @if(true == false != (true.==(false)))
+// Hello1
+// @else
+// lols1
+// @else
+// @if(true == false != (true.==(false)))
+// Hello2
+// @else
+// lols2
+// """),
+// "<p>lols2</p>"
+// )
+// }
+ }
+ }
+
+}
diff --git a/scalatex/api/src/test/scala/scalatex/ErrorTests.scala b/scalatex/api/src/test/scala/scalatex/ErrorTests.scala
new file mode 100644
index 0000000..d8cd4f5
--- /dev/null
+++ b/scalatex/api/src/test/scala/scalatex/ErrorTests.scala
@@ -0,0 +1,373 @@
+package scalatex
+
+import utest._
+import scalatex.stages._
+import scalatags.Text.all._
+import scalatex.Internals.{DebugFailure, twRuntimeErrors}
+
+/**
+* Created by haoyi on 7/14/14.
+*/
+object ErrorTests extends TestSuite{
+ def check(x: => Unit, expectedMsg: String, expectedError: String) = {
+ val DebugFailure(msg, pos) = intercept[DebugFailure](x)
+ def format(str: String) = {
+ val whitespace = " \t\n".toSet
+ "\n" + str.dropWhile(_ == '\n')
+ .reverse
+ .dropWhile(whitespace.contains)
+ .reverse
+ }
+ // Format these guys nicely to normalize them and make them
+ // display nicely in the assert error message if it blows up
+ val formattedPos = format(pos)
+ val formattedExpectedPos = format(expectedError)
+
+ assert(msg.contains(expectedMsg))
+ assert(formattedPos == formattedExpectedPos)
+
+ }
+ val tests = TestSuite{
+
+
+ 'simple - check(
+ twRuntimeErrors("omg @notInScope lol"),
+ """not found: value notInScope""",
+ """
+ twRuntimeErrors("omg @notInScope lol"),
+ ^
+ """
+ )
+
+ 'chained{
+ 'properties {
+ * - check(
+ twRuntimeErrors("omg @math.lol lol"),
+ """object lol is not a member of package math""",
+ """
+ twRuntimeErrors("omg @math.lol lol"),
+ ^
+ """
+ )
+
+ * - check(
+ twRuntimeErrors("omg @math.E.lol lol"),
+ """value lol is not a member of Double""",
+ """
+ twRuntimeErrors("omg @math.E.lol lol"),
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("omg @_root_.scala.math.lol lol"),
+ """object lol is not a member of package math""",
+ """
+ twRuntimeErrors("omg @_root_.scala.math.lol lol"),
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("omg @_root_.scala.gg.lol lol"),
+ """object gg is not a member of package scala""",
+ """
+ twRuntimeErrors("omg @_root_.scala.gg.lol lol"),
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("omg @_root_.ggnore.math.lol lol"),
+ """object ggnore is not a member of package <root>""",
+ """
+ twRuntimeErrors("omg @_root_.ggnore.math.lol lol"),
+ ^
+ """
+ )
+ }
+ 'calls{
+ * - check(
+ twRuntimeErrors("@scala.QQ.abs(-10).tdo(10).sum.z"),
+ """object QQ is not a member of package scala""",
+ """
+ twRuntimeErrors("@scala.QQ.abs(-10).tdo(10).sum.z"),
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("@scala.math.abs(-10).tdo(10).sum.z"),
+ "value tdo is not a member of Int",
+ """
+ twRuntimeErrors("@scala.math.abs(-10).tdo(10).sum.z"),
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("@scala.math.abs(-10).to(10).sum.z"),
+ "value z is not a member of Int",
+ """
+ twRuntimeErrors("@scala.math.abs(-10).to(10).sum.z"),
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("@scala.math.abs(-10).to(10).sum.z()"),
+ "value z is not a member of Int",
+ """
+ twRuntimeErrors("@scala.math.abs(-10).to(10).sum.z()"),
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("@scala.math.abs(-10).cow.sum.z"),
+ "value cow is not a member of Int",
+ """
+ twRuntimeErrors("@scala.math.abs(-10).cow.sum.z"),
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("@scala.smath.abs.cow.sum.z"),
+ "object smath is not a member of package scala",
+ """
+ twRuntimeErrors("@scala.smath.abs.cow.sum.z"),
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("@scala.math.cos('omg)"),
+ "type mismatch",
+ """
+ twRuntimeErrors("@scala.math.cos('omg)"),
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("@scala.math.cos[omg]('omg)"),
+ "not found: type omg",
+ """
+ twRuntimeErrors("@scala.math.cos[omg]('omg)"),
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("""
+ I am cow hear me moo
+ @scala.math.abs(-10).tdo(10).sum.z
+ I weigh twice as much as you
+ """),
+ "value tdo is not a member of Int",
+ """
+ @scala.math.abs(-10).tdo(10).sum.z
+ ^
+ """
+ )
+ }
+ 'curlies{
+ * - check(
+ twRuntimeErrors("@p{@Seq(1, 2, 3).foldLeft(0)}"),
+ "missing arguments for method foldLeft",
+ """
+ twRuntimeErrors("@p{@Seq(1, 2, 3).foldLeft(0)}"),
+ ^
+ """
+ )
+
+ * - check(
+ twRuntimeErrors("@Nil.foldLeft{XY}"),
+ "missing arguments for method foldLeft",
+ """
+ twRuntimeErrors("@Nil.foldLeft{XY}"),
+ ^
+ """
+ )
+
+// * - check(
+// twRuntimeErrors("@Seq(1).map{(y: String) => omg}"),
+// "type mismatch",
+// """
+// twRuntimeErrors("@Seq(1).map{(y: String) => omg}"),
+// ^
+// """
+// )
+// * - check(
+// twRuntimeErrors("@Nil.map{ omg}"),
+// "too many arguments for method map",
+// """
+// twRuntimeErrors("@Nil.map{ omg}"),
+// ^
+// """
+// )
+ }
+ 'callContents{
+ * - check(
+ twRuntimeErrors("@scala.math.abs((1, 2).wtf)"),
+ "value wtf is not a member of (Int, Int)",
+ """
+ twRuntimeErrors("@scala.math.abs((1, 2).wtf)"),
+ ^
+ """
+ )
+
+ * - check(
+ twRuntimeErrors("@scala.math.abs((1, 2).swap._1.toString().map(_.toString.wtf))"),
+ "value wtf is not a member of String",
+ """
+ twRuntimeErrors("@scala.math.abs((1, 2).swap._1.toString().map(_.toString.wtf))"),
+ ^
+ """
+ )
+ }
+ }
+ 'ifElse{
+ 'oneLine {
+ * - check(
+ twRuntimeErrors("@if(math > 10){ 1 }else{ 2 }"),
+ "object > is not a member of package math",
+ """
+ twRuntimeErrors("@if(math > 10){ 1 }else{ 2 }"),
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("@if(true){ (@math.pow(10)) * 10 }else{ 2 }"),
+ "Unspecified value parameter y",
+ """
+ twRuntimeErrors("@if(true){ (@math.pow(10)) * 10 }else{ 2 }"),
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("@if(true){ * 10 }else{ @math.sin(3, 4, 5) }"),
+ "too many arguments for method sin: (x: Double)Double",
+ """
+ twRuntimeErrors("@if(true){ * 10 }else{ @math.sin(3, 4, 5) }"),
+ ^
+ """
+ )
+ }
+ 'multiLine{
+ * - check(
+ twRuntimeErrors("""
+ Ho Ho Ho
+
+ @if(math != 10)
+ I am a cow
+ @else
+ You are a cow
+ GG
+ """),
+ "object != is not a member of package math",
+ """
+ @if(math != 10)
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("""
+ Ho Ho Ho
+
+ @if(4 != 10)
+ I am a cow @math.lols
+ @else
+ You are a cow
+ GG
+ """),
+ "object lols is not a member of package math",
+ """
+ I am a cow @math.lols
+ ^
+ """
+ )
+ * - check(
+ twRuntimeErrors("""
+ Ho Ho Ho
+
+ @if(12 != 10)
+ I am a cow
+ @else
+ @math.E.toString.gog(1)
+ GG
+ """),
+ "value gog is not a member of String",
+ """
+ @math.E.toString.gog(1)
+ ^
+ """
+ )
+ }
+ }
+ 'forLoop{
+ 'oneLine{
+ 'header - check(
+ twRuntimeErrors("omg @for(x <- (0 + 1 + 2) omglolol (10 + 11 + 2)){ hello }"),
+ """value omglolol is not a member of Int""",
+ """
+ twRuntimeErrors("omg @for(x <- (0 + 1 + 2) omglolol (10 + 11 + 2)){ hello }"),
+ ^
+ """
+ )
+
+ 'body - check(
+ twRuntimeErrors("omg @for(x <- 0 until 10){ @((x, 2) + (1, 2)) }"),
+ """too many arguments for method +""",
+ """
+ twRuntimeErrors("omg @for(x <- 0 until 10){ @((x, 2) + (1, 2)) }"),
+ ^
+ """
+ )
+ }
+ 'multiLine{
+ 'body - check(
+ twRuntimeErrors("""
+ omg
+ @for(x <- 0 until 10)
+ I am cow hear me moo
+ I weigh twice as much as @x.kkk
+ """),
+ """value kkk is not a member of Int""",
+ """
+ I weigh twice as much as @x.kkk
+ ^
+ """
+ )
+ }
+ }
+ 'multiLine{
+ 'missingVar - check(
+ twRuntimeErrors("""
+ omg @notInScope lol
+ """),
+ """not found: value notInScope""",
+ """
+ omg @notInScope lol
+ ^
+ """
+ )
+// 'wrongType - check(
+// twRuntimeErrors("""
+// omg @{() => ()} lol
+// """),
+// """type mismatch""",
+// """
+// omg @{() => ()} lol
+// ^
+// """
+// )
+
+ 'bigExpression - check(
+ twRuntimeErrors("""
+ @{
+ val x = 1 + 2
+ val y = new Object()
+ val z = y * x
+ x
+ }
+ """),
+ "value * is not a member of Object",
+ """
+ val z = y * x
+ ^
+ """
+ )
+ }
+ }
+}
diff --git a/scalatex/api/src/test/scala/scalatex/ParserTests.scala b/scalatex/api/src/test/scala/scalatex/ParserTests.scala
new file mode 100644
index 0000000..9a4ee63
--- /dev/null
+++ b/scalatex/api/src/test/scala/scalatex/ParserTests.scala
@@ -0,0 +1,424 @@
+package scalatex
+
+
+import org.parboiled2._
+import scalaParser.ScalaSyntax
+
+import scalatex.stages.{Trim, Parser, Ast}
+import scalatex.stages.Ast.Block.{IfElse, For, Text}
+import Ast.Chain.Args
+
+object ParserTests extends utest.TestSuite{
+ import Ast._
+ import utest._
+ def check[T](input: String, parse: Parser => scala.util.Try[T], expected: T) = {
+ val parsed = parse(new Parser(input)).get
+ assert(parsed == expected)
+ }
+ def tests = TestSuite{
+ 'Trim{
+ def wrap(s: String) = "|" + s + "|"
+ * - {
+ val trimmed = wrap(stages.Trim.old("""
+ i am cow
+ hear me moo
+ i weigh twice as much as you
+ """))
+ val expected = wrap("""
+ |i am cow
+ | hear me moo
+ | i weigh twice as much as you
+ |""".stripMargin)
+ assert(trimmed == expected)
+
+ }
+ * - {
+ val trimmed = wrap(stages.Trim.old(
+ """
+ @{"lol" * 3}
+ @{
+ val omg = "omg"
+ omg * 2
+ }
+ """
+ ))
+ val expected = wrap(
+ """
+ |@{"lol" * 3}
+ |@{
+ | val omg = "omg"
+ | omg * 2
+ |}
+ |""".stripMargin
+ )
+ assert(trimmed == expected)
+ }
+ 'dropTrailingWhitespace - {
+
+ val trimmed = wrap(stages.Trim.old(
+ Seq(
+ " i am a cow ",
+ " hear me moo ",
+ " i weigh twice as much as you"
+ ).mkString("\n")
+ ))
+ val expected = wrap(
+ Seq(
+ "i am a cow",
+ " hear me moo",
+ " i weigh twice as much as you"
+ ).mkString("\n")
+ )
+ assert(trimmed == expected)
+ }
+ }
+ 'Text {
+ * - check("i am a cow", _.Text.run(), Block.Text("i am a cow"))
+ * - check("i am a @cow", _.Text.run(), Block.Text("i am a "))
+ * - check("i am a @@cow", _.Text.run(), Block.Text("i am a @cow"))
+ * - check("i am a @@@cow", _.Text.run(), Block.Text("i am a @"))
+ * - check("i am a @@@@cow", _.Text.run(), Block.Text("i am a @@cow"))
+
+ }
+ 'Code{
+ 'identifier - check("@gggg ", _.Code.run(), "gggg")
+ 'parens - check("@(1 + 1)lolsss\n", _.Code.run(), "(1 + 1)")
+ 'curlies - check("@{{1} + (1)} ", _.Code.run(), "{{1} + (1)}")
+ 'blocks - check("@{val x = 1; 1} ", _.Code.run(), "{val x = 1; 1}")
+ 'weirdBackticks - check("@{`{}}{()@`}\n", _.Code.run(), "{`{}}{()@`}")
+ }
+ 'MiscCode{
+ 'imports{
+ * - check("@import math.abs", _.Header.run(), "import math.abs")
+ * - check("@import math.{abs, sin}", _.Header.run(), "import math.{abs, sin}")
+ }
+ 'headerblocks{
+ check(
+ """@import math.abs
+ |@import math.sin
+ |
+ |hello world
+ |""".stripMargin,
+ _.HeaderBlock.run(),
+ Ast.Header(
+ "import math.abs\nimport math.sin",
+ Ast.Block(
+ Seq(Text("\n", 33), Text("\n", 34), Text("hello world", 35), Text("\n", 46)),
+ 33
+ )
+ )
+ )
+ }
+ 'caseclass{
+ check(
+ """@case class Foo(i: Int, s: String)
+ """.stripMargin,
+ _.Header.run(),
+ "case class Foo(i: Int, s: String)"
+ )
+ }
+
+ }
+ 'Block{
+ * - check("{i am a cow}", _.BraceBlock.run(), Block(Seq(Block.Text("i am a cow", 1)), 1))
+ * - check("{i @am a @cow}", _.BraceBlock.run(),
+ Block(Seq(
+ Block.Text("i ", 1),
+ Chain("am",Seq(), 3),
+ Block.Text(" a ", 6),
+ Chain("cow",Seq(), 9)
+ ), 1)
+ )
+ }
+ 'Chain{
+ * - check("@omg.bbq[omg].fff[fff](123) ", _.ScalaChain.run(),
+ Chain("omg",Seq(
+ Chain.Prop("bbq", 4),
+ Chain.TypeArgs("[omg]", 8),
+ Chain.Prop("fff", 13),
+ Chain.TypeArgs("[fff]", 17),
+ Chain.Args("(123)", 22)
+ ))
+ )
+ * - check("@omg{bbq}.cow(moo){a @b}\n", _.ScalaChain.run(),
+ Chain("omg",Seq(
+ Block(Seq(Text("bbq", 5)), 5),
+ Chain.Prop("cow", 9),
+ Chain.Args("(moo)", 13),
+ Block(Seq(Text("a ", 19), Chain("b", Nil, 21)), 19)
+ ))
+ )
+ }
+ 'ControlFlow{
+ 'for {
+ 'for - check(
+ "@for(x <- 0 until 3){lol}",
+ _.ForLoop.run(),
+ For("for(x <- 0 until 3)", Block(Seq(Text("lol", 21)), 21))
+ )
+ 'forBlock - check(
+ """
+ |@for(x <- 0 until 3)
+ | lol""".stripMargin,
+ _.Body.run(),
+ Block(Seq(
+ Text("\n"),
+ For(
+ "for(x <- 0 until 3)",
+ Block(Seq(Text("\n ", 21), Text("lol", 24)), 21),
+ 1
+ )
+ ))
+ )
+ 'forBlockBraces - check(
+ """
+ |@for(x <- 0 until 3){
+ | lol
+ |}""".stripMargin,
+ _.Body.run(),
+ Block(Seq(
+ Text("\n"),
+ For(
+ "for(x <- 0 until 3)",
+ Block(Seq(Text("\n ", 22), Text("lol", 25), Text("\n", 28)), 22),
+ 1
+ )
+ ))
+ )
+ }
+ 'ifElse {
+ 'if - check(
+ "@if(true){lol}",
+ _.IfElse.run(),
+ IfElse("if(true)", Block(Seq(Text("lol", 10)), 10), None)
+ )
+ 'ifElse - check(
+ "@if(true){lol}else{ omg }",
+ _.IfElse.run(),
+ IfElse("if(true)", Block(Seq(Text("lol", 10)), 10), Some(Block(Seq(Text(" omg ", 19)), 19)))
+ )
+ 'ifBlock - check(
+ """
+ |@if(true)
+ | omg""".stripMargin,
+ _.IfElse.run(),
+ IfElse("if(true)", Block(Seq(Text("\n ", 10), Text("omg", 13)), 10), None, 1)
+ )
+ 'ifBlockElseBlock - check(
+ """
+ |@if(true)
+ | omg
+ |@else
+ | wtf""".stripMargin,
+ _.IfElse.run(),
+ IfElse(
+ "if(true)",
+ Block(Seq(Text("\n ", 10), Text("omg", 13)), 10),
+ Some(Block(Seq(Text("\n ", 22), Text("wtf", 25)), 22)),
+ 1
+ )
+ )
+ 'ifBlockElseBraceBlock - check(
+ """@if(true){
+ | omg
+ |}else{
+ | wtf
+ |}""".stripMargin,
+ _.IfElse.run(),
+ IfElse(
+ "if(true)",
+ Block(Seq(Text("\n ", 10), Text("omg", 13), Text("\n", 16)), 10),
+ Some(Block(Seq(Text("\n ", 23), Text("wtf", 26), Text("\n", 29)), 23)),
+ 0
+ )
+ )
+ 'ifBlockElseBraceBlockNested - {
+ val res = Parser(Trim.old(
+ """
+ @p
+ @if(true){
+ Hello
+ }else{
+ lols
+ }
+ """))
+ val expected =
+ Block(Vector(
+ Text("\n"),
+ Chain("p",Vector(Block(Vector(
+ Text("\n ", 3),
+ IfElse("if(true)",
+ Block(Vector(
+ Text("\n ", 16), Text("Hello", 21), Text("\n ", 26)
+ ), 16),
+ Some(Block(Vector(
+ Text("\n ", 35), Text("lols", 40), Text("\n ", 44)
+ ), 35)),
+ 6
+ )), 3)), 1),
+ Text("\n", 48)
+ ))
+ assert(res == expected)
+ }
+ 'ifElseBlock - check(
+ """@if(true){
+ | omg
+ |}else
+ | wtf""".stripMargin,
+ _.IfElse.run(),
+ IfElse(
+ "if(true)",
+ Block(Seq(Text("\n ", 10), Text("omg", 13), Text("\n", 16)), 10),
+ Some(Block(Seq(Text("\n ", 22), Text("wtf", 25)), 22))
+ )
+ )
+ }
+
+ }
+ 'Body{
+ 'indents - check(
+ """
+ |@omg
+ | @wtf
+ | @bbq
+ | @lol""".stripMargin,
+ _.Body.run(),
+ Block(Seq(
+ Text("\n"),
+ Chain("omg",Seq(Block(Seq(
+ Text("\n ", 5),
+ Chain("wtf",Seq(Block(Seq(
+ Text("\n ", 7),
+ Chain("bbq",Seq(Block(Seq(
+ Text("\n ", 9),
+ Chain("lol",Seq(), 16)
+ ), 9)), 12)
+ ), 7)), 8)
+ ), 5)), 1)
+ ))
+ )
+ 'dedents - check(
+ """
+ |@omg
+ | @wtf
+ |@bbq""".stripMargin,
+ _.Body.run(),
+ Block(Seq(
+ Text("\n"),
+ Chain("omg",Seq(Block(
+ Seq(
+ Text("\n ", 5),
+ Chain("wtf",Seq(), 8)
+ ),
+ 5
+ )), 1),
+ Text("\n", 12),
+ Chain("bbq", Seq(), 13)
+ ))
+ )
+ 'braces - check(
+ """
+ |@omg{
+ | @wtf
+ |}
+ |@bbq""".stripMargin,
+ _.Body.run(),
+ Block(Seq(
+ Text("\n"),
+ Chain("omg",Seq(Block(
+ Seq(
+ Text("\n ", 6),
+ Chain("wtf",Seq(), 9),
+ Text("\n", 13)
+ ),
+ 6
+ )), 1),
+ Text("\n", 15),
+ Chain("bbq", Seq(), 16)
+ ))
+ )
+ 'dedentText - check(
+ """
+ |@omg("lol", 1, 2)
+ | @wtf
+ |bbq""".stripMargin,
+ _.Body.run(),
+ Block(Seq(
+ Text("\n"),
+ Chain("omg",Seq(
+ Args("""("lol", 1, 2)""", 5),
+ Block(Seq(
+ Text("\n ", 18),
+ Chain("wtf",Seq(), 21)
+ ), 18)
+ ), 1),
+ Text("\n", 25),
+ Text("bbq", 26)
+ ))
+ )
+ * - check(
+ """
+ |@omg("lol",
+ |1,
+ | 2
+ | )
+ | wtf
+ |bbq""".stripMargin,
+ _.Body.run(),
+ Block(Seq(
+ Text("\n", 0),
+ Chain("omg",Seq(
+ Args("(\"lol\",\n1,\n 2\n )", 5),
+ Block(Seq(
+ Text("\n ", 30), Text("wtf", 33)
+ ), 30)
+ ), 1),
+ Text("\n", 36),
+ Text("bbq", 37)
+ ), 0)
+ )
+ 'codeBlock - check(
+ """@{
+ | val omg = "omg"
+ | omg * 2
+ |}""".stripMargin,
+ _.Code.run(),
+ """{
+ | val omg = "omg"
+ | omg * 2
+ |}""".stripMargin
+ )
+ 'codeBlocks - check(
+ """
+ |@{"lol" * 3}
+ |@{
+ | val omg = "omg"
+ | omg * 2
+ |}""".stripMargin,
+ _.Body.run(),
+ Block(Seq(
+ Text("\n"),
+ Chain("{\"lol\" * 3}", Seq(), 1),
+ Text("\n", 13),
+ Chain("""{
+ | val omg = "omg"
+ | omg * 2
+ |}""".stripMargin,
+ Seq(),
+ 14
+ )
+ ))
+ )
+ }
+// 'Test{
+// check(
+// "@{() => ()}",
+// _.Code.run(),
+// ""
+// )
+// }
+ }
+}
+
+
+
diff --git a/scalatex/api/src/test/scala/scalatex/TestUtil.scala b/scalatex/api/src/test/scala/scalatex/TestUtil.scala
new file mode 100644
index 0000000..5a72677
--- /dev/null
+++ b/scalatex/api/src/test/scala/scalatex/TestUtil.scala
@@ -0,0 +1,16 @@
+package scalatex
+
+import utest._
+
+
+object TestUtil {
+ implicit def stringify(f: scalatags.Text.all.Frag) = f.render
+ def check(rendered: String*) = {
+ val collapsed = rendered.map(collapse)
+ val first = collapsed(0)
+ assert(collapsed.forall(_ == first))
+ }
+ def collapse(s: String): String = {
+ s.replaceAll("[ \n]", "")
+ }
+}
diff --git a/scalatex/build.sbt b/scalatex/build.sbt
new file mode 100644
index 0000000..4769bad
--- /dev/null
+++ b/scalatex/build.sbt
@@ -0,0 +1,44 @@
+val sharedSettings = Seq(
+ version := "0.1.0",
+ organization := "com.lihaoyi",
+ crossScalaVersions:= Seq("2.10.4", "2.11.2"),
+ scalaVersion := "2.11.4",
+ libraryDependencies += "com.lihaoyi" %% "acyclic" % "0.1.2" % "provided",
+ addCompilerPlugin("com.lihaoyi" %% "acyclic" % "0.1.2"),
+ autoCompilerPlugins := true
+)
+
+lazy val scalaParser = project.settings(sharedSettings:_*)
+ .settings(
+ name := "scala-parser-lite",
+ libraryDependencies ++= Seq(
+ "com.lihaoyi" %% "utest" % "0.2.4",
+ "org.parboiled" %% "parboiled" % "2.0.1"
+ ),
+ testFrameworks += new TestFramework("utest.runner.JvmFramework")
+ )
+lazy val api = project.settings(sharedSettings:_*)
+ .dependsOn(scalaParser)
+ .settings(
+ name := "scalatex-api",
+ libraryDependencies ++= Seq(
+ "com.lihaoyi" %% "utest" % "0.2.4",
+ "com.scalatags" %% "scalatags" % "0.4.2",
+ "org.scala-lang" % "scala-reflect" % scalaVersion.value,
+ "org.parboiled" %% "parboiled" % "2.0.1"
+ ),
+ testFrameworks += new TestFramework("utest.runner.JvmFramework")
+ )
+
+lazy val scalatexSbtPlugin = project.settings(sharedSettings:_*)
+ .settings(
+ name := "scalatex-sbt-plugin",
+ scalaVersion := "2.10.4",
+ sbtPlugin := true
+)
+lazy val compilerPlugin = project.settings(sharedSettings:_*)
+ .dependsOn(api)
+ .settings(
+ name := "scalatex-compiler-plugin",
+ libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value
+) \ No newline at end of file
diff --git a/scalatex/compilerPlugin/src/main/resources/scalac-plugin.xml b/scalatex/compilerPlugin/src/main/resources/scalac-plugin.xml
new file mode 100755
index 0000000..a446f13
--- /dev/null
+++ b/scalatex/compilerPlugin/src/main/resources/scalac-plugin.xml
@@ -0,0 +1,4 @@
+<plugin>
+ <name>demo-plugin</name>
+ <classname>scalatex.CompilerPlugin</classname>
+</plugin> \ No newline at end of file
diff --git a/scalatex/compilerPlugin/src/main/scala/scalatex/CompilerPlugin.scala b/scalatex/compilerPlugin/src/main/scala/scalatex/CompilerPlugin.scala
new file mode 100755
index 0000000..e122de5
--- /dev/null
+++ b/scalatex/compilerPlugin/src/main/scala/scalatex/CompilerPlugin.scala
@@ -0,0 +1,79 @@
+package scalatex
+
+import java.nio.file.Paths
+
+import scala.reflect.internal.util.BatchSourceFile
+import scala.reflect.io.VirtualFile
+import scala.tools.nsc.{ Global, Phase }
+import scala.tools.nsc.plugins.{ Plugin, PluginComponent }
+
+class CompilerPlugin(val global: Global) extends Plugin {
+ import global._
+
+ override def init(options: List[String], error: String => Unit): Boolean = true
+
+ val name = "scalatex"
+ val description = "Compiles scalatex files into Scala compilation units"
+ val components = List[PluginComponent](DemoComponent)
+ private object DemoComponent extends PluginComponent {
+
+ val global = CompilerPlugin.this.global
+ import global._
+
+ override val runsAfter = List("parser")
+ override val runsBefore = List("namer")
+
+ val phaseName = "Demo"
+
+ override def newPhase(prev: Phase) = new GlobalPhase(prev) {
+ val splitOptions = options.map(o => o.splitAt(o.indexOf(":")+1))
+ val scalatexRoots = splitOptions.collect{case ("root:", p) => p}
+ override def run() = {
+ def recursiveListFiles(f: java.io.File): Iterator[java.io.File] = {
+ val (dirs, files) =
+ Option(f.listFiles())
+ .toSeq
+ .flatten
+ .partition(_.isDirectory)
+ files.iterator ++ dirs.iterator.flatMap(recursiveListFiles)
+ }
+ for {
+ scalatexRoot <- scalatexRoots
+ file <- recursiveListFiles(new java.io.File(scalatexRoot))
+ } {
+ val name = file.getCanonicalPath
+ val fakeJfile = new java.io.File(name)
+ val txt = io.Source.fromFile(name).mkString
+ val virtualFile = new VirtualFile(name) {
+ override def file = fakeJfile
+ }
+ val sourceFile = new BatchSourceFile(virtualFile, txt)
+ val unit = new CompilationUnit(sourceFile)
+ val objectName = name.slice(name.lastIndexOf('/')+1, name.lastIndexOf('.'))
+ val pkgName =
+ Paths.get(scalatexRoot)
+ .relativize(fakeJfile.getParentFile.toPath)
+ .toString
+ .split("/")
+ .map(s => s"package $s")
+ .mkString("\n")
+
+ val shim = s"""
+ $pkgName
+ import scalatags.Text.all._
+
+ object $objectName{
+ def apply() = scalatex.twf("${name}")
+ }
+ """
+ unit.body = global.newUnitParser(shim).parse()
+ global.currentRun.compileLate(unit)
+ }
+ }
+
+ def name: String = phaseName
+
+ def apply(unit: global.CompilationUnit): Unit = {}
+ }
+ }
+}
diff --git a/scalatex/project/build.properties b/scalatex/project/build.properties
new file mode 100644
index 0000000..748703f
--- /dev/null
+++ b/scalatex/project/build.properties
@@ -0,0 +1 @@
+sbt.version=0.13.7
diff --git a/scalatex/project/build.sbt b/scalatex/project/build.sbt
new file mode 100644
index 0000000..f1db6ad
--- /dev/null
+++ b/scalatex/project/build.sbt
@@ -0,0 +1 @@
+addSbtPlugin("com.lihaoyi" % "utest-js-plugin" % "0.2.4")
diff --git a/scalatex/scalaParser/src/main/scala/scalaParser/ScalaSyntax.scala b/scalatex/scalaParser/src/main/scala/scalaParser/ScalaSyntax.scala
new file mode 100644
index 0000000..de9f039
--- /dev/null
+++ b/scalatex/scalaParser/src/main/scala/scalaParser/ScalaSyntax.scala
@@ -0,0 +1,416 @@
+package scalaParser
+import acyclic.file
+import language.implicitConversions
+import syntax._
+import org.parboiled2._
+
+/**
+ * Parser for Scala syntax.
+ *
+ * The `G` parameter that gets passed in to each rule stands for
+ * "Greedy", and determines whether or not that rule is to consume
+ * newlines after the last terminal in that rule. We need to pass it
+ * everywhere so it can go all the way to the last terminal deep
+ * inside the parse tree, which can then decide whether or not to
+ * consume whitespace.
+ *
+ * The vast majority of terminals will consume newlines; only rules
+ * which occur in {} blocks won't have their terminals consume newlines,
+ * and only the *last* terminal in the rule will be affected.
+ * That's why the parser does terminals-consume-newlines-by-default,
+ * and leaves it up to the dev to thread the `G` variable where-ever
+ * we want the opposite behavior.
+ */
+class ScalaSyntax(val input: ParserInput) extends Parser with Basic with Identifiers with Literals {
+ // Aliases for common things. These things are used in almost every parser
+ // in the file, so it makes sense to keep them short.
+ type B = Boolean
+ val t = true
+ type R0 = Rule0
+ /**
+ * Parses all whitespace, excluding newlines. This is only
+ * really useful in e.g. {} blocks, where we want to avoid
+ * capturing newlines so semicolon-inference would work
+ */
+ def WS = rule { zeroOrMore(Basic.WhitespaceChar | Literals.Comment) }
+
+ /**
+ * Parses whitespace, including newlines.
+ * This is the default for most things
+ */
+ def WL = rule{ zeroOrMore(Basic.WhitespaceChar | Literals.Comment | Basic.Newline) }
+
+
+
+ /**
+ * By default, all strings and characters greedily
+ * capture all whitespace immediately after the token.
+ */
+ implicit private[this] def wspStr(s: String): R0 = rule { WL ~ str(s) }
+ implicit private[this] def wspChar(s: Char): R0 = rule { WL ~ ch(s) }
+
+ /**
+ * Most keywords don't just require the correct characters to match,
+ * they have to ensure that subsequent characters *don't* match in
+ * order for it to be a keyword. This enforces that rule for key-words
+ * (W) and key-operators (O) which have different non-match criteria.
+ */
+ object K {
+ def W(s: String) = rule {
+ WL ~ Key.W(s)
+ }
+
+ def O(s: String) = rule {
+ WL ~ Key.O(s)
+ }
+ }
+
+
+ def pos = cursor -> cursorChar
+
+ /**
+ * helper printing function
+ */
+ def pr(s: String) = rule { run(println(s"LOGGING $cursor: $s")) }
+
+ def Id = rule { WL ~ Identifiers.Id }
+ def VarId = rule { WL ~ Identifiers.VarId }
+ def Literal = rule { WL ~ Literals.Literal }
+ def Semi = rule { WS ~ Basic.Semi }
+ def Semis = rule { oneOrMore(Semi) }
+ def Newline = rule { WL ~ Basic.Newline }
+
+ def QualId = rule { WL ~ oneOrMore(Id).separatedBy('.') }
+ def Ids = rule { oneOrMore(Id) separatedBy ',' }
+
+ def Path: R0 = rule {
+ zeroOrMore(Id ~ '.') ~ K.W("this") ~ zeroOrMore(Id).separatedBy('.') |
+ StableId
+ }
+ def StableId: R0 = rule {
+ zeroOrMore(Id ~ '.') ~ (K.W("this") | K.W("super") ~ optional(ClassQualifier)) ~ '.' ~ oneOrMore(Id).separatedBy('.') |
+ Id ~ zeroOrMore(WL ~ '.' ~ WL ~ Id)
+ }
+
+ def ClassQualifier = rule { '[' ~ Id ~ ']' }
+
+ def Type: R0 = rule {
+ FunctionArgTypes ~ K.O("=>") ~ Type | InfixType ~ optional(WL ~ ExistentialClause)
+ }
+ def FunctionArgTypes = rule {
+ InfixType | '(' ~ optional(oneOrMore(ParamType) separatedBy ',') ~ ')'
+ }
+
+ def ExistentialClause = rule { "forSome" ~ '{' ~ oneOrMore(ExistentialDcl).separatedBy(Semi) }
+ def ExistentialDcl = rule { K.W("type") ~ TypeDcl | K.W("val") ~ ValDcl }
+
+ def InfixType = rule {
+ CompoundType ~ zeroOrMore(WL ~ Id ~ optional(Newline) ~ CompoundType)
+ }
+ def CompoundType = rule {
+ oneOrMore(AnnotType).separatedBy(WL ~ K.W("with")) ~ optional(Refinement)
+ }
+ def AnnotType = rule {
+ SimpleType ~ zeroOrMore(WL ~ Annotation)
+ }
+ def SimpleType: R0 = rule {
+ BasicType ~
+ optional(WL ~ '#' ~ Id) ~
+ optional(WL ~ TypeArgs)
+ }
+ def BasicType: R0 = rule {
+ '(' ~ Types ~ ')' |
+ Path ~ '.' ~ K.W("type") |
+ StableId
+ }
+ def TypeArgs = rule { '[' ~ Types ~ "]" }
+ def Types = rule { oneOrMore(Type).separatedBy(',') }
+ def Refinement = rule {
+ optional(Newline) ~ '{' ~ oneOrMore(RefineStat).separatedBy(Semi) ~ "}"
+ }
+ def RefineStat = rule { "type" ~ TypeDef | Dcl | MATCH }
+ def TypePat = rule { CompoundType }
+ def Ascription = rule {
+ ":" ~ ("_" ~ "*" | InfixType | oneOrMore(Annotation))
+ }
+
+ def ParamType = rule { K.O("=>") ~ Type | Type ~ "*" | Type }
+
+ def Expr: R0 = rule {
+ (Bindings | optional(K.W("implicit")) ~ Id | "_") ~ K.O("=>") ~ Expr |
+ Expr1
+ }
+ def Expr1: R0 = rule {
+ IfCFlow |
+ WhileCFlow |
+ TryCFlow |
+ DoWhileCFlow |
+ ForCFlow |
+ K.W("throw") ~ Expr |
+ K.W("return") ~ optional(Expr) |
+ SimpleExpr ~ K.O("=") ~ Expr |
+ PostfixExpr ~ optional("match" ~ '{' ~ CaseClauses ~ "}" | Ascription)
+
+ }
+ def IfCFlow = rule { "if" ~ '(' ~ Expr ~ ')' ~ zeroOrMore(Newline) ~ Expr ~ optional(optional(Semi) ~ K.W("else") ~ Expr) }
+ def WhileCFlow = rule { "while" ~ '(' ~ Expr ~ ')' ~ zeroOrMore(Newline) ~ Expr }
+ def TryCFlow = rule {
+ K.W("try") ~ Expr ~
+ optional(WL ~ K.W("catch") ~ Expr) ~
+ optional(WL ~ K.W("finally") ~ Expr)
+ }
+
+ def DoWhileCFlow = rule { K.W("do") ~ Expr ~ optional(Semi) ~ "while" ~ '(' ~ Expr ~ ")" }
+ def ForCFlow = rule {
+ "for" ~
+ ('(' ~ Enumerators ~ ')' | '{' ~ Enumerators ~ '}') ~
+ zeroOrMore(Newline) ~
+ optional(K.W("yield")) ~
+ Expr }
+ def NotNewline: R0 = rule{ &( WS ~ noneOf("\n") )}
+ def PostfixExpr: R0 = rule { InfixExpr ~ optional(NotNewline ~ Id ~ optional(Newline)) }
+ def InfixExpr: R0 = rule {
+ PrefixExpr ~
+ zeroOrMore(
+ NotNewline ~
+ Id ~
+ optional(Newline) ~
+ PrefixExpr
+ )
+ }
+ def PrefixExpr = rule { optional(WL ~ anyOf("-+~!")) ~ SimpleExpr }
+
+ def SimpleExpr: R0 = rule {
+ SimpleExpr1 ~
+ zeroOrMore(WL ~ ('.' ~ Id | TypeArgs | ArgumentExprs)) ~
+ optional(WL ~ "_")
+ }
+
+ def SimpleExpr1 = rule{
+ K.W("new") ~ (ClassTemplate | TemplateBody) |
+ BlockExpr |
+ Literal |
+ Path |
+ K.W("_") |
+ '(' ~ optional(Exprs) ~ ")"
+ }
+
+
+
+ def Exprs: R0 = rule { oneOrMore(Expr).separatedBy(',') }
+ def ArgumentExprs: R0 = rule {
+ '(' ~ optional(Exprs ~ optional(K.O(":") ~ K.W("_") ~ '*')) ~ ")" |
+ optional(Newline) ~ BlockExpr
+ }
+
+ def BlockExpr: R0 = rule { '{' ~ (CaseClauses | Block) ~ "}" }
+ def BlockEnd: R0 = rule{ optional(Semis) ~ &("}" | "case") }
+ def Block: R0 = rule {
+ optional(Semis) ~
+ (
+ BlockStats ~ optional(Semis ~ ResultExpr) ~ BlockEnd |
+ ResultExpr ~ BlockEnd |
+ MATCH ~ BlockEnd
+ )
+ }
+ def BlockStats: R0 = rule{
+ oneOrMore(BlockStat).separatedBy(Semis)
+ }
+ def BlockStat: R0 = rule {
+ Import |
+ zeroOrMore(Annotation) ~ (optional(K.W("implicit") | K.W("lazy")) ~ Def | zeroOrMore(LocalModifier) ~ TmplDef) |
+ Expr1
+ }
+ def ResultExpr: R0 = rule {
+ (Bindings | optional(K.W("implicit")) ~ Id | "_") ~ K.W("=>") ~ Block | Expr1
+ }
+ def Enumerators: R0 = rule { Generator ~ zeroOrMore(Semi ~ Enumerator) ~ WL }
+ def Enumerator: R0 = rule { Generator | Guard | Pattern1 ~ K.O("=") ~ Expr }
+ def Generator: R0 = rule { Pattern1 ~ K.O("<-") ~ Expr ~ optional(WL ~ Guard) }
+ def CaseClauses: R0 = rule { oneOrMore(CaseClause) }
+ def CaseClause: R0 = rule { K.W("case") ~ Pattern ~ optional(Guard) ~ K.O("=>") ~ Block }
+ def Guard: R0 = rule { K.W("if") ~ PostfixExpr }
+ def Pattern: R0 = rule {
+ oneOrMore(Pattern1).separatedBy('|')
+ }
+ def Pattern1: R0 = rule {
+ K.W("_") ~ K.O(":") ~ TypePat | VarId ~ K.O(":") ~ TypePat | Pattern2
+ }
+ def Pattern2: R0 = rule {
+ VarId ~ "@" ~ Pattern3 | Pattern3 | VarId
+ }
+ def Pattern3: R0 = rule {
+ SimplePattern ~ zeroOrMore(Id ~ SimplePattern)
+ }
+ def SimplePattern: R0 = rule {
+ K.W("_") |
+ Literal |
+ '(' ~ optional(Patterns) ~ ')' |
+ (
+ StableId ~
+ optional(
+ '(' ~
+ (optional(Patterns ~ ',') ~ optional(VarId ~ '@') ~ K.W("_") ~ '*' | optional(Patterns)) ~
+ ')'
+ )
+ ) |
+ VarId
+ }
+ def Patterns: R0 = rule { K.W("_") ~ '*' | oneOrMore(Pattern).separatedBy(',') }
+
+ def TypeParamClause: R0 = rule { '[' ~ oneOrMore(VariantTypeParam).separatedBy(',') ~ ']' }
+ def FunTypeParamClause: R0 = rule { '[' ~ oneOrMore(TypeParam).separatedBy(',') ~ ']' }
+ def VariantTypeParam: R0 = rule { zeroOrMore(Annotation) ~ optional(anyOf("+-")) ~ TypeParam }
+ def TypeParam: R0 = rule {
+ (Id | K.W("_")) ~
+ optional(TypeParamClause) ~
+ optional(K.O(">:") ~ Type) ~
+ optional(K.O("<:") ~ Type) ~
+ zeroOrMore(K.O("<%") ~ Type) ~
+ zeroOrMore(K.O(":") ~ Type)
+ }
+ def ParamClauses: R0 = rule { zeroOrMore(ParamClause) ~ optional(optional(Newline) ~ '(' ~ K.W("implicit") ~ Params ~ ')') }
+ def ParamClause: R0 = rule { optional(Newline) ~ '(' ~ optional(Params) ~ ')' }
+ def Params: R0 = rule { zeroOrMore(Param).separatedBy(',') }
+ def Param: R0 = rule { zeroOrMore(Annotation) ~ Id ~ optional(K.O(":") ~ ParamType) ~ optional(K.O("=") ~ Expr) }
+ def ClassParamClauses: R0 = rule { zeroOrMore(ClassParamClause) ~ optional(optional(Newline) ~ '(' ~ K.W("implicit") ~ ClassParam ~ ")") }
+ def ClassParamClause: R0 = rule { optional(Newline) ~ '(' ~ optional(ClassParams) ~ ")" }
+ def ClassParams: R0 = rule { oneOrMore(ClassParam).separatedBy(',') }
+ def ClassParam: R0 = rule { zeroOrMore(Annotation) ~ optional(zeroOrMore(Modifier) ~ (K.W("val") | K.W("var"))) ~ Id ~ K.O(":") ~ ParamType ~ optional(K.O("=") ~ Expr) }
+
+ def Bindings: R0 = rule { '(' ~ zeroOrMore(Binding).separatedBy(',') ~ ')' }
+ def Binding: R0 = rule { (Id | K.W("_")) ~ optional(K.O(":") ~ Type) }
+
+ def Modifier: R0 = rule { LocalModifier | AccessModifier | K.W("override") }
+ def LocalModifier: R0 = rule { K.W("abstract") | K.W("final") | K.W("sealed") | K.W("implicit") | K.W("lazy") }
+ def AccessModifier: R0 = rule { (K.W("private") | K.W("protected")) ~ optional(AccessQualifier) }
+ def AccessQualifier: R0 = rule { '[' ~ (K.W("this") | Id) ~ ']' }
+
+ def Annotation: R0 = rule { '@' ~ SimpleType ~ zeroOrMore(WL ~ ArgumentExprs) }
+ def ConstrAnnotation: R0 = rule { '@' ~ SimpleType ~ ArgumentExprs }
+
+ def TemplateBody: R0 = rule {
+ '{' ~
+ optional(SelfType) ~
+ zeroOrMore(TemplateStat).separatedBy(Semis) ~
+ '}'
+ }
+ def TemplateStat: R0 = rule {
+ Import |
+ zeroOrMore(Annotation ~ optional(Newline)) ~ zeroOrMore(Modifier) ~ (Def | Dcl) |
+ Expr
+ }
+
+ def SelfType: R0 = rule { K.W("this") ~ K.O(":") ~ Type ~ K.O("=>") | Id ~ optional(K.O(":") ~ Type) ~ K.O("=>") }
+
+ def Import: R0 = rule { K.W("import") ~ oneOrMore(ImportExpr).separatedBy(',') }
+
+ def ImportExpr: R0 = rule {
+ StableId ~ optional('.' ~ ("_" | ImportSelectors))
+ }
+ def ImportSelectors: R0 = rule { '{' ~ zeroOrMore(ImportSelector ~ ',') ~ (ImportSelector | K.W("_")) ~ "}" }
+ def ImportSelector: R0 = rule { Id ~ optional(K.O("=>") ~ (Id | K.W("_"))) }
+
+ def Dcl: R0 = rule {
+ K.W("val") ~ ValDcl |
+ K.W("var") ~ VarDcl |
+ K.W("def") ~ FunDcl |
+ K.W("type") ~ zeroOrMore(Newline) ~ TypeDcl
+ }
+ def ValDcl: R0 = rule { Ids ~ K.O(":") ~ Type }
+ def VarDcl: R0 = rule { Ids ~ K.O(":") ~ Type }
+ def FunDcl: R0 = rule { FunSig ~ optional(WL ~ K.O(":") ~ Type) }
+ def FunSig: R0 = rule { Id ~ optional(FunTypeParamClause) ~ ParamClauses }
+ def TypeDcl: R0 = rule {
+ Id ~
+ optional(WL ~ TypeParamClause) ~
+ optional(WL ~ K.O(">:") ~ Type) ~
+ optional(WL ~ K.O("<:") ~ Type)
+ }
+
+ def PatVarDef: R0 = rule { K.W("val") ~ PatDef | K.W("var") ~ VarDef }
+ def Def: R0 = rule { K.W("def") ~ FunDef | K.W("type") ~ zeroOrMore(Newline) ~ TypeDef | PatVarDef | TmplDef }
+ def PatDef: R0 = rule { oneOrMore(Pattern2).separatedBy(',') ~ optional(K.O(":") ~ Type) ~ K.O("=") ~ Expr }
+ def VarDef: R0 = rule { Ids ~ K.O(":") ~ Type ~ K.O("=") ~ K.W("_") | PatDef }
+ def FunDef: R0 = rule {
+ K.W("this") ~ ParamClause ~ ParamClauses ~ (K.O("=") ~ ConstrExpr | optional(Newline) ~ ConstrBlock) |
+ FunSig ~
+ (
+ optional(K.O(":") ~ Type) ~ K.O("=") ~ optional(K.W("macro")) ~ Expr |
+ optional(Newline) ~ '{' ~ Block ~ "}"
+ )
+ }
+ def TypeDef: R0 = rule { Id ~ optional(TypeParamClause) ~ K.O("=") ~ Type }
+
+ def TmplDef: R0 = rule {
+ K.W("trait") ~ TraitDef |
+ optional(K.W("case")) ~ (K.W("class") ~ ClassDef |
+ K.W("object") ~ ObjectDef)
+ }
+ def ClassDef: R0 = rule {
+ Id ~
+ optional(TypeParamClause) ~
+ zeroOrMore(ConstrAnnotation) ~
+ optional(AccessModifier) ~
+ ClassParamClauses ~
+ ClassTemplateOpt
+ }
+ def TraitDef: R0 = rule { Id ~ optional(TypeParamClause) ~ TraitTemplateOpt }
+ def ObjectDef: R0 = rule { Id ~ ClassTemplateOpt }
+ def ClassTemplateOpt: R0 = rule {
+ WL ~ K.W("extends") ~ ClassTemplate |
+ optional(WL ~ optional(K.W("extends")) ~ TemplateBody)
+ }
+ def TraitTemplateOpt: R0 = rule { K.W("extends") ~ TraitTemplate | optional(optional(K.W("extends")) ~ TemplateBody) }
+ def ClassTemplate: R0 = rule {
+ optional(EarlyDefs) ~
+ ClassParents ~
+ optional(WL ~ TemplateBody)
+ }
+
+ def TraitTemplate: R0 = rule {
+ optional(EarlyDefs) ~ TraitParents ~ optional(TemplateBody)
+ }
+ def ClassParents: R0 = rule {
+ Constr ~ zeroOrMore(WL ~ K.W("with") ~ AnnotType)
+ }
+ def TraitParents: R0 = rule {
+ AnnotType ~ zeroOrMore(WL ~ K.W("with") ~ AnnotType)
+ }
+ def Constr: R0 = rule {
+ AnnotType ~ zeroOrMore(WL ~ ArgumentExprs)
+ }
+ def EarlyDefs: R0 = rule {
+ '{' ~ optional(oneOrMore(EarlyDef).separatedBy(Semis)) ~ '}' ~ K.W("with")
+ }
+ def EarlyDef: R0 = rule {
+ zeroOrMore(Annotation ~ optional(Newline)) ~ zeroOrMore(Modifier) ~ PatVarDef
+ }
+ def ConstrExpr: R0 = rule { ConstrBlock | SelfInvocation }
+ def ConstrBlock: R0 = rule { '{' ~ SelfInvocation ~ zeroOrMore(Semis ~ BlockStat) ~ '}' }
+ def SelfInvocation: R0 = rule { K.W("this") ~ oneOrMore(ArgumentExprs) }
+
+ def TopStatSeq: R0 = rule { oneOrMore(TopStat).separatedBy(Semis) }
+ def TopStat: R0 = rule {
+ Packaging |
+ PackageObject |
+ Import |
+ zeroOrMore(Annotation ~ optional(Newline)) ~ zeroOrMore(Modifier) ~ TmplDef
+ }
+ def Packaging: R0 = rule { K.W("package") ~ QualId ~ '{' ~ TopStatSeq ~ '}' }
+ def PackageObject: R0 = rule { K.W("package") ~ K.W("object") ~ ObjectDef }
+ def TopPackageSeq: R0 = rule{
+ oneOrMore(K.W("package") ~ QualId).separatedBy(Semis)
+ }
+ def CompilationUnit: Rule1[String] = rule {
+ capture(
+ pr("CompulationUnit 0") ~
+ optional(Semis) ~
+ pr("CompulationUnit 1") ~
+ (TopPackageSeq ~ optional(Semis ~ TopStatSeq) | TopStatSeq) ~
+ optional(Semis) ~
+ WL
+
+ )
+ }
+}
diff --git a/scalatex/scalaParser/src/main/scala/scalaParser/syntax/Basic.scala b/scalatex/scalaParser/src/main/scala/scalaParser/syntax/Basic.scala
new file mode 100644
index 0000000..8d3232a
--- /dev/null
+++ b/scalatex/scalaParser/src/main/scala/scalaParser/syntax/Basic.scala
@@ -0,0 +1,51 @@
+package scalaParser
+package syntax
+import acyclic.file
+import org.parboiled2._
+
+trait Basic { self: Parser =>
+ object Basic{
+ def UnicodeExcape = rule { "\\u" ~ 4.times(HexDigit) }
+
+
+ //Numbers and digits
+ def HexDigit = rule { Digit | "a" - "f" | "A" - "Z" }
+ def Digit = rule { "0" | NonZeroDigit }
+ def NonZeroDigit = rule { "1" - "9" }
+ def HexNumeral = rule { "0x" ~ oneOrMore(HexDigit) }
+ def DecimalNumeral = rule(oneOrMore(Digit))
+ def ExponentPart = rule { anyOf("Ee") ~ optional(anyOf("+-")) ~ oneOrMore(Digit) }
+ def FloatType = rule { anyOf("FfDd") }
+
+ def Parentheses = rule { "(" | ")" | "[" | "]" | "{" | "}" }
+ def DelimiterChar = rule { "'" | "\"" | "." | ";" | "," }
+
+ def WhitespaceChar = rule { "\u0020" | "\u0009" }
+ def Newline = rule { "\r\n" | "\n" }
+ def Semi = rule { ';' | oneOrMore(Newline) }
+ def OperatorChar = rule {
+ anyOf("""!#$%&*+-/:<=>?@\^|~""") |
+ CharPredicate.from(_.getType match {
+ case Character.OTHER_SYMBOL | Character.MATH_SYMBOL => true; case _ => false
+ })
+ }
+ def Letter = rule { Upper | Lower | CharPredicate.from(c => c.isLetter | c.isDigit) }
+ def Lower = rule { "a" - "z" | "$" | "_" | CharPredicate.from(_.isLower) }
+ def Upper = rule { "A" - "Z" | CharPredicate.from(_.isUpper) }
+ }
+ /**
+ * Most keywords don't just require the correct characters to match,
+ * they have to ensure that subsequent characters *don't* match in
+ * order for it to be a keyword. This enforces that rule for key-words
+ * (W) and key-operators (O) which have different non-match criteria.
+ */
+ object Key {
+ def W(s: String) = rule {
+ str(s) ~ !(Basic.Letter | Basic.Digit)
+ }
+
+ def O(s: String) = rule {
+ str(s) ~ !Basic.OperatorChar
+ }
+ }
+}
diff --git a/scalatex/scalaParser/src/main/scala/scalaParser/syntax/Identifiers.scala b/scalatex/scalaParser/src/main/scala/scalaParser/syntax/Identifiers.scala
new file mode 100644
index 0000000..4bc972f
--- /dev/null
+++ b/scalatex/scalaParser/src/main/scala/scalaParser/syntax/Identifiers.scala
@@ -0,0 +1,35 @@
+package scalaParser
+package syntax
+import acyclic.file
+import org.parboiled2._
+
+trait Identifiers { self: Parser with Basic =>
+ object Identifiers{
+ import Basic._
+ def Operator = rule(oneOrMore(OperatorChar))
+
+ def VarId = rule {
+ !(Keywords ~ (WhitespaceChar | Newline | "//" | "/*")) ~ Lower ~ IdRest
+ }
+ def PlainId = rule { Upper ~ IdRest | VarId | !(Keywords ~ (WhitespaceChar | Newline | "//" | "/*")) ~ Operator }
+ def Id = rule { PlainId | ("`" ~ oneOrMore(noneOf("`")) ~ "`") }
+ def IdRest = rule {
+ zeroOrMore(zeroOrMore("_") ~ oneOrMore(!"_" ~ Letter | Digit)) ~
+ optional(oneOrMore("_") ~ optional(Operator))
+ }
+
+
+ def AlphabetKeywords = rule {
+ "abstract" | "case" | "catch" | "class" | "def" | "do" | "else" | "extends" | "false" | "finally" | "final" | "finally" | "forSome" | "for" | "if" |
+ "implicit" | "import" | "lazy" | "match" | "new" | "null" | "object" | "override" | "package" | "private" | "protected" | "return" |
+ "sealed" | "super" | "this" | "throw" | "trait" | "try" | "true" | "type" | "val" | "var" | "while" | "with" | "yield" | "_"
+ }
+ def SymbolicKeywords = rule{
+ ":" | ";" | "=>" | "=" | "<-" | "<:" | "<%" | ">:" | "#" | "@" | "\u21d2" | "\u2190"
+ }
+ def Keywords = rule {
+ AlphabetKeywords ~ !Letter | SymbolicKeywords ~ !OperatorChar
+
+ }
+ }
+}
diff --git a/scalatex/scalaParser/src/main/scala/scalaParser/syntax/Literals.scala b/scalatex/scalaParser/src/main/scala/scalaParser/syntax/Literals.scala
new file mode 100644
index 0000000..9fd9d5b
--- /dev/null
+++ b/scalatex/scalaParser/src/main/scala/scalaParser/syntax/Literals.scala
@@ -0,0 +1,58 @@
+package scalaParser
+package syntax
+import acyclic.file
+import org.parboiled2._
+
+trait Literals { self: Parser with Basic with Identifiers =>
+ object Literals{
+ import Basic._
+ def FloatingPointLiteral = rule {
+
+ "." ~ oneOrMore(Digit) ~ optional(ExponentPart) ~ optional(FloatType) |
+ oneOrMore(Digit) ~ (
+ "." ~ oneOrMore(Digit) ~ optional(ExponentPart) ~ optional(FloatType) |
+ ExponentPart ~ optional(FloatType) |
+ optional(ExponentPart) ~ FloatType
+ )
+ }
+
+ def IntegerLiteral = rule { (DecimalNumeral | HexNumeral) ~ optional(anyOf("Ll")) }
+
+ def BooleanLiteral = rule { Key.W("true") | Key.W("false") }
+
+ def MultilineComment: Rule0 = rule { "/*" ~ zeroOrMore(MultilineComment | !"*/" ~ ANY) ~ "*/" }
+ def Comment: Rule0 = rule {
+ MultilineComment |
+ "//" ~ zeroOrMore(!Basic.Newline ~ ANY) ~ &(Basic.Newline | EOI)
+ }
+
+ def Literal = rule {
+ (optional("-") ~ (FloatingPointLiteral | IntegerLiteral)) |
+ BooleanLiteral |
+ CharacterLiteral |
+ StringLiteral |
+ SymbolLiteral |
+ (Key.W("null") ~ !(Basic.Letter | Basic.Digit))
+ }
+
+
+ def EscapedChars = rule { '\\' ~ anyOf("rnt\\\"") }
+
+ // Note that symbols can take on the same values as keywords!
+ def SymbolLiteral = rule { ''' ~ (Identifiers.PlainId | Identifiers.Keywords) }
+
+ def CharacterLiteral = rule { ''' ~ (UnicodeExcape | EscapedChars | !'\\' ~ CharPredicate.from(isPrintableChar)) ~ ''' }
+
+ def MultiLineChars = rule { zeroOrMore(optional('"') ~ optional('"') ~ noneOf("\"")) }
+ def StringLiteral = rule {
+ (optional(Identifiers.Id) ~ "\"\"\"" ~ MultiLineChars ~ ("\"\"\"" ~ zeroOrMore('"'))) |
+ (optional(Identifiers.Id) ~ '"' ~ zeroOrMore("\\\"" | noneOf("\n\"")) ~ '"')
+ }
+
+ def isPrintableChar(c: Char): Boolean = {
+ val block = Character.UnicodeBlock.of(c)
+ !Character.isISOControl(c) && !Character.isSurrogate(c) && block != null && block != Character.UnicodeBlock.SPECIALS
+ }
+ }
+}
+
diff --git a/scalatex/scalaParser/src/test/scala/scalaParser/SyntaxTest.scala b/scalatex/scalaParser/src/test/scala/scalaParser/SyntaxTest.scala
new file mode 100644
index 0000000..fe5fc2c
--- /dev/null
+++ b/scalatex/scalaParser/src/test/scala/scalaParser/SyntaxTest.scala
@@ -0,0 +1,448 @@
+package scalaParser
+
+import org.parboiled2.ParseError
+import utest._
+import utest.framework.Test
+import utest.util.Tree
+
+import scala.util.{Failure, Success}
+
+object SyntaxTest extends TestSuite{
+ def check[T](input: String) = {
+ println("Checking...")
+ new ScalaSyntax(input).CompilationUnit.run() match{
+ case Failure(f: ParseError) =>
+ println(f.position)
+ println(f.formatExpectedAsString)
+ println(f.formatTraces)
+ throw new Exception(f.position + "\t" + f.formatTraces)
+ case Success(parsed) =>
+ assert(parsed == input)
+ }
+ }
+ println("running")
+ def tests = TestSuite{
+ 'unit {
+ * - check(
+ "package torimatomeru"
+
+ )
+ * - check(
+ """package torimatomeru
+ |
+ |package lols
+ """.stripMargin
+ )
+ * - check(
+ """package torimatomeru
+ |import a
+ |import b
+ """.stripMargin
+ )
+ * - check(
+ """
+ |package torimatomeru
+ |
+ |import org.parboiled2.ParseError
+ |import utest._
+ |import utest.framework.Test
+ |import utest.util.Tree
+ |
+ |import scala.util.{Failure, Success}
+ |
+ |object SyntaxTest extends TestSuite
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object SyntaxTest extends TestSuite{
+ | def check[T](input: String) = {
+ |
+ | }
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object SyntaxTest{
+ | a()
+ | throw 1
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object SyntaxTest extends TestSuite{
+ | {
+ | println
+ | throw 1
+ | }
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """package scalatex
+ |
+ |
+ |import org.parboiled2._
+ |import torimatomeru.ScalaSyntax
+ |
+ |import scalatex.stages.{Trim, Parser, Ast}
+ |import scalatex.stages.Ast.Block.{IfElse, For, Text}
+ |import Ast.Chain.Args
+ |
+ |object ParserTests extends utest.TestSuite{
+ | import Ast._
+ | import utest._
+ | def check[T](input: String, parse: Parser => scala.util.Try[T], expected: T) = {
+ | val parsed = parse(new Parser(input)).get
+ | assert(parsed == expected)
+ | }
+ | def tests = TestSuite{}
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object Moo{
+ | a
+ | .b
+ |
+ | c
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object Moo{
+ | filename
+ | .asInstanceOf[Literal]
+ |10
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object Cow{
+ | ().mkString
+ |
+ | 1
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object O{
+ | private[this] val applyMacroFull = 1
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object O{
+ | private[this] def applyMacroFull(c: Context)
+ | (expr: c.Expr[String],
+ | runtimeErrors: Boolean,
+ | debug: Boolean)
+ | : c.Expr[Frag] = {
+ | }
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object O{
+ | class DebugFailure extends Exception
+ |
+ | 1
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |package torimatomeru
+ |
+ |package syntax
+ |
+ |import org.parboiled2._
+ |
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object Foo{
+ | 0 match {
+ | case A | B => 0
+ | }
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object Compiler{
+ |
+ | def apply = {
+ | def rec = t match {
+ | case 0 => 0
+ | }
+ |
+ | rec(tree)
+ | }
+ |}
+ |
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object O {
+ | A(A(A(A(A(A(A(A())))))))
+ |}
+ |
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object O{
+ | A(A(A(A(A(A(A(A(A(A(A(A(A(A(A(A())))))))))))))))
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object L{
+ | a.b = c
+ | a().b = c
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object L{
+ | a b c
+ | d = 1
+ |}
+ """.stripMargin
+ )
+
+ * - check(
+ """/* __ *\
+ |** ________ ___ / / ___ __ ____ Scala.js CLI **
+ |** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+ |** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+ |** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+ |** |/____/ **
+ |\* */
+ |
+ |package scala.scalajs.cli
+ |
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object O{
+ | for {
+ | a <- b
+ | c <- d
+ | } {
+ | 1
+ | }
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object O{
+ | val jarFile =
+ | try { 1 }
+ | catch { case _: F => G }
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object F{
+ | func{ case _: F => fail }
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object Foo{
+ | val a = d // g
+ | val b = e // h
+ | val c = f
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object L{
+ | x match{
+ | case y.Y(z) => z
+ | }
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """object K{
+ | val a: B {
+ | val c: D
+ | }
+ |
+ | 1
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object LOLS{
+ | def run() {}
+ |
+ | def apply() {}
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object O{
+ | a =:= b.c
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object K{
+ | a(
+ | 1: _*
+ | )
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object P{
+ | tree match {
+ | case stats :+ expr => 1
+ | }
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object K{
+ | val trueA = 1
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object K{
+ | val nullo :: cow = 1
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object K{
+ | val omg_+ = 1
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object K{
+ | val + = 1
+ | var * = 2
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |object O{
+ | c match {
+ | case b_ => 1
+ | }
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |trait Basic {
+ | b match {
+ | case C => true; case _ => false
+ | }
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """trait Basic {
+ | !a.b
+ |}
+ """.stripMargin
+ )
+ * - check(
+ """
+ |class Parser {
+ | {() => }
+ |}
+ |
+ """.stripMargin
+ )
+ * - check(
+ """
+ |
+ |
+ |
+ |package omg
+ |;
+ |
+ |;
+ |
+ |;
+ |class Parser
+ |;
+ |
+ |;
+ |
+ |;
+ """.stripMargin
+ )
+ }
+ def checkFile(path: String) = check(io.Source.fromFile(path).mkString)
+ 'file{
+
+ * - checkFile("test.txt")
+ * - checkFile("scalaParser/src/main/scala/scalaParser/syntax/Basic.scala")
+ * - checkFile("scalaParser/src/main/scala/scalaParser/syntax/Identifiers.scala")
+ * - checkFile("scalaParser/src/main/scala/scalaParser/syntax/Literals.scala")
+ * - checkFile("scalaParser/src/main/scala/scalaParser/ScalaSyntax.scala")
+
+ * - checkFile("scalaParser/src/test/scala/scalaParser/SyntaxTest.scala")
+
+
+ * - checkFile("scalatexApi/src/main/scala/scalatex/stages/Compiler.scala")
+ * - checkFile("scalatexApi/src/main/scala/scalatex/stages/Parser.scala")
+ * - checkFile("scalatexApi/src/main/scala/scalatex/stages/Trim.scala")
+ * - checkFile("scalatexApi/src/main/scala/scalatex/package.scala")
+
+ * - checkFile("scalatexApi/src/test/scala/scalatex/ParserTests.scala")
+ * - checkFile("scalatexApi/src/test/scala/scalatex/BasicTests.scala")
+ * - checkFile("scalatexApi/src/test/scala/scalatex/ErrorTests.scala")
+ * - checkFile("scalatexApi/src/test/scala/scalatex/TestUtil.scala")
+
+ * - checkFile("scalatexPlugin/src/main/scala/scalatex/ScalaTexPlugin.scala")
+ }
+
+// 'omg{
+// val root = new java.io.File("../scala-js/")
+// def listFiles(s: java.io.File): Iterator[String] = {
+// val (dirs, files) = s.listFiles().toIterator.partition(_.isDirectory)
+// files.map(_.getPath) ++ dirs.flatMap(listFiles)
+// }
+// for(f <- listFiles(root).filter(_.endsWith(".scala"))){
+// println("CHECKING " + f)
+// checkFile(f)
+// }
+// }
+ }
+} \ No newline at end of file
diff --git a/scalatex/scalatexSbtPlugin/src/main/scala/SbtPlugin.scala b/scalatex/scalatexSbtPlugin/src/main/scala/SbtPlugin.scala
new file mode 100644
index 0000000..f09dc17
--- /dev/null
+++ b/scalatex/scalatexSbtPlugin/src/main/scala/SbtPlugin.scala
@@ -0,0 +1,16 @@
+package scalatex
+
+import sbt.Keys._
+import sbt._
+object SbtPlugin extends sbt.Plugin{
+ val scalatexDirectory = taskKey[sbt.File]("Clone stuff from github")
+ override val settings = Seq(
+ scalatexDirectory := sourceDirectory.value / "scalatex",
+ scalacOptions += {
+ "-P:scalatex:root:" + scalatexDirectory.value.getCanonicalPath
+ },
+ watchSources += scalatexDirectory.value,
+ addCompilerPlugin("com.lihaoyi" %% "scalatex-compiler-plugin" % "0.1.0"),
+ libraryDependencies += "com.lihaoyi" %% "scalatex-api" % "0.1.0"
+ )
+}