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

jlibs.nblr.codegen.java.JavaCodeGenerator Maven / Gradle / Ivy

/**
 * Copyright 2015 Santhosh Kumar Tekuri
 *
 * The JLibs authors license this file to you 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 jlibs.nblr.codegen.java;

import jlibs.core.lang.StringUtil;
import jlibs.nblr.Syntax;
import jlibs.nblr.actions.BufferAction;
import jlibs.nblr.actions.ErrorAction;
import jlibs.nblr.codegen.CodeGenerator;
import jlibs.nblr.matchers.Any;
import jlibs.nblr.matchers.Matcher;
import jlibs.nblr.matchers.Not;
import jlibs.nblr.rules.*;
import jlibs.nbp.NBParser;

import java.io.File;
import java.io.IOException;
import java.util.*;

import static jlibs.core.annotation.processing.Printer.MINUS;
import static jlibs.core.annotation.processing.Printer.PLUS;

/**
 * @author Santhosh Kumar T
 */
public class JavaCodeGenerator extends CodeGenerator{
    public JavaCodeGenerator(Syntax syntax){
        super(syntax);
    }

    @Override
    protected void printTitleComment(String title){
        printer.println("/*-------------------------------------------------[ " + title + " ]---------------------------------------------------*/");
    }

    @Override
    protected void startParser(){
        String className[] = className(stringProperty(PARSER_CLASS_NAME));
        if(className[0].length()>0){
            printer.printlns(
                "package "+className[0]+";",
                ""
            );
        }

        printer.importClass(IOException.class);
        printer.println("import static java.lang.Character.*;");
        printer.emptyLine(true);
        printer.printClassDoc();

        printer.printlns(
            "public"+(booleanProperty(PARSER_FINAL) ? " final " :" ")+"class "+className[1]+" extends "+stringProperty(PARSER_SUPER_CLASS)+"{",
                PLUS
        );
    }

    private String[] className(String className){
        String pakage = "";
        String simpleName = className;
        int dot = simpleName.lastIndexOf('.');
        if(dot!=-1){
            pakage = simpleName.substring(0, dot);
            simpleName = simpleName.substring(dot+1);
        }
        return new String[]{ pakage, simpleName };
    }

    @Override
    protected void finishParser(int maxLookAhead){
        for(Map.Entry entry: finishAllMethods.entrySet()){
            printer.emptyLine(true);
            printer.printlns(
                "private int finishAll_"+entry.getValue()+"(int ch) throws IOException{",
                    PLUS,
                    "while(ch!=EOC && "+condition(entry.getKey(), -1)+"){",
                        PLUS,
                        "consume(ch);",
                        "ch = codePoint();",
                        MINUS,
                    "}",
                    "return ch;",
                    MINUS,
                 "}"
            );
        }

        String className = className(stringProperty(PARSER_CLASS_NAME))[1];
        String debuggerArgs = debuggable ? "handler, " : "";
        printer.emptyLine(true);
        printer.printlns(
                "@Override",
                "public void onSuccessful() throws Exception{",
                    PLUS,
                    "handler.onSuccessful();",
                    MINUS,
                "}",
                "",
                "@Override",
                "public Exception fatalError(String message){",
                    PLUS,
                    "return handler.fatalError(message);",
                    MINUS,
                "}",
                "",
                "protected final "+ stringProperty(HANDLER_CLASS_NAME) +" handler;",
                "public "+className+"("+ stringProperty(HANDLER_CLASS_NAME) +" handler, int startingRule){",
                    PLUS,
                    "super("+debuggerArgs+maxLookAhead+", startingRule);",
                    "this.handler = handler;",
                    MINUS,
                "}",
                MINUS,
            "}"
        );
    }

    @Override
    protected void printMatcherMethod(Matcher matcher){
        if(!matcher.canInline()){
            printer.printlns(
                "private static boolean "+matcher.name+"(int ch){",
                    PLUS,
                    "return "+matcher.javaCode("ch")+';',
                    MINUS,
                "}"
            );
        }
    }

    @Override
    protected void addRuleID(String name, int id){
        printer.println("public static final int RULE_"+name.toUpperCase()+" = "+id+';');
    }

    @Override
    protected void startRuleMethod(Rule rule){
        printer.printlns(
            "private boolean "+rule.name+"() throws Exception{",
                PLUS,
                "int ch;",
                "while(true){",
                    PLUS,
                    "switch(curState){",
                        PLUS
        );
    }

    @Override
    protected void startCase(int id){
        printer.printlns(
            "case "+id+":",
                PLUS
        );
    }

    @Override
    protected void endCase(){
        printer.printlns(
            MINUS
        );
    }

    @Override
    protected void finishRuleMethod(Rule rule){
        printer.printlns(
                        "default:",
                            PLUS,
                            "throw new Error(\"impossible state: \"+curState);",
                            MINUS,
                        MINUS,
                    "}",
                    MINUS,
                "}",
                MINUS,
            "}"
        );
    }

    @Override
    protected void startCallRuleMethod(){
        String prefix = debuggable ? "_" : "";
        printer.printlns(
            "@Override",
            "protected final boolean "+prefix+"callRule() throws Exception{",
                PLUS,
                "if(SHOW_STATS)",
                    PLUS,
                    "callRuleCount++;",
                    MINUS,
                    "int rule = stack[free-2];"
        );
        if(!debuggable){
            printer.printlns(
                    "if(rule<0){",
                        PLUS,
                        "if(rule==RULE_DYNAMIC_STRING_MATCH)",
                            PLUS,
                            "return matchString(dynamicStringToBeMatched);",
                            MINUS,
                        "else",
                            PLUS,
                            "return matchString(STRING_IDS[-rule]);",
                            MINUS,
                        MINUS,
                    "}"
            );
        }
        printer.printlns(
                    "switch(rule){",
                        PLUS
        );
    }

    @Override
    protected void callRuleMethod(String ruleName){
        printer.println("return "+ruleName+"();");
    }

    @Override
    protected void finishCallRuleMethod(){
        printer.printlns(
                    "default:",
                        PLUS,
                        "throw new Error(\"impossible rule: \"+stack[free-2]);",
                        MINUS,
                    MINUS,
                "}",
                MINUS,
            "}"
        );
    }

    private Rule curRule;

    @Override
    protected void addRoutes(Routes routes){
        if(routes.determinateRoutes.size()>0 || routes.indeterminateRoute!=null){
            printer.printlns(
                "if((ch=codePoint())==EOC)",
                    PLUS,
                    "return false;",
                    MINUS
            );
        }
        curRule = routes.rule;
        String expected = "expected(ch, \""+ StringUtil.toLiteral(routes.toString(), false)+"\");";

        boolean lookAheadBufferReqd = routes.maxLookAhead>1;
        boolean addElse = false;
        int lastLookAhead = 0;
        boolean addToLookAhead = true;
        for(int lookAhead: routes.lookAheads()){
            if(lookAheadBufferReqd){
                if(lookAhead>1){
                    if(addToLookAhead){
                        printer.printlns("addToLookAhead(ch);");
                        addToLookAhead = false;
                    }
                    String prefix = lookAhead==lastLookAhead+1 ? "if" : "while";
                    printer.printlns(
                        prefix+"(ch!=EOF && lookAhead.length()<"+lookAhead+"){",
                            PLUS,
                            "if((ch=codePoint())==EOC)",
                                PLUS,
                                "return false;",
                                MINUS,
                            "addToLookAhead(ch);",
                            MINUS,
                        "}"
                    );
                }

                int len = lookAhead==1 ? 0 : lookAhead;
                printer.printlns(
                    "if(lookAhead.length()=="+len+"){",
                        PLUS
                );
            }
            addElse = print(routes.determinateRoutes(lookAhead), 1 , lookAhead==1 ? false : lookAheadBufferReqd);
            if(lookAheadBufferReqd){
                printer.printlns(
                        MINUS,
                    "}"
                );
            }
            lastLookAhead = lookAhead;
        }

        if(routes.indeterminateRoute !=null){
            Path path = routes.indeterminateRoute.route()[0];
            Matcher matcher = path.matcher();
            if(addElse)
                printer.print("else ");
            startIf(condition(matcher, 0));
            travelPath(path, true, true);
            endIf(1);
            addElse = true;
        }

        if(lookAheadBufferReqd)
            addElse = false;
        if(routes.routeStartingWithEOF!=null){
            if(addElse){
                printer.printlns(
                    "else {",
                        PLUS
                );
            }
            travelPath(routes.routeStartingWithEOF, false, lookAheadBufferReqd);
            if(addElse){
                printer.printlns(
                        MINUS,
                    "}"
                );
            }
        }else{
            if(addElse)
                printer.print("else ");
            printer.println(expected);
        }
    }

    @SuppressWarnings({"UnnecessaryLocalVariable"})
    private boolean print(List routes, int depth, boolean consumeLookAhead){
        List> groups = new ArrayList>();
        Matcher matcher = null;
        for(Path route: routes){
            Path path = route.route()[depth-1];
            Matcher curMatcher = path.matcher();
            if(curMatcher==null)
                curMatcher = eofMatcher;

            if(matcher==null || !curMatcher.same(matcher)){
                groups.add(new ArrayList());
                matcher = curMatcher;
            }
            groups.get(groups.size()-1).add(route);
        }

        // move all routes that can use finishAll to the beginning
        for(int i=1; i group = groups.get(i);
            Path route = group.get(0);
            String finishAll = checkFinishAll(route, consumeLookAhead);
            if(finishAll!=null){ // move to beginning
                groups.remove(i);
                groups.add(0, group);
            }
        }

        int i = -1;
        boolean addElse = true;
        int elseAfter = 0;
        for(List group: groups){
            ++i;
            Path route = group.get(0);

            String finishAll = checkFinishAll(route, consumeLookAhead);
            if(finishAll!=null && i==0){
                elseAfter = 1;
                addElse = false;
                useFinishAll(route.matcher(), finishAll, false, groups.size()>1);
                continue;
            }

            matcher = route.route()[depth-1].matcher();
            boolean endIf = false;
            if(matcher!=null){
                int lookAheadIndex = route.depth>1 && depth!=route.depth ? depth-1 : -1;
                if(i>elseAfter)
                    printer.print("else ");
                startIf(condition(matcher, lookAheadIndex));
                endIf = true;
            }
            if(depth0){
            printer.printlns(
                    MINUS,
                "}"
            );
        }
    }

    private StringBuilder nodesToBeExecuted = new StringBuilder();
    private int consumeLALen = 0;
    private boolean consumeLAFirst = false;
    private void printlnConsumeLA(){
        if(consumeLALen>0){
            if(consumeLALen==1)
                printer.println("consume(FROM_LA);");
            else
                printer.println("consumeLookAhead("+consumeLALen+");");
        }
    }

    private void println(String line){
        if(consumeLAFirst)
            printlnConsumeLA();
        if(nodesToBeExecuted.length()>0){
            printer.println("handler.execute(stack[free-2], "+nodesToBeExecuted+");");
            nodesToBeExecuted.setLength(0);
        }
        if(!consumeLAFirst)
            printlnConsumeLA();
        printer.println(line);

        consumeLAFirst = false;
        consumeLALen = 0;
    }

    private int unnamed_finishAllMethods = 0;
    private Map finishAllMethods = new LinkedHashMap();
    public static final String FINISH_ALL = "finishAll";
    public static final String FINISH_ALL_OTHER_THAN = "finishAll_OtherThan";
    private String addToFinishAll(Matcher matcher){
        boolean not = false;
        Matcher givenMatcher = matcher;
        if(matcher instanceof Not){
            not = true;
            matcher = ((Not)matcher).delegate;
        }
        if(matcher instanceof Any){
            Any any = (Any)matcher;
            if(any.chars!=null && any.chars.length==1)
                return not ? FINISH_ALL_OTHER_THAN : FINISH_ALL;
        }
        matcher = givenMatcher;

        String name = null;
        if(matcher.name!=null)
            name = finishAllMethods.get(matcher);
        else{
            for(Map.Entry entry: finishAllMethods.entrySet()){
                if(entry.getKey().same(matcher)){
                    name = entry.getValue();
                    break;
                }
            }
        }
        if(name==null){
            name = matcher.name;
            if(name==null)
                name = String.valueOf(++unnamed_finishAllMethods);
            finishAllMethods.put(matcher, name);
        }
        return name;
    }

    private String checkFinishAll(Path path, boolean consumeLookAhead){
        if(!consumeLookAhead && path.parent==null){
            if(path.size()==3 && path.get(0)==path.get(2)){ // loop
                Node node = (Node)path.get(0);
                Edge edge = (Edge)path.get(1);
                if(node.action==null && edge.matcher!=null && !edge.fallback)
                    return addToFinishAll(edge.matcher);
            }
        }
        return null;
    }

    private void useFinishAll(Matcher matcher, String name, boolean addContinue, boolean storeReturnValue){
        String methodCall;
        if(name.equals(FINISH_ALL) || name.equals(FINISH_ALL_OTHER_THAN)){
            Any any = (Any)(name.equals(FINISH_ALL_OTHER_THAN) ? ((Not)matcher).delegate : matcher);
            methodCall = name+"(ch, "+Matcher.toJava(any.chars[0])+')';
        }else
            methodCall = "finishAll_"+name+"(ch)";

        if(!addContinue && storeReturnValue)
            methodCall = "(ch="+methodCall+")";
        printer.printlns(
            "if("+methodCall+"==EOC)",
                PLUS,
                "return false;",
                MINUS
        );
        if(addContinue)
            printer.println("continue;");
    }

    private void travelRoute(Path route, boolean consumeLookAhead, boolean resetLookAhead){
        handlerMethodCalled = false;
        pushCount = 0;
        consumeLAFirst = false;
        consumeLALen = 0;
        Destination dest = new Destination(consumeLookAhead, curRule, null);
        for(Path path: route.route())
            dest = _travelPath(dest.rule, path, consumeLookAhead);
        if(resetLookAhead)
            println("lookAhead.reset();");
        returnDestination(dest);
    }

    private void travelPath(Path path, boolean consumeLookAhead, boolean resetLookAhead){
        handlerMethodCalled = false;
        pushCount = 0;
        consumeLAFirst = false;
        consumeLALen = 0;
        Destination dest = _travelPath(curRule, path, consumeLookAhead);
        if(resetLookAhead)
            println("lookAhead.reset();");
        returnDestination(dest);
    }

    private void returnDestination(Destination dest){
        if(!dest.consumedFromLookAhead && pushCount>1)
            System.out.print("");
        int state = dest.state();
        if(state<0){
            println("curState = -1;");
            println("return true;");
        }else if(dest.rule==curRule){
            if(!debuggable && Node.DYNAMIC_STRING_MATCH.equals(dest.node.name)){
                addState(dest.node);
                println("push(RULE_DYNAMIC_STRING_MATCH, "+state+", 0);");
                println("curState = 0;");
                println("return true;");
            }else{
                addState(dest.node);
                println("curState = "+state+";");
                if(handlerMethodCalled){
                    println("if(stop)");
                    printer.printlns(
                        PLUS,
                            "return false;",
                        MINUS
                    );
                }
                if(statesPending.isEmpty() || statesPending.iterator().next()!=dest.node)
                    println("continue;");
            }
        }else{
            println("curState = "+state+";");
            if(dest.rule.id<0)
                println("return matchString(STRING_IDS[-RULE_"+dest.rule.name.toUpperCase()+"]);");
            else
                println("return "+dest.rule.name+"();");
        }
    }

    class Destination{
        boolean consumedFromLookAhead;
        Rule rule;
        Node node;

        Destination(boolean consumedFromLookAhead, Rule rule, Node node){
            this.consumedFromLookAhead = consumedFromLookAhead;
            this.rule = rule;
            this.node = node;
        }

        public int state(){
            return node.outgoing.size()==0 ? -1 : node.id;
        }
    }

    public static boolean COELSCE_LA_CONSUME_CALLS = false;
    private boolean handlerMethodCalled;
    private int pushCount;
    private Destination _travelPath(Rule rule, Path path, boolean consumeLookAhead){
        nodesToBeExecuted.setLength(0);

        Deque ruleStack = new ArrayDeque();
        ruleStack.push(rule);
        Node destNode = null;

        int index = -1;
        boolean wasNode = false;
        for(Object obj: path){
            ++index;
            if(obj instanceof Node){
                if(wasNode){
                    println("free -= 2;");
                    ruleStack.pop();
                }
                wasNode = true;

                Node node = (Node)obj;

                if(index0)
                            nodesToBeExecuted.append(", ");
                        nodesToBeExecuted.append(node.id);
                    }else if(node.action!=null)
                        printer.println(node.action.javaCode()+';');
                }
                destNode = node;
            }else if(obj instanceof Edge){
                wasNode = false;
                Edge edge = (Edge)obj;
                if(edge.ruleTarget!=null){
                    destNode = null;
                    int stateAfterRule = new Routes(ruleStack.peek(), edge.target).isEOF() ? -1 : edge.target.id;
                    println("push(RULE_"+edge.ruleTarget.rule.name.toUpperCase()+", "+stateAfterRule+", "+edge.ruleTarget.node().id+");");
                    pushCount++;
                    if(stateAfterRule!=-1 && ruleStack.peek()==curRule)
                        addState(edge.target);
                    ruleStack.push(edge.ruleTarget.rule);
                }
                else if(edge.matcher!=null){
                    destNode = edge.target;
                    if(consumeLookAhead){
                        if(COELSCE_LA_CONSUME_CALLS){
                            if(nodesToBeExecuted.length()==0)
                                consumeLAFirst = true;
                            consumeLALen++;
                        }else
                            println("consume(FROM_LA);");
                    }else
                        println("consume(ch);");
                }
            }
        }
        return new Destination(consumeLookAhead, ruleStack.peek(), destNode);
    }

    /*-------------------------------------------------[ Handler ]---------------------------------------------------*/

    protected void startHandler(){
        printer.printClassDoc();

        String className[] = className(stringProperty(HANDLER_CLASS_NAME));
        if(className[0].length()>0){
            printer.printlns(
                "package "+className[0]+";",
                ""
            );
        }

        String keyWord = booleanProperty(HANDLER_IS_CLASS) ? "class" : "interface";
        printer.printlns(
            "public "+keyWord+" "+className[1]+"{",
                PLUS
        );
    }

    private String consumerMethodModifier(){
        String parserPackage = className(stringProperty(PARSER_CLASS_NAME))[0];
        String consumerPackage = className(stringProperty(HANDLER_CLASS_NAME))[0];
        return parserPackage.equals(consumerPackage) ? "protected" : "public";
    }

    protected void addPublishMethod(String name){
        if(booleanProperty(HANDLER_IS_CLASS)){
            printer.printlns(
                consumerMethodModifier()+" void "+name+"(Chars data){",
                    PLUS,
                        "System.out.println(\""+name+"(\\\"\"+data+\"\\\")\");",
                    MINUS,
                "}"
            );
        }else
            printer.println("public void "+name+"(Chars data);");
    }

    protected void addEventMethod(String name){
        if(booleanProperty(HANDLER_IS_CLASS)){
            printer.printlns(
                consumerMethodModifier()+" void "+name+"(){",
                    PLUS,
                        "System.out.println(\""+name+"\");",
                    MINUS,
                "}"
            );
        }else
            printer.println("public void "+name+"();");
    }

    protected void finishHandler(){
        if(booleanProperty(HANDLER_IS_CLASS)){
            printer.emptyLine(true);
            printer.printlns(
                    "@Override",
                    "public void fatalError(String message) throws E{",
                        PLUS,
                        "throw new Exception(message);",
                        MINUS,
                    "}"
            );
        }
        printer.printlns(
                MINUS,
            "}"
        );
    }

    /*-------------------------------------------------[ StringIDs ]---------------------------------------------------*/

    @Override
    protected void startStringIDs(){
        printer.printlns(
            "int STRING_IDS[][] = {",
                PLUS,
                "{}, // dummy one"
        );
    }

    @Override
    protected void addStringID(int[] codePoints){
        StringBuilder buff = new StringBuilder(Arrays.toString(codePoints));
        buff.setCharAt(0, '{');
        buff.setCharAt(buff.length()-1, '}');
        buff.append(", // ").append(new String(codePoints, 0, codePoints.length));
        printer.println(buff.toString());
    }

    @Override
    protected void finishStringIDs(){
        printer.printlns(
                MINUS,
            "};"
        );
    }

    /*-------------------------------------------------[ Customization ]---------------------------------------------------*/

    public static final String PARSER_CLASS_NAME = "PARSER_CLASS_NAME";
    public static final String PARSER_FINAL = "PARSER_FINAL";
    private static final String PARSER_SUPER_CLASS = "PARSER_SUPER_CLASS";
    public static final String HANDLER_CLASS_NAME = "HANDLER_CLASS_NAME";
    public static final String HANDLER_IS_CLASS = "HANDLER_IS_CLASS";
    public static final String SOURCE_DIR = "SOURCE_DIR";

    public static final Properties DEFAULTS = new Properties();
    static{
        DEFAULTS.put(PARSER_CLASS_NAME, "UntitledParser");
        DEFAULTS.put(PARSER_FINAL, "true");
        DEFAULTS.put(PARSER_SUPER_CLASS, NBParser.class.getName());

        DEFAULTS.put(HANDLER_CLASS_NAME, "UntitledHandler");
        DEFAULTS.put(HANDLER_IS_CLASS, "false");

        DEFAULTS.put(SOURCE_DIR, "src");
    }
    public final Properties properties = new Properties(DEFAULTS);
    private final Properties debugProperties = new Properties(properties);
    {
        debugProperties.put(PARSER_SUPER_CLASS, "jlibs.nblr.editor.debug.DebuggableNBParser");
        debugProperties.put(HANDLER_CLASS_NAME, "jlibs.nblr.editor.debug.Debugger");
    }

    private String stringProperty(String name){
        return (debuggable ? debugProperties : properties).getProperty(name);
    }

    private boolean booleanProperty(String name){
        return "true".equals(stringProperty(name));
    }

    public File fileProperty(String name){
        String className = stringProperty(name);
        return new File(stringProperty(SOURCE_DIR), className.replace('.', '/')+".java");
    }

    public void setDebuggable(){
        debuggable = true;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy