Permalink
Browse files

Initial commit

  • Loading branch information...
sirthias committed Apr 18, 2013
0 parents commit 4b4405ea23a0e8fd57681a084cfc0ffa3257140b
@@ -0,0 +1,28 @@
+# SBT
+boot/
+lib_managed/
+src_managed/
+test-output/
+target/
+.history
+
+# IntelliJ
+.idea/
+*.iml
+*.ipr
+*.iws
+out/
+
+# Eclipse
+.cache
+.classpath
+.project
+.scala_dependencies
+.settings
+.target/
+
+# Mac
+.DS_Store
+
+# Other
+*.pyc
@@ -0,0 +1,40 @@
+import scalariform.formatter.preferences._
+
+name := "parboiled"
+
+version := "2.0.0-SNAPSHOT"
+
+homepage := Some(new URL("http://parboiled.org"))
+
+organization := "org.parboiled"
+
+organizationHomepage := Some(new URL("http://parboiled.org"))
+
+description := "A Scala 2.10+ library for elegant construction of efficient PEG parsers"
+
+startYear := Some(2009)
+
+licenses := Seq("Apache 2" -> new URL("http://www.apache.org/licenses/LICENSE-2.0.txt"))
+
+scalaVersion := "2.10.1"
+
+scalacOptions ++= Seq(
+ "-unchecked",
+ "-deprecation",
+ "-Xlint",
+ "-language:_",
+ "-encoding", "UTF-8"
+)
+
+libraryDependencies ++= Seq(
+ "org.scala-lang" % "scala-reflect" % "2.10.1" % "compile",
+ "org.specs2" %% "specs2" % "1.14" % "test"
+)
+
+scalariformSettings
+
+ScalariformKeys.preferences := FormattingPreferences()
+ .setPreference(RewriteArrowSymbols, true)
+ .setPreference(AlignParameters, true)
+ .setPreference(AlignSingleLineCaseStatements, true)
+ .setPreference(DoubleIndentClassDeclaration, true)
@@ -0,0 +1 @@
+sbt.version=0.12.3
@@ -0,0 +1,3 @@
+name := "parboiled-sbt-build"
+
+addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.0.1")
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2009-2013 Mathias Doenitz
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.parboiled
+
+import scala.reflect.macros.{ TypecheckException, Context }
+
+/**
+ * A utility which ensures that a code fragment does not typecheck.
+ * See: http://stackoverflow.com/questions/15125457/testing-an-assertion-that-something-must-not-compile
+ *
+ * Credit: Stefan Zeiger (@StefanZeiger) and Miles Sabin (@milessabin)
+ */
+object CompilerError {
+ def verify(code: String): Unit = macro applyImplNoExp
+ def verify(code: String, expectedErrorMsg: String): Unit = macro applyImpl
+
+ def applyImplNoExp(c: Context)(code: c.Expr[String]) = applyImpl(c)(code, null)
+
+ def applyImpl(c: Context)(code: c.Expr[String], expectedErrorMsg: c.Expr[String]): c.Expr[Unit] = {
+ import c.universe._
+
+ val Expr(Literal(Constant(codeStr: String))) = code
+ val expected = expectedErrorMsg match {
+ case null ""
+ case Expr(Literal(Constant(s: String))) s
+ }
+
+ try {
+ c.typeCheck(c.parse("{ " + codeStr + " }"))
+ c.abort(c.enclosingPosition, "Compiler error expected but not encountered. Expected: " + expected)
+ } catch {
+ case e: TypecheckException if expected.isEmpty // any exception is fine
+ case e: TypecheckException if e.getMessage.contains(expected) // ok
+ case e: TypecheckException
+ c.abort(c.enclosingPosition,
+ s"Encountered compiler error differs from expected:\n" +
+ s" Actual: ${e.getMessage}\n" +
+ s" Expected that error message contains: $expected")
+ }
+ reify(())
+ }
+}
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2009-2013 Mathias Doenitz
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.parboiled
+
+import scala.reflect.macros.Context
+
+abstract class Parser(input: ParserInput) {
+
+ def rule(r: Rule): Boolean = macro Parser.ruleImpl
+
+ implicit def charRule(c: Char) = Rule()
+ implicit def stringRule(stringLiteral: String) = macro Parser.stringRuleImpl
+}
+
+object Parser {
+ class GrammarException(_msg: String) extends RuntimeException(_msg)
+
+ type ParserContext = Context { type PrefixType = Parser }
+
+ /**
+ * Expands a string literal to a corresponding rule definition,
+ * e.g. "abc" is expanded to `'a' ~ 'b' ~ 'c'`.
+ */
+ def stringRuleImpl(c: ParserContext)(stringLiteral: c.Expr[String]): c.Expr[Rule] = {
+ import c.universe._
+ val chars = stringLiteral match {
+ case Expr(Literal(Constant(""))) fail("String literal in rule definitions must not be empty")
+ case Expr(Literal(Constant(s: String))) s.toList
+ case _ fail("Strings in rule definitions have to be literals")
+ }
+ def wrap(char: Char) = Apply(Select(c.prefix.tree, newTermName("charRule")), List(Literal(Constant(char))))
+ val tree = chars.tail.foldLeft(wrap(chars.head)) {
+ case (acc, char) Apply(Select(acc, newTermName("$tilde")), List(wrap(char)))
+ }
+ c.Expr[Rule](tree)
+ }
+
+ def ruleImpl(c: ParserContext)(r: c.Expr[Rule]): c.Expr[Boolean] = {
+ import c.universe._
+
+ println("rule: " + show(r.tree))
+ //println("rule: " + showRaw(r.tree))
+
+ reify {
+ false
+ }
+ }
+
+ private def fail(errorMsg: String) = throw new GrammarException(errorMsg)
+
+}
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009-2013 Mathias Doenitz
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.parboiled
+
+import java.nio.charset.Charset
+
+sealed abstract class ParserInput {
+ def charAt(ix: Int): Char
+ def length: Int
+ def sliceString(start: Int, end: Int): String
+ override def toString: String = sliceString(0, length)
+}
+
+// bimorphic ParserInput implementation
+// Note: make sure to not add another implementation, otherwise usage of this class
+// might turn megamorphic at the call-sites thereby effectively disabling method inlining!
+object ParserInput {
+ val UTF8 = Charset.forName("UTF-8")
+
+ implicit def apply(bytes: Array[Byte]): ParserInput = apply(bytes, UTF8)
+ def apply(bytes: Array[Byte], charset: Charset): ParserInput =
+ new ParserInput {
+ def charAt(ix: Int) = bytes(ix).toChar
+ def length = bytes.length
+ def sliceString(start: Int, end: Int) = new String(bytes, start, end - start, charset)
+ }
+ implicit def apply(chars: Array[Char]): ParserInput =
+ new ParserInput {
+ def charAt(ix: Int) = chars(ix)
+ def length = chars.length
+ def sliceString(start: Int, end: Int) = new String(chars, start, end - start)
+ }
+
+ private val field = classOf[String].getDeclaredField("value")
+ field.setAccessible(true)
+
+ implicit def apply(string: String): ParserInput = apply {
+ // http://stackoverflow.com/questions/8894258/fastest-way-to-iterate-over-all-the-chars-in-a-string
+ if (string.length > 64) field.get(string).asInstanceOf[Array[Char]]
+ else string.toCharArray
+ }
+}
+
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2009-2013 Mathias Doenitz
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.parboiled
+
+/**
+ * Phantom type for which no instance can be created.
+ * Only used for type-checking during compile-time.
+ */
+sealed abstract class Rule private () {
+ def ~(that: Rule): Rule
+ def |(that: Rule): Rule
+}
+
+object Rule {
+ class NotAvailableAtRuntimeException extends RuntimeException
+
+ def apply(): Rule = throw new NotAvailableAtRuntimeException
+}
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2009-2013 Mathias Doenitz
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.parboiled
+
+import org.specs2.mutable.Specification
+
+class ParserSpec extends Specification {
+
+ class TestParser(_input: ParserInput) extends Parser(_input) {
+ def ABC = rule { 'a' ~ 'b' ~ 'c' }
+ def DEF = rule { "def" }
+ }
+
+ "The new parboiled parser" should {
+
+ "successfully recognize valid input" in {
+ new TestParser("abc").ABC must beTrue
+ }
+
+ "successfully recognize valid input" in {
+ new TestParser("acb").ABC must beFalse
+ }
+
+ "properly expand string literals to a sequence of char rules" in {
+ new TestParser("def").DEF must beTrue
+ new TestParser("dfe").DEF must beFalse
+ }
+
+ "disallow compilation of an illegal string rule" in {
+ CompilerError.verify(
+ """class TestParser(_input: ParserInput) extends Parser(_input) {
+ val string = "def"
+ def Illegal = rule { string } // not allowed, strings must be given as literals
+ }""",
+ "Strings in rule definitions have to be literals")
+ }
+ }
+
+}

0 comments on commit 4b4405e

Please sign in to comment.