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

org.protelis.lang.ProtelisLoader Maven / Gradle / Ivy

There is a newer version: 17.6.0
Show newest version
/*
 * Copyright (C) 2022, Danilo Pianini and contributors listed in the project's build.gradle.kts 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 com.google.common.base.Splitter;
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.ImmutableList;
import com.google.common.collect.Iterables;
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 org.apache.commons.io.IOUtils;
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.emf.ecore.util.Diagnostician;
import org.eclipse.xtext.common.types.JvmFeature;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
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.datatype.JVMEntity;
import org.protelis.lang.interpreter.ProtelisAST;
import org.protelis.lang.interpreter.impl.AlignedMap;
import org.protelis.lang.interpreter.impl.All;
import org.protelis.lang.interpreter.impl.AssignmentOp;
import org.protelis.lang.interpreter.impl.BinaryOp;
import org.protelis.lang.interpreter.impl.ConditionalSideEffect;
import org.protelis.lang.interpreter.impl.Constant;
import org.protelis.lang.interpreter.impl.CreateTuple;
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.Invoke;
import org.protelis.lang.interpreter.impl.JvmConstant;
import org.protelis.lang.interpreter.impl.NBRCall;
import org.protelis.lang.interpreter.impl.Self;
import org.protelis.lang.interpreter.impl.ShareCall;
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.interpreter.util.HashingFunnel;
import org.protelis.lang.interpreter.util.HoodOp;
import org.protelis.lang.interpreter.util.Java8CompatibleFunnel;
import org.protelis.lang.interpreter.util.Reference;
import org.protelis.lang.loading.Metadata;
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.Builtin;
import org.protelis.parser.protelis.BuiltinHoodOp;
import org.protelis.parser.protelis.Declaration;
import org.protelis.parser.protelis.DoubleVal;
import org.protelis.parser.protelis.Expression;
import org.protelis.parser.protelis.ExpressionList;
import org.protelis.parser.protelis.FunctionDef;
import org.protelis.parser.protelis.GenericHood;
import org.protelis.parser.protelis.IfWithoutElse;
import org.protelis.parser.protelis.InvocationArguments;
import org.protelis.parser.protelis.It;
import org.protelis.parser.protelis.Lambda;
import org.protelis.parser.protelis.LongLambda;
import org.protelis.parser.protelis.MethodCall;
import org.protelis.parser.protelis.Mux;
import org.protelis.parser.protelis.NBR;
import org.protelis.parser.protelis.OldLongLambda;
import org.protelis.parser.protelis.OldShortLambda;
import org.protelis.parser.protelis.ProtelisModule;
import org.protelis.parser.protelis.Rep;
import org.protelis.parser.protelis.RepInitialize;
import org.protelis.parser.protelis.Scalar;
import org.protelis.parser.protelis.Share;
import org.protelis.parser.protelis.ShareInitialize;
import org.protelis.parser.protelis.Statement;
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.parser.protelis.Yield;
import org.protelis.vm.ProtelisProgram;
import org.protelis.vm.impl.SimpleProgramImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static org.protelis.lang.ProtelisLoadingUtilities.IT;
import static org.protelis.lang.ProtelisLoadingUtilities.argumentsToExpressionStream;
import static org.protelis.lang.ProtelisLoadingUtilities.referenceFor;
import static org.protelis.lang.ProtelisLoadingUtilities.referenceListFor;

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

    private static final String HOOD_END = "Hood";
    private static final ThreadLocal> LOADED_RESOURCES = ThreadLocal.withInitial(() ->
            CacheBuilder.newBuilder()
                .expireAfterAccess(1, TimeUnit.MINUTES)
                .build()
    );
    private static final Logger LOGGER = LoggerFactory.getLogger(ProtelisLoader.class);
    private static final String OPEN_J9_EMF_WORKED_AROUND = "Working around OpenJ9 + Eclipse EMF bug."
            + "See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=549084"
            + "and https://github.com/eclipse/openj9/issues/6370";
    private static final String PROTELIS_FILE_EXTENSION = "pt";
    private static final Pattern REGEX_PROTELIS_IMPORT = Pattern.compile(
        "^\\s*import\\s+((?:\\w+:)*\\w+)\\s+",
        Pattern.MULTILINE
    );
    private static final Pattern REGEX_PROTELIS_MODULE = Pattern.compile("(?:\\w+:)*\\w+");

    private static final LoadingCache FLYWEIGHT = CacheBuilder
        .newBuilder()
        .maximumSize(1000)
        .build(
            new CacheLoader<>() {
                @Override
                @Nonnull
                public ProtelisProgram load(@Nonnull final Resource resource) {
                    Objects.requireNonNull(resource);
                    if (!resource.getErrors().isEmpty()) {
                        final String moduleName = Optional.ofNullable(resource.getContents())
                            .map(it -> it.get(0))
                            .map(it -> (ProtelisModule) it)
                            .map(ProtelisModule::getName)
                            .orElse("without declared module");
                        final StringBuilder sb = new StringBuilder(100)
                            .append("Program ")
                            .append(moduleName)
                            .append(" from resource ")
                            .append(resource.getURI())
                            .append(" cannot be created because of the following errors:\n");
                        boolean first = true;
                        for (final Diagnostic d : Lists.reverse(recursivelyCollectErrors(resource))) {
                            if (first) {
                                sb.append("MOST LIKELY CAUSE ==> ");
                                first = false;
                            }
                            sb.append("Error");
                            if (d.getLocation() != null) {
                                final String place = Iterables.get(Splitter.on('#').split(d.getLocation()), 0);
                                sb.append(" in ").append(place);
                            }
                            try {
                                final int line = d.getLine();
                                sb.append(", line ").append(line);
                            } catch (final UnsupportedOperationException e) { // NOPMD
                                // The line information is not available
                            }
                            try {
                                final int column = d.getColumn();
                                sb.append(", column ").append(column);
                            } catch (final UnsupportedOperationException e) { // NOPMD
                                // The column information is not available
                            }
                            sb.append(": ").append(d.getMessage()).append('\n');
                        }
                        throw new IllegalArgumentException(sb.toString());
                    }
                    final ProtelisModule root = (ProtelisModule) resource.getContents().get(0);
                    Objects.requireNonNull(Objects.requireNonNull(root).getProgram(),
                        "The provided resource does not contain any main program, and can not be executed.");
                    Diagnostician.INSTANCE.validate(root).getChildren()
                        .forEach(it -> LOGGER.warn("severity {}: {}", it.getSeverity(), it.getMessage()));
                    return new SimpleProgramImpl(root, Dispatch.block(root.getProgram()));
                }
            }
        );

    private static final ThreadLocal XTEXT = ThreadLocal.withInitial(() -> {
        final Injector guiceInjector = new ProtelisStandaloneSetup().createInjectorAndDoEMFRegistration();
        final XtextResourceSet xtext = guiceInjector.getInstance(XtextResourceSet.class);
        xtext.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE);
        return xtext;
    });

    private ProtelisLoader() {
    }

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

    @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "False positive")
    private static void loadResourcesRecursively(
            final XtextResourceSet target,
            final String programURI,
            final Set alreadyInQueue) throws IOException {
        final ResolvedResource resource = new ResolvedResource(programURI);
        if (LOADED_RESOURCES.get().getIfPresent(programURI) == null && !alreadyInQueue.contains(programURI)) {
            alreadyInQueue.add(programURI);
            final URI uri = workAroundOpenJ9EMFBug(() -> URI.createURI(resource.realURI));
            if (resource.exists()) {
                try (InputStream is = resource.openStream()) {
                    loadStringResources(target, is, alreadyInQueue);
                }
                LOADED_RESOURCES.get().put(programURI, workAroundOpenJ9EMFBug(() -> target.getResource(uri, true)));
            } else {
                throw new IllegalStateException("expected resource " + resource + " was not found");
            }
        }
    }

    private static void loadStringResources(
        final XtextResourceSet target,
        final InputStream is,
        final Set alreadyInQueue
    ) throws IOException {
        final String ss = IOUtils.toString(is, StandardCharsets.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);
        }
    }

    private static void loadStringResources(final XtextResourceSet target, final InputStream is) throws IOException {
        loadStringResources(target, is, new LinkedHashSet<>());
    }

        private static Metadata metadataFor(final EObject origin) {
        final INode grammarElement = NodeModelUtils.getNode(origin);
        final int startLine = grammarElement.getStartLine();
        final int endLine = grammarElement.getEndLine();
        return new Metadata() {
            private static final long serialVersionUID = 1L;
            @Override
            public int getEndLine() {
                return endLine;
            }
            @Override
            public int getStartLine() {
                return startLine;
            }
        };
    }

    /**
     * @param resource
     *            the {@link Resource} containing the program to execute
     * @return a {@link ProtelisProgram}
     */
    public static ProtelisProgram parse(@Nonnull final Resource resource) {
        try {
            return FLYWEIGHT.get(resource);
        } catch (Exception e) { // NOPMD: this is intentional.
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * @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(program).matches()) { final String programURI = "classpath:/" + program.replace(':', '/') + "." + PROTELIS_FILE_EXTENSION; final Optional programResource = resourceFromURIString(programURI) .map(ProtelisLoader::parse); if (programResource.isPresent()) { return programResource.get(); } } return resourceFromURIString(program) .map(ProtelisLoader::parse) .orElseGet(() -> parseAnonymousModule(program)); } catch (IOException e) { throw new IllegalStateException(program + " looks like an URI, but its resolution failed (see cause)", e); } } /** * @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) { 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 */ @SuppressWarnings("unused") public static ProtelisProgram parseURI(final String programURI) throws IOException { return parse(resourceFromURIString(programURI).orElseThrow(IllegalArgumentException::new)); } private static List recursivelyCollectErrors(final Resource resource) { return resource.getResourceSet().getResources().stream() .map(Resource::getErrors) .filter(err -> !err.isEmpty()) .flatMap(Collection::stream) .collect(Collectors.toList()); } /** * @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 String programId = "dummy:/protelis-generated-program-" + Hashing.sha512().hashString(program, StandardCharsets.UTF_8) + ".pt"; final URI uri = workAroundOpenJ9EMFBug(() -> URI.createURI(programId)); 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 resource associated with anonymous program: " + e.getMessage(), 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 Optional resourceFromURIString(final String programURI) throws IOException { final ResolvedResource resource = new ResolvedResource(programURI); if (resource.exists()) { loadResourcesRecursively(XTEXT.get(), programURI); final URI uri = workAroundOpenJ9EMFBug(() -> URI.createURI(resource.realURI)); return Optional.of(XTEXT.get().getResource(uri, true)); } else { return Optional.empty(); } } private static R workAroundOpenJ9EMFBug(final Supplier fun) { try { return fun.get(); } catch (AssertionError e) { LOGGER.warn(OPEN_J9_EMF_WORKED_AROUND, e); return fun.get(); } } private static final class Dispatch { private static final Cache VIRTUAL_METHOD_TABLE = CacheBuilder.newBuilder().weakKeys().build(); private static final HashingFunnel FUNNEL = new Java8CompatibleFunnel(); private static ProtelisAST alignedMap(@Nonnull final org.protelis.parser.protelis.AlignedMap alMap) { return new AlignedMap( FUNNEL, metadataFor(alMap), expression(alMap.getArg()), expression(alMap.getCond()), expression(alMap.getOp()), expression(alMap.getDefault()) ); } private static AssignmentOp assignment(final Assignment assignment) { return new AssignmentOp( metadataFor(assignment), referenceFor(assignment.getRefVar()), expression(assignment.getRight()) ); } private static ProtelisAST block(@Nonnull final Block block) { final List statements = block.getStatements(); if (statements.size() == 1) { return statement(statements.get(0)); } return new All(metadataFor(block), statements.stream().map(Dispatch::statement).collect(Collectors.toList())); } @SuppressWarnings("unchecked") private static ProtelisAST blockUnsafe(@Nonnull final Block block) { return (ProtelisAST) block(block); } @SuppressWarnings("deprecation") private static ProtelisAST builtin(@Nonnull final Builtin expression) { if (expression instanceof org.protelis.parser.protelis.AlignedMap) { return alignedMap((org.protelis.parser.protelis.AlignedMap) expression); } final Metadata meta = metadataFor(expression); if (expression instanceof org.protelis.parser.protelis.Env) { return new Env(meta); } if (expression instanceof org.protelis.parser.protelis.Eval) { return new Eval(meta, expression(((org.protelis.parser.protelis.Eval) expression).getArg())); } if (expression instanceof BuiltinHoodOp) { final BuiltinHoodOp hood = (BuiltinHoodOp) expression; return new HoodCall(meta, expression(hood.getArg()), HoodOp.get(hood.getName().replace(HOOD_END, "")), hood.isInclusive()); } if (expression instanceof GenericHood) { final GenericHood hood = (GenericHood) expression; final boolean inclusive = hood.getName().length() > 4; final ProtelisAST nullResult = expression(hood.getDefault()); final ProtelisAST> field = expression(hood.getArg()); final VarUse ref = hood.getReference(); if (ref == null) { return new GenericHoodCall(meta, inclusive, lambda(hood.getOp()), nullResult, field); } if (ref.getReference() instanceof JvmOperation) { return new GenericHoodCall(meta, inclusive, (JvmOperation) ref.getReference(), nullResult, field); } return new GenericHoodCall(meta, inclusive, variableUnsafe(ref), nullResult, field); } if (expression instanceof It) { return new Variable(meta, IT); } if (expression instanceof Mux) { final Mux mux = (Mux) expression; return new TernaryOp(meta, mux.getName(), expression(mux.getCond()), block(mux.getThen()), block(mux.getElse())); } if (expression instanceof org.protelis.parser.protelis.Self) { return new Self(meta); } throw new IllegalStateException("Unknown builtin of type " + expression.getClass().getSimpleName()); } private static AssignmentOp declaration(final Declaration declaration) { final VarDef name = declaration.getName(); return new AssignmentOp(metadataFor(declaration), referenceFor(name), expression(declaration.getRight())); } @SuppressWarnings("unchecked") private static ProtelisAST expression(final Expression expression) { return (ProtelisAST) expressionRaw(expression); } private static List> expressionList(@Nullable final ExpressionList list) { return Optional.ofNullable(list) .>map(ExpressionList::getArgs) .orElseGet(Collections::emptyList) .stream() .map(Dispatch::expression) .collect(ImmutableList.toImmutableList()); } private static ProtelisAST expressionRaw(final Expression expression) { if (expression instanceof Builtin) { return builtin((Builtin) expression); } if (expression instanceof org.protelis.parser.protelis.If) { return ifOp((org.protelis.parser.protelis.If) expression); } if (expression instanceof Lambda) { return lambda((Lambda) expression); } if (expression instanceof NBR) { return nbr((NBR) expression); } if (expression instanceof Rep) { return rep((Rep) expression); } if (expression instanceof Scalar) { return scalar((Scalar) expression); } if (expression instanceof Share) { return share((Share) expression); } if (expression instanceof VarUse) { return variable((VarUse) expression); } /* * Pure expression */ final List elements = expression.getElements(); final Metadata meta = metadataFor(expression); switch (elements.size()) { case 1: return new UnaryOp(meta, expression.getName(), expression((Expression) elements.get(0))); case 2: final ProtelisAST first = expression((Expression) expression.getElements().get(0)); final EObject second = expression.getElements().get(1); if (expression.getName() == null && second instanceof InvocationArguments) { // Invoke final InvocationArguments invokeArgs = (InvocationArguments) second; if (first instanceof Constant) { final Object constant = ((Constant) first).getConstantValue(); if (constant instanceof FunctionDefinition) { // It's a plain function call, possibly on a lambda, don't go through Invoke return new FunctionCall(meta, (FunctionDefinition) constant, invocationArguments(invokeArgs)); } } // TODO: Drop "apply", and allow only standard invocations with better system return new Invoke(meta, "apply", first, invocationArguments(invokeArgs)); } if (".".equals(expression.getName()) && second instanceof MethodCall) { // Method call final MethodCall method = (MethodCall) second; return new Invoke(meta, method.getName(), first, invocationArguments(method.getArguments())); } if (expression.getName() != null) { return new BinaryOp(meta, expression.getName(), first, expression((Expression) second)); } default: throw new IllegalStateException("Unknown AST node " + expression); } } private static ProtelisAST ifOp(final org.protelis.parser.protelis.If ifOp) { return new If<>(metadataFor(ifOp), expression(ifOp.getCond()), blockUnsafe(ifOp.getThen()), block(ifOp.getElse())); } private static ConditionalSideEffect ifWithoutElse(final IfWithoutElse ifOp) { final Metadata meta = metadataFor(ifOp); final List> then = ifOp.getThen().stream() .map(Dispatch::statement) .collect(Collectors.toList()); final ProtelisAST thenBranch = then.size() == 1 ? then.get(0) : new All(meta, then); return new ConditionalSideEffect(meta, expression(ifOp.getCond()), thenBranch); } private static List> invocationArguments(@Nonnull final InvocationArguments args) { return argumentsToExpressionStream(args) .map(Dispatch::expression) .collect(ImmutableList.toImmutableList()); } private static Constant lambda(@Nonnull final Lambda expression) { final List arguments = expression instanceof LongLambda ? ((LongLambda) expression).getArgs().getArgs() : expression instanceof OldLongLambda ? Optional.ofNullable(((OldLongLambda) expression).getArgs()) .>map(VarDefList::getArgs) .orElseGet(Collections::emptyList) : expression instanceof OldShortLambda ? Collections.singletonList(((OldShortLambda) expression).getSingleArg()) : Collections.emptyList(); final FunctionDefinition lambda = new FunctionDefinition(expression, referenceListFor(arguments), block(expression.getBody())); return new Constant<>(metadataFor(expression), lambda); } private static NBRCall nbr(final NBR nbr) { return new NBRCall<>(metadataFor(nbr), expression(nbr.getArg())); } private static ShareCall rep(final Rep rep) { final Metadata meta = metadataFor(rep); final RepInitialize init = rep.getInit(); final Optional local = Optional.of(referenceFor(init.getX())); final Optional> yield = Optional.ofNullable(rep.getYields()) .map(Yield::getBody) .map(Dispatch::blockUnsafe); return new ShareCall<>(meta, local, Optional.empty(), expression(init.getW()), block(rep.getBody()), yield); } private static ProtelisAST scalar(@Nonnull final Scalar expression) { final Metadata meta = metadataFor(expression); if (expression instanceof BooleanVal) { return new Constant<>(meta, ((BooleanVal) expression).isVal()); } if (expression instanceof DoubleVal) { return new Constant<>(meta, ((DoubleVal) expression).getVal()); } if (expression instanceof StringVal) { return new Constant<>(meta, ((StringVal) expression).getVal()); } if (expression instanceof TupleVal) { return new CreateTuple(meta, expressionList(((TupleVal) expression).getArgs())); } throw new IllegalStateException("Unknown scalar of type " + expression.getClass().getSimpleName()); } private static ShareCall share(final Share share) { final ShareInitialize init = share.getInit(); final Optional local = Optional.ofNullable(init.getLocal()).map(ProtelisLoadingUtilities::referenceFor); final Optional field = Optional.ofNullable(init.getField()).map(ProtelisLoadingUtilities::referenceFor); final Optional> yield = Optional.ofNullable(share.getYields()) .map(Yield::getBody) .map(Dispatch::blockUnsafe); return new ShareCall<>(metadataFor(share), local, field, expression(init.getW()), block(share.getBody()), yield); } private static ProtelisAST statement(@Nonnull final Statement statement) { if (statement instanceof Expression) { return expression((Expression) statement); } if (statement instanceof Declaration) { return declaration((Declaration) statement); } if (statement instanceof Assignment) { return assignment((Assignment) statement); } if (statement instanceof IfWithoutElse) { return ifWithoutElse((IfWithoutElse) statement); } throw new IllegalStateException("Unknown statement of type " + statement.getClass().getSimpleName()); } private static ProtelisAST variable(@Nonnull final VarUse expression) { /* * VarUse can reference: * * - JvmFeature (imported methods or fields) * * - FunctionDef (imported or locally defined Protelis functions) * * - VarDef (variables defined in scope, parameters in scope) * * The former can be treated as constants. They are immutable and do not require restriction * (as they cannot bind a Field). */ final Metadata meta = metadataFor(expression); final EObject ref = expression.getReference(); if (ref instanceof JvmFeature) { /* * JVMFeature is not serializable */ return new JvmConstant(meta, new JVMEntity((JvmFeature) ref)); } if (ref instanceof FunctionDef) { final FunctionDef functionDefinition = (FunctionDef) ref; try { final var target = VIRTUAL_METHOD_TABLE.get( functionDefinition, () -> new FunctionDefinition( functionDefinition, () -> functionBody(functionDefinition) ) ); return new Constant<>(meta, target); } catch (ExecutionException e) { throw new IllegalStateException(e); } } return new Variable(meta, referenceFor(ref)); } @SuppressWarnings("unchecked") private static ProtelisAST variableUnsafe(final VarUse expression) { return (ProtelisAST) variable(expression); } private static ProtelisAST functionBody(final FunctionDef fun) { if (fun.getSingleExpression() == null) { return block(fun.getBody()); } else { return expression(fun.getSingleExpression()); } } } private static final class ResolvedResource { private static final String CLASSPATH_PROTOCOL = "classpath:"; private final String classpathURL; private final String realURI; private ResolvedResource(final String programURI) { realURI = (programURI.startsWith("/") ? CLASSPATH_PROTOCOL : "") + programURI; classpathURL = realURI.startsWith(CLASSPATH_PROTOCOL) ? realURI.substring(CLASSPATH_PROTOCOL.length() + 1) : realURI; } private boolean exists() { return Thread.currentThread().getContextClassLoader().getResource(classpathURL) != null; } private InputStream openStream() { return Thread.currentThread().getContextClassLoader().getResourceAsStream(classpathURL); } @Override public String toString() { return "From classpath: " + classpathURL + ", complete URI: " + realURI; } } }