eo-parser/src/main/java/org/eolang/parser/XeEoListener.java

Summary

Maintainability
F
1 wk
Test Coverage
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016-2024 Objectionary.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.eolang.parser;

import com.jcabi.manifests.Manifests;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.List;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.commons.text.StringEscapeUtils;
import org.cactoos.iterable.Mapped;
import org.cactoos.text.Joined;
import org.eolang.parser.xmir.XmirInfo;
import org.xembly.Directive;
import org.xembly.Directives;

/**
 * The EO grammar listener for ANTLR4 walker.
 *
 * @checkstyle CyclomaticComplexityCheck (500 lines)
 * @checkstyle ClassFanOutComplexityCheck (500 lines)
 * @checkstyle MethodCountCheck (1300 lines)
 * @since 0.1
 * @todo #2841:30min Change severity on comments validation. Current severity on comments validation
 *  is "warning". We need to change it to "error" to prevent users from ignoring this type of error.
 *  But firstly we have to make "eo-runtime" documented well. After it's done - we need to turn
 *  on "failOnWarning" trigger in pom.xml inside "eo-runtime.
 */
@SuppressWarnings({
    "PMD.TooManyMethods",
    "PMD.AvoidDuplicateLiterals",
    "PMD.ExcessivePublicCount",
    "PMD.ExcessiveClassLength",
    "PMD.GodClass"
})
public final class XeEoListener implements EoListener, Iterable<Directive> {
    /**
     * Info about xmir.
     */
    private static final XmirInfo INFO = new XmirInfo();

    /**
     * Minimum allowed comment length.
     */
    @SuppressWarnings("PMD.LongVariable")
    private static final int MIN_COMMENT_LENGTH = 64;

    /**
     * Meta for testing.
     */
    private static final String TESTS_META = "tests";

    /**
     * The name of it.
     */
    private final String name;

    /**
     * Xembly directives we are building (mutable).
     */
    private final Directives dirs;

    /**
     * Xembly directives for objects (mutable).
     */
    private final Objects objects;

    /**
     * Xembly directives to build errors.
     */
    private final Directives errors;

    /**
     * When we start.
     */
    private final long start;

    /**
     * If metas has "+tests" meta.
     */
    private boolean tests;

    /**
     * Ctor.
     *
     * @param name The name of it
     */
    public XeEoListener(final String name) {
        this.name = name;
        this.dirs = new Directives();
        this.errors = new Directives();
        this.objects = new Objects.ObjXembly();
        this.start = System.nanoTime();
    }

    @Override
    public void enterProgram(final EoParser.ProgramContext ctx) {
        this.dirs.add("program")
            .attr("name", this.name)
            .attr("version", Manifests.read("EO-Version"))
            .attr("revision", Manifests.read("EO-Revision"))
            .attr("dob", Manifests.read("EO-Dob"))
            .attr(
                "time",
                ZonedDateTime.now(ZoneOffset.UTC).format(
                    DateTimeFormatter.ISO_INSTANT
                )
            )
            .comment(XeEoListener.INFO)
            .add("listing").set(new SourceText(ctx)).up()
            .add("errors").up()
            .add("sheets").up()
            .add("license").up()
            .add("metas").up();
    }

    @Override
    public void exitProgram(final EoParser.ProgramContext ctx) {
        this.dirs
            .xpath("/program/errors")
            .append(this.errors).up()
            .attr("ms", (System.nanoTime() - this.start) / (1000L * 1000L))
            .up();
    }

    @Override
    public void enterEop(final EoParser.EopContext ctx) {
        // Nothing here
    }

    @Override
    public void exitEop(final EoParser.EopContext ctx) {
        // Nothing here
    }

    @Override
    public void enterLicense(final EoParser.LicenseContext ctx) {
        this.dirs.addIf("license").set(
            new Joined(
                "\n",
                new Mapped<>(
                    cmt -> cmt.getText().substring(1).trim(),
                    ctx.COMMENTARY()
                )
            )
        ).up();
    }

    @Override
    public void exitLicense(final EoParser.LicenseContext ctx) {
        // Nothing here
    }

    @Override
    public void enterMetas(final EoParser.MetasContext ctx) {
        this.dirs.addIf("metas");
        for (final TerminalNode node : ctx.META()) {
            final String[] pair = node.getText().split(" ", 2);
            final String head = pair[0].substring(1);
            if (XeEoListener.TESTS_META.equals(head)) {
                this.tests = true;
            }
            this.dirs.add("meta")
                .attr("line", node.getSymbol().getLine())
                .add("head").set(head).up()
                .add("tail");
            if (pair.length > 1) {
                this.dirs.set(pair[1].trim()).up();
                for (final String part : pair[1].trim().split(" ")) {
                    this.dirs.add("part").set(part).up();
                }
            } else {
                this.dirs.up();
            }
            this.dirs.up();
        }
        this.dirs.up();
    }

    @Override
    public void exitMetas(final EoParser.MetasContext ctx) {
        // Nothing here
    }

    @Override
    public void enterObjects(final EoParser.ObjectsContext ctx) {
        this.dirs.add("objects");
    }

    @Override
    public void exitObjects(final EoParser.ObjectsContext ctx) {
        this.dirs.append(this.objects).up();
    }

    @Override
    public void enterComment(final EoParser.CommentContext ctx) {
        // Nothing here
    }

    @Override
    public void exitComment(final EoParser.CommentContext ctx) {
        // Nothing here
    }

    @Override
    public void enterCommentOptional(final EoParser.CommentOptionalContext ctx) {
        this.validateComment(ctx, ctx.comment());
    }

    @Override
    public void exitCommentOptional(final EoParser.CommentOptionalContext ctx) {
        // Nothing here
    }

    @Override
    public void enterCommentMandatory(final EoParser.CommentMandatoryContext ctx) {
        this.validateComment(ctx, ctx.comment());
    }

    @Override
    public void exitCommentMandatory(final EoParser.CommentMandatoryContext ctx) {
        // Nothing here
    }

    @Override
    public void enterObject(final EoParser.ObjectContext ctx) {
        // Nothing here
    }

    @Override
    public void exitObject(final EoParser.ObjectContext ctx) {
        // Nothing here
    }

    @Override
    public void enterSlave(final EoParser.SlaveContext ctx) {
        // Nothing here
    }

    @Override
    public void exitSlave(final EoParser.SlaveContext ctx) {
        // Nothing here
    }

    @Override
    public void enterMaster(final EoParser.MasterContext ctx) {
        // Nothing here
    }

    @Override
    public void exitMaster(final EoParser.MasterContext ctx) {
        // Nothing here
    }

    @Override
    public void enterSubMaster(final EoParser.SubMasterContext ctx) {
        // Nothing here
    }

    @Override
    public void exitSubMaster(final EoParser.SubMasterContext ctx) {
        // Nothing here
    }

    @Override
    public void enterMasterBody(final EoParser.MasterBodyContext ctx) {
        // Nothing here
    }

    @Override
    public void exitMasterBody(final EoParser.MasterBodyContext ctx) {
        // Nothing here
    }

    @Override
    public void enterJust(final EoParser.JustContext ctx) {
        // Nothing here
    }

    @Override
    public void exitJust(final EoParser.JustContext ctx) {
        // Nothing here
    }

    @Override
    public void enterJustNamed(final EoParser.JustNamedContext ctx) {
        // Nothing here
    }

    @Override
    public void exitJustNamed(final EoParser.JustNamedContext ctx) {
        // Nothing here
    }

    @Override
    @SuppressWarnings("PMD.ConfusingTernary")
    public void enterAtom(final EoParser.AtomContext ctx) {
        this.startObject(ctx);
        if (ctx.type().NAME() != null) {
            this.objects.prop("atom", ctx.type().NAME().getText());
        } else if (ctx.type().QUESTION() != null) {
            this.objects.prop("atom", ctx.type().QUESTION().getText());
        }
        this.objects.leave();
    }

    @Override
    public void exitAtom(final EoParser.AtomContext ctx) {
        // Nothing here
    }

    @Override
    public void enterFormation(final EoParser.FormationContext ctx) {
        this.startAbstract(ctx);
    }

    @Override
    public void exitFormation(final EoParser.FormationContext ctx) {
        // Nothing here
    }

    @Override
    public void enterInnersOrEol(final EoParser.InnersOrEolContext ctx) {
        // Nothing here
    }

    @Override
    public void exitInnersOrEol(final EoParser.InnersOrEolContext ctx) {
        // Nothing here
    }

    @Override
    public void enterInners(final EoParser.InnersContext ctx) {
        this.objects.enter();
    }

    @Override
    public void exitInners(final EoParser.InnersContext ctx) {
        this.objects.leave();
    }

    @Override
    public void enterAttributes(final EoParser.AttributesContext ctx) {
        this.objects.enter();
    }

    @Override
    public void exitAttributes(final EoParser.AttributesContext ctx) {
        this.objects.leave();
    }

    @Override
    public void enterAttribute(final EoParser.AttributeContext ctx) {
        this.startObject(ctx).prop("name", ctx.NAME().getText());
    }

    @Override
    public void exitAttribute(final EoParser.AttributeContext ctx) {
        this.objects.leave();
    }

    @Override
    public void enterType(final EoParser.TypeContext ctx) {
        // Nothing here
    }

    @Override
    public void exitType(final EoParser.TypeContext ctx) {
        // Nothing here
    }

    @Override
    public void enterApplication(final EoParser.ApplicationContext ctx) {
        // Nothing here
    }

    @Override
    public void exitApplication(final EoParser.ApplicationContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHapplicationExtended(final EoParser.HapplicationExtendedContext ctx) {
        // Nothing here
    }

    @Override
    public void exitHapplicationExtended(final EoParser.HapplicationExtendedContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHapplicationReversed(final EoParser.HapplicationReversedContext ctx) {
        // Nothing here
    }

    @Override
    public void exitHapplicationReversed(final EoParser.HapplicationReversedContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHapplication(final EoParser.HapplicationContext ctx) {
        // Nothing here
    }

    @Override
    public void exitHapplication(final EoParser.HapplicationContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHapplicationHead(final EoParser.HapplicationHeadContext ctx) {
        // Nothing here
    }

    @Override
    public void exitHapplicationHead(final EoParser.HapplicationHeadContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHapplicationHeadExtended(
        final EoParser.HapplicationHeadExtendedContext ctx
    ) {
        // Nothing here
    }

    @Override
    public void exitHapplicationHeadExtended(
        final EoParser.HapplicationHeadExtendedContext ctx
    ) {
        // Nothing here
    }

    @Override
    @SuppressWarnings("PMD.ConfusingTernary")
    public void enterApplicable(final EoParser.ApplicableContext ctx) {
        this.startObject(ctx);
        final String base;
        if (ctx.STAR() != null) {
            base = "tuple";
            this.objects.prop("star");
        } else if (ctx.NAME() != null) {
            base = ctx.NAME().getText();
        } else if (ctx.PHI() != null) {
            base = "@";
        } else {
            base = "";
        }
        if (!base.isEmpty()) {
            this.objects.prop("base", base);
        }
        this.objects.leave();
    }

    @Override
    public void exitApplicable(final EoParser.ApplicableContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHapplicationTail(final EoParser.HapplicationTailContext ctx) {
        this.objects.enter();
    }

    @Override
    public void exitHapplicationTail(final EoParser.HapplicationTailContext ctx) {
        this.objects.leave();
    }

    @Override
    public void enterHapplicationTailReversed(final EoParser.HapplicationTailReversedContext ctx) {
        // Nothing here
    }

    @Override
    public void exitHapplicationTailReversed(final EoParser.HapplicationTailReversedContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHapplicationTailReversedFirst(final EoParser.HapplicationTailReversedFirstContext ctx) {
        this.objects.enter();
    }

    @Override
    public void exitHapplicationTailReversedFirst(final EoParser.HapplicationTailReversedFirstContext ctx) {
        this.objects.leave();
    }

    @Override
    public void enterHapplicationArg(final EoParser.HapplicationArgContext ctx) {
        // Nothing here
    }

    @Override
    public void exitHapplicationArg(final EoParser.HapplicationArgContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplication(final EoParser.VapplicationContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVapplication(final EoParser.VapplicationContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplicationHead(final EoParser.VapplicationHeadContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVapplicationHead(final EoParser.VapplicationHeadContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplicationHeadNamed(final EoParser.VapplicationHeadNamedContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVapplicationHeadNamed(final EoParser.VapplicationHeadNamedContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplicationArgs(final EoParser.VapplicationArgsContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVapplicationArgs(final EoParser.VapplicationArgsContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplicationArgsReversed(final EoParser.VapplicationArgsReversedContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVapplicationArgsReversed(final EoParser.VapplicationArgsReversedContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplicationArgsSpecific(final EoParser.VapplicationArgsSpecificContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVapplicationArgsSpecific(final EoParser.VapplicationArgsSpecificContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplicationArgBound(final EoParser.VapplicationArgBoundContext ctx) {
        this.objects.enter();
    }

    @Override
    public void exitVapplicationArgBound(final EoParser.VapplicationArgBoundContext ctx) {
        this.objects.leave();
    }

    @Override
    public void enterVapplicationArgBoundCurrent(final EoParser.VapplicationArgBoundCurrentContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVapplicationArgBoundCurrent(final EoParser.VapplicationArgBoundCurrentContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplicationArgBoundNext(final EoParser.VapplicationArgBoundNextContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVapplicationArgBoundNext(final EoParser.VapplicationArgBoundNextContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplicationArgUnbound(final EoParser.VapplicationArgUnboundContext ctx) {
        this.objects.enter();
    }

    @Override
    public void exitVapplicationArgUnbound(final EoParser.VapplicationArgUnboundContext ctx) {
        this.objects.leave();
    }

    @Override
    public void enterVapplicationArgUnboundCurrent(final EoParser.VapplicationArgUnboundCurrentContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVapplicationArgUnboundCurrent(final EoParser.VapplicationArgUnboundCurrentContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplicationArgUnboundNext(final EoParser.VapplicationArgUnboundNextContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVapplicationArgUnboundNext(final EoParser.VapplicationArgUnboundNextContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplicationArgHapplicationBound(
        final EoParser.VapplicationArgHapplicationBoundContext ctx
    ) {
        // Nothing here
    }

    @Override
    public void exitVapplicationArgHapplicationBound(
        final EoParser.VapplicationArgHapplicationBoundContext ctx
    ) {
        // Nothing here
    }

    @Override
    public void enterVapplicationArgHapplicationUnbound(
        final EoParser.VapplicationArgHapplicationUnboundContext ctx
    ) {
        // Nothing here
    }

    @Override
    public void exitVapplicationArgHapplicationUnbound(
        final EoParser.VapplicationArgHapplicationUnboundContext ctx
    ) {
        // Nothing here
    }

    @Override
    public void enterVapplicationHeadAs(
        final EoParser.VapplicationHeadAsContext ctx
    ) {
        // Nothing here
    }

    @Override
    public void exitVapplicationHeadAs(final EoParser.VapplicationHeadAsContext ctx) {
        // Nothing here
    }

    @Override
    public void enterFormationNameless(final EoParser.FormationNamelessContext ctx) {
        this.startAbstract(ctx);
    }

    @Override
    public void exitFormationNameless(final EoParser.FormationNamelessContext ctx) {
        // Nothing here
    }

    @Override
    public void enterFormationNamedOrNameless(final EoParser.FormationNamedOrNamelessContext ctx) {
        // Nothing here
    }

    @Override
    public void exitFormationNamedOrNameless(final EoParser.FormationNamedOrNamelessContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplicationArgVanonymBound(
        final EoParser.VapplicationArgVanonymBoundContext ctx
    ) {
        this.startAbstract(ctx);
    }

    @Override
    public void exitVapplicationArgVanonymBound(
        final EoParser.VapplicationArgVanonymBoundContext ctx
    ) {
        // Nothing here
    }

    @Override
    public void enterAttributesAs(final EoParser.AttributesAsContext ctx) {
        // Nothing here
    }

    @Override
    public void exitAttributesAs(final EoParser.AttributesAsContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplicationArgHanonymBoundBody(final EoParser.VapplicationArgHanonymBoundBodyContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVapplicationArgHanonymBoundBody(final EoParser.VapplicationArgHanonymBoundBodyContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVapplicationArgHanonymBound(
        final EoParser.VapplicationArgHanonymBoundContext ctx
    ) {
        // Nothing here
    }

    @Override
    public void exitVapplicationArgHanonymBound(
        final EoParser.VapplicationArgHanonymBoundContext ctx
    ) {
        // Nothing here
    }

    @Override
    public void enterVapplicationArgHanonymUnbound(
        final EoParser.VapplicationArgHanonymUnboundContext ctx
    ) {
        // Nothing here
    }

    @Override
    public void exitVapplicationArgHanonymUnbound(
        final EoParser.VapplicationArgHanonymUnboundContext ctx
    ) {
        // Nothing here
    }

    @Override
    public void enterHformation(final EoParser.HformationContext ctx) {
        this.startAbstract(ctx);
    }

    @Override
    public void exitHformation(final EoParser.HformationContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHanonym(final EoParser.HanonymContext ctx) {
        // Nothing here
    }

    @Override
    public void exitHanonym(final EoParser.HanonymContext ctx) {
        // Nothing here
    }

    @Override
    public void enterOnlyphi(final EoParser.OnlyphiContext ctx) {
        this.startAbstract(ctx).enter();
    }

    @Override
    public void exitOnlyphi(final EoParser.OnlyphiContext ctx) {
        // Nothing here
    }

    @Override
    public void enterOnlyphiTail(final EoParser.OnlyphiTailContext ctx) {
        this.objects.enter().prop("name", "@").leave().leave();
    }

    @Override
    public void exitOnlyphiTail(final EoParser.OnlyphiTailContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHanonymInner(final EoParser.HanonymInnerContext ctx) {
        this.objects.enter();
    }

    @Override
    public void exitHanonymInner(final EoParser.HanonymInnerContext ctx) {
        this.objects.leave();
    }

    @Override
    public void enterMethod(final EoParser.MethodContext ctx) {
        // Nothing here
    }

    @Override
    public void exitMethod(final EoParser.MethodContext ctx) {
        // Nothing here
    }

    @Override
    public void enterMethodNamed(final EoParser.MethodNamedContext ctx) {
        // Nothing here
    }

    @Override
    public void exitMethodNamed(final EoParser.MethodNamedContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHmethod(final EoParser.HmethodContext ctx) {
        // Nothing here
    }

    @Override
    public void exitHmethod(final EoParser.HmethodContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHmethodOptional(final EoParser.HmethodOptionalContext ctx) {
        // Nothing here
    }

    @Override
    public void exitHmethodOptional(final EoParser.HmethodOptionalContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHmethodExtended(final EoParser.HmethodExtendedContext ctx) {
        // Nothing here
    }

    @Override
    public void exitHmethodExtended(final EoParser.HmethodExtendedContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHmethodVersioned(final EoParser.HmethodVersionedContext ctx) {
        // Nothing here
    }

    @Override
    public void exitHmethodVersioned(final EoParser.HmethodVersionedContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHmethodExtendedVersioned(
        final EoParser.HmethodExtendedVersionedContext ctx
    ) {
        // Nothing here
    }

    @Override
    public void exitHmethodExtendedVersioned(
        final EoParser.HmethodExtendedVersionedContext ctx
    ) {
        // Nothing here
    }

    @Override
    public void enterHmethodHead(final EoParser.HmethodHeadContext ctx) {
        // Nothing here
    }

    @Override
    public void exitHmethodHead(final EoParser.HmethodHeadContext ctx) {
        // Nothing here
    }

    @Override
    public void enterHmethodHeadExtended(final EoParser.HmethodHeadExtendedContext ctx) {
        // Nothing here
    }

    @Override
    public void exitHmethodHeadExtended(final EoParser.HmethodHeadExtendedContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVmethod(final EoParser.VmethodContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVmethod(final EoParser.VmethodContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVmethodVersioned(final EoParser.VmethodVersionedContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVmethodVersioned(final EoParser.VmethodVersionedContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVmethodOptional(final EoParser.VmethodOptionalContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVmethodOptional(final EoParser.VmethodOptionalContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVmethodHead(final EoParser.VmethodHeadContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVmethodHead(final EoParser.VmethodHeadContext ctx) {
        // Nothing here
    }

    @Override
    public void enterMethodTailOptional(final EoParser.MethodTailOptionalContext ctx) {
        // Nothing here
    }

    @Override
    public void exitMethodTailOptional(final EoParser.MethodTailOptionalContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVmethodHeadApplicationTail(final EoParser.VmethodHeadApplicationTailContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVmethodHeadApplicationTail(final EoParser.VmethodHeadApplicationTailContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVmethodHeadVapplication(final EoParser.VmethodHeadVapplicationContext ctx) {
        // Nothing here
    }

    @Override
    public void exitVmethodHeadVapplication(final EoParser.VmethodHeadVapplicationContext ctx) {
        // Nothing here
    }

    @Override
    public void enterMethodTail(final EoParser.MethodTailContext ctx) {
        // Nothing here
    }

    @Override
    public void exitMethodTail(final EoParser.MethodTailContext ctx) {
        this.objects
            .enter()
            .prop("method")
            .xprop("base", "concat('.',@base)")
            .xprop("pos", "@pos - 1")
            .leave();
    }

    @Override
    public void enterMethodTailVersioned(final EoParser.MethodTailVersionedContext ctx) {
        this.startObject(ctx)
            .prop("base", String.format(".%s", ctx.NAME().getText()))
            .prop("method");
    }

    @Override
    public void exitMethodTailVersioned(final EoParser.MethodTailVersionedContext ctx) {
        this.objects.leave();
    }

    @Override
    @SuppressWarnings("PMD.ConfusingTernary")
    public void enterBeginner(final EoParser.BeginnerContext ctx) {
        this.startObject(ctx);
        if (ctx.data() == null) {
            final String base;
            if (ctx.XI() != null) {
                base = "$";
            } else if (ctx.STAR() != null) {
                base = "tuple";
                this.objects.prop("star");
            } else if (ctx.ROOT() != null) {
                base = "Q";
            } else if (ctx.HOME() != null) {
                base = "QQ";
            } else {
                base = "";
            }
            if (!base.isEmpty()) {
                this.objects.prop("base", base);
            }
        }
    }

    @Override
    public void exitBeginner(final EoParser.BeginnerContext ctx) {
        this.objects.leave();
    }

    @Override
    @SuppressWarnings("PMD.ConfusingTernary")
    public void enterFinisher(final EoParser.FinisherContext ctx) {
        this.startObject(ctx);
        final String base;
        if (ctx.NAME() != null) {
            base = ctx.NAME().getText();
        } else if (ctx.PHI() != null) {
            base = "@";
        } else if (ctx.RHO() != null) {
            base = "^";
        } else if (ctx.SIGMA() != null) {
            base = "&";
        } else {
            base = "";
        }
        if (!base.isEmpty()) {
            this.objects.prop("base", base);
        }
    }

    @Override
    public void exitFinisher(final EoParser.FinisherContext ctx) {
        this.objects.leave();
    }

    @Override
    public void enterBeginnerOrFinisher(final EoParser.BeginnerOrFinisherContext ctx) {
        // Nothing here
    }

    @Override
    public void exitBeginnerOrFinisher(final EoParser.BeginnerOrFinisherContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVersioned(final EoParser.VersionedContext ctx) {
        this.startObject(ctx).prop("base", ctx.NAME().getText());
    }

    @Override
    public void exitVersioned(final EoParser.VersionedContext ctx) {
        this.objects.leave();
    }

    @Override
    public void enterReversed(final EoParser.ReversedContext ctx) {
        // Nothing here
    }

    @Override
    public void exitReversed(final EoParser.ReversedContext ctx) {
        this.objects.enter().xprop("base", "concat('.',@base)").leave();
    }

    @Override
    public void enterAname(final EoParser.AnameContext ctx) {
        this.objects
            .enter()
            .prop(
                "name",
                String.format(
                    "OBJ-%d-%d",
                    ctx.getStart().getLine(),
                    ctx.getStart().getCharPositionInLine()
                )
            )
            .leave();
    }

    @Override
    public void exitAname(final EoParser.AnameContext ctx) {
        // Nothing here
    }

    @Override
    public void enterOname(final EoParser.OnameContext ctx) {
        // Nothing here
    }

    @Override
    public void exitOname(final EoParser.OnameContext ctx) {
        if (ctx.CONST() != null) {
            this.objects.enter().prop("const").leave();
        }
    }

    @Override
    @SuppressWarnings("PMD.ConfusingTernary")
    public void enterSuffix(final EoParser.SuffixContext ctx) {
        this.objects.enter();
        if (ctx.PHI() != null) {
            this.objects.prop("name", ctx.PHI().getText());
        } else if (ctx.NAME() != null) {
            this.objects.prop("name", ctx.NAME().getText());
        }
    }

    @Override
    public void exitSuffix(final EoParser.SuffixContext ctx) {
        this.objects.leave();
    }

    @Override
    public void enterSpacedArrow(final EoParser.SpacedArrowContext ctx) {
        // Nothing here
    }

    @Override
    public void exitSpacedArrow(final EoParser.SpacedArrowContext ctx) {
        // Nothing here
    }

    @Override
    public void enterScope(final EoParser.ScopeContext ctx) {
        // Nothing here
    }

    @Override
    public void exitScope(final EoParser.ScopeContext ctx) {
        // Nothing here
    }

    @Override
    public void enterVersion(final EoParser.VersionContext ctx) {
        if (ctx.VER() != null) {
            this.objects.prop("ver", ctx.VER().getText());
        }
    }

    @Override
    public void exitVersion(final EoParser.VersionContext ctx) {
        // Nothing here
    }

    @Override
    @SuppressWarnings("PMD.ConfusingTernary")
    public void enterAs(final EoParser.AsContext ctx) {
        this.objects.enter();
        final String has;
        if (ctx.NAME() != null) {
            has = ctx.NAME().getText();
        } else if (ctx.INT() != null) {
            has = ctx.INT().getText();
        } else {
            has = "^";
        }
        this.objects.prop("as", has);
    }

    @Override
    public void exitAs(final EoParser.AsContext ctx) {
        this.objects.leave();
    }

    @Override
    @SuppressWarnings("PMD.ConfusingTernary")
    public void enterData(final EoParser.DataContext ctx) {
        final String type;
        final String data;
        final String base;
        final String text = ctx.getText();
        if (ctx.BYTES() != null) {
            type = "bytes";
            base = "bytes";
            data = text.replaceAll("\\s+", "").replace("-", " ").trim();
        } else if (ctx.FLOAT() != null) {
            type = "bytes";
            base = "float";
            data = XeEoListener.bytesToHex(
                ByteBuffer
                    .allocate(Long.BYTES)
                    .putDouble(Double.parseDouble(text))
                    .array()
            );
        } else if (ctx.INT() != null) {
            type = "bytes";
            base = "int";
            data = XeEoListener.bytesToHex(
                ByteBuffer
                    .allocate(Long.BYTES)
                    .putLong(Long.parseLong(text))
                    .array()
            );
        } else if (ctx.HEX() != null) {
            type = "bytes";
            base = "int";
            data = XeEoListener.bytesToHex(
                ByteBuffer
                    .allocate(Long.BYTES)
                    .putLong(Long.parseLong(text.substring(2), 16))
                    .array()
            );
        } else if (ctx.STRING() != null) {
            type = "bytes";
            base = "string";
            data = XeEoListener.bytesToHex(
                StringEscapeUtils.unescapeJava(
                    text.substring(1, text.length() - 1)
                ).getBytes(StandardCharsets.UTF_8)
            );
        } else if (ctx.TEXT() != null) {
            type = "bytes";
            base = "string";
            final int indent = ctx.getStart().getCharPositionInLine();
            data = XeEoListener.bytesToHex(
                StringEscapeUtils.unescapeJava(
                    XeEoListener.trimMargin(text, indent)
                ).getBytes(StandardCharsets.UTF_8)
            );
        } else {
            throw new ParsingException(
                String.format(
                    "Unknown data type at line #%d",
                    ctx.getStart().getLine()
                ),
                new IllegalArgumentException(),
                ctx.getStart().getLine()
            );
        }
        this.objects.prop("data", type);
        this.objects.prop("base", base);
        this.objects.data(data);
    }

    @Override
    public void exitData(final EoParser.DataContext ctx) {
        // Nothing here
    }

    @Override
    public void visitTerminal(final TerminalNode node) {
        // This method is created by ANTLR and can't be removed
    }

    // We don't do anything here. We let the error nodes stay in the
    // tree. Later the syntax analysis will hit them and raise
    // ParsingException, with proper information about them. Here we
    // don't do anything, to not pollute the error reporting with
    // duplicated.
    @Override
    public void visitErrorNode(final ErrorNode node) {
        // This method is created by ANTLR and can't be removed
    }

    @Override
    public void enterEveryRule(final ParserRuleContext ctx) {
        // This method is created by ANTLR and can't be removed
    }

    @Override
    public void exitEveryRule(final ParserRuleContext ctx) {
        // This method is created by ANTLR and can't be removed
    }

    @Override
    public Iterator<Directive> iterator() {
        return this.dirs.iterator();
    }

    /**
     * Start object.
     *
     * @param ctx Context.
     * @return Started object.
     */
    private Objects startObject(final ParserRuleContext ctx) {
        return this.objects.start(
            ctx.getStart().getLine(),
            ctx.getStart().getCharPositionInLine()
        );
    }

    /**
     * Start abstract object.
     *
     * @param ctx Context
     * @return Xembly objects after creating abstract object
     */
    private Objects startAbstract(final ParserRuleContext ctx) {
        return this.startObject(ctx).prop("abstract").leave();
    }

    /**
     * Validate comment in front of abstract objects.
     * @param ctx Context
     * @param comments List of comment contexts
     */
    private void validateComment(
        final ParserRuleContext ctx,
        final List<EoParser.CommentContext> comments
    ) {
        if(this.tests || comments.isEmpty()) {
            return;
        }
        final String comment = String.join(
            "",
            comments.stream().map(
                context -> context.COMMENTARY().getText().substring(1).trim()
            ).collect(Collectors.joining(""))
        );
        final String length = String.format(
            "Comment must be at least %d characters long",
            XeEoListener.MIN_COMMENT_LENGTH
        );
        final String warning = "warning";
        if (comment.isEmpty()) {
            this.addError(ctx, "comment-length-check", warning, length);
        } else {
            if (comment.length() < XeEoListener.MIN_COMMENT_LENGTH) {
                this.addError(ctx, "comment-length-check", warning, length);
            }
            if (comment.chars().anyMatch(chr -> chr < 32 || chr > 127)) {
                this.addError(
                    ctx,
                    "comment-content-check",
                    warning,
                    "Comment must contain only ASCII printable characters: 0x20-0x7f"
                );
            }
            if (!Character.isUpperCase(comment.charAt(0))) {
                this.addError(
                    ctx,
                    "comment-start-character-check",
                    warning,
                    "Comment must start with capital letter"
                );
            }
            if (comment.charAt(comment.length() - 1) != '.') {
                this.addError(
                    ctx,
                    "comment-ending-check",
                    warning,
                    "Comment must end with dot"
                );
            }
        }
    }

    /**
     * Add error to {@link XeEoListener#errors} directives.
     * @param ctx Context
     * @param check Check type
     * @param severity Error severity level
     * @param message Error message
     */
    private void addError(
        final ParserRuleContext ctx,
        final String check,
        final String severity,
        final String message
    ) {
        this.errors.add("error")
            .attr("line", ctx.getStart().getLine())
            .attr("check", check)
            .attr("severity", severity)
            .set(message)
            .up();
    }

    /**
     * Trim margin from text block.
     *
     * @param text Text block.
     * @param indent Indentation level.
     * @return Trimmed text.
     */
    private static String trimMargin(final String text, final int indent) {
        final String rexp = "[\\s]{%d}";
        final String cutted = text
            .substring(3, text.length() - 3).trim();
        final String[] splitted = cutted.split("\n");
        StringBuilder res = new StringBuilder();
        for (final String line : splitted) {
            res.append(line.replaceAll(String.format(rexp, indent), "")).append('\n');
        }
        if (res.length() > 0 && res.charAt(0) == '\n') {
            res = new StringBuilder(res.substring(1));
        }
        if (res.length() > 0 && res.charAt(res.length() - 1) == '\n') {
            res = new StringBuilder(res.substring(0, res.length() - 1));
        }
        return res.toString();
    }

    /**
     * Bytes to HEX.
     *
     * @param bytes Bytes.
     * @return Hexadecimal value as string.
     */
    private static String bytesToHex(final byte... bytes) {
        final StringJoiner out = new StringJoiner(" ");
        for (final byte bty : bytes) {
            out.add(String.format("%02X", bty));
        }
        return out.toString();
    }
}