diff options
Diffstat (limited to 'api/src/main/scala')
-rw-r--r-- | api/src/main/scala/twist/Util.scala | 10 | ||||
-rw-r--r-- | api/src/main/scala/twist/package.scala | 88 | ||||
-rw-r--r-- | api/src/main/scala/twist/stages/Compiler.scala | 161 | ||||
-rw-r--r-- | api/src/main/scala/twist/stages/IndentHandler.scala | 105 | ||||
-rw-r--r-- | api/src/main/scala/twist/stages/Parser.scala | 790 |
5 files changed, 0 insertions, 1154 deletions
diff --git a/api/src/main/scala/twist/Util.scala b/api/src/main/scala/twist/Util.scala deleted file mode 100644 index c7b3598..0000000 --- a/api/src/main/scala/twist/Util.scala +++ /dev/null @@ -1,10 +0,0 @@ -package twist -import acyclic.file - -object Util { - - implicit class Pipeable[T](t: T){ - def |>[V](f: T => V): V = f(t) - } -} - diff --git a/api/src/main/scala/twist/package.scala b/api/src/main/scala/twist/package.scala deleted file mode 100644 index a70212a..0000000 --- a/api/src/main/scala/twist/package.scala +++ /dev/null @@ -1,88 +0,0 @@ -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 twist.stages.Compiler -import scala.language.experimental.macros -import acyclic.file - -package object twist { - import Util._ - /** - * 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 twDebug(expr: String): Frag = macro applyMacroDebug - - def applyMacro(c: Context)(expr: c.Expr[String]): c.Expr[Frag] = applyMacroFull(c)(expr, false) - - def applyMacroDebug(c: Context)(expr: c.Expr[String]): c.Expr[Frag] = applyMacroFull(c)(expr, true) - - def applyMacroFile(c: Context)(filename: c.Expr[String]): c.Expr[Frag] = { - import c.universe._ - val s = filename.tree - .asInstanceOf[Literal] - .value - .value - .asInstanceOf[String] - val txt = io.Source.fromFile(s).mkString |> stages.IndentHandler - val sourceFile = new BatchSourceFile( - new PlainFile(s), - txt.toCharArray - ) - - compileThing(c)(txt, sourceFile, 0, false) - } - - case class DebugFailure(msg: String, pos: String) extends Exception(msg) - - private[this] def applyMacroFull(c: Context)(expr: c.Expr[String], runtimeErrors: Boolean): c.Expr[Frag] = { - import c.universe._ - val s = expr.tree - .asInstanceOf[Literal] - .value - .value - .asInstanceOf[String] - val stringStart = - expr.tree - .pos - .lineContent - .drop(expr.tree.pos.column) - .take(2) - compileThing(c)( - s |> stages.IndentHandler, - expr.tree.pos.source, - expr.tree.pos.point + (if (stringStart == "\"\"") 1 else -1), - runtimeErrors - ) - } - } - - def compileThing(c: Context)(s: String, source: SourceFile, point: Int, runtimeErrors: Boolean) = { - import c.universe._ - def compile(s: String): c.Tree = { - - - val realPos = new OffsetPosition(source, point).asInstanceOf[c.universe.Position] - - Compiler(c)(realPos, s |> stages.Parser) - } - - import c.Position - try { - c.Expr(c.typeCheck(compile(s))) - } 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 twist.Internals.DebugFailure($msg, $posMsg)""") - } - } - - } - -} diff --git a/api/src/main/scala/twist/stages/Compiler.scala b/api/src/main/scala/twist/stages/Compiler.scala deleted file mode 100644 index e00c373..0000000 --- a/api/src/main/scala/twist/stages/Compiler.scala +++ /dev/null @@ -1,161 +0,0 @@ -package twist.stages -import acyclic.file - -import scala.reflect.macros.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. - */ -object Compiler{ - val WN = TwistNodes - def apply(c: Context)(literalPos: c.Position, template: WN.Template): c.Tree = { - - import c.universe._ - def fragType = tq"scalatags.Text.all.Frag" - def posFor(offset: Int) = { - new OffsetPosition( - literalPos.source, - offset - ).asInstanceOf[c.universe.Position] - } - def compileTree(frag: WN.TemplateTree): Tree = { - -// println(frag) - val fragPos = posFor(literalPos.point + frag.offset) - -// println(s"${frag.offset}\n${literalPos.point}\n${pos.point}\n$frag\n") - - val f: Tree = frag match { - case WN.Plain(text, offset) => q"$text" - 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) x.pos = posFor(x.pos.point + fragPos.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)) - a2.pos = a.pos - f2.pos = f.pos - a2 - case Ident(x: TermName) if x.decoded == fresh => - q"Seq[$fragType](..$b)" - } - - val out = rec(skeleton) - - - out - - 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 => - x.pos = posFor(x.pos.point + fragPos.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 => - x.pos = posFor(x.pos.point + fragPos.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 newPos = posFor(fragPos.point + t.pos.point + first.length - fresh.length) - val res = t match { - case Apply(fun, args) => - for(arg <- args; tree <- arg if tree.pos != NoPosition){ - tree.pos = posFor(tree.pos.point + newPos.point - t.pos.point) - } - - Apply(rec(fun), args) - case Select(qualifier, name) => Select(rec(qualifier), name) - case Ident(x: TermName) if x.decoded == fresh => l - } - res.pos = newPos - -// println(Position.formatMessage(newPos.asInstanceOf[scala.reflect.internal.util.Position], "", true)) - res - } - - - rec(skeleton) - - case (l, WN.Block(ws, None, content, _)) => - q"$l(..${content.map(compileTree(_))})" - - case (l, WN.Block(ws, Some(args), content, _)) => - val snippet = s"{$args ()}" - val skeleton = c.parse(snippet) - val Function(vparamss, body) = skeleton - - val func = Function(vparamss, q"Seq[$fragType](..${content.map(compileTree(_))})") - func.pos = posFor(fragPos.point + skeleton.pos.point) - q"$l($func)" - } - - s - } - -// f.foreach(_.pos = pos) - -// println("XXX " + f.pos) -// println("F " + f) - 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"""{ - import scalatags.Text.all._ - ..${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/api/src/main/scala/twist/stages/IndentHandler.scala b/api/src/main/scala/twist/stages/IndentHandler.scala deleted file mode 100644 index 1a59046..0000000 --- a/api/src/main/scala/twist/stages/IndentHandler.scala +++ /dev/null @@ -1,105 +0,0 @@ -package twist.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())) - - val exprRemainder = successRemainder(Parser.parse(current.trim, _.expression())).filter(_ == current.trim) - - - /** - * 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" - - 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/api/src/main/scala/twist/stages/Parser.scala b/api/src/main/scala/twist/stages/Parser.scala deleted file mode 100644 index 925cbcc..0000000 --- a/api/src/main/scala/twist/stages/Parser.scala +++ /dev/null @@ -1,790 +0,0 @@ -package twist.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 |