Parser.java
package io.jstach.rainbowgum.pattern.internal;
import java.util.List;
import java.util.Objects;
import org.jspecify.annotations.Nullable;
import io.jstach.rainbowgum.pattern.Padding;
import io.jstach.rainbowgum.pattern.internal.Node.CompositeNode;
import io.jstach.rainbowgum.pattern.internal.Node.FormattingNode;
import io.jstach.rainbowgum.pattern.internal.Node.KeywordNode;
import io.jstach.rainbowgum.pattern.internal.Node.LiteralNode;
// ~=lambda
// E = TE|T
// Left factorization
// E = T(E|~)
// Eopt = E|~
// replace E|~ with Eopt in E
// E = TEopt
// T = LITERAL | '%' C | '%' FORMAT_MODIFIER C
// C = SIMPLE_KEYWORD OPTION | COMPOSITE_KEYWORD COMPOSITE
// OPTION = {...} | ~
// COMPOSITE = E ')' OPTION
/*
* Ported from Logback's grammar-driven parser (see the grammar comment above) - the
* statement switches here have local declarations and multi-statement case bodies, so
* converting them to arrow-style expression switches isn't a mechanical rename and
* risks subtly changing this hand-ported parser's control flow.
*/
@SuppressWarnings("StatementSwitchToExpressionSwitch")
public class Parser {
// public final static Map<String, String> DEFAULT_COMPOSITE_CONVERTER_MAP = new
// HashMap<String, String>();
// private final static String REPLACE_CONVERTER_WORD = "replace";
// static {
// DEFAULT_COMPOSITE_CONVERTER_MAP.put(Token.BARE_COMPOSITE_KEYWORD_TOKEN.getValue().toString(),
// IdentityCompositeConverter.class.getName());
// DEFAULT_COMPOSITE_CONVERTER_MAP.put(REPLACE_CONVERTER_WORD,
// ReplacingCompositeConverter.class.getName());
// }
private final List<Token> tokenList;
private int pointer = 0;
public Parser(String pattern) throws ScanException {
this(pattern, new RegularEscaper());
}
public Parser(String pattern, ParserEscaper parserEscaper) throws ScanException {
try {
TokenStream ts = new TokenStream(pattern, parserEscaper);
this.tokenList = ts.tokenize();
}
catch (IllegalArgumentException npe) {
throw new ScanException("Failed to initialize Parser", npe);
}
}
// /**
// * When the parsing step is done, the Node list can be transformed into a
// * converter chain.
// *
// * @param top
// * @param converterMap
// * @return
// */
// public PatternRegistry<E> compile(final Node top, Map<String, String>
// converterMap) {
// Compiler<E> compiler = new Compiler<E>(top, converterMap);
// compiler.setContext(context);
// // compiler.setStatusManager(statusManager);
// return compiler.compile();
// }
public Node parse() throws ScanException {
return E();
}
interface NodeBuilder<T extends Node> {
T next(Node node);
}
// E = TEopt
Node E() throws ScanException {
NodeBuilder<?> t = T();
if (t == null) {
return Node.end();
}
Node eOpt = Objects.requireNonNull(Eopt());
if (eOpt != Node.end()) {
// t.setNext(eOpt);
return t.next(eOpt);
}
return t.next(Node.end());
}
// Eopt = E|~
Node Eopt() throws ScanException {
_debug("in Eopt()");
Token next = getCurrentToken();
_debug("Current token is ", next);
if (next == null) {
return Node.end();
}
else {
return E();
}
}
// T = LITERAL | '%' C | '%' FORMAT_MODIFIER C
@Nullable NodeBuilder<?> T() throws ScanException {
var t = expectNotNull(getCurrentToken(), "a LITERAL or '%'");
switch (t.type()) {
case Token.LITERAL:
advanceTokenPointer();
return n -> new LiteralNode(n, t.value());
case Token.PERCENT:
advanceTokenPointer();
_debug("% token found");
Padding fi;
NodeBuilder<FormattingNode> c;
var u = expectNotNull(getCurrentToken(), "a FORMAT_MODIFIER, SIMPLE_KEYWORD or COMPOUND_KEYWORD");
if (u.type() == Token.FORMAT_MODIFIER) {
fi = Padding.valueOf(u.value());
advanceTokenPointer();
c = C(fi);
}
else {
c = C(null);
}
return c;
default:
return null;
}
}
NodeBuilder<FormattingNode> C(@Nullable Padding padding) throws ScanException {
Token t = getCurrentToken();
_debug("in C()");
_debug("Current token is ", t);
t = expectNotNull(t, "a LEFT_PARENTHESIS or KEYWORD");
int type = t.type();
switch (type) {
case Token.SIMPLE_KEYWORD:
return SINGLE(padding);
case Token.COMPOSITE_KEYWORD:
advanceTokenPointer();
return COMPOSITE(padding, t.value());
default:
throw new IllegalStateException("Unexpected token " + t);
}
}
NodeBuilder<FormattingNode> SINGLE(@Nullable Padding padding) throws ScanException {
_debug("in SINGLE()");
Token t = expectNotNull(getNextToken(), "a SINGLE");
_debug("==", t);
Token ot = getCurrentToken();
List<String> optionList;
if (ot != null && ot.type() == Token.OPTION) {
optionList = ot.optionsList();
advanceTokenPointer();
}
else {
optionList = List.of();
}
return n -> new KeywordNode(n, padding, t.value(), optionList);
}
NodeBuilder<FormattingNode> COMPOSITE(@Nullable Padding padding, String keyword) throws ScanException {
Node childNode = E();
Token t = getNextToken();
if (t == null || t.type() != Token.RIGHT_PARENTHESIS) {
String msg = "Expecting RIGHT_PARENTHESIS token but got " + t;
throw new ScanException(msg);
}
Token ot = getCurrentToken();
List<String> optionList;
if (ot != null && ot.type() == Token.OPTION) {
optionList = ot.optionsList();
advanceTokenPointer();
}
else {
optionList = List.of();
}
return n -> new CompositeNode(n, padding, keyword, optionList, childNode);
}
@Nullable Token getNextToken() {
if (pointer < tokenList.size()) {
return tokenList.get(pointer++);
}
return null;
}
@Nullable Token getCurrentToken() {
if (pointer < tokenList.size()) {
return tokenList.get(pointer);
}
return null;
}
void advanceTokenPointer() {
pointer++;
}
Token expectNotNull(@Nullable Token t, String expected) {
if (t == null) {
throw new IllegalStateException("All tokens consumed but was expecting " + expected);
}
return t;
}
/*
* Intentionally disabled debug tracing hooks (called throughout this parser) - the
* parameters exist so re-enabling the println below doesn't require touching every
* call site.
*/
@SuppressWarnings("UnusedVariable")
private static void _debug(String msg) {
// System.out.println(msg);
}
@SuppressWarnings("UnusedVariable")
private static void _debug(String msg, @Nullable Object arg) {
// System.out.println(msg + arg);
}
}