Only add whitespace around not-escaped parentheses

Change-Id: I4ebfb44e10e67a9d21442203e7a6b0ff203d88c6
diff --git a/parser/grammar_parser.go b/parser/grammar_parser.go
index dadd0a2..81ebe62 100644
--- a/parser/grammar_parser.go
+++ b/parser/grammar_parser.go
@@ -134,9 +134,19 @@
 	input = strings.ReplaceAll(input, " & ", "&")
 	input = strings.ReplaceAll(input, " | ", "|")
 
-	// Add spaces around parentheses to help the parser
-	input = strings.ReplaceAll(input, "(", " ( ")
-	input = strings.ReplaceAll(input, ")", " ) ")
+	// Add spaces around parentheses that are not escaped
+	// We need to be careful not to break escape sequences like \(
+	result := make([]rune, 0, len(input)*2)
+	runes := []rune(input)
+	for i, r := range runes {
+		if (r == '(' || r == ')') && (i == 0 || runes[i-1] != '\\') {
+			// Only add spaces if the parenthesis is not escaped
+			result = append(result, ' ', r, ' ')
+		} else {
+			result = append(result, r)
+		}
+	}
+	input = string(result)
 
 	// Remove any extra spaces
 	input = strings.TrimSpace(input)
@@ -164,9 +174,19 @@
 	input = strings.ReplaceAll(input, " | ", "|")
 	input = strings.ReplaceAll(input, " <> ", "<>")
 
-	// Add spaces around parentheses to help the parser
-	input = strings.ReplaceAll(input, "(", " ( ")
-	input = strings.ReplaceAll(input, ")", " ) ")
+	// Add spaces around parentheses that are not escaped
+	// We need to be careful not to break escape sequences like \(
+	result := make([]rune, 0, len(input)*2)
+	runes := []rune(input)
+	for i, r := range runes {
+		if (r == '(' || r == ')') && (i == 0 || runes[i-1] != '\\') {
+			// Only add spaces if the parenthesis is not escaped
+			result = append(result, ' ', r, ' ')
+		} else {
+			result = append(result, r)
+		}
+	}
+	input = string(result)
 
 	// Remove any extra spaces
 	input = strings.TrimSpace(input)
diff --git a/parser/grammar_parser_test.go b/parser/grammar_parser_test.go
index c6f96d1..189ff68 100644
--- a/parser/grammar_parser_test.go
+++ b/parser/grammar_parser_test.go
@@ -314,6 +314,34 @@
 			},
 		},
 		{
+			name:  "PAV mapping with special character",
+			input: "[$\\(] <> [ADV & PronType:Dem]",
+			expected: &MappingResult{
+				Upper: &ast.Token{
+					Wrap: &ast.Term{
+						Key:   "$(",
+						Match: ast.MatchEqual,
+					},
+				},
+				Lower: &ast.Token{
+					Wrap: &ast.TermGroup{
+						Relation: ast.AndRelation,
+						Operands: []ast.Node{
+							&ast.Term{
+								Key:   "ADV",
+								Match: ast.MatchEqual,
+							},
+							&ast.Term{
+								Key:   "PronType",
+								Value: "Dem",
+								Match: ast.MatchEqual,
+							},
+						},
+					},
+				},
+			},
+		},
+		{
 			name:    "Invalid mapping syntax",
 			input:   "[PAV] -> [ADV]",
 			wantErr: true,