001/* 002 * Java Genetic Algorithm Library (jenetics-8.1.0). 003 * Copyright (c) 2007-2024 Franz Wilhelmstötter 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 * Author: 018 * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) 019 */ 020package io.jenetics.ext.rewriting; 021 022import static java.lang.String.format; 023import static java.util.Objects.requireNonNull; 024import static io.jenetics.internal.util.Hashes.hash; 025 026import java.io.IOException; 027import java.io.InvalidObjectException; 028import java.io.ObjectInput; 029import java.io.ObjectInputStream; 030import java.io.ObjectOutput; 031import java.io.Serial; 032import java.io.Serializable; 033import java.util.HashSet; 034import java.util.Map; 035import java.util.Optional; 036import java.util.Set; 037import java.util.function.Function; 038import java.util.stream.Collectors; 039 040import io.jenetics.ext.rewriting.TreePattern.Var; 041import io.jenetics.ext.util.Tree; 042import io.jenetics.ext.util.Tree.Path; 043import io.jenetics.ext.util.TreeNode; 044 045/** 046 * Represents a tree rewrite rule. A rewrite rule consists of a match pattern, 047 * which must be matched, and a substitution pattern, which is expanded and 048 * replaces the variables in the pattern. Some simple <em>arithmetic</em> 049 * rewrite rules. 050 * <pre> {@code 051 * add($x,0) -> $x 052 * mul($x,1) -> $x 053 * } </pre> 054 * The <em>substitution</em> pattern may only use variables, already defined in 055 * the <em>match</em> pattern. So, the creation of the following rewrite rule s 056 * would lead to an {@link IllegalArgumentException}: 057 * <pre> {@code 058 * add($x,0) -> $y 059 * mul(0,1) -> mul($x,1) 060 * } </pre> 061 * 062 * @see <a href="https://en.wikipedia.org/wiki/Rewriting#Term_rewriting_systems"> 063 * Tree rewriting systems</a> 064 * 065 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 066 * @version 5.0 067 * @since 5.0 068 */ 069public final class TreeRewriteRule<V> implements TreeRewriter<V>, Serializable { 070 071 @Serial 072 private static final long serialVersionUID = 1L; 073 074 private final TreePattern<V> _left; 075 private final TreePattern<V> _right; 076 077 /** 078 * Create a new rewrite rule from the given <em>matching</em> ({@code left}) 079 * and <em>replacement</em> ({@code right}) pattern. 080 * 081 * @param left the matching pattern of the rule 082 * @param right the substitution pattern 083 * @throws NullPointerException if one of the arguments is {@code null} 084 * @throws IllegalArgumentException if the <em>template</em> pattern uses 085 * variables not defined in the <em>matcher</em> pattern 086 */ 087 public TreeRewriteRule( 088 final TreePattern<V> left, 089 final TreePattern<V> right 090 ) { 091 _left = requireNonNull(left); 092 _right = requireNonNull(right); 093 094 final Set<Var<V>> undefined = new HashSet<>(_right.vars()); 095 undefined.removeAll(_left.vars()); 096 097 if (!undefined.isEmpty()) { 098 throw new IllegalArgumentException(format( 099 "Some template variables are not defined in the matcher '%s': %s", 100 this, 101 undefined.stream() 102 .map(v -> format("%s", v)) 103 .collect(Collectors.joining(", ")) 104 )); 105 } 106 } 107 108 /** 109 * Return the rule matching pattern. 110 * 111 * @return the rule matching pattern 112 */ 113 public TreePattern<V> left() { 114 return _left; 115 } 116 117 /** 118 * Return the replacement pattern of the rule. 119 * 120 * @return the replacement pattern of the rule 121 */ 122 public TreePattern<V> right() { 123 return _right; 124 } 125 126 /** 127 * Maps {@code this} rewrite rule from type {@code V} to type {@code B}. 128 * 129 * @param mapper the type mapper 130 * @param <B> the target type 131 * @return a new rewrite rule for the mapped type 132 * @throws NullPointerException if the {@code mapper} is {@code null} 133 */ 134 public <B> TreeRewriteRule<B> map(final Function<? super V, ? extends B> mapper) { 135 return new TreeRewriteRule<>(_left.map(mapper), _right.map(mapper)); 136 } 137 138 @Override 139 public int rewrite(final TreeNode<V> tree, final int limit) { 140 requireNonNull(tree); 141 142 int rewritten = 0; 143 Optional<TreeMatchResult<V>> result; 144 do { 145 result = left().matcher(tree).results().findFirst(); 146 147 result.ifPresent(res -> rewrite(res, tree)); 148 rewritten += result.isPresent() ? 1 : 0; 149 } while (result.isPresent() && rewritten < limit); 150 151 return rewritten; 152 } 153 154 private void rewrite( 155 final TreeMatchResult<V> result, 156 final TreeNode<V> tree 157 ) { 158 final Map<Var<V>, Tree<V, ?>> vars = result.vars(); 159 final TreeNode<V> r = _right.expand(vars); 160 161 final Path path = result.tree().childPath(); 162 tree.replaceAtPath(path, r); 163 } 164 165 @Override 166 public int hashCode() { 167 return hash(_left, hash(_right)); 168 } 169 170 @Override 171 public boolean equals(final Object obj) { 172 return obj == this || 173 obj instanceof TreeRewriteRule<?> other && 174 _left.equals(other._left) && 175 _right.equals(other._right); 176 } 177 178 @Override 179 public String toString() { 180 return format("%s -> %s", _left, _right); 181 } 182 183 /** 184 * Compiles the string representation of a rewrite rule: 185 * <pre> {@code 186 * add($x,0) -> $x 187 * mul($x,1) -> $x 188 * } </pre> 189 * 190 * @param <V> the tree node type 191 * @param rule the rewrite rule 192 * @param mapper the mapper function which converts the node value into the 193 * actual type {@code V} 194 * @return a new rewrite rule, compiled from the given rule string 195 * @throws IllegalArgumentException if the rewrite rule is invalid 196 * @throws NullPointerException if on of the arguments is {@code null} 197 */ 198 public static <V> TreeRewriteRule<V> parse( 199 final String rule, 200 final Function<? super String, ? extends V> mapper 201 ) { 202 final String[] parts = rule.split("->"); 203 if (parts.length == 1) { 204 throw new IllegalArgumentException(format( 205 "Invalid rewrite rule; missing separator '->': %s", rule 206 )); 207 } 208 if (parts.length > 2) { 209 throw new IllegalArgumentException(format( 210 "Invalid rewrite rule; found %d separators '->': %s", 211 parts.length - 1, rule 212 )); 213 } 214 215 return new TreeRewriteRule<>( 216 TreePattern.compile(parts[0], mapper), 217 TreePattern.compile(parts[1], mapper) 218 ); 219 } 220 221 /** 222 * Compiles the string representation of a rewrite rule: 223 * <pre> {@code 224 * add($x,0) -> $x 225 * mul($x,1) -> $x 226 * } </pre> 227 * 228 * @param rule the rewrite rule 229 * @return a new rewrite rule, compiled from the given rule string 230 * @throws IllegalArgumentException if the rewrite rule is invalid 231 * @throws NullPointerException if on of the arguments is {@code null} 232 */ 233 public static TreeRewriteRule<String> parse(final String rule) { 234 return parse(rule, Function.identity()); 235 } 236 237 238 /* ************************************************************************* 239 * Java object serialization 240 * ************************************************************************/ 241 242 @Serial 243 private Object writeReplace() { 244 return new SerialProxy(SerialProxy.TREE_REWRITE_RULE, this); 245 } 246 247 @Serial 248 private void readObject(final ObjectInputStream stream) 249 throws InvalidObjectException 250 { 251 throw new InvalidObjectException("Serialization proxy required."); 252 } 253 254 void write(final ObjectOutput out) throws IOException { 255 out.writeObject(_left); 256 out.writeObject(_right); 257 } 258 259 @SuppressWarnings({"unchecked", "rawtypes"}) 260 static Object read(final ObjectInput in) 261 throws IOException, ClassNotFoundException 262 { 263 final TreePattern left = (TreePattern)in.readObject(); 264 final TreePattern right = (TreePattern)in.readObject(); 265 return new TreeRewriteRule(left, right); 266 } 267 268}