From 6f4a07010e8f48f687b509c95a0eb24612244867 Mon Sep 17 00:00:00 2001 From: homedirectory Date: Wed, 21 Feb 2024 11:50:29 +0200 Subject: [PATCH] #2196, #2178 Provide our own BNF representation and a fluent API for building it This gives us more control over the BNF representation which will assist with the transformation into ANTLR grammar format (among other ones). --- .../java/fielden/platform/eql/fling/BNF.java | 298 ++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 platform-eql-grammar/src/main/java/fielden/platform/eql/fling/BNF.java diff --git a/platform-eql-grammar/src/main/java/fielden/platform/eql/fling/BNF.java b/platform-eql-grammar/src/main/java/fielden/platform/eql/fling/BNF.java new file mode 100644 index 0000000000..992cf2df45 --- /dev/null +++ b/platform-eql-grammar/src/main/java/fielden/platform/eql/fling/BNF.java @@ -0,0 +1,298 @@ +package fielden.platform.eql.fling; + +import il.ac.technion.cs.fling.internal.grammar.rules.*; + +import java.util.*; +import java.util.stream.Stream; + +import static java.util.Collections.unmodifiableSet; +import static java.util.stream.Collectors.joining; + +public record BNF( + Set tokens, + Set variables, + Variable start, + Set rules +) { + + public BNF(final Set tokens, final Set variables, final Variable start, final Set rules) { + this.tokens = unmodifiableSet(new LinkedHashSet<>(tokens)); + this.variables = unmodifiableSet(new LinkedHashSet<>(variables)); + this.start = start; + this.rules = unmodifiableSet(new LinkedHashSet<>(rules)); + } + + /** @return stream of all grammar symbols */ + public Stream symbols() { + return Stream.concat(tokens.stream(), variables.stream()); + } + + @Override + public String toString() { + return "<Σ=" + tokens + ", Γ=" + variables + ", ε=" + start + ", R=" + rules + ">"; + } + + public sealed interface Rule permits Derivation, Specialization { + + Variable lhs(); + + Stream rhs(); + + /** + * Returns all variables present in the right-hand side of this rule. + */ + default Stream variables() { + return Stream.concat(variables(components()), variables(quantifiedSymbols())); + } + + /** + * Returns all tokens present in the right-hand side of this rule. + */ + default Stream tokens() { + return Stream.concat(tokens(components()), tokens(quantifiedSymbols())); + } + + private Stream components() { + return rhs().flatMap(Collection::stream); + } + + private Stream quantifiedSymbols() { + return quantifiers().flatMap(Quantifier::symbols); + } + + private Stream quantifiers() { + return components().filter(Component::isQuantifier).map(Component::asQuantifier); + } + + private static Stream tokens(final Stream components) { + return components.filter(Component::isToken).map(Component::asToken); + } + + private static Stream variables(final Stream components) { + return components.filter(Component::isVariable).map(Component::asVariable); + } + } + + public final static class Derivation implements Rule { + private final Variable lhs; + private final List rhs; + + public Derivation(Variable lhs, List rhs) { + this.lhs = lhs; + this.rhs = List.copyOf(rhs); + } + + @Override + public Stream rhs() { + return rhs.stream(); + } + + @Override + public Variable lhs() { + return lhs; + } + + @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); + } + + @Override + public String toString() { + return "%s -> %s".formatted( + lhs.name(), + rhs.stream().map(body -> body.stream().map(Component::toString).collect(joining(" "))) + .collect(joining(" | "))); + } + } + + public record Specialization(Variable lhs, List specializers) implements Rule { + @Override + public Stream rhs() { + return specializers.stream().map(Body::new); + } + + @Override + public String toString() { + return "%s -> %s".formatted(lhs, specializers.stream().map(Variable::name).collect(joining(" | "))); + } + } + + public interface Fluent { + + static IBnfBody start(Variable start) { + return new FluentImpl(start); + } + + interface IBnfBody { + IDerivation derive(Variable v); + ISpecialization specialize(Variable v); + BNF build(); + } + + interface IDerivation { + IDerivationTail to(final TempComponent... cs); + } + + interface IDerivationTail extends IBnfBody { + IDerivationTail or(final TempComponent... cs); + } + + interface ISpecialization { + IBnfBody into(final Variable... vs); + } + + } + + private static class FluentImpl implements Fluent, Fluent.IBnfBody { + protected final Builder builder; + + FluentImpl(final Variable start) { + this.builder = new Builder(start); + } + + FluentImpl(final Builder builder) { + this.builder = builder; + } + + @Override + public IDerivation derive(final Variable v) { + if (buildsRule()) + builder.add(finishRule()); + return new Derivation(builder, v); + } + + @Override + public ISpecialization specialize(final Variable v) { + if (buildsRule()) + builder.add(finishRule()); + return new Specialization(builder, v); + } + + @Override + public BNF build() { + builder.add(finishRule()); + return new BNF(builder.tokens, builder.variables, builder.start, builder.rules); + } + + protected Rule finishRule() { throw new UnsupportedOperationException(); } + + protected boolean buildsRule() { return false; } + + private static class Derivation extends FluentImpl implements IDerivation, IDerivationTail { + private final Variable lhs; + private final List bodies = new ArrayList<>(); + + Derivation(final Builder builder, final Variable lhs) { + super(builder); + this.lhs = lhs; + } + + @Override + public IDerivationTail to(final TempComponent... cs) { + bodies.add(new Body(FluentImpl.normalize(cs).toList())); + return this; + } + + @Override + public IDerivationTail or(final TempComponent... cs) { + bodies.add(new Body(FluentImpl.normalize(cs).toList())); + return this; + } + + @Override + protected Rule finishRule() { + return new BNF.Derivation(lhs, bodies); + } + + @Override + protected boolean buildsRule() { + return true; + } + } + + private static class Specialization extends FluentImpl implements ISpecialization { + private final Variable lhs; + private final List rhs = new ArrayList<>(); + + Specialization(final Builder builder, final Variable lhs) { + super(builder); + this.lhs = lhs; + } + + @Override + public IBnfBody into(final Variable... vs) { + Collections.addAll(rhs, vs); + return this; + } + + @Override + protected Rule finishRule() { + return new BNF.Specialization(lhs, rhs); + } + + @Override + protected boolean buildsRule() { + return true; + } + } + + private static Stream normalize(final TempComponent... cs) { + return Arrays.stream(cs).map(TempComponent::normalize); + } + + } + + private static class Builder { + private final Set tokens = new LinkedHashSet<>(); + private final Set variables = new LinkedHashSet<>(); + private final Set rules = new LinkedHashSet<>(); + private final Variable start; + + private Builder(Variable start) { + this.start = start; + add(start); + } + + private void add(final Rule rule) { + add(rule.lhs()); + rule.rhs().forEach(this::add); + rules.add(rule); + } + + private void add(Body body) { + body.forEach(this::add); + } + + private void add(final Quantifier q) { + q.symbols().forEach(this::add); + } + + private void add(final Variable v) { + variables.add(v); + } + + private void add(final Token t) { + tokens.add(t); + } + + private void add(final Component component) { + switch (component) { + case Quantifier q -> add(q); + case Token tok -> add(tok); + case Variable v -> add(v); + default -> throw new IllegalStateException("Unexpected value: " + component); + } + } + + } + +}