org.protelis.lang.ProtelisLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.protelis.lang.ProtelisLoader.java

Source

/*******************************************************************************
 * Copyright (C) 2014, 2015, Danilo Pianini and contributors
 * listed in the project's build.gradle or pom.xml file.
 *
 * This file is part of Protelis, and is distributed under the terms of
 * the GNU General Public License, with a linking exception, as described
 * in the file LICENSE.txt in this project's top directory.
 *******************************************************************************/
package org.protelis.lang;

import static java8.util.stream.StreamSupport.stream;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.danilopianini.lang.util.FasterString;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.Resource.Diagnostic;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.resource.XtextResourceSet;
import org.eclipse.xtext.util.StringInputStream;
import org.protelis.lang.datatype.Field;
import org.protelis.lang.datatype.FunctionDefinition;
import org.protelis.lang.interpreter.AnnotatedTree;
import org.protelis.lang.interpreter.impl.AlignedMap;
import org.protelis.lang.interpreter.impl.All;
import org.protelis.lang.interpreter.impl.BinaryOp;
import org.protelis.lang.interpreter.impl.Constant;
import org.protelis.lang.interpreter.impl.CreateTuple;
import org.protelis.lang.interpreter.impl.CreateVar;
import org.protelis.lang.interpreter.impl.DotOperator;
import org.protelis.lang.interpreter.impl.Env;
import org.protelis.lang.interpreter.impl.Eval;
import org.protelis.lang.interpreter.impl.FunctionCall;
import org.protelis.lang.interpreter.impl.GenericHoodCall;
import org.protelis.lang.interpreter.impl.HoodCall;
import org.protelis.lang.interpreter.impl.If;
import org.protelis.lang.interpreter.impl.MethodCall;
import org.protelis.lang.interpreter.impl.NBRCall;
import org.protelis.lang.interpreter.impl.RepCall;
import org.protelis.lang.interpreter.impl.Self;
import org.protelis.lang.interpreter.impl.TernaryOp;
import org.protelis.lang.interpreter.impl.UnaryOp;
import org.protelis.lang.interpreter.impl.Variable;
import org.protelis.lang.util.HoodOp;
import org.protelis.lang.util.Reference;
import org.protelis.parser.ProtelisStandaloneSetup;
import org.protelis.parser.protelis.Assignment;
import org.protelis.parser.protelis.Block;
import org.protelis.parser.protelis.BooleanVal;
import org.protelis.parser.protelis.BuiltinHoodOp;
import org.protelis.parser.protelis.Call;
import org.protelis.parser.protelis.DoubleVal;
import org.protelis.parser.protelis.ExprList;
import org.protelis.parser.protelis.Expression;
import org.protelis.parser.protelis.FunctionDef;
import org.protelis.parser.protelis.GenericHood;
import org.protelis.parser.protelis.Import;
import org.protelis.parser.protelis.Lambda;
import org.protelis.parser.protelis.ProtelisModule;
import org.protelis.parser.protelis.Mux;
import org.protelis.parser.protelis.NBR;
import org.protelis.parser.protelis.Pi;
import org.protelis.parser.protelis.Rep;
import org.protelis.parser.protelis.StringVal;
import org.protelis.parser.protelis.TupleVal;
import org.protelis.parser.protelis.VarDef;
import org.protelis.parser.protelis.VarDefList;
import org.protelis.parser.protelis.VarUse;
import org.protelis.vm.ProtelisProgram;
import org.protelis.vm.impl.SimpleProgramImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import com.google.common.base.Charsets;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.google.common.hash.Hashing;
import com.google.inject.Injector;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java8.util.Maps;
import java8.util.Optional;
import java8.util.function.BiFunction;
import java8.util.function.BinaryOperator;
import java8.util.function.Functions;
import java8.util.stream.Collectors;
import java8.util.stream.StreamSupport;

/**
 * Main entry-point class for loading/parsing Protelis programs.
 */
public final class ProtelisLoader {

    private static final Logger L = LoggerFactory.getLogger("Protelis Loader");
    private static final ThreadLocal<XtextResourceSet> XTEXT = new ThreadLocal<XtextResourceSet>() {
        @Override
        protected XtextResourceSet initialValue() {
            final Injector guiceInjector = new ProtelisStandaloneSetup().createInjectorAndDoEMFRegistration();
            final XtextResourceSet xtext = guiceInjector.getInstance(XtextResourceSet.class);
            xtext.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE);
            return xtext;
        }
    };
    private static final Pattern REGEX_PROTELIS_MODULE = Pattern.compile("(?:\\w+:)*\\w+");
    private static final Pattern REGEX_PROTELIS_IMPORT = Pattern.compile("import\\s+((?:\\w+:)*\\w+)\\s+",
            Pattern.DOTALL);
    private static final ThreadLocal<PathMatchingResourcePatternResolver> RESOLVER = new ThreadLocal<PathMatchingResourcePatternResolver>() {
        @Override
        protected PathMatchingResourcePatternResolver initialValue() {
            return new PathMatchingResourcePatternResolver();
        }
    };
    private static final String PROTELIS_FILE_EXTENSION = "pt";
    private static final String HOOD_END = "Hood";
    private static final ThreadLocal<Cache<String, Resource>> LOADED_RESOURCES = new ThreadLocal<Cache<String, Resource>>() {
        @Override
        protected Cache<String, Resource> initialValue() {
            return CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.SECONDS).build();
        }
    };
    private static final LoadingCache<Object, Reference> REFERENCES = CacheBuilder.newBuilder()
            .expireAfterAccess(1, TimeUnit.SECONDS).build(new CacheLoader<Object, Reference>() {
                @Override
                public Reference load(final Object key) {
                    return new Reference(key);
                }
            });

    private ProtelisLoader() {
    }

    /**
     * @param program
     *            Protelis module, program file or program to be prepared for
     *            execution. It must be one of:
     * 
     *            i) a valid Protelis qualifier name (Java like name, colon
     *            separated);
     * 
     *            ii) a valid {@link URI} string;
     * 
     *            iii) a valid Protelis program.
     * 
     *            Those possibilities are checked in order.
     * 
     *            The URI String can be in the form of a URL like
     *            "file:///home/user/protelis/myProgram" or a location relative
     *            to the classpath. In case, for instance,
     *            "/my/package/myProgram.pt" is passed, it will be automatically
     *            get converted to "classpath:/my/package/myProgram.pt". All the
     *            Protelis modules your program relies upon must be included in
     *            your Java classpath. The Java classpath scanning is done
     *            automatically by this constructor, linking is performed by
     *            Xtext transparently. {@link URI}s of type "platform:/" are
     *            supported, for those who work within an Eclipse environment.
     * @return an {@link ProtelisProgram} comprising the constructed program
     * @throws IllegalArgumentException
     *             when the program has errors
     */
    public static ProtelisProgram parse(final String program) {
        if (Objects.requireNonNull(program, "null is not a valid Protelis program, not a valid Protelis module")
                .isEmpty()) {
            throw new IllegalArgumentException("The empty string is not a valid program, nor a valid module name");
        }
        try {
            if (REGEX_PROTELIS_MODULE
                    .matcher(Objects.requireNonNull(program, "The Protelis Program can not be null")).matches()) {
                return parseURI("classpath:/" + program.replace(':', '/') + "." + PROTELIS_FILE_EXTENSION);
            }
            return parseURI(program);
        } catch (IOException e) {
            L.debug("{} is not a URI that points to a resolvable resource, nor is classpath:/{}.pt", program,
                    program);
            return parseAnonymousModule(program);
        }
    }

    /**
     * @param program
     *            A valid Protelis program to be prepared for execution.
     * 
     *            All the Protelis modules your program relies upon must be
     *            included in your Java classpath. The Java classpath scanning
     *            is done automatically by this constructor, linking is
     *            performed by Xtext transparently. {@link URI}s of type
     *            "platform:/" are supported, for those who work within an
     *            Eclipse environment.
     * @return a {@link ProtelisProgram}
     * @throws IllegalArgumentException
     *             when the program has errors
     */
    public static ProtelisProgram parseAnonymousModule(final String program) throws IllegalArgumentException {
        return parse(resourceFromString(program));
    }

    /**
     * @param programURI
     *            Protelis program file to be prepared for execution. It must be
     *            a either a valid {@link URI} string, for instance
     *            "file:///home/user/protelis/myProgram" or a location relative
     *            to the classpath. In case, for instance,
     *            "/my/package/myProgram.pt" is passed, it will be automatically
     *            get converted to "classpath:/my/package/myProgram.pt". All the
     *            Protelis modules your program relies upon must be included in
     *            your Java classpath. The Java classpath scanning is done
     *            automatically by this constructor, linking is performed by
     *            Xtext transparently. {@link URI}s of type "platform:/" are
     *            supported, for those who work within an Eclipse environment.
     * @return a new {@link ProtelisProgram}
     * @throws IOException
     *             when the resource cannot be found
     * @throws IllegalArgumentException
     *             when the program has errors
     */
    public static ProtelisProgram parseURI(final String programURI) throws IOException, IllegalArgumentException {
        return parse(resourceFromURIString(programURI));
    }

    private static Resource resourceFromURIString(final String programURI) throws IOException {
        loadResourcesRecursively(XTEXT.get(), programURI);
        final String realURI = (programURI.startsWith("/") ? "classpath:" : "") + programURI;
        final URI uri = URI.createURI(realURI);
        return XTEXT.get().getResource(uri, true);
    }

    private static void loadResourcesRecursively(final XtextResourceSet target, final String programURI)
            throws IOException {
        loadResourcesRecursively(target, programURI, new LinkedHashSet<>());
    }

    private static void loadResourcesRecursively(final XtextResourceSet target, final String programURI,
            final Set<String> alreadyInQueue) throws IOException {
        final String realURI = (programURI.startsWith("/") ? "classpath:" : "") + programURI;
        if (LOADED_RESOURCES.get().getIfPresent(realURI) == null && !alreadyInQueue.contains(realURI)) {
            alreadyInQueue.add(realURI);
            final URI uri = URI.createURI(realURI);
            final org.springframework.core.io.Resource protelisFile = RESOLVER.get().getResource(realURI);
            final InputStream is = protelisFile.getInputStream();
            final String ss = IOUtils.toString(is, "UTF-8");
            is.close();
            final Matcher matcher = REGEX_PROTELIS_IMPORT.matcher(ss);
            while (matcher.find()) {
                final int start = matcher.start(1);
                final int end = matcher.end(1);
                final String imp = ss.substring(start, end);
                final String classpathResource = "classpath:/" + imp.replace(":", "/") + "."
                        + PROTELIS_FILE_EXTENSION;
                loadResourcesRecursively(target, classpathResource, alreadyInQueue);
            }
            LOADED_RESOURCES.get().put(realURI, target.getResource(uri, true));
        }
    }

    /**
     * @param program
     *            the program in String format
     * @return a dummy:/ resource that can be used to interpret the program
     */
    public static Resource resourceFromString(final String program) {
        final URI uri = URI.createURI("dummy:/protelis-generated-program-"
                + Hashing.sha512().hashString(program, StandardCharsets.UTF_8) + ".pt");
        synchronized (XTEXT) {
            Resource r = XTEXT.get().getResource(uri, false);
            if (r == null) {
                try (InputStream in = new StringInputStream(program)) {
                    loadStringResources(XTEXT.get(), in);
                } catch (IOException e) {
                    throw new IllegalStateException("Couldn't get resources associated with anonymous program", e);
                }
                r = XTEXT.get().createResource(uri);
                try (InputStream in = new StringInputStream(program)) {
                    r.load(in, XTEXT.get().getLoadOptions());
                } catch (IOException e) {
                    throw new IllegalStateException("I/O error while reading in RAM: this must be tough.", e);
                }
            }
            return r;
        }
    }

    private static void loadStringResources(final XtextResourceSet target, final InputStream is)
            throws IOException {
        final Set<String> alreadyInQueue = new LinkedHashSet<>();
        final String ss = IOUtils.toString(is, "UTF-8");
        final Matcher matcher = REGEX_PROTELIS_IMPORT.matcher(ss);
        while (matcher.find()) {
            final int start = matcher.start(1);
            final int end = matcher.end(1);
            final String imp = ss.substring(start, end);
            final String classpathResource = "classpath:/" + imp.replace(":", "/") + "." + PROTELIS_FILE_EXTENSION;
            loadResourcesRecursively(target, classpathResource, alreadyInQueue);
        }
    }

    /**
     * @param resource
     *            the {@link Resource} containing the program to execute
     * @return a {@link ProtelisProgram}
     */
    public static ProtelisProgram parse(final Resource resource) {
        Objects.requireNonNull(resource);
        if (!resource.getErrors().isEmpty()) {
            final StringBuilder sb = new StringBuilder(
                    "The Protelis program cannot be created because of the following errors:\n");
            for (final Diagnostic d : recursivelyCollectErrors(resource)) {
                sb.append("Error");
                if (d.getLocation() != null) {
                    final String place = d.getLocation().toString().split("#")[0];
                    sb.append(" in ");
                    sb.append(place);
                }
                try {
                    final int line = d.getLine();
                    sb.append(", line ");
                    sb.append(line);
                } catch (final UnsupportedOperationException e) { // NOPMD 
                    // The line information is not available
                }
                try {
                    final int column = d.getColumn();
                    sb.append(", column ");
                    sb.append(column);
                } catch (final UnsupportedOperationException e) { // NOPMD 
                    // The column information is not available
                }
                sb.append(": ");
                sb.append(d.getMessage());
                sb.append('\n');
            }
            throw new IllegalArgumentException(sb.toString());
        }
        final ProtelisModule root = (ProtelisModule) resource.getContents().get(0);
        assert root != null;
        Objects.requireNonNull(root.getProgram(),
                "The provided resource does not contain any main program, and can not be executed.");
        /*
         * Create the function headers.
         * 
         * 1) Take all the imports in reverse order (the first declared is the
         * one actually declared in case of name conflict), insert them with the
         * two possible names (fully qualified and short). This operation must
         * be recursive (for dealing with imports of imports).
         * 
         * 2) Override conflicting names with local names
         */
        final Map<FunctionDef, FunctionDefinition> nameToFun = new LinkedHashMap<>();
        recursivelyInitFunctions(root, nameToFun);
        /*
         * Function definitions are in place, now create function bodies. Bodies
         * may contain lambdas, lambdas are named using processing order, as a
         * consequence function bodies must be evaluated sequentially.
         */
        final Map<Reference, FunctionDefinition> refToFun = stream(nameToFun.keySet()).collect(
                Collectors.toMap(ProtelisLoader::toR, nameToFun::get, throwException(), LinkedHashMap::new));
        Maps.forEach(nameToFun, (fd, fun) -> fun.setBody(Dispatch.translate(fd.getBody(), refToFun)));
        /*
         * Create the main program
         */
        return new SimpleProgramImpl(root, Dispatch.translate(root.getProgram(), refToFun), refToFun);
    }

    private static List<AnnotatedTree<?>> callArgs(final Call call, final Map<Reference, FunctionDefinition> env) {
        return exprListArgs(call.getArgs(), env);
    }

    private static List<AnnotatedTree<?>> exprListArgs(final ExprList l,
            final Map<Reference, FunctionDefinition> env) {
        return Optional.ofNullable(l).map(ExprList::getArgs).map(StreamSupport::stream)
                .map(s -> s.map(e -> Dispatch.translate(e, env)))
                .map(s -> s.collect(Collectors.<AnnotatedTree<?>>toList())).orElse(Collections.emptyList());
    }

    @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "This enum is not meant to get Serialized")
    private enum Dispatch {
        ALIGNED_MAP(org.protelis.parser.protelis.AlignedMap.class, (e, m) -> {
            final org.protelis.parser.protelis.AlignedMap alMap = (org.protelis.parser.protelis.AlignedMap) e;
            return new AlignedMap(translate(alMap.getArg(), m), translate(alMap.getCond(), m),
                    translate(alMap.getOp(), m), translate(alMap.getDefault(), m));
        }), ASSIGNMENT(Assignment.class, (e, m) -> new CreateVar(toR(((Assignment) e).getRefVar()),
                translate(((Assignment) e).getRight(), m), false)), BLOCK(Block.class, (e, m) -> {
                    final List<AnnotatedTree<?>> statements = new LinkedList<>();
                    for (Block b = (Block) e; b != null; b = b.getOthers()) {
                        statements.add(translate(b.getFirst(), m));
                    }
                    return new All(statements);
                }), BOOLEAN(BooleanVal.class, (e, m) -> new Constant<>(((BooleanVal) e).isVal())), BUILTIN_HOOD(
                        BuiltinHoodOp.class, (e, m) -> {
                            final BuiltinHoodOp hood = (BuiltinHoodOp) e;
                            return new HoodCall(translate(hood.getArg(), m),
                                    HoodOp.get(hood.getName().replace(HOOD_END, "")), hood.isInclusive());
                        }), CALL(Call.class, (e, m) -> {
                            final Call call = (Call) e;
                            final EObject ref = call.getReference();
                            if (ref instanceof JvmOperation) {
                                return new MethodCall((JvmOperation) ref, callArgs(call, m));
                            }
                            return new FunctionCall(m.get(toR(ref)), callArgs(call, m));
                        }), DECLARATION(VarDef.class, (e, m) -> new CreateVar(toR(e),
                                translate(((VarDef) e).getRight(), m), true)), DOUBLE(DoubleVal.class,
                                        (e, m) -> new Constant<>(((DoubleVal) e).getVal())), E(
                                                org.protelis.parser.protelis.E.class,
                                                (e, m) -> new Constant<>(Math.E)), ENV(
                                                        org.protelis.parser.protelis.Env.class,
                                                        (e, m) -> new Env()), EVAL(
                                                                org.protelis.parser.protelis.Eval.class,
                                                                (e, m) -> new Eval(translate(
                                                                        ((org.protelis.parser.protelis.Eval) e)
                                                                                .getArg(),
                                                                        m))), EXPRESSION(Expression.class,
                                                                                (e, m) -> {
                                                                                    final Expression exp = (Expression) e;
                                                                                    if (exp.getMethodName() != null) {
                                                                                        return new DotOperator(
                                                                                                exp.getMethodName(),
                                                                                                translate(exp
                                                                                                        .getLeft(),
                                                                                                        m),
                                                                                                exprListArgs(exp
                                                                                                        .getArgs(),
                                                                                                        m));
                                                                                    }
                                                                                    if (exp.getV() != null) {
                                                                                        return translate(exp.getV(),
                                                                                                m);
                                                                                    }
                                                                                    if (exp.getLeft() == null) {
                                                                                        return new UnaryOp(
                                                                                                exp.getName(),
                                                                                                translate(exp
                                                                                                        .getRight(),
                                                                                                        m));
                                                                                    }
                                                                                    if (exp.getRight() == null) {
                                                                                        return new UnaryOp(
                                                                                                exp.getName(),
                                                                                                translate(exp
                                                                                                        .getLeft(),
                                                                                                        m));
                                                                                    }
                                                                                    return new BinaryOp(
                                                                                            exp.getName(),
                                                                                            translate(exp.getLeft(),
                                                                                                    m),
                                                                                            translate(
                                                                                                    exp.getRight(),
                                                                                                    m));
                                                                                }), GENERIC_HOOD(GenericHood.class,
                                                                                        (e, m) -> {
                                                                                            final GenericHood hood = (GenericHood) e;
                                                                                            final boolean inclusive = hood
                                                                                                    .getName()
                                                                                                    .length() > 4;
                                                                                            final AnnotatedTree<?> nullResult = translate(
                                                                                                    hood.getDefault(),
                                                                                                    m);
                                                                                            final AnnotatedTree<Field> field = translate(
                                                                                                    hood.getArg(),
                                                                                                    m);
                                                                                            final EObject ref = hood
                                                                                                    .getReference();
                                                                                            if (ref == null) {
                                                                                                return new GenericHoodCall(
                                                                                                        inclusive,
                                                                                                        translate(
                                                                                                                hood.getOp(),
                                                                                                                m),
                                                                                                        nullResult,
                                                                                                        field);
                                                                                            }
                                                                                            if (ref instanceof VarUse) {
                                                                                                final VarUse refVar = (VarUse) ref;
                                                                                                if (refVar
                                                                                                        .getReference() instanceof JvmOperation) {
                                                                                                    return new GenericHoodCall(
                                                                                                            inclusive,
                                                                                                            (JvmOperation) refVar
                                                                                                                    .getReference(),
                                                                                                            nullResult,
                                                                                                            field);
                                                                                                }
                                                                                                return new GenericHoodCall(
                                                                                                        inclusive,
                                                                                                        translate(
                                                                                                                ref,
                                                                                                                m),
                                                                                                        nullResult,
                                                                                                        field);
                                                                                            } else {
                                                                                                throw new IllegalStateException(
                                                                                                        "Unexpected type of function in hood call: "
                                                                                                                + ref.getClass());
                                                                                            }
                                                                                        }), IF(org.protelis.parser.protelis.If.class,
                                                                                                (e, m) -> {
                                                                                                    final org.protelis.parser.protelis.If ifop = (org.protelis.parser.protelis.If) e;
                                                                                                    return new If<>(
                                                                                                            translate(
                                                                                                                    ifop.getCond(),
                                                                                                                    m),
                                                                                                            translate(
                                                                                                                    ifop.getThen(),
                                                                                                                    m),
                                                                                                            translate(
                                                                                                                    ifop.getElse(),
                                                                                                                    m));
                                                                                                }), LAMBDA(
                                                                                                        Lambda.class,
                                                                                                        (e, m) -> {
                                                                                                            final Lambda l = (Lambda) e;
                                                                                                            final EObject argobj = l
                                                                                                                    .getArgs();
                                                                                                            final List<VarDef> args = argobj == null
                                                                                                                    ? Collections
                                                                                                                            .emptyList()
                                                                                                                    : argobj instanceof VarDef
                                                                                                                            ? Lists.newArrayList(
                                                                                                                                    (VarDef) l
                                                                                                                                            .getArgs())
                                                                                                                            : ((VarDefList) argobj)
                                                                                                                                    .getArgs();
                                                                                                            final AnnotatedTree<?> body = translate(
                                                                                                                    l.getBody(),
                                                                                                                    m);
                                                                                                            final String base = Base64
                                                                                                                    .encodeBase64String(
                                                                                                                            Hashing.sha512()
                                                                                                                                    .hashString(
                                                                                                                                            body.toString(),
                                                                                                                                            Charsets.UTF_8)
                                                                                                                                    .asBytes());
                                                                                                            final FunctionDefinition lambda = new FunctionDefinition(
                                                                                                                    "$anon$" + base,
                                                                                                                    toR(args));
                                                                                                            lambda.setBody(
                                                                                                                    body);
                                                                                                            return new Constant<>(
                                                                                                                    lambda);
                                                                                                        }), MUX(Mux.class,
                                                                                                                (e, m) -> {
                                                                                                                    final Mux mux = (Mux) e;
                                                                                                                    return new TernaryOp(
                                                                                                                            mux.getName(),
                                                                                                                            translate(
                                                                                                                                    mux.getCond(),
                                                                                                                                    m),
                                                                                                                            translate(
                                                                                                                                    mux.getThen(),
                                                                                                                                    m),
                                                                                                                            translate(
                                                                                                                                    mux.getElse(),
                                                                                                                                    m));
                                                                                                                }), NBR(NBR.class,
                                                                                                                        (e, m) -> new NBRCall(
                                                                                                                                translate(
                                                                                                                                        ((NBR) e)
                                                                                                                                                .getArg(),
                                                                                                                                        m))), PI(
                                                                                                                                                Pi.class,
                                                                                                                                                (e, m) -> new Constant<>(
                                                                                                                                                        Math.PI)), REP(
                                                                                                                                                                Rep.class,
                                                                                                                                                                (e, m) -> new RepCall<>(
                                                                                                                                                                        toR(((Rep) e)
                                                                                                                                                                                .getInit()
                                                                                                                                                                                .getX()),
                                                                                                                                                                        translate(
                                                                                                                                                                                ((Rep) e)
                                                                                                                                                                                        .getInit()
                                                                                                                                                                                        .getW(),
                                                                                                                                                                                m),
                                                                                                                                                                        translate(
                                                                                                                                                                                ((Rep) e)
                                                                                                                                                                                        .getBody(),
                                                                                                                                                                                m))), SELF(
                                                                                                                                                                                        org.protelis.parser.protelis.Self.class,
                                                                                                                                                                                        (e, m) -> e instanceof org.protelis.parser.protelis.Self
                                                                                                                                                                                                ? new Self()
                                                                                                                                                                                                : null), STRING(
                                                                                                                                                                                                        StringVal.class,
                                                                                                                                                                                                        (e, m) -> new Constant<>(
                                                                                                                                                                                                                ((StringVal) e)
                                                                                                                                                                                                                        .getVal())), TUPLE(
                                                                                                                                                                                                                                TupleVal.class,
                                                                                                                                                                                                                                (e, m) -> new CreateTuple(
                                                                                                                                                                                                                                        exprListArgs(
                                                                                                                                                                                                                                                ((TupleVal) e)
                                                                                                                                                                                                                                                        .getArgs(),
                                                                                                                                                                                                                                                m))), VARIABLE(
                                                                                                                                                                                                                                                        VarUse.class,
                                                                                                                                                                                                                                                        (e, m) -> new Variable(
                                                                                                                                                                                                                                                                toR(((VarUse) e)
                                                                                                                                                                                                                                                                        .getReference())));

        private BiFunction<EObject, Map<Reference, FunctionDefinition>, AnnotatedTree<?>> translator;
        private Class<? extends EObject> type;

        Dispatch(final Class<? extends EObject> type,
                final BiFunction<EObject, Map<Reference, FunctionDefinition>, AnnotatedTree<?>> translator) {
            this.translator = translator;
            this.type = type;
        }

        @SuppressWarnings("unchecked")
        public static <T> AnnotatedTree<T> translate(final EObject o,
                final Map<Reference, FunctionDefinition> functions) {
            for (final Dispatch d : values()) {
                if (d.type.isAssignableFrom(o.getClass())) {
                    return (AnnotatedTree<T>) d.translator.apply(o, functions);
                }
            }
            throw new IllegalStateException(o + " could not be mapped to a Protelis interpreter entity.");
        }

    }

    private static List<Diagnostic> recursivelyCollectErrors(final Resource resource) {
        return StreamSupport.parallelStream(resource.getResourceSet().getResources()).map(Resource::getErrors)
                .filter(err -> !err.isEmpty()).flatMap(StreamSupport::stream).collect(Collectors.toList());
    }

    private static void recursivelyInitFunctions(final ProtelisModule module,
            final Map<FunctionDef, FunctionDefinition> nameToFun) {
        recursivelyInitFunctions(module, nameToFun, new LinkedHashSet<>());
    }

    private static void recursivelyInitFunctions(final ProtelisModule module,
            final Map<FunctionDef, FunctionDefinition> nameToFun, final Set<ProtelisModule> completed) {
        if (!completed.contains(module)) {
            completed.add(module);
            /*
             * Init imports functions, in reverse order
             */
            final EList<Import> imports = module.getProtelisImport();
            for (int i = imports.size() - 1; i >= 0; i--) {
                final Import protelisImport = imports.get(i);
                recursivelyInitFunctions(protelisImport.getModule(), nameToFun, completed);
            }
            /*
             * Init local functions
             */
            nameToFun.putAll(stream(module.getDefinitions()).collect(Collectors.toMap(Functions.identity(),
                    fd -> new FunctionDefinition(
                            new FasterString(Optional.ofNullable(module.getName()).orElse("") + fd.getName()),
                            toR(extractArgs(fd))))));
        }
    }

    private static List<VarDef> extractArgs(final FunctionDef e) {
        return e.getArgs() != null && e.getArgs().getArgs() != null ? e.getArgs().getArgs()
                : Collections.emptyList();
    }

    private static Reference toR(final Object o) {
        try {
            return REFERENCES.get(o);
        } catch (ExecutionException e) {
            throw new IllegalStateException("Unable to create a reference for " + o, e);
        }
    }

    private static List<Reference> toR(final List<?> l) {
        return stream(l).map(ProtelisLoader::toR).collect(Collectors.toList());
    }

    private static <T> BinaryOperator<T> throwException() {
        return (x, y) -> {
            throw new IllegalStateException("This is a bug in Protelis.");
        };
    }

}