InternalSchemaParser.java

/*
 * Copyright 2017 Michael Mackenzie High
 *
 * 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.
 */
package com.mackenziehigh.sexpr.internal.schema;

import com.mackenziehigh.sexpr.SList;
import com.mackenziehigh.sexpr.Schema.Match;
import com.mackenziehigh.sexpr.Sexpr;
import com.mackenziehigh.sexpr.SourceLocation;
import com.mackenziehigh.sexpr.exceptions.ParsingFailedException;
import com.mackenziehigh.sexpr.internal.schema.InternalSchema.Rule;
import com.mackenziehigh.sexpr.internal.schema.InternalSchema.SequenceElement;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Stack;
import java.util.stream.Collectors;

public final class InternalSchemaParser
{
    private final InternalSchema g = new InternalSchema();

    private final InternalSchema b;

    private final Stack<Object> stack = new Stack<>();

    public InternalSchemaParser (final InternalSchema schemaBeingBuilt)
    {
        this.b = schemaBeingBuilt;

        g.defineRoot("ROOT");

        // (ROOT = (seq (star (ref STATEMENT))))
        seq("ROOT", star(ref("STATEMENT")));

        // (STATEMENT = (either (ref ROOT_DECLARATION)
        //                      (ref ASSIGNMENT)
        //                      (ref RULE)))
        either("STATEMENT",
               ref("ROOT_DECLARATION"),
               ref("ASSIGNMENT"));

        // (RULE = (either (ref SEQUENCE)
        //                 (ref OR)
        //                 (ref AND)
        //                 (ref NOT)
        //                 (ref REF)
        //                 (ref ATOM)
        //                 (ref WORD)
        //                 (ref PREDICATE)))
        either("RULE",
               ref("SEQUENCE"),
               ref("OR"),
               ref("AND"),
               ref("NOT"),
               ref("REF"),
               ref("ATOM"),
               ref("WORD"),
               ref("PREDICATE"));

        // (ROOT_DECLARATION = (seq (word 'root') (ref NAME)))
        assign("ROOT_DECLARATION", seq(once(atom("root")), once(ref("NAME"))));

        // (ASSIGNMENT = (seq (ref NAME) (word '=') (ref RULE)))
        assign("ASSIGNMENT", seq(once(ref("NAME")), once(atom("[=]")), once(ref("RULE"))));

        // (SEQUENCE = (seq (word 'seq') (star (ref ELEMENT))))
        assign("SEQUENCE", seq(once(atom("seq")), star(ref("ELEMENT"))));

        // (ELEMENT = (either (ref OPTION)
        //                    (ref STAR)
        //                    (ref PLUS)
        //                    (ref REPEAT)
        //                    (ref RULE)))
        either("ELEMENT",
               ref("OPTION"),
               ref("STAR"),
               ref("PLUS"),
               ref("REPEAT"),
               ref("RULE"));

        // (OPTION = (seq (word 'option') (ref RULE)))
        assign("OPTION", seq(once(atom("option")), once(ref("RULE"))));

        // (STAR = (seq (word 'star') (ref RULE)))
        assign("STAR", seq(once(atom("star")), once(ref("RULE"))));

        // (PLUS = (seq (word 'plus') (ref RULE)))
        assign("PLUS", seq(once(atom("plus")), once(ref("RULE"))));

        // (REPEAT = (seq (word 'repeat') (ref RULE) (atom '[0-9]+') (atom '[0-9]+')))
        assign("REPEAT", seq(once(atom("repeat")),
                             once(ref("RULE")),
                             once(atom("[0-9]+")),
                             once(atom("[0-9]+"))));

        // (OR = (seq (word 'either') (star (ref RULE))))
        assign("OR", seq(once(atom("either")), star(ref("RULE"))));

        // (AND = (seq (word 'and') (star (ref RULE))))
        assign("AND", seq(once(atom("and")), star(ref("RULE"))));

        // (NOT = (seq (word 'not') (ref RULE)))
        assign("NOT", seq(once(atom("not")), once(ref("RULE"))));

        // (REF = (seq (word 'ref') (ref NAME)))
        assign("REF", seq(once(atom("ref")), once(ref("NAME"))));

        // (ATOM = (seq (word 'atom') (option (atom))))
        assign("ATOM", seq(once(atom("atom")), option(atom(".*"))));

        // (WORD = (seq (word 'word') (atom)))
        assign("WORD", seq(once(atom("word")), once(atom(".*"))));

        // (PREDICATE = (seq (word 'predicate') (ref NAME)))
        assign("PREDICATE", seq(once(atom("predicate")), once(ref("NAME"))));

        // (NAME = (atom '[A-Za-z_][A-Za-z_0-9]*'))
        assign("NAME", atom(".*"));

        /**
         * Bind Translation Actions.
         */
        final String TRANSLATE = "TRANSLATE";
        g.definePass(TRANSLATE);
        g.defineAfterAction(TRANSLATE, "ROOT_DECLARATION", this::translateRoot);
        g.defineAfterAction(TRANSLATE, "ASSIGNMENT", this::translateAssign);
        g.defineAfterAction(TRANSLATE, "SEQUENCE", this::translateSeq);
        g.defineAfterAction(TRANSLATE, "OPTION", this::translateOption);
        g.defineAfterAction(TRANSLATE, "STAR", this::translateStar);
        g.defineAfterAction(TRANSLATE, "PLUS", this::translatePlus);
        g.defineAfterAction(TRANSLATE, "REPEAT", this::translateRepeat);
        g.defineAfterAction(TRANSLATE, "OR", this::translateEither);
        g.defineAfterAction(TRANSLATE, "AND", this::translateAnd);
        g.defineAfterAction(TRANSLATE, "NOT", this::translateNot);
        g.defineAfterAction(TRANSLATE, "ATOM", this::translateAtom);
        g.defineAfterAction(TRANSLATE, "WORD", this::translateKeyword);
        g.defineAfterAction(TRANSLATE, "PREDICATE", this::translatePredicate);
        g.defineAfterAction(TRANSLATE, "REF", this::translateRef);
    }

    private Rule seq (final String name,
                      final SequenceElement... elements)
    {
        final Rule anonRule = g.defineSequenceRule(Arrays.asList(elements));
        final Rule namedRule = g.defineNamedRule(name, anonRule.name());
        return namedRule;
    }

    private Rule seq (final SequenceElement... elements)
    {
        return g.defineSequenceRule(Arrays.asList(elements));
    }

    private Rule assign (final String name,
                         final Rule value)
    {
        return either(name, value);
    }

    private Rule either (final String name,
                         final Rule... options)
    {
        final Rule anonRule = g.defineOrRule(Arrays.asList(options).stream().map(x -> x.name()).collect(Collectors.toList()));
        final Rule namedRule = g.defineNamedRule(name, anonRule.name());
        return namedRule;
    }

    private SequenceElement repeat (final Rule rule,
                                    final int min,
                                    final int max)
    {
        final String element = rule.name();

        return new SequenceElement()
        {
            @Override
            public String element ()
            {
                return element;
            }

            @Override
            public int minimum ()
            {
                return min;
            }

            @Override
            public int maximum ()
            {
                return max;
            }
        };
    }

    private SequenceElement once (final Rule rule)
    {
        return repeat(rule, 1, 1);
    }

    private SequenceElement option (final Rule rule)
    {
        return repeat(rule, 0, 1);
    }

    private SequenceElement star (final Rule rule)
    {
        return repeat(rule, 0, Integer.MAX_VALUE);
    }

    private Rule atom (final String regex)
    {
        return g.defineRegexRule(regex);
    }

    private Rule ref (final String name)
    {
        final Rule anonRule = g.defineReference(name);
        final Rule namedRule = g.defineNamedRule(name + "_" + anonRule.name(), anonRule.name());
        return namedRule;
    }

    private void translateRoot (final Sexpr<?> node)
    {
        final String name = node.asList().get(1).toString();
        b.defineRoot(name);
    }

    private void translateAssign (final Sexpr<?> node)
    {
        final String name = node.asList().get(0).toString();
        final Rule value = (Rule) stack.pop();
        b.defineNamedRule(name, value.name());
    }

    private void translateSeq (final Sexpr<?> node)
    {
        final int elementCount = node.asList().size() - 1;

        final LinkedList<SequenceElement> elements = new LinkedList<>();

        for (int i = 0; i < elementCount; i++)
        {
            final SequenceElement element = convertToSequenceElement(stack.pop());
            elements.addFirst(element);
        }

        final Rule rule = b.defineSequenceRule(elements);
        stack.push(rule);
    }

    private SequenceElement convertToSequenceElement (final Object object)
    {
        if (object instanceof SequenceElement)
        {
            return (SequenceElement) object;
        }

        final Rule element = (Rule) object;

        return new SequenceElement()
        {
            @Override
            public String element ()
            {
                return element.name();
            }

            @Override
            public int minimum ()
            {
                return 1;
            }

            @Override
            public int maximum ()
            {
                return 1;
            }
        };
    }

    private void translateOption (final Sexpr<?> node)
    {
        final Rule element = (Rule) stack.pop();
        translateRepeat(element, 0, 1);
    }

    private void translateStar (final Sexpr<?> node)
    {
        final Rule element = (Rule) stack.pop();
        translateRepeat(element, 0, Integer.MAX_VALUE);
    }

    private void translatePlus (final Sexpr<?> node)
    {
        final Rule element = (Rule) stack.pop();
        translateRepeat(element, 1, Integer.MAX_VALUE);
    }

    private void translateRepeat (final Sexpr<?> node)
    {
        final Rule element = (Rule) stack.pop();
        final int minimum = Integer.parseInt(node.asList().get(2).toString());
        final int maximum = Integer.parseInt(node.asList().get(3).toString());
        translateRepeat(element, minimum, maximum);
    }

    private void translateRepeat (final Rule rule,
                                  final int minimum,
                                  final int maximum)
    {
        final SequenceElement seqelm = new SequenceElement()
        {
            @Override
            public String element ()
            {
                return rule.name();
            }

            @Override
            public int minimum ()
            {
                return minimum;
            }

            @Override
            public int maximum ()
            {
                return maximum;
            }
        };

        stack.push(seqelm);
    }

    private void translateEither (final Sexpr<?> node)
    {
        final int elementCount = node.asList().size() - 1;

        final LinkedList<String> elements = new LinkedList<>();

        for (int i = 0; i < elementCount; i++)
        {
            final Rule element = (Rule) stack.pop();
            elements.addFirst(element.name());
        }

        final Rule rule = b.defineOrRule(elements);
        stack.push(rule);
    }

    private void translateAnd (final Sexpr<?> node)
    {
        final int elementCount = node.asList().size() - 1;

        final LinkedList<String> elements = new LinkedList<>();

        for (int i = 0; i < elementCount; i++)
        {
            final Rule element = (Rule) stack.pop();
            elements.addFirst(element.name());
        }

        final Rule rule = b.defineAndRule(elements);
        stack.push(rule);
    }

    private void translateNot (final Sexpr<?> node)
    {
        final Rule operand = (Rule) stack.pop();
        final Rule rule = b.defineNotRule(operand.name());
        stack.push(rule);
    }

    private void translateAtom (final Sexpr<?> node)
    {
        final String regex = node.asList().size() == 1 ? ".*" : node.asList().get(1).toString();
        final Rule rule = b.defineRegexRule(regex);
        stack.push(rule);
    }

    private void translateKeyword (final Sexpr<?> node)
    {
        final String word = node.asList().get(1).toString();
        final Rule rule = b.defineConstantRule(word);
        stack.push(rule);
    }

    private void translatePredicate (final Sexpr<?> node)
    {
        final String name = node.asList().get(1).toString();
        final Rule rule = b.definePredicateRule(name);
        stack.push(rule);
    }

    private void translateRef (final Sexpr<?> node)
    {
        final String name = node.asList().get(1).toString();
        final Rule rule = b.defineReference(name);
        stack.push(rule);
    }

    public static InternalSchema parse (final InternalSchema schemaBeingBuilt,
                                        final String location,
                                        final String schema)
    {

        final InternalSchemaParser parser = new InternalSchemaParser(schemaBeingBuilt);

        final SList objectSchema = SList.parse(location, schema);

        final Match match = parser.g.match(objectSchema);

        if (match.isFailure())
        {
            final int errorLineNumber = match.lastSuccess().isPresent() ? match.lastSuccess().get().location().line() : 0;
            final int errorColumnNumber = match.lastSuccess().isPresent() ? match.lastSuccess().get().location().column() : 0;
            final SourceLocation errorLocation = new SourceLocation(location, errorLineNumber, errorColumnNumber);
            throw new ParsingFailedException(errorLocation);
        }

        match.execute();

        final InternalSchema result = parser.b;
        return result;
    }
}