All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.vesalainen.parser.ParserCompiler Maven / Gradle / Ivy

Go to download

Java Lookahead Parser Generator. Generator produces LALR(k) parsers. Grammar rules are entered using annotations. Rule annotation can be attached to reducer method, which keeps rule and it's action together.

The newest version!
/*
 * Copyright (C) 2012 Timo Vesalainen
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.vesalainen.parser;

import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.Checksum;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import static javax.lang.model.type.TypeKind.DECLARED;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import org.vesalainen.bcc.AccessFlags.FieldFlags;
import org.vesalainen.bcc.Block;
import org.vesalainen.bcc.FieldInitializer;
import org.vesalainen.bcc.FragmentCompiler;
import org.vesalainen.bcc.IllegalConversionException;
import org.vesalainen.bcc.LookupList;
import org.vesalainen.bcc.MethodCompiler;
import org.vesalainen.bcc.SubClass;
import org.vesalainen.bcc.model.El;
import org.vesalainen.bcc.model.ExecutableElementImpl.MethodBuilder;
import org.vesalainen.bcc.model.Jav;
import org.vesalainen.bcc.model.Typ;
import org.vesalainen.grammar.AnnotatedGrammar;
import org.vesalainen.grammar.GTerminal;
import org.vesalainen.grammar.Grammar;
import org.vesalainen.grammar.state.AmbiguousExpressionException;
import org.vesalainen.grammar.state.DFA;
import org.vesalainen.grammar.state.DFAState;
import org.vesalainen.grammar.state.IllegalExpressionException;
import org.vesalainen.grammar.state.NFA;
import org.vesalainen.grammar.state.NFAState;
import org.vesalainen.grammar.state.Scope;
import org.vesalainen.lpg.Item;
import org.vesalainen.lpg.LALRKParserGenerator;
import org.vesalainen.lpg.Lr0State;
import org.vesalainen.lpg.State;
import static org.vesalainen.parser.ParserConstants.*;
import static org.vesalainen.parser.ParserFeature.*;
import org.vesalainen.parser.annotation.GrammarDef;
import org.vesalainen.parser.annotation.ParseMethod;
import org.vesalainen.parser.annotation.ParserContext;
import org.vesalainen.parser.annotation.RecoverMethod;
import org.vesalainen.parser.annotation.Rule;
import org.vesalainen.parser.annotation.Rules;
import org.vesalainen.parser.annotation.Terminal;
import org.vesalainen.parser.annotation.TraceMethod;
import org.vesalainen.parser.util.ChecksumProvider;
import org.vesalainen.parser.util.Input;
import org.vesalainen.parser.util.InputReader;
import org.vesalainen.parser.util.PeekableIterator;
import org.vesalainen.regex.MatchCompiler;
import org.vesalainen.util.EnumSetFlagger;
import org.vesalainen.util.HashMapSet;
import org.vesalainen.util.MapSet;

/**
 *
 * @author tkv
 */
public class ParserCompiler extends GenClassCompiler
{
    private final Grammar grammar;
    private final Map,Integer> inputMap = new HashMap<>();
    private final MapSet,State> inputSetUsageMap = new HashMapSet<>();
    private final Map expectedMap = new HashMap<>();
    private ExecutableElement recoverMethod;
    private ExecutableElement traceMethod;
    private boolean implementsParserInfo;
    private final Set implementedAbstractMethods = new HashSet<>();
    private int nextInput;

    private int lrkLevel;
    private final Jav jav = new Jav();
    private final EnumSet usedTypes = EnumSet.noneOf(TypeKind.class);
    private int stackSize;
    private final EnumSet features =  EnumSet.noneOf(ParserFeature.class);

    /**
     * Creates a parser using grammar.
     * @param superClass Super class for parser. Possible parser annotations
     * are not processed.
     * @throws IOException
     */
    public ParserCompiler(TypeElement superClass) throws IOException
    {
        super(superClass);
        this.grammar = createGrammar(superClass);
        GrammarDef grammarDef = superClass.getAnnotation(GrammarDef.class);
        if (grammarDef != null)
        {
            lrkLevel = grammarDef.lrkLevel();
        }
    }
    private static Grammar createGrammar(TypeElement superClass) throws IOException
    {
        GrammarDef gDef = superClass.getAnnotation(GrammarDef.class);
        if (gDef == null)
        {
            throw new ParserException("@GrammarDef missing from "+superClass);
        }
        String grammar = gDef.grammar();
        if (grammar != null && !grammar.isEmpty())
        {
            throw new UnsupportedOperationException("@GrammarDef.grammar not supported");
        }
        return new AnnotatedGrammar(superClass);
    }
    @Override
    public void compile() throws IOException
    {
        if (Typ.isAssignable(superClass.asType(), Typ.getTypeFor(ParserInfo.class)))
        {
            implementsParserInfo = true;
        }
        findFeatures();
        resolveRecoverAndTrace();
        overrideAbstractMethods();
        if (features.contains(SingleThread))
        {
            compileInstanceVars();
        }
        compileParseMethods(subClass);
        compileInputs();
        if (implementsParserInfo)
        {
            compileParserInfo();
        }
        super.compile();
    }

    public int getLrkLevel()
    {
        return lrkLevel;
    }
    int getInputNumber(Set inputSet, State state)
    {
        inputSetUsageMap.add(inputSet, state);
        Integer n = inputMap.get(inputSet);
        if (n == null)
        {
            nextInput++;
            StringBuilder sb = new StringBuilder();
            for (GTerminal t : inputSet)
            {
                String expression = t.getUnescapedExpression();
                if (expression == null)
                {
                    expression = t.getName();
                }
                if (sb.length() == 0)
                {
                    sb.append("\n  "+t.getName()+"="+expression+"\n");
                }
                else
                {
                    sb.append("| "+t.getName()+"="+expression+"\n");
                }
            }
            expectedMap.put(nextInput, sb.toString());
            inputMap.put(inputSet, nextInput);
            if (grammar.getTraceLevel() > 1)
            {
                System.err.println();
                System.err.println("State "+state.getNumber());
                System.err.println("uses input"+nextInput);
                System.err.println("Expects:");
                System.err.println(sb.toString());
                System.err.println();
            }
            return nextInput;
        }
        else
        {
            return n;
        }
    }
    private void findFeatures()
    {
        for (ExecutableElement method : ElementFilter.methodsIn(El.getAllMembers(superClass)))
        {
            ParseMethod pm = method.getAnnotation(ParseMethod.class);
            if (pm != null)
            {   
                features.addAll(ParserFeature.get(pm));
            }
        }
    }
    private void compileParseMethods(SubClass subClass) throws IOException
    {
        for (final ExecutableElement method : ElementFilter.methodsIn(El.getAllMembers(superClass)))
        {
            final ParseMethod pm = method.getAnnotation(ParseMethod.class);
            if (pm != null)
            {   
                final EnumSet features = ParserFeature.get(pm);
                ExecutableType executableType = (ExecutableType) method.asType();
                final List contextList = new ArrayList<>();
                final TypeMirror parseReturnType = method.getReturnType();
                final List parameters = method.getParameters();
                if (parameters.size() == 0)
                {
                    throw new IllegalArgumentException("@ParseMethod method "+method+" must have at least one parameter");
                }
                if (features.contains(LowerCase) && features.contains(UpperCase))
                {
                    throw new IllegalArgumentException("@ParseMethod method "+method+" has both lower- and upper-case set");
                }
                List parseParameters = new ArrayList<>();
                parseParameters.addAll(executableType.getParameterTypes());
                parseParameters.set(0, Typ.getTypeFor(InputReader.class));
                for (int ii=1;ii pList = new ArrayList<>();
                            pList.add(parameters.get(0).asType());  // InputReader
                            if (pm.size() != -1)
                            {
                                pList.add(Typ.Int);                 // int size
                            }
                            if (!pm.charSet().isEmpty())
                            {
                                pList.add(Typ.String);              // String charSet
                            }
                            pList.add(El.getTypeElement(EnumSet.class.getCanonicalName()).asType());               // EnumSet features
                            ExecutableElement irc = El.getMethod(El.getTypeElement(Input.class.getCanonicalName()), "getInstance", pList.toArray(new TypeMirror[pList.size()]));
                            if (irc == null)
                            {
                                throw new ParserException(method+" signature not compatible with any Input getInstance method\n"+
                                        "params:"+pList);
                            }
                            tload(IN);
                            if (pm.size() != -1)
                            {
                                iconst(pm.size());
                            }
                            if (!pm.charSet().isEmpty())
                            {
                                tconst(pm.charSet());
                            }
                            tconst(ParserFeature.class);
                            tconst(EnumSetFlagger.getFlag(features));
                            invokestatic(El.getMethod(EnumSetFlagger.class, "getSet", Class.class, int.class));
                            invoke(irc);
                        }
                        dup();
                        tstore(InputReader);
                        if (features.contains(UseChecksum))
                        {
                            if (!Typ.isAssignable(superClass.asType(), Typ.getTypeFor(ChecksumProvider.class)))
                            {
                                throw new IllegalArgumentException(UseChecksum+" without parser implementing ChecksumProvider");
                            }
                            if (features.contains(UsePushback))
                            {
                                throw new IllegalArgumentException(UseChecksum+" with "+UsePushback+" doesn't work");
                            }
                            tload(InputReader);
                            tload(This);
                            invoke(El.getMethod(ChecksumProvider.class, "getChecksum"));
                            invoke(El.getMethod(InputReader.class, "setChecksum", Checksum.class));
                        }
                        for (int ii=0;ii resolvInitializers() throws IOException
    {
        List initializers = super.resolvInitializers();
        if (features.contains(SingleThread))
        {
            initializers.add(FieldInitializer.getArrayInstance(El.getField(subClass, STATESTACK), Typ.getArrayType(Typ.getTypeFor(int.class)), stackSize));
            initializers.add(FieldInitializer.getArrayInstance(El.getField(subClass, TYPESTACK), Typ.getArrayType(Typ.getTypeFor(int.class)), stackSize));
            initializers.add(FieldInitializer.getArrayInstance(El.getField(subClass, VALUESTACK), Typ.getArrayType(Typ.getTypeFor(Object.class)), stackSize));
            initializers.add(FieldInitializer.getArrayInstance(El.getField(subClass, SOURCESTACK), Typ.getArrayType(Typ.getTypeFor(String.class)), stackSize));
            initializers.add(FieldInitializer.getArrayInstance(El.getField(subClass, LINESTACK), Typ.getArrayType(Typ.getTypeFor(int.class)), stackSize));
            initializers.add(FieldInitializer.getArrayInstance(El.getField(subClass, COLUMNSTACK), Typ.getArrayType(Typ.getTypeFor(int.class)), stackSize));
            initializers.add(FieldInitializer.getArrayInstance(El.getField(subClass, OFFSETSTACK), Typ.getArrayType(Typ.getTypeFor(int.class)), stackSize));
        }
        return initializers;
    }

    @Override
    public void compileConstructors() throws IOException
    {
        FragmentCompiler fc = new FragmentCompiler() 
        {
            @Override
            public void compile(MethodCompiler c) throws IOException
            {
                if (features.contains(SingleThread))
                {
                    for (TypeKind ot : usedTypes)
                    {
                        // value stack
                        c.tload(This);
                        c.getField(VALUESTACK);  // array
                        c.iconst(Typ.getTypeNumber(ot));   // index
                        c.newarray(Typ.getArrayType(Typ.normalizeType(ot)), stackSize);
                        c.aastore();
                    }
                    c.assignDefault(SP);
                    c.assignDefault(TOKEN);
                    c.assignDefault(CURTOK);
                    c.assignDefault(CURTYPE);
                    c.assignDefault(LASTATE);
                    c.assignDefault(LATOKEN);
                    c.assignDefault(LALENGTH);
                    c.assignDefault(THROWABLE);
                    c.assignDefault(RuleDescription);
                    c.assignDefault(ExpectedDescription);
                    c.assignDefault(LastToken);
                }
            }
        };
        super.compileConstructors(fc);
    }

    private void compileParserInfo() throws IOException
    {
        compileGetToken();
        compileGetRule();
        compileGetExpected();
    }
    private void compileGetToken() throws IOException
    {
        MethodCompiler mc = new MethodCompiler()
        {
            @Override
            protected void implement() throws IOException
            {
                nameArgument(TOKEN, 1);
                LookupList list = new LookupList();
                for (String symbol : grammar.getSymbols())
                {
                    int number = grammar.getNumber(symbol);
                    list.addLookup(number, symbol.toString());
                }
                tload(TOKEN);
                optimizedSwitch("error", list);
                for (String symbol : grammar.getSymbols())
                {
                    fixAddress(symbol.toString());
                    ldc(symbol.toString());
                    treturn();
                }
                fixAddress("error");
                ldc("unknown token");
                treturn();
            }
        };
        subClass.defineMethod(mc, java.lang.reflect.Modifier.PUBLIC, GETTOKEN, String.class, int.class);
    }
    private void compileGetRule() throws IOException
    {
        MethodCompiler mc = new MethodCompiler()
        {
            @Override
            protected void implement() throws IOException
            {
                nameArgument(RULE, 1);
                LookupList list = new LookupList();
                Map ruleDesc = grammar.getRuleDescriptions();
                for (int number : ruleDesc.keySet())
                {
                    list.addLookup(number, "rule-"+number);
                }
                tload(RULE);
                optimizedSwitch("error", list);
                for (int number : ruleDesc.keySet())
                {
                    fixAddress("rule-"+number);
                    ldc(ruleDesc.get(number));
                    treturn();
                }
                fixAddress("error");
                ldc("unknown rule");
                treturn();
            }
        };
        subClass.defineMethod(mc, java.lang.reflect.Modifier.PUBLIC, GETRULE, String.class, int.class);
    }
    private void compileGetExpected() throws IOException
    {
        MethodCompiler mc = new MethodCompiler()
        {
            @Override
            protected void implement() throws IOException
            {
                nameArgument(INPUT, 1);
                LookupList list = new LookupList();
                for (int number : expectedMap.keySet())
                {
                    list.addLookup(number, INPUT+number);
                }
                tload(INPUT);
                optimizedSwitch("error", list);
                for (int number : expectedMap.keySet())
                {
                    fixAddress(INPUT+number);
                    ldc(expectedMap.get(number));
                    treturn();
                }
                fixAddress("error");
                ldc("unknown input");
                treturn();
            }
        };
        subClass.defineMethod(mc, java.lang.reflect.Modifier.PUBLIC, GETEXPECTED, String.class, int.class);
    }

    private void compileInputs() throws IOException
    {
        for (Set set : inputMap.keySet())
        {
            if (!set.isEmpty())
            {
                int inputNumber = inputMap.get(set);
                NFA nfa = null;
                Scope> nfaScope = new Scope<>(INPUT+inputNumber);
                Scope> dfaScope = new Scope<>(INPUT+inputNumber);
                for (GTerminal terminal : set)
                {
                    if (terminal.getExpression() != null)
                    {
                        if (nfa == null)
                        {
                            nfa = terminal.createNFA(nfaScope);
                        }
                        else
                        {
                            NFA nfa2 = terminal.createNFA(nfaScope);
                            nfa = new NFA(nfaScope, nfa, nfa2);
                        }
                    }
                }
                try
                {
                    if (nfa != null)
                    {
                        DFA dfa = nfa.constructDFA(dfaScope);
                        MatchCompiler ic = new MatchCompiler<>(dfa, ERROR, EOF);
                        subClass.defineMethod(ic, java.lang.reflect.Modifier.PRIVATE, INPUT+inputNumber, int.class, InputReader.class);
                    }
                    else
                    {
                        EofCompiler ec = new EofCompiler();
                        subClass.defineMethod(ec, java.lang.reflect.Modifier.PRIVATE, INPUT+inputNumber, int.class, InputReader.class);
                    }
                }
                catch (AmbiguousExpressionException ex)
                {
                    String s1 = grammar.getSymbol((Integer)ex.getToken1());
                    String s2 = grammar.getSymbol((Integer)ex.getToken2());
                    throw new AmbiguousGrammarException("expression "+getExpected(inputNumber)+"used in "+getInputUsageFor(set)+" is ambiguous. conflicting symbols "+s1+" and "+s2, ex);
                }
                catch (IllegalExpressionException ex)
                {
                    throw new AmbiguousGrammarException("grammar is ambiguous "+set+" accepts same string "+ex.getMessage()+"used in "+getInputUsageFor(set), ex);
                }
            }
        }
    }
    String getInputUsageFor(Set set) throws IOException
    {
        Set stateSet = inputSetUsageMap.get(set);
        if (stateSet != null)
        {
            StringBuilder sb = new StringBuilder();
            for (State state : stateSet)
            {
                if (state instanceof Lr0State)
                {
                    Lr0State s = (Lr0State) state;
                    PeekableIterator ni  = s.getKernelItemsPtr();
                    while (ni.hasNext())
                    {
                        sb.append("\n");
                        Item i = ni.next();
                        i.print(sb);
                    }
                    ni  = s.getCompleteItemsPtr();
                    while (ni.hasNext())
                    {
                        sb.append("\n");
                        Item i = ni.next();
                        i.print(sb);
                    }
                }
            }
            sb.append("\n");
            return sb.toString();
        }
        else
        {
            throw new IllegalArgumentException("state for input set "+set+" not found");
        }
    }
    String getExpected(int inputNumber)
    {
        return expectedMap.get(inputNumber);
    }
    private void resolveRecoverAndTrace()
    {
        for (ExecutableElement m : ElementFilter.methodsIn(superClass.getEnclosedElements()))
        {
            if (m.getAnnotation(RecoverMethod.class) != null)
            {
                if (recoverMethod != null)
                {
                    throw new IllegalArgumentException("there can be only one @RecoverMethod");
                }
                recoverMethod = m;
            }
            if (m.getAnnotation(TraceMethod.class) != null)
            {
                if (traceMethod != null)
                {
                    throw new IllegalArgumentException("there can be only one @TraceMethod");
                }
                traceMethod = m;
            }
        }
    }
    /**
     * Implement abstract method which have either one parameter and returning something or
     * void type not returning anything.
     * @throws IOException
     */
    private void overrideAbstractMethods() throws IOException
    {
        for (final ExecutableElement method : El.getEffectiveMethods(superClass))
        {
            if (method.getModifiers().contains(Modifier.ABSTRACT))
            {
                if (
                        method.getAnnotation(Terminal.class) != null || 
                        method.getAnnotation(Rule.class) != null || 
                        method.getAnnotation(Rules.class) != null )
                {
                    implementedAbstractMethods.add(method);
                    MethodCompiler mc = new MethodCompiler()
                    {
                        @Override
                        protected void implement() throws IOException
                        {
                            TypeMirror returnType = method.getReturnType();
                            List params = method.getParameters();
                            if (returnType.getKind() != TypeKind.VOID && params.size() == 1)
                            {
                                nameArgument(ARG, 1);
                                try
                                {
                                    convert(ARG, returnType);
                                }
                                catch (IllegalConversionException ex)
                                {
                                    throw new IOException("bad conversion with "+method, ex);
                                }
                                treturn();
                            }
                            else
                            {
                                if (returnType.getKind() == TypeKind.VOID && params.size() == 0)
                                {
                                    treturn();
                                }
                                else
                                {
                                    throw new IllegalArgumentException("cannot implement abstract method "+method);
                                }
                            }
                        }
                    };
                    subClass.overrideMethod(mc, method, Modifier.PROTECTED);
                }
            }
        }
    }
    

    /**
     * Creates a byte code source file to dir. File content is similar to the
     * output of javap utility. Your IDE might be able to use this file for debugging
     * the actual byte code. (NetBeans can if this file located like java source
     * files)
     *
     * Example dir = c:\src class is foo.bar.Main Source file path is
     * c:\src\foo\bar\Main.jasm
     * @param env
     * @throws IOException
     */
    public void createSource(ProcessingEnvironment env) throws IOException
    {
        subClass.createSourceFile(env);
    }

    Grammar getGrammar()
    {
        return grammar;
    }

    ExecutableElement getRecoverMethod()
    {
        return recoverMethod;
    }

    boolean implementsParserInfo()
    {
        return implementsParserInfo;
    }

    boolean implementedAbstract(ExecutableElement reducer)
    {
        return implementedAbstractMethods.contains(reducer);
    }

    ExecutableElement getTraceMethod()
    {
        return traceMethod;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy