Skip to content
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.
You can’t perform that action at this time.