Support escaping inm grammar rules

Change-Id: I87e01605be27f165cfb4eaee69315c312e51f326
diff --git a/cmd/termmapper/fuzz_test.go b/cmd/termmapper/fuzz_test.go
index e5dd7a5..c3efbe4 100644
--- a/cmd/termmapper/fuzz_test.go
+++ b/cmd/termmapper/fuzz_test.go
@@ -120,7 +120,7 @@
 		}
 
 		// Verify that the response is valid JSON
-		var result map[string]interface{}
+		var result map[string]any
 		if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
 			t.Errorf("invalid JSON response: %v", err)
 		}
@@ -258,7 +258,7 @@
 			assert.Equal(t, tt.expectedCode, resp.StatusCode)
 
 			// Check error message
-			var result map[string]interface{}
+			var result map[string]any
 			err = json.NewDecoder(resp.Body).Decode(&result)
 			require.NoError(t, err)
 			errMsg, ok := result["error"].(string)
@@ -267,3 +267,14 @@
 		})
 	}
 }
+
+// # Run fuzzing for 1 minute
+// go test -fuzz=FuzzTransformEndpoint -fuzztime=1m ./cmd/termmapper
+//
+// # Run fuzzing until a crash is found or Ctrl+C is pressed
+// go test -fuzz=FuzzTransformEndpoint ./cmd/termmapper
+//
+// # Run fuzzing with verbose output
+// go test -fuzz=FuzzTransformEndpoint -v ./cmd/termmapper
+//
+// go test -run=FuzzTransformEndpoint/testdata/fuzz/FuzzTransformEndpoint/$SEED
diff --git a/cmd/termmapper/main_test.go b/cmd/termmapper/main_test.go
index 7b83811..07bd176 100644
--- a/cmd/termmapper/main_test.go
+++ b/cmd/termmapper/main_test.go
@@ -239,7 +239,7 @@
 				assert.Equal(t, tt.expectedError, errResp["error"])
 			} else {
 				// Compare JSON responses
-				var expected, actual interface{}
+				var expected, actual any
 				err = json.Unmarshal([]byte(tt.expectedBody), &expected)
 				require.NoError(t, err)
 				err = json.Unmarshal(body, &actual)
diff --git a/mapper/mapper_test.go b/mapper/mapper_test.go
index b2fb377..a15d69b 100644
--- a/mapper/mapper_test.go
+++ b/mapper/mapper_test.go
@@ -411,7 +411,7 @@
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			// Parse input JSON
-			var inputData interface{}
+			var inputData any
 			err := json.Unmarshal([]byte(tt.input), &inputData)
 			require.NoError(t, err)
 
@@ -424,7 +424,7 @@
 			require.NoError(t, err)
 
 			// Parse expected JSON
-			var expectedData interface{}
+			var expectedData any
 			err = json.Unmarshal([]byte(tt.expected), &expectedData)
 			require.NoError(t, err)
 
@@ -506,7 +506,7 @@
 	}`
 
 	// Parse input JSON
-	var inputData interface{}
+	var inputData any
 	err = json.Unmarshal([]byte(input), &inputData)
 	require.NoError(t, err)
 
@@ -515,7 +515,7 @@
 	require.NoError(t, err)
 
 	// Parse expected JSON
-	var expectedData interface{}
+	var expectedData any
 	err = json.Unmarshal([]byte(expected), &expectedData)
 	require.NoError(t, err)
 
@@ -857,7 +857,7 @@
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			// Parse input JSON
-			var inputData interface{}
+			var inputData any
 			err := json.Unmarshal([]byte(tt.input), &inputData)
 			require.NoError(t, err)
 
@@ -870,7 +870,7 @@
 			require.NoError(t, err)
 
 			// Parse expected JSON
-			var expectedData interface{}
+			var expectedData any
 			err = json.Unmarshal([]byte(tt.expected), &expectedData)
 			require.NoError(t, err)
 
diff --git a/parser/grammar_parser.go b/parser/grammar_parser.go
index 8e46611..dadd0a2 100644
--- a/parser/grammar_parser.go
+++ b/parser/grammar_parser.go
@@ -97,7 +97,7 @@
 // NewGrammarParser creates a new grammar parser with optional default foundry and layer
 func NewGrammarParser(defaultFoundry, defaultLayer string) (*GrammarParser, error) {
 	lex := lexer.MustSimple([]lexer.SimpleRule{
-		{Name: "Ident", Pattern: `[a-zA-Z][a-zA-Z0-9_]*`},
+		{Name: "Ident", Pattern: `(?:[a-zA-Z$]|\\.)(?:[a-zA-Z0-9_$]|\\.)*`},
 		{Name: "Punct", Pattern: `[\[\]()&\|=:/]|<>`},
 		{Name: "Whitespace", Pattern: `\s+`},
 	})
@@ -288,26 +288,48 @@
 	return ast.AndRelation
 }
 
+// unescapeString handles unescaping of backslash-escaped characters
+func unescapeString(s string) string {
+	if s == "" {
+		return s
+	}
+
+	result := make([]byte, 0, len(s))
+	i := 0
+	for i < len(s) {
+		if s[i] == '\\' && i+1 < len(s) {
+			// Escape sequence found, add the escaped character
+			result = append(result, s[i+1])
+			i += 2
+		} else {
+			// Regular character
+			result = append(result, s[i])
+			i++
+		}
+	}
+	return string(result)
+}
+
 // parseSimpleTerm converts a SimpleTerm into an AST Term node
 func (p *GrammarParser) parseSimpleTerm(term *SimpleTerm) (ast.Node, error) {
 	var foundry, layer, key, value string
 
 	switch {
 	case term.WithFoundryLayer != nil:
-		foundry = term.WithFoundryLayer.Foundry
-		layer = term.WithFoundryLayer.Layer
-		key = term.WithFoundryLayer.Key
-		value = term.WithFoundryLayer.Value
+		foundry = unescapeString(term.WithFoundryLayer.Foundry)
+		layer = unescapeString(term.WithFoundryLayer.Layer)
+		key = unescapeString(term.WithFoundryLayer.Key)
+		value = unescapeString(term.WithFoundryLayer.Value)
 	case term.WithFoundryKey != nil:
-		foundry = term.WithFoundryKey.Foundry
-		key = term.WithFoundryKey.Key
+		foundry = unescapeString(term.WithFoundryKey.Foundry)
+		key = unescapeString(term.WithFoundryKey.Key)
 	case term.WithLayer != nil:
-		layer = term.WithLayer.Layer
-		key = term.WithLayer.Key
-		value = term.WithLayer.Value
+		layer = unescapeString(term.WithLayer.Layer)
+		key = unescapeString(term.WithLayer.Key)
+		value = unescapeString(term.WithLayer.Value)
 	case term.SimpleKey != nil:
-		key = term.SimpleKey.Key
-		value = term.SimpleKey.Value
+		key = unescapeString(term.SimpleKey.Key)
+		value = unescapeString(term.SimpleKey.Value)
 	default:
 		return nil, fmt.Errorf("invalid term: no valid form found")
 	}
diff --git a/parser/grammar_parser_test.go b/parser/grammar_parser_test.go
index f7fefc9..c6f96d1 100644
--- a/parser/grammar_parser_test.go
+++ b/parser/grammar_parser_test.go
@@ -67,6 +67,28 @@
 				},
 			},
 		},
+		{
+			name:           "Special symbol",
+			input:          "[$\\(]",
+			defaultFoundry: "opennlp",
+			defaultLayer:   "p",
+			expected: &SimpleTerm{
+				SimpleKey: &KeyTerm{
+					Key: "$(",
+				},
+			},
+		},
+		{
+			name:           "Multiple escaped characters",
+			input:          "[\\&\\|\\=]",
+			defaultFoundry: "opennlp",
+			defaultLayer:   "p",
+			expected: &SimpleTerm{
+				SimpleKey: &KeyTerm{
+					Key: "&|=",
+				},
+			},
+		},
 	}
 
 	for _, tt := range tests {
@@ -81,6 +103,12 @@
 			}
 			require.NoError(t, err)
 			require.NotNil(t, grammar.Token, "Expected token expression")
+
+			// For testing purposes, unescape the key in the simple term
+			if grammar.Token.Expr.First.Simple.SimpleKey != nil {
+				grammar.Token.Expr.First.Simple.SimpleKey.Key = unescapeString(grammar.Token.Expr.First.Simple.SimpleKey.Key)
+			}
+
 			assert.Equal(t, tt.expected, grammar.Token.Expr.First.Simple)
 		})
 	}
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 25b2ad1..424743d 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -11,7 +11,7 @@
 
 // normalizeJSON normalizes JSON by parsing and re-marshaling it
 func normalizeJSON(t *testing.T, data json.RawMessage) json.RawMessage {
-	var v interface{}
+	var v any
 	err := json.Unmarshal(data, &v)
 	require.NoError(t, err)
 
@@ -708,7 +708,7 @@
 	require.NoError(t, err)
 
 	// Compare JSON objects
-	var expected, actual interface{}
+	var expected, actual any
 	err = json.Unmarshal([]byte(input), &expected)
 	require.NoError(t, err)
 	err = json.Unmarshal(output, &actual)
@@ -758,7 +758,7 @@
 	require.NoError(t, err)
 
 	// Compare JSON objects
-	var expected, actual interface{}
+	var expected, actual any
 	err = json.Unmarshal([]byte(input), &expected)
 	require.NoError(t, err)
 	err = json.Unmarshal(output, &actual)