io.jenetics.ext.grammar.Bnf 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.grammar;
import static java.lang.Character.isDigit;
import static java.lang.Character.isWhitespace;
import static io.jenetics.ext.internal.parser.CharSequenceTokenizer.isAlphabetic;
import java.util.stream.Collectors;
import io.jenetics.ext.internal.parser.ParsingException;
/**
* This class contains methods for parsing and formatting context-free
* grammars in
* BNF
* format.
* {@snippet lang="java":
* final Cfg grammar = Bnf.parse("""
* ::= | | '(' ')'
* ::= + | - | * | /
* ::= x | y
* ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
* """
* );
* }
*
* @author Franz Wilhelmstötter
* @since 7.1
* @version 7.1
*/
public final class Bnf {
private Bnf() {}
static boolean isSymbolChar(final int ch) {
return switch (ch) {
case '<', '>', '|', ':', '=' -> true;
default -> false;
};
}
static boolean isStringChar(final char c) {
return !isWhitespace(c) && !isSymbolChar(c);
}
static boolean isIdChar(final char c) {
return isAlphabetic(c) || isDigit(c) || (c == '-');
}
/**
* Parses the given BNF {@code grammar} string to a {@link Cfg} object. The
* following example shows the grammar of a simple arithmetic expression.
*
* {@code
* ::= | | '(' ')'
* ::= + | - | * | /
* ::= x | y
* ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
* }
*
* @param grammar the BNF {@code grammar} string
* @return the parsed {@code BNF} object
* @throws ParsingException if the given grammar is invalid
* @throws NullPointerException it the given {@code grammar} string is
* {@code null}
*/
public static Cfg parse(final String grammar) {
final var tokenizer = new BnfTokenizer(grammar);
final var parser = new BnfParser(tokenizer);
return parser.parse();
}
/**
* Formats the given CFG as BNF grammar string.
*
* @param grammar the CFG to format as BNF
* @return the BNF formatted grammar string
* @throws NullPointerException if the give {@code grammar} is {@code null}
*/
public static String format(final Cfg> grammar) {
return grammar.rules().stream()
.map(Bnf::format)
.collect(Collectors.joining("\n"));
}
private static String format(final Cfg.Rule> rule) {
return String.format(
"%s ::= %s",
format(rule.start()),
rule.alternatives().stream()
.map(Bnf::format)
.collect(Collectors.joining("\n | "))
);
}
private static String format(final Cfg.Expression> expr) {
return expr.symbols().stream()
.map(Bnf::format)
.collect(Collectors.joining(" "));
}
private static String format(final Cfg.Symbol> symbol) {
if (symbol instanceof Cfg.NonTerminal> nt) {
return String.format("<%s>", nt.name());
} else if (symbol instanceof Cfg.Terminal> t) {
return "'" + t.name()
.replace("\\", "\\\\")
.replace("'", "\\'") + "'";
}
throw new AssertionError();
}
}