From 8f232668fe68d8b0bd070977ee22c1d71fe4f424 Mon Sep 17 00:00:00 2001 From: Mats Rydberg Date: Thu, 11 Apr 2019 14:20:45 +0200 Subject: [PATCH] Support escaping in string literals Allows backticks to be used in string values Co-authored-by: Philip Stutz --- .../tools/tck/values/CypherValueParser.scala | 28 +++++++++++++++++-- .../tools/tck/CypherValueParserTest.scala | 12 ++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/tools/tck-api/src/main/scala/org/opencypher/tools/tck/values/CypherValueParser.scala b/tools/tck-api/src/main/scala/org/opencypher/tools/tck/values/CypherValueParser.scala index 6b9bafa89e..1fe6624f4e 100644 --- a/tools/tck-api/src/main/scala/org/opencypher/tools/tck/values/CypherValueParser.scala +++ b/tools/tck-api/src/main/scala/org/opencypher/tools/tck/values/CypherValueParser.scala @@ -30,7 +30,7 @@ import fastparse.Parsed.{Failure, Success} import fastparse._ import org.opencypher.tools.tck.values.Connection.{backward, forward} -case class CypherValueParseException(msg: String) extends Exception(msg) +case class CypherValueParseException(msg: String, expected: String) extends Exception(msg) class CypherValueParser(val orderedLists: Boolean) { def parse(s: String): CypherValue = { @@ -50,7 +50,7 @@ class CypherValueParser(val orderedLists: Boolean) { |$locationPointer | |${extra.trace().msg}""".stripMargin - throw CypherValueParseException(msg) + throw CypherValueParseException(msg, expected) } } @@ -102,7 +102,10 @@ class CypherValueParser(val orderedLists: Boolean) { } private def string[_: P]: P[CypherString] = - P("'" ~ CharsWhile(_ != ''', 0).!.map(CypherString) ~ "'") + P("'" ~/ (stringChunk | backslash | escape).rep.!.map { s => + val escaped = s.replaceAllLiterally("\\'", "'").replaceAllLiterally("\\\\", "\\") + CypherString(escaped) + } ~ "'") private def float[_: P]: P[CypherFloat] = P("-".? ~ floatRepr).!.map { s => @@ -136,6 +139,25 @@ class CypherValueParser(val orderedLists: Boolean) { private def symbolicName[_: P]: P[String] = CharsWhileIn("a-zA-Z0-9$_").! + /** + * A 'simple' string chunk; without apostrophes or backslash (escape sequence) + */ + private def stringChunk[_: P]: P[Unit] = { + CharsWhile(c => c != ''' && c != '\\') + } + /** + * We escape apostrophes inside strings using a backslash + */ + private def escape[_: P]: P[Unit] = { + P("\\") ~/ P("'") + } + /** + * Since backslash is used + */ + private def backslash[_: P]: P[Unit] = { + P("\\\\") + } + private def digits[_: P]: P[Unit] = CharsWhileIn("0-9") private def floatRepr[_: P]: P[Unit] = (digits ~~ "." ~~ digits ~~ exponent.?) | diff --git a/tools/tck-api/src/test/scala/org/opencypher/tools/tck/CypherValueParserTest.scala b/tools/tck-api/src/test/scala/org/opencypher/tools/tck/CypherValueParserTest.scala index c62b1f7d8b..f6c498755d 100644 --- a/tools/tck-api/src/test/scala/org/opencypher/tools/tck/CypherValueParserTest.scala +++ b/tools/tck-api/src/test/scala/org/opencypher/tools/tck/CypherValueParserTest.scala @@ -64,6 +64,18 @@ class CypherValueParserTest extends FunSuite with Matchers { CypherValue("null") should equal(CypherNull) } + test("string escaping") { + CypherValue("'The Devil\\'s Advocate'") should equal(CypherString("The Devil's Advocate")) + CypherValue("'\\\\'") should equal(CypherString("\\")) + CypherValue("'\\''") should equal(CypherString("'")) + CypherValue("'\\'\\''") should equal(CypherString("''")) + } + + test("incorrect escaping") { + (the[CypherValueParseException] thrownBy CypherValue("'\\'")).expected should equal("\"'\"") + (the[CypherValueParseException] thrownBy CypherValue("'''")).expected should equal("end-of-input") + } + test("floats in exponent form") { CypherValue(".4e10") should equal(CypherFloat(.4e10)) CypherValue(".4e-10") should equal(CypherFloat(.4e-10))