Skip to content

Commit

Permalink
Added full set of escape characters
Browse files Browse the repository at this point in the history
  • Loading branch information
systay committed Jul 17, 2012
1 parent 0c92569 commit 5eb1af5
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 12 deletions.
4 changes: 4 additions & 0 deletions cypher/CHANGES.txt
@@ -1,3 +1,7 @@
1.9.M01
--------------------
o Added all the escape characters that Java uses

1.8.M06 (2012-07-06)
--------------------
o Fixes problem when graph elements are deleted multiple times in the same query
Expand Down
18 changes: 18 additions & 0 deletions cypher/src/docs/dev/expressions.txt
Expand Up @@ -16,3 +16,21 @@ An expression in Cypher can be:
* An aggregate function -- `avg(x.prop)`, `count(*)`
* Relationship types -- `:REL_TYPE`, +:\`REL TYPE`+, `:REL1|REL2`
* A path-pattern: `a-->()<--b`

= Note on string literals =
Strings can contain escape sequences such as the ones defined for Java:

[options="header", cols=">,<", width="50%"]
|===================
|Escape seq|Character
|`\t`|Tab
|`\b`|Backspace
|`\n`|Newline
|`\r`|Carriage return
|`\f`|Form feed
|`\'`|Single quote
|`\"`|Double quote
|`\\`|Backslash
|===================


2 changes: 1 addition & 1 deletion cypher/src/main/scala/org/neo4j/cypher/CypherParser.scala
Expand Up @@ -41,7 +41,7 @@ class CypherParser(version: String) {
v match {
case "1.7" => v17.parse(q)
case "1.8" => v18.parse(q)
case "1.9" => v18.parse(q)
case "1.9" => v19.parse(q)
case _ => throw new SyntaxException("Versions supported are 1.7, 1.8 and 1.9")
}
}
Expand Down
Expand Up @@ -41,7 +41,7 @@ case class ReturnItem(expression: Expression, name: String, renamed: Boolean = f
extends ReturnColumn {
def expressions(symbols: SymbolTable) = Map(name -> expression)

override def toString = name
override def toString = expression + " as `"+ name + "`"

def rename(newName: String) = ReturnItem(expression, newName, renamed = true)
}
Expand Up @@ -37,4 +37,6 @@ case class Literal(v: Any) extends Expression {
def calculateType(symbols: SymbolTable): CypherType = CypherType.fromJava(v)

def symbolTableDependencies = Set()

override def toString() = "Literal(" + v.toString + ")"
}
Expand Up @@ -83,21 +83,35 @@ trait Expressions extends Base with ParserPattern with Predicates {
var result: Option[ParseResult[Expression]] = None

while (!ls.isEmpty && result.isEmpty) {
val (pref, suf) = ls span { c => c != '\\' && c != startChar }
val (pref, suf) = ls span {
c => c != '\\' && c != startChar
}
idx += pref.length
sb ++= pref

if (suf.isEmpty)
if (suf.isEmpty) {
result = Some(Failure("end of string missing", in))
} else {

val first: Char = suf(0)
first match {
case c if c == startChar =>
val first: Char = suf(0)
if (first == startChar) {
result = Some(Success(Literal(sb.result()), in.drop(idx - in.offset + 2)))
case '\\' if suf(1) == '\''||suf(1)=='\"' =>
sb.append(suf(1))
idx += 2
ls = suf.drop(2)
} else {
val (escChars, afterEscape) = suf.splitAt(2)

if (escChars.size == 1) {
result = Some(Failure("invalid escape sequence", in))
} else {

ls = afterEscape
idx += 2

parseEscapeChars(escChars.tail, in) match {
case Left(c) => sb.append(c)
case Right(failure) => result = Some(failure)
}
}
}
}
}

Expand All @@ -108,6 +122,22 @@ trait Expressions extends Base with ParserPattern with Predicates {
}
}

case class EscapeProduct(result: Option[ParseResult[Expression]])

private def parseEscapeChars(suf: List[Char], in:Input): Either[Char, Failure] = suf match {
case '\\' :: tail => Left('\\')
case 'a' :: tail => Left('\u0007')
case 'b' :: tail => Left('\b')
case 'f' :: tail => Left('\f')
case 'n' :: tail => Left('\n')
case 'r' :: tail => Left('\r')
case 't' :: tail => Left('\t')
case 'v' :: tail => Left('\u000b')
case '\'' :: tail => Left('\'')
case '"' :: tail => Left('"')
case _ => Right(Failure("invalid escape sequence", in))
}

def numberLiteral: Parser[Expression] = number ^^ (x => {
val value: Any = if (x.contains("."))
x.toDouble
Expand Down
21 changes: 20 additions & 1 deletion cypher/src/test/scala/org/neo4j/cypher/CypherParserTest.scala
Expand Up @@ -45,13 +45,20 @@ class CypherParserTest extends JUnitSuite with Assertions {
returns(ReturnItem(Literal("apa"), "\"apa\"")))
}

@Test def should_return_string_literal_with_escaped_quote_in() {
@Test def should_return_string_literal_with_escaped_quote_in1_8() {
testFrom_1_8("start s = node(1) return \"ap\\\"a\"",
Query.
start(NodeById("s", 1)).
returns(ReturnItem(Literal("ap\"a"), "\"ap\\\"a\"")))
}

@Test def should_return_string_literal_with_escaped_quote_in() {
testFrom_1_9("start s = node(1) return \"a\\tp\\\"a\"",
Query.
start(NodeById("s", 1)).
returns(ReturnItem(Literal("a\tp\"a"), "\"a\\tp\\\"a\"")))
}

@Test def allTheNodes() {
testAll("start s = NODE(*) return s",
Query.
Expand Down Expand Up @@ -1794,6 +1801,10 @@ foreach(x in [1,2,3] :
test_1_9(query, expectedQuery)
}

def testFrom_1_9(query: String, expectedQuery: Query) {
test_1_9(query, expectedQuery)
}

def testAll(query: String, expectedQuery: Query) {
test_1_7(query, expectedQuery)
test_1_8(query, expectedQuery)
Expand All @@ -1804,6 +1815,14 @@ foreach(x in [1,2,3] :
test_1_7(queryText, queryAst)
}

@Test
def special_should_not_exists() {
val parser = new CypherParser()
val v18 = parser.parse("cypher 1.8 start s = node(1) return \"ap\\\"a\"")
val v19 = parser.parse("cypher 1.9 start s = node(1) return \"ap\\\"a\"")
assertEquals(v18, v19)
}

def testQuery(version: Option[String], query: String, expectedQuery: Query) {
val parser = new CypherParser()

Expand Down

0 comments on commit 5eb1af5

Please sign in to comment.