Optimize mappings
diff --git a/mapper/mapper.go b/mapper/mapper.go
index 424896e..0bbe39f 100644
--- a/mapper/mapper.go
+++ b/mapper/mapper.go
@@ -148,18 +148,30 @@
 	// Store original node for rewrite if needed
 	var originalNode ast.Node
 	if opts.AddRewrites {
-		originalBytes, err := parser.SerializeToJSON(node)
-		if err != nil {
-			return nil, fmt.Errorf("failed to serialize original node for rewrite: %w", err)
-		}
-		originalNode, err = parser.ParseJSON(originalBytes)
-		if err != nil {
-			return nil, fmt.Errorf("failed to parse original node for rewrite: %w", err)
-		}
+		originalNode = node.Clone()
 	}
 
+	// Pre-check foundry/layer overrides to optimize processing
+	var patternFoundry, patternLayer, replacementFoundry, replacementLayer string
+	if opts.Direction { // true means AtoB
+		patternFoundry, patternLayer = opts.FoundryA, opts.LayerA
+		replacementFoundry, replacementLayer = opts.FoundryB, opts.LayerB
+	} else {
+		patternFoundry, patternLayer = opts.FoundryB, opts.LayerB
+		replacementFoundry, replacementLayer = opts.FoundryA, opts.LayerA
+	}
+
+	// Create a pattern cache key for memoization
+	type patternCacheKey struct {
+		ruleIndex     int
+		foundry       string
+		layer         string
+		isReplacement bool
+	}
+	patternCache := make(map[patternCacheKey]ast.Node)
+
 	// Apply each rule to the AST
-	for _, rule := range rules {
+	for i, rule := range rules {
 		// Create pattern and replacement based on direction
 		var pattern, replacement ast.Node
 		if opts.Direction { // true means AtoB
@@ -178,40 +190,55 @@
 			replacement = token.Wrap
 		}
 
-		// Create deep copies of pattern and replacement to avoid modifying the original parsed rules
-		patternBytes, err := parser.SerializeToJSON(pattern)
-		if err != nil {
-			return nil, fmt.Errorf("failed to serialize pattern for copying: %w", err)
-		}
-		patternCopy, err := parser.ParseJSON(patternBytes)
-		if err != nil {
-			return nil, fmt.Errorf("failed to parse pattern copy: %w", err)
+		// First, quickly check if the pattern could match without creating a full matcher
+		// This is a lightweight pre-check to avoid expensive operations
+		if !m.couldPatternMatch(node, pattern) {
+			continue
 		}
 
-		replacementBytes, err := parser.SerializeToJSON(replacement)
-		if err != nil {
-			return nil, fmt.Errorf("failed to serialize replacement for copying: %w", err)
-		}
-		replacementCopy, err := parser.ParseJSON(replacementBytes)
-		if err != nil {
-			return nil, fmt.Errorf("failed to parse replacement copy: %w", err)
+		// Get or create pattern with overrides
+		patternKey := patternCacheKey{ruleIndex: i, foundry: patternFoundry, layer: patternLayer, isReplacement: false}
+		processedPattern, exists := patternCache[patternKey]
+		if !exists {
+			// Clone pattern only when needed
+			processedPattern = pattern.Clone()
+			// Apply foundry and layer overrides only if they're non-empty
+			if patternFoundry != "" || patternLayer != "" {
+				ast.ApplyFoundryAndLayerOverrides(processedPattern, patternFoundry, patternLayer)
+			}
+			patternCache[patternKey] = processedPattern
 		}
 
-		// Apply foundry and layer overrides to the copies
-		if opts.Direction { // true means AtoB
-			applyFoundryAndLayerOverrides(patternCopy, opts.FoundryA, opts.LayerA)
-			applyFoundryAndLayerOverrides(replacementCopy, opts.FoundryB, opts.LayerB)
-		} else {
-			applyFoundryAndLayerOverrides(patternCopy, opts.FoundryB, opts.LayerB)
-			applyFoundryAndLayerOverrides(replacementCopy, opts.FoundryA, opts.LayerA)
+		// Create a temporary matcher to check for actual matches
+		tempMatcher, err := matcher.NewMatcher(ast.Pattern{Root: processedPattern}, ast.Replacement{Root: &ast.Term{}})
+		if err != nil {
+			return nil, fmt.Errorf("failed to create temporary matcher: %w", err)
 		}
 
-		// Create matcher and apply replacement using the copies
-		m, err := matcher.NewMatcher(ast.Pattern{Root: patternCopy}, ast.Replacement{Root: replacementCopy})
+		// Only proceed if there's an actual match
+		if !tempMatcher.Match(node) {
+			continue
+		}
+
+		// Get or create replacement with overrides (lazy evaluation)
+		replacementKey := patternCacheKey{ruleIndex: i, foundry: replacementFoundry, layer: replacementLayer, isReplacement: true}
+		processedReplacement, exists := patternCache[replacementKey]
+		if !exists {
+			// Clone replacement only when we have a match
+			processedReplacement = replacement.Clone()
+			// Apply foundry and layer overrides only if they're non-empty
+			if replacementFoundry != "" || replacementLayer != "" {
+				ast.ApplyFoundryAndLayerOverrides(processedReplacement, replacementFoundry, replacementLayer)
+			}
+			patternCache[replacementKey] = processedReplacement
+		}
+
+		// Create the actual matcher and apply replacement
+		actualMatcher, err := matcher.NewMatcher(ast.Pattern{Root: processedPattern}, ast.Replacement{Root: processedReplacement})
 		if err != nil {
 			return nil, fmt.Errorf("failed to create matcher: %w", err)
 		}
-		node = m.Replace(node)
+		node = actualMatcher.Replace(node)
 	}
 
 	// Wrap the result in a token if the input was a token
@@ -374,34 +401,87 @@
 	return true
 }
 
-// applyFoundryAndLayerOverrides recursively applies foundry and layer overrides to terms
-func applyFoundryAndLayerOverrides(node ast.Node, foundry, layer string) {
+// couldPatternMatch performs a lightweight check to see if a pattern could potentially match a node
+// This is an optimization to avoid expensive operations when there's clearly no match possible
+func (m *Mapper) couldPatternMatch(node, pattern ast.Node) bool {
+	if pattern == nil {
+		return true
+	}
 	if node == nil {
-		return
+		return false
+	}
+
+	// Handle Token wrappers
+	if token, ok := pattern.(*ast.Token); ok {
+		pattern = token.Wrap
+	}
+	if token, ok := node.(*ast.Token); ok {
+		node = token.Wrap
+	}
+
+	// For simple terms, check basic compatibility
+	if patternTerm, ok := pattern.(*ast.Term); ok {
+		// Check if there's any term in the node structure that could match
+		return m.hasMatchingTerm(node, patternTerm)
+	}
+
+	// For TermGroups, we need to check all possible matches
+	if patternGroup, ok := pattern.(*ast.TermGroup); ok {
+		if patternGroup.Relation == ast.OrRelation {
+			// For OR relations, any operand could match
+			for _, op := range patternGroup.Operands {
+				if m.couldPatternMatch(node, op) {
+					return true
+				}
+			}
+			return false
+		} else {
+			// For AND relations, all operands must have potential matches
+			for _, op := range patternGroup.Operands {
+				if !m.couldPatternMatch(node, op) {
+					return false
+				}
+			}
+			return true
+		}
+	}
+
+	// For other cases, assume they could match (conservative approach)
+	return true
+}
+
+// hasMatchingTerm checks if there's any term in the node structure that could match the pattern term
+func (m *Mapper) hasMatchingTerm(node ast.Node, patternTerm *ast.Term) bool {
+	if node == nil {
+		return false
 	}
 
 	switch n := node.(type) {
 	case *ast.Term:
-		if foundry != "" {
-			n.Foundry = foundry
-		}
-		if layer != "" {
-			n.Layer = layer
-		}
+		// Check if this term could match the pattern
+		// We only check key as that's the most distinctive attribute
+		return n.Key == patternTerm.Key
 	case *ast.TermGroup:
+		// Check all operands
 		for _, op := range n.Operands {
-			applyFoundryAndLayerOverrides(op, foundry, layer)
+			if m.hasMatchingTerm(op, patternTerm) {
+				return true
+			}
 		}
+		return false
 	case *ast.Token:
-		if n.Wrap != nil {
-			applyFoundryAndLayerOverrides(n.Wrap, foundry, layer)
-		}
+		return m.hasMatchingTerm(n.Wrap, patternTerm)
 	case *ast.CatchallNode:
-		if n.Wrap != nil {
-			applyFoundryAndLayerOverrides(n.Wrap, foundry, layer)
+		if n.Wrap != nil && m.hasMatchingTerm(n.Wrap, patternTerm) {
+			return true
 		}
 		for _, op := range n.Operands {
-			applyFoundryAndLayerOverrides(op, foundry, layer)
+			if m.hasMatchingTerm(op, patternTerm) {
+				return true
+			}
 		}
+		return false
+	default:
+		return false
 	}
 }