diff options
6 files changed, 243 insertions, 162 deletions
diff --git a/scalatexApi/src/main/scala/scalatex/package.scala b/scalatexApi/src/main/scala/scalatex/package.scala index eb3ba6e..e2566cf 100644 --- a/scalatexApi/src/main/scala/scalatex/package.scala +++ b/scalatexApi/src/main/scala/scalatex/package.scala @@ -78,13 +78,9 @@ package object scalatex { def compile(s: String): c.Tree = { val realPos = new OffsetPosition(source, point).asInstanceOf[c.universe.Position] - Compiler(c)(realPos, new Parser(s).Body.run().get) - } - def normalize(str: String) = { - val lines = str.split("\n") - val offset = lines.iterator.map(_.takeWhile(_ == ' ').length).min - lines.iterator.map(_.drop(offset)).mkString("\n") + Compiler(c)(realPos, Parser(stages.Trim(s))) } + import c.Position try { diff --git a/scalatexApi/src/main/scala/scalatex/stages/Compiler.scala b/scalatexApi/src/main/scala/scalatex/stages/Compiler.scala index a305199..19cec10 100644 --- a/scalatexApi/src/main/scala/scalatex/stages/Compiler.scala +++ b/scalatexApi/src/main/scala/scalatex/stages/Compiler.scala @@ -6,9 +6,7 @@ import scala.reflect.macros.whitebox.Context import scala.reflect.internal.util.{Position, OffsetPosition} /** - * Walks the parsed AST, converting it into an un-structured Scala source-blob - * which when compiled results in a function that can be used to generate the - * given Frag at runtime. + * Walks the parsed AST, converting it into a structured Scala c.Tree */ object Compiler{ @@ -17,7 +15,7 @@ object Compiler{ import c.universe._ def fragType = tq"scalatags.Text.all.Frag" - + println(template) 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)}" diff --git a/scalatexApi/src/main/scala/scalatex/stages/Parser.scala b/scalatexApi/src/main/scala/scalatex/stages/Parser.scala index d8878de..0bba713 100644 --- a/scalatexApi/src/main/scala/scalatex/stages/Parser.scala +++ b/scalatexApi/src/main/scala/scalatex/stages/Parser.scala @@ -4,26 +4,15 @@ package stages import org.parboiled2._ import torimatomeru.ScalaSyntax import Util._ -trait Ast{ - def offset: Int -} -object Ast{ - case class Block(parts: Seq[Block.Sub], offset: Int = 0) extends Chain.Sub - object Block{ - trait Sub extends Ast - case class Text(txt: String, 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 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 - } -} -object Parser{ - def parse(input: String): Ast.Block = { +/** + * 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 => Ast.Block){ + def apply(input: String): Ast.Block = { new Parser(input).Body.run().get } } @@ -95,3 +84,22 @@ class Parser(input: ParserInput, indent: Int = 0) extends ScalaSyntax(input) { } } } + +trait Ast{ + def offset: Int +} +object Ast{ + case class Block(parts: Seq[Block.Sub], offset: Int = 0) extends Chain.Sub + object Block{ + trait Sub extends Ast + case class Text(txt: String, 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 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 + } +}
\ No newline at end of file diff --git a/scalatexApi/src/main/scala/scalatex/stages/Trim.scala b/scalatexApi/src/main/scala/scalatex/stages/Trim.scala new file mode 100644 index 0000000..02d9cc4 --- /dev/null +++ b/scalatexApi/src/main/scala/scalatex/stages/Trim.scala @@ -0,0 +1,23 @@ +package scalatex.stages + +/** + * 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){ + def apply(str: String) = { + val lines = str.split("\n") + val offset = lines.iterator + .filter(_.length > 0) + .next() + .takeWhile(_ == ' ') + .length + lines.iterator + .map(_.drop(offset).replaceFirst("\\s+$", "")) + .mkString("\n") + } +} diff --git a/scalatexApi/src/test/scala/scalatex/BasicTests.scala b/scalatexApi/src/test/scala/scalatex/BasicTests.scala index 91291dd..c7e2d6a 100644 --- a/scalatexApi/src/test/scala/scalatex/BasicTests.scala +++ b/scalatexApi/src/test/scala/scalatex/BasicTests.scala @@ -11,42 +11,42 @@ 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|" -// ) -// } + + '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 -// """ -// ) + '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 + """ + ) } // 'imports{ // object Whee{ @@ -70,20 +70,20 @@ object BasicTests extends TestSuite{ // """ // ) // } -// 'parenArgumentLists{ -// 'attributes{ -// check( -// tw(""" -// @div(id:="my-id"){ omg } -// @div(id:="my-id") -// omg -// """), -// """ -// <divid="my-id">omg</div> -// <divid="my-id">omg</div> -// """ -// ) -// } + '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( @@ -101,51 +101,51 @@ object BasicTests extends TestSuite{ // """ // ) // } -// } -// '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> -// """ -// ) -// } + } + '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(""" @@ -162,39 +162,39 @@ object BasicTests extends TestSuite{ // """ // ) // } -// '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> -// """ -// ) -// } -// } + '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(""" @@ -420,6 +420,6 @@ object BasicTests extends TestSuite{ // "<p>lols2</p>" // ) // } -// } + } } } diff --git a/scalatexApi/src/test/scala/scalatex/ParserTests.scala b/scalatexApi/src/test/scala/scalatex/ParserTests.scala index b6e64ec..45d6b50 100644 --- a/scalatexApi/src/test/scala/scalatex/ParserTests.scala +++ b/scalatexApi/src/test/scala/scalatex/ParserTests.scala @@ -16,7 +16,63 @@ object ParserTests extends utest.TestSuite{ assert(parsed.get == expected) } def tests = TestSuite{ - 'Test { + 'Trim{ + def wrap(s: String) = "|" + s + "|" + * - { + val trimmed = wrap(stages.Trim(""" + 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( + """ + @{"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( + 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")) @@ -63,7 +119,7 @@ object ParserTests extends utest.TestSuite{ ) } 'Body{ - * - check( + 'indents - check( """ |@omg | @wtf @@ -84,7 +140,7 @@ object ParserTests extends utest.TestSuite{ )))) )) ) - * - check( + 'dedents - check( """ |@omg | @wtf @@ -101,7 +157,7 @@ object ParserTests extends utest.TestSuite{ Chain("bbq", Seq()) )) ) - * - check( + 'dedentText - check( """ |@omg("lol", 1, 2) | @wtf @@ -139,7 +195,7 @@ object ParserTests extends utest.TestSuite{ // Text("bbq") // )) // ) - * - check( + 'codeBlocks - check( """ |@{"lol" * 3} |@{ |