io.jenetics.ext.util.ParenthesesTreeParser Maven / Gradle / Ivy
The newest version!
/*
* Java Genetic Algorithm Library (jenetics-8.1.0).
* Copyright (c) 2007-2024 Franz Wilhelmstötter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Author:
* Franz Wilhelmstötter ([email protected])
*/
package io.jenetics.ext.util;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static io.jenetics.ext.util.ParenthesesTrees.ESCAPE_CHAR;
import static io.jenetics.ext.util.ParenthesesTrees.unescape;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.function.Function;
/**
* Parses a parentheses string into a {@code TreeNode} object.
*
* @author Franz Wilhelmstötter
* @version 6.0
* @since 4.3
*/
final class ParenthesesTreeParser {
private ParenthesesTreeParser() {}
/**
* Represents a parentheses tree string token.
*/
record Token(String seq, int pos) {}
/**
* Tokenize the given parentheses string.
*
* @param value the parentheses string
* @return the parentheses string tokens
* @throws NullPointerException if the given {@code value} is {@code null}
*/
static List tokenize(final String value) {
final List tokens = new ArrayList<>();
char pc = '\0';
int pos = 0;
final StringBuilder token = new StringBuilder();
for (int i = 0; i < value.length(); ++i) {
final char c = value.charAt(i);
if (isTokenSeparator(c) && pc != ESCAPE_CHAR) {
tokens.add(new Token(token.toString(), pos));
tokens.add(new Token(Character.toString(c), i));
token.setLength(0);
pos = i;
} else {
token.append(c);
}
pc = c;
}
if (!token.isEmpty()) {
tokens.add(new Token(token.toString(), pos));
}
return tokens;
}
private static boolean isTokenSeparator(final char c) {
return switch (c) {
case '(', ')', ',' -> true;
default -> false;
};
}
/**
* Parses the given parentheses' tree string
*
* @since 4.3
*
* @param the tree node value type
* @param value the parentheses tree string
* @param mapper the mapper which converts the serialized string value to
* the desired type
* @return the parsed tree object
* @throws NullPointerException if one of the arguments is {@code null}
* @throws IllegalArgumentException if the given parentheses tree string
* doesn't represent a valid tree
*/
static TreeNode parse(
final String value,
final Function super String, ? extends B> mapper
) {
requireNonNull(value);
requireNonNull(mapper);
final TreeNode root = TreeNode.of();
final Deque> parents = new ArrayDeque<>();
TreeNode current = root;
for (Token token : tokenize(value.trim())) {
switch (token.seq) {
case "(" -> {
if (current == null) {
throw new IllegalArgumentException(format(
"Illegal parentheses tree string: '%s'.",
value
));
}
final TreeNode tn1 = TreeNode.of();
current.attach(tn1);
parents.push(current);
current = tn1;
}
case "," -> {
if (parents.isEmpty()) {
throw new IllegalArgumentException(format(
"Expect '(' at position %d.",
token.pos
));
}
final TreeNode tn2 = TreeNode.of();
assert parents.peek() != null;
parents.peek().attach(tn2);
current = tn2;
}
case ")" -> {
if (parents.isEmpty()) {
throw new IllegalArgumentException(format(
"Unbalanced parentheses at position %d.",
token.pos
));
}
current = parents.pop();
if (parents.isEmpty()) {
current = null;
}
}
default -> {
if (current == null) {
throw new IllegalArgumentException(format(
"More than one root element at pos %d: '%s'.",
token.pos, value
));
}
if (current.value() == null) {
current.value(mapper.apply(unescape(token.seq)));
}
}
}
}
if (!parents.isEmpty()) {
throw new IllegalArgumentException(
"Unbalanced parentheses: " + value
);
}
return root;
}
}