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

org.parboiled.transform.process.GroupClassGenerator Maven / Gradle / Ivy

/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 *
 * 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 org.parboiled.transform.process;

import com.github.parboiled1.grappa.transform.CodeBlock;
import com.google.common.base.Preconditions;
import me.qmx.jitescript.util.CodegenUtils;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.parboiled.Context;
import org.parboiled.ContextAware;
import org.parboiled.transform.AsmUtils;
import org.parboiled.transform.InstructionGraphNode;
import org.parboiled.transform.InstructionGroup;
import org.parboiled.transform.ParserClassNode;
import org.parboiled.transform.RuleMethod;

import javax.annotation.Nonnull;

import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.RETURN;

public abstract class GroupClassGenerator
    implements RuleMethodProcessor
{
    private final boolean forceCodeBuilding;
    protected ParserClassNode classNode;
    protected RuleMethod method;

    protected GroupClassGenerator(final boolean forceCodeBuilding)
    {
        this.forceCodeBuilding = forceCodeBuilding;
    }

    @Override
    public final void process(@Nonnull final ParserClassNode classNode,
        @Nonnull final RuleMethod method)
    {
        this.classNode = Preconditions.checkNotNull(classNode, "classNode");
        this.method = Preconditions.checkNotNull(method, "method");

        for (final InstructionGroup group: method.getGroups())
            if (appliesTo(group.getRoot()))
                loadGroupClass(group);
    }

    protected abstract boolean appliesTo(InstructionGraphNode group);

    private void loadGroupClass(final InstructionGroup group)
    {
        createGroupClassType(group);
        final String className = group.getGroupClassType().getClassName();
        final ClassLoader classLoader
            = classNode.getParentClass().getClassLoader();

        final Class groupClass;
        synchronized (AsmUtils.class) {
            groupClass = AsmUtils.findLoadedClass(className, classLoader);
            if (groupClass == null || forceCodeBuilding) {
                final byte[] groupClassCode = generateGroupClassCode(group);
                group.setGroupClassCode(groupClassCode);
                if (groupClass == null)
                    AsmUtils.loadClass(className, groupClassCode, classLoader);
            }
        }
    }

    private void createGroupClassType(final InstructionGroup group)
    {
        final String s = classNode.name;
        /*
         * If the parser has no package, the group will be an embedded class
         * to the parser class
         */
        final int lastSlash = classNode.name.lastIndexOf('/');
        final String groupName = group.getName();
        final String pkg = lastSlash >= 0 ? s.substring(0, lastSlash) : s;
        final String groupClassInternalName = pkg  + '/' + groupName;
        group.setGroupClassType(Type.getObjectType(groupClassInternalName));
    }

    protected final byte[] generateGroupClassCode(final InstructionGroup group)
    {
        final ClassWriter classWriter
            = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        generateClassBasics(group, classWriter);
        generateFields(group, classWriter);
        generateConstructor(classWriter);
        generateMethod(group, classWriter);
        return classWriter.toByteArray();
    }

    private void generateClassBasics(final InstructionGroup group,
        final ClassWriter cw)
    {
        cw.visit(Opcodes.V1_6, ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC,
            group.getGroupClassType().getInternalName(), null,
            getBaseType().getInternalName(), null);
        cw.visitSource(classNode.sourceFile, null);
    }

    protected abstract Type getBaseType();

    private static void generateFields(final InstructionGroup group,
        final ClassWriter cw)
    {
        // TODO: fix the below comment; those "two members" should be split
        // CAUTION: the FieldNode has illegal access flags and an illegal
        // value field since these two members are reused for other
        // purposes, so we need to write out the field "manually" here
        // rather than just call "field.accept(cw)"
        for (final FieldNode field: group.getFields())
            cw.visitField(ACC_PUBLIC + ACC_SYNTHETIC, field.name, field.desc,
                null, null);

    }

    private void generateConstructor(final ClassWriter cw)
    {
        final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "",
            CodegenUtils.sig(void.class, String.class), null, null);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitMethodInsn(INVOKESPECIAL, getBaseType().getInternalName(),
            "", CodegenUtils.sig(void.class, String.class), false);
        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0); // trigger automatic computing
    }

    protected abstract void generateMethod(InstructionGroup group,
        ClassWriter cw);

    protected static void insertSetContextCalls(final InstructionGroup group,
        int localVarIx)
    {
        final InsnList instructions = group.getInstructions();
        final CodeBlock block = CodeBlock.newCodeBlock();

        for (final InstructionGraphNode node: group.getNodes()) {
            if (!node.isCallOnContextAware())
                continue;

            final AbstractInsnNode insn = node.getInstruction();

            if (node.getPredecessors().size() > 1) {
                // store the target of the call in a new local variable
                final AbstractInsnNode loadTarget = node.getPredecessors()
                    .get(0).getInstruction();

                block.clear().dup().astore(++localVarIx);
                instructions.insert(loadTarget, block.getInstructionList());

                // immediately before the call get the target from the local var
                // and set the context on it
                instructions.insertBefore(insn, new VarInsnNode(ALOAD,
                    localVarIx));
            } else {
                // if we have only one predecessor the call does not take any
                // parameters and we can skip the storing and loading of the
                // invocation target
                instructions.insertBefore(insn, new InsnNode(DUP));
            }

            block.clear()
                .aload(1)
                .invokeinterface(CodegenUtils.p(ContextAware.class),
                    "setContext", CodegenUtils.sig(void.class, Context.class));

            instructions.insertBefore(insn, block.getInstructionList());
        }
    }

    protected static void convertXLoads(final InstructionGroup group)
    {
        final String owner = group.getGroupClassType().getInternalName();

        InsnList insnList;

        for (final InstructionGraphNode node : group.getNodes()) {
            if (!node.isXLoad())
                continue;

            final VarInsnNode insn = (VarInsnNode) node.getInstruction();
            final FieldNode field = group.getFields().get(insn.var);
            final FieldInsnNode fieldNode = new FieldInsnNode(GETFIELD, owner,
                field.name, field.desc);

            insnList = group.getInstructions();

            // insert the correct GETFIELD after the xLoad
            insnList.insert(insn, fieldNode);
            // change the load to ALOAD 0
            insnList.set(insn, new VarInsnNode(ALOAD, 0));
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy