From 51fa045f8c747e42dc12db6a9af6dbc924ac8808 Mon Sep 17 00:00:00 2001 From: homedirectory Date: Tue, 27 Feb 2024 12:01:01 +0200 Subject: [PATCH] #2178 Enhance the BNF abstraction with the Alternation type --- .../fielden/platform/bnf/Alternation.java | 45 +++++++++++++++++ .../java/fielden/platform/bnf/Derivation.java | 48 ++----------------- .../java/fielden/platform/bnf/FluentBNF.java | 10 ++-- .../java/fielden/platform/bnf/Notation.java | 12 +++-- .../java/fielden/platform/bnf/OneOrMore.java | 9 +++- .../java/fielden/platform/bnf/Optional.java | 9 +++- .../java/fielden/platform/bnf/Quantifier.java | 14 ++++++ .../main/java/fielden/platform/bnf/Rule.java | 9 +--- .../java/fielden/platform/bnf/Sequence.java | 24 +++++++++- .../fielden/platform/bnf/Specialization.java | 5 +- .../java/fielden/platform/bnf/Symbol.java | 13 +++++ .../main/java/fielden/platform/bnf/Term.java | 10 ++++ .../java/fielden/platform/bnf/ZeroOrMore.java | 9 +++- .../fielden/platform/bnf/util/BnfToG4.java | 40 ++++++++++------ .../fielden/platform/bnf/util/BnfToHtml.java | 39 ++++++++++----- .../fielden/platform/bnf/util/BnfToText.java | 19 ++++++-- .../platform/bnf/util/BnfVerifier.java | 4 +- 17 files changed, 223 insertions(+), 96 deletions(-) create mode 100644 platform-eql-grammar/src/main/java/fielden/platform/bnf/Alternation.java create mode 100644 platform-eql-grammar/src/main/java/fielden/platform/bnf/Quantifier.java diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Alternation.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Alternation.java new file mode 100644 index 0000000000..7ec1593e2b --- /dev/null +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Alternation.java @@ -0,0 +1,45 @@ +package fielden.platform.bnf; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.joining; + +public record Alternation(List options, TermMetadata metadata) implements Notation { + + public Alternation(final List options, final TermMetadata metadata) { + this.options = List.copyOf(options); + this.metadata = metadata; + } + + public Alternation(List options) { + this(options, TermMetadata.EMPTY_METADATA); + } + + @Override + public Alternation annotate(TermMetadata.Key key, V value) { + return new Alternation(options, TermMetadata.merge(metadata(), key, value)); + } + + @Override + public Alternation normalize() { + return metadata == TermMetadata.EMPTY_METADATA ? this : new Alternation(options); + } + + @Override + public Alternation recMap(final Function mapper) { + return new Alternation(options.stream().map(seq -> seq.recMap(mapper)).toList(), metadata); + } + + @Override + public Stream flatten() { + return options.stream().flatMap(Sequence::flatten); + } + + @Override + public String toString() { + return options().stream().map(Term::toString).collect(joining(" | ")); + } + +} diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Derivation.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Derivation.java index 32ad358336..879b2e6095 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Derivation.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Derivation.java @@ -1,55 +1,17 @@ package fielden.platform.bnf; -import java.util.List; -import java.util.Objects; import java.util.function.Function; -import java.util.stream.Stream; -import static java.util.stream.Collectors.joining; +public record Derivation(Variable lhs, Alternation rhs) implements Rule { -public final class Derivation implements Rule { - - private final Variable lhs; - private final List rhs; - - public Derivation(Variable lhs, List rhs) { - this.lhs = lhs; - this.rhs = rhs; - } - - @Override - public Stream rhs() { - return rhs.stream(); - } - - @Override - public Variable lhs() { - return lhs; - } - - public Rule map(final Function mapper) { - return new Derivation(lhs, rhs.stream().map(seq -> seq.map(mapper)).toList()); - } - - @Override - public boolean equals(Object obj) { - return obj == this || - (obj instanceof Derivation that && - Objects.equals(this.lhs, that.lhs) && - Objects.equals(this.rhs, that.rhs)); - } - - @Override - public int hashCode() { - return Objects.hash(lhs, rhs); + public Rule recMap(final Function mapper) { + return new Derivation(lhs, rhs.recMap(mapper)); } @Override public String toString() { - return "%s -> %s".formatted( - lhs.name(), - rhs.stream().map(terms -> terms.stream().map(Term::toString).collect(joining(" "))) - .collect(joining(" | "))); + return "%s -> %s".formatted(lhs.name(), rhs); } + } diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/FluentBNF.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/FluentBNF.java index cb265c019f..2db846f8ca 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/FluentBNF.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/FluentBNF.java @@ -93,14 +93,13 @@ public IDerivationTail or(final Term... terms) { @Override protected Rule finishRule() { - return new Derivation(lhs, bodies); + return new Derivation(lhs, new Alternation(bodies)); } @Override protected boolean buildsRule() { return true; } - } private static final class SpecializationImpl extends BnfBodyImpl implements FluentBNF.ISpecialization { @@ -142,12 +141,15 @@ private static final class Builder { void add(final Rule rule) { add(rule.lhs()); - rule.rhs().forEach(this::add); + rule.rhs().options().forEach(this::add); rules.add(rule); } void add(final Notation notation) { - add(notation.term()); + switch (notation) { + case Alternation alternation -> alternation.options().forEach(this::add); + case Quantifier quantifier -> add(quantifier.term()); + } } void add(final Sequence sequence) { diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Notation.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Notation.java index fdf02c32da..1f7c1969f5 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Notation.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Notation.java @@ -1,11 +1,10 @@ package fielden.platform.bnf; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; -public sealed interface Notation extends Term permits OneOrMore, Optional, ZeroOrMore { - - Term term(); +public sealed interface Notation extends Term permits Alternation, Quantifier { static OneOrMore repeat1(Term term) { return new OneOrMore(term); @@ -40,4 +39,11 @@ static Optional opt(Term term, Term... terms) { return new Optional(new Sequence(list)); } + static Alternation oneOf(Term term, Term... terms) { + var list = new ArrayList(1 + terms.length); + list.add(Sequence.of(term)); + Arrays.stream(terms).map(Sequence::of).forEach(list::add); + return new Alternation(list); + } + } diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/OneOrMore.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/OneOrMore.java index 45fc0e449b..0689409e49 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/OneOrMore.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/OneOrMore.java @@ -1,6 +1,8 @@ package fielden.platform.bnf; -public record OneOrMore(Term term, TermMetadata metadata) implements Notation { +import java.util.function.Function; + +public record OneOrMore(Term term, TermMetadata metadata) implements Quantifier { public OneOrMore(Term term) { this(term, TermMetadata.EMPTY_METADATA); @@ -16,6 +18,11 @@ public OneOrMore normalize() { return new OneOrMore(term); } + @Override + public OneOrMore recMap(final Function mapper) { + return new OneOrMore(term.recMap(mapper), metadata); + } + @Override public String toString() { return "{%s}+".formatted(term); diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Optional.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Optional.java index df845c975d..761f416582 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Optional.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Optional.java @@ -1,6 +1,8 @@ package fielden.platform.bnf; -public record Optional(Term term, TermMetadata metadata) implements Notation { +import java.util.function.Function; + +public record Optional(Term term, TermMetadata metadata) implements Quantifier { public Optional(Term term) { this(term, TermMetadata.EMPTY_METADATA); @@ -16,6 +18,11 @@ public Optional normalize() { return new Optional(term); } + @Override + public Optional recMap(final Function mapper) { + return new Optional(term.recMap(mapper), metadata); + } + @Override public String toString() { return "{%s}?".formatted(term); diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Quantifier.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Quantifier.java new file mode 100644 index 0000000000..137acd3148 --- /dev/null +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Quantifier.java @@ -0,0 +1,14 @@ +package fielden.platform.bnf; + +import java.util.stream.Stream; + +public sealed interface Quantifier extends Notation permits OneOrMore, Optional, ZeroOrMore { + + Term term(); + + @Override + default Stream flatten() { + return term().flatten(); + } + +} diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Rule.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Rule.java index 180ad5a3ca..f107f535d7 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Rule.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Rule.java @@ -1,16 +1,9 @@ package fielden.platform.bnf; -import java.util.Collection; -import java.util.stream.Stream; - public sealed interface Rule permits Derivation, Specialization { Variable lhs(); - Stream rhs(); - - default Stream rhsTerms() { - return rhs().flatMap(Collection::stream); - } + Alternation rhs(); } diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Sequence.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Sequence.java index bc4b53a050..84edfe8d39 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Sequence.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Sequence.java @@ -6,6 +6,8 @@ public final class Sequence implements List, Term { + private static final Sequence EMPTY_SEQUENCE = new Sequence(); + private final List terms; private final TermMetadata metadata; @@ -22,6 +24,16 @@ public Sequence(Term... terms) { this(Arrays.asList(terms), TermMetadata.EMPTY_METADATA); } + public static Sequence of(Term... terms) { + if (terms.length == 0) { + return EMPTY_SEQUENCE; + } + else if (terms.length == 1 && terms[0] instanceof Sequence seq) { + return seq; + } + return new Sequence(terms); + } + @Override public Term normalize() { return new Sequence(terms); @@ -32,8 +44,18 @@ public Sequence annotate(final TermMetadata.Key key, final V value) { return new Sequence(terms, TermMetadata.merge(metadata, key, value)); } + @Override + public Sequence recMap(final Function mapper) { + return new Sequence(terms.stream().map(t -> t.recMap(mapper)).toList(), metadata); + } + public Sequence map(final Function mapper) { - return new Sequence(terms.stream().map(mapper).toList()); + return new Sequence(terms.stream().map(mapper).toList(), metadata); + } + + @Override + public Stream flatten() { + return terms.stream().flatMap(Term::flatten); } @Override diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Specialization.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Specialization.java index 16e15e8adf..d46df9b13f 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Specialization.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Specialization.java @@ -1,15 +1,14 @@ package fielden.platform.bnf; import java.util.List; -import java.util.stream.Stream; import static java.util.stream.Collectors.joining; public record Specialization(Variable lhs, List specializers) implements Rule { @Override - public Stream rhs() { - return specializers.stream().map(Sequence::new); + public Alternation rhs() { + return new Alternation(specializers.stream().map(Sequence::new).toList()); } @Override diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Symbol.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Symbol.java index 1835a3a924..7ef71c66db 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Symbol.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Symbol.java @@ -1,7 +1,20 @@ package fielden.platform.bnf; +import java.util.function.Function; +import java.util.stream.Stream; + public sealed interface Symbol extends Term permits Variable, Terminal { String name(); + @Override + default Stream flatten() { + return Stream.of(this); + } + + @Override + default Term recMap(Function mapper) { + return mapper.apply(this); + } + } diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Term.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Term.java index f2c508db22..66c49ef15f 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/Term.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/Term.java @@ -1,5 +1,8 @@ package fielden.platform.bnf; +import java.util.function.Function; +import java.util.stream.Stream; + /** * The most general grammar term. */ @@ -20,4 +23,11 @@ default TermMetadata metadata() { Term annotate(TermMetadata.Key key, V value); + Stream flatten(); + + /** + * Recursive map. + */ + Term recMap(Function mapper); + } diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/ZeroOrMore.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/ZeroOrMore.java index 8e64900ace..b447d64b8b 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/ZeroOrMore.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/ZeroOrMore.java @@ -1,6 +1,8 @@ package fielden.platform.bnf; -public record ZeroOrMore(Term term, TermMetadata metadata) implements Notation { +import java.util.function.Function; + +public record ZeroOrMore(Term term, TermMetadata metadata) implements Quantifier { public ZeroOrMore(Term term) { this(term, TermMetadata.EMPTY_METADATA); @@ -16,6 +18,11 @@ public ZeroOrMore normalize() { return new ZeroOrMore(term); } + @Override + public ZeroOrMore recMap(final Function mapper) { + return new ZeroOrMore(term.recMap(mapper), metadata); + } + @Override public String toString() { return "{%s}*".formatted(term); diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfToG4.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfToG4.java index 513e0b87c4..257400f0cb 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfToG4.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfToG4.java @@ -114,7 +114,7 @@ protected String convert(Rule rule) { }; return "%s :\n %s\n;".formatted( convert(rule.lhs()), - rule.rhs().map(this::convert).map(labeler).collect(joining("\n | "))); + rule.rhs().options().stream().map(this::convert).map(labeler).collect(joining("\n | "))); } protected String makeAltLabelName(Rule rule, String alt) { @@ -165,33 +165,45 @@ protected String convert(Token token) { } protected String convert(final Notation notation) { - final String q = switch (notation) { - case ZeroOrMore $ -> "*"; - case OneOrMore $ -> "+"; - case Optional $ -> "?"; + return switch (notation) { + case Quantifier quantifier -> convert(quantifier); + case Alternation alternation -> convert(alternation); }; + } - final String s = convert(notation.term()); + protected String convert(final Alternation alternation) { + final List options = alternation.options(); + if (options.size() == 1) { + return convert(options.getFirst()); + } + return options.stream().map(this::convert).collect(joining(" | ", "(", ")")); + } - final Function wrapper = switch (notation.term()) { - case Sequence seq -> (seq.size() > 1) ? str -> "(%s)".formatted(str) : Function.identity(); + protected String convert(final Quantifier quantifier) { + final String q = switch (quantifier) { + case ZeroOrMore x -> "*"; + case OneOrMore x -> "+"; + case Optional x -> "?"; + }; + final Function wrapper = switch (quantifier.term()) { + case Sequence seq when (seq.size() > 1) -> "(%s)"::formatted; default -> Function.identity(); }; - return wrapper.apply(s) + q; + return wrapper.apply(convert(quantifier.term())) + q; } static boolean isSingleTerminalRule(final Rule rule) { return switch (rule) { case Specialization $ -> false; - case Derivation derivation -> derivation.rhs() - .allMatch(body -> body.size() == 1 && body.getFirst() instanceof Terminal); + case Derivation derivation -> derivation.rhs().options().stream() + .allMatch(seq -> seq.size() == 1 && seq.getFirst() instanceof Terminal); }; } protected Collection generateFiles() { return originalBnf.rules().stream() - .flatMap(Rule::rhsTerms) + .flatMap(rule -> rule.rhs().flatten()) .mapMulti(typeFilter(Token.class)) .filter(Token::hasParameters) .map(Token::normalize) @@ -270,7 +282,7 @@ private static CodeBlock makeStatements(final Collection co private static BNF stripParameters(final BNF bnf) { final Set newRules = bnf.rules().stream() .map(rule -> switch (rule) { - case Derivation derivation -> derivation.map(term -> switch (term) { + case Derivation derivation -> derivation.recMap(term -> switch (term) { case Token token -> token.stripParameters(); default -> term; }); @@ -294,7 +306,7 @@ private static BNF stripParameters(final BNF bnf) { */ private static Rule deduplicate(Rule rule) { return switch (rule) { - case Derivation d -> new Derivation(d.lhs(), d.rhs().distinct().toList()); + case Derivation d -> new Derivation(d.lhs(), new Alternation(d.rhs().options().stream().distinct().toList(), d.rhs().metadata())); case Specialization s -> new Specialization(s.lhs(), s.specializers().stream().distinct().toList()); }; } diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfToHtml.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfToHtml.java index 3dbbacd6f9..5b5b4c6e8e 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfToHtml.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfToHtml.java @@ -4,6 +4,7 @@ import j2html.TagCreator; import j2html.tags.DomContent; +import java.util.List; import java.util.Spliterator; import java.util.Spliterators; import java.util.function.Consumer; @@ -90,14 +91,12 @@ protected DomContent toHtml(Rule rule) { var first = tr( td(a(rule.lhs().name()).attr("name", rule.lhs().name()).withClass(NONTERMINAL_LHS_CLASS)), td("")); - var rest = rule.rhs().map(this::ruleBodyToHtml); + var rest = rule.rhs().options().stream().map(this::ruleBodyToHtml); return each(first, TagCreator.each(rest)); } - protected DomContent ruleBodyToHtml(final Sequence seq) { - return tr( - td(), - td(each(withDelimiter(seq.stream().map(this::toHtml), text(" "))))); + protected DomContent ruleBodyToHtml(final Term term) { + return tr(td(), td(toHtml(term))); } protected DomContent toHtml(Term term) { @@ -161,15 +160,33 @@ private static DomContent wrapParameter(String text) { return span("<%s>".formatted(text)).withClass(PARAMETER_CLASS); } - protected DomContent toHtml(Notation notation) { - String q = switch (notation) { - case ZeroOrMore $ -> "*"; - case OneOrMore $ -> "+"; - case Optional $ -> "?"; + protected DomContent toHtml(final Notation notation) { + return switch (notation) { + case Alternation alternation -> toHtml(alternation); + case Quantifier quantifier -> toHtml(quantifier); + }; + } + + protected DomContent toHtml(final Alternation alternation) { + final List options = alternation.options(); + if (options.size() == 1) { + return toHtml(options.getFirst()); + } + return each( + text("{"), + each(withDelimiter(options.stream().map(this::toHtml), text(" | "))), + text("}")); + } + + protected DomContent toHtml(final Quantifier quantifier) { + final String q = switch (quantifier) { + case ZeroOrMore x -> "*"; + case OneOrMore x -> "+"; + case Optional x -> "?"; }; return each( text("{"), - toHtml(notation.term()), + toHtml(quantifier.term()), text("}"), text(q) ); diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfToText.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfToText.java index 12811723c5..f5d6244f5c 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfToText.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfToText.java @@ -17,7 +17,7 @@ public String bnfToText(BNF bnf) { protected String toString(Rule rule) { final int prefixLen = rule.lhs().name().length(); - return rule.rhs() + return rule.rhs().options().stream() .map(this::toString) .collect(joining("\n%s | ".formatted(" ".repeat(prefixLen)), "%s = ".formatted(rule.lhs().name()), ";")); } @@ -81,14 +81,25 @@ protected String toString(VarArityParameter parameter) { private static String wrapParameter(String text) { return "<%s>".formatted(text); } - + protected String toString(Notation notation) { - String q = switch (notation) { + return switch (notation) { + case Alternation alternation -> toString(alternation); + case Quantifier quantifier -> toString(quantifier); + }; + } + + protected String toString(Alternation alternation) { + return alternation.options().stream().map(this::toString).collect(joining(" | ")); + } + + protected String toString(Quantifier quantifier) { + String q = switch (quantifier) { case ZeroOrMore $ -> "*"; case OneOrMore $ -> "+"; case Optional $ -> "?"; }; - return "{%s}%s".formatted(toString(notation.term()), q); + return "{%s}%s".formatted(toString(quantifier.term()), q); } } diff --git a/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfVerifier.java b/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfVerifier.java index 2269c435e4..b10be9920c 100644 --- a/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfVerifier.java +++ b/platform-eql-grammar/src/main/java/fielden/platform/bnf/util/BnfVerifier.java @@ -2,7 +2,6 @@ import fielden.platform.bnf.BNF; import fielden.platform.bnf.Rule; -import fielden.platform.bnf.Term; import fielden.platform.bnf.Variable; import org.apache.commons.collections4.CollectionUtils; @@ -25,7 +24,8 @@ public static void verifyBnf(BNF bnf) { } private static Stream ruleRhsVariables(Rule rule) { - return rule.rhsTerms().mapMulti((term, sink) -> {if (term instanceof Variable v) sink.accept(v);}); + return rule.rhs().flatten() + .mapMulti((term, sink) -> {if (term instanceof Variable v) sink.accept(v);}); } }