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

org.protelis.lang.interpreter.impl.ShareCall Maven / Gradle / Ivy

There is a newer version: 17.6.0
Show newest version
/*
 * Copyright (C) 2021, Danilo Pianini and contributors listed in the project's build.gradle.kts 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.interpreter.impl;

import static org.protelis.lang.interpreter.util.Bytecode.REP;
import static org.protelis.lang.interpreter.util.Bytecode.SHARE;
import static org.protelis.lang.interpreter.util.Bytecode.SHARE_BODY;
import static org.protelis.lang.interpreter.util.Bytecode.SHARE_INIT;
import static org.protelis.lang.interpreter.util.Bytecode.SHARE_YIELD;

import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

import javax.annotation.Nonnull;

import org.protelis.lang.datatype.DeviceUID;
import org.protelis.lang.datatype.Field;
import org.protelis.lang.datatype.impl.FieldMapImpl.Builder;
import org.protelis.lang.interpreter.ProtelisAST;
import org.protelis.lang.interpreter.util.Bytecode;
import org.protelis.lang.interpreter.util.Reference;
import org.protelis.lang.loading.Metadata;
import org.protelis.vm.ExecutionContext;

import com.google.common.base.Optional;

/**
 * Share construct. Supersedes the previous rep implementation.
 * See publication at: https://lmcs.episciences.org/6816
 *
 * @param  superscript / export type
 * @param  returned type
 */
public final class ShareCall extends AbstractPersistedTree {
    private static final long serialVersionUID = 1L;
    private final Optional fieldName;
    private final Optional localName;
    private final Optional> yield;
    private final ProtelisAST init;
    private final ProtelisAST body;

    /**
     * Convenience constructor with {@link java.util.Optional}.
     *
     * @param metadata
     *            A {@link Metadata} object containing information about the code that generated this AST node.
     * @param localName
     *            variable name
     * @param fieldName
     *            name of the field version
     * @param init
     *            initial value
     * @param body
     *            body
     * @param yield
     *            body
     */
    public ShareCall(
            @Nonnull final Metadata metadata,
            @Nonnull final java.util.Optional localName,
            @Nonnull final java.util.Optional fieldName,
            @Nonnull final ProtelisAST init,
            @Nonnull final ProtelisAST body,
            @Nonnull final java.util.Optional> yield) {
        this(metadata, toGuava(localName), toGuava(fieldName), init, body, toGuava(yield));
    }

    /**
     * @param metadata
     *            A {@link Metadata} object containing information about the code that generated this AST node.
     * @param localName
     *            variable name
     * @param fieldName
     *            name of the field version
     * @param init
     *            initial value
     * @param body
     *            body
     * @param yield
     *            body
     */
    public ShareCall(
            @Nonnull final Metadata metadata,
            @Nonnull final Optional localName,
            @Nonnull final Optional fieldName,
            @Nonnull final ProtelisAST init,
            @Nonnull final ProtelisAST body,
            @Nonnull final Optional> yield) {
        super(metadata, init, body);
        if (!(localName.isPresent() || fieldName.isPresent())) {
            throw new IllegalArgumentException("Share cannot get initialized without at least a variable bind.");
        }
        this.localName = localName;
        this.fieldName = fieldName;
        this.init = init;
        this.body = body;
        this.yield = yield.transform(it ->  {
            if (it instanceof AbstractProtelisAST) {
                return (AbstractProtelisAST) it;
            }
            throw new IllegalStateException("class type " + it.getClass().getName() + " unkown and unsupported");
        });
    }

    private Field alignField(final DeviceUID localDevice, final Field init, final Field local, final Field nbr) {
        final Builder builder = new Builder<>();
        for (final Map.Entry entry : nbr.iterable()) {
            final DeviceUID otherDevice = entry.getKey();
            if (!localDevice.equals(otherDevice)) {
                final S value = local.containsKey(otherDevice) ? local.get(otherDevice) : init.get(otherDevice);
                builder.add(otherDevice, value);
            }
        }
        return builder.build(localDevice, local.get(localDevice));
    }

    @SuppressWarnings("unchecked")
    private java.util.Optional> asFieldOrEmpty(final S value, final boolean condition) {
        return java.util.Optional.of(value)
                .filter(it -> condition && it instanceof Field)
                .map(it -> (Field) it);
    }

    @SuppressWarnings("unchecked")
    @Override
    public T evaluate(final ExecutionContext context) {
        final S initValue = context.runInNewStackFrame(SHARE_INIT.getCode(), init::eval);
        final S localValue = ensureType(loadState(context, () -> initValue));
        final boolean localIsField = localValue instanceof Field;
        if (localIsField && !(initValue instanceof Field)) {
            throw new IllegalStateException(
                "The local value " + localValue
                    + " is a field, but the default one " + initValue + " is not: "
                    + initValue.getClass().getSimpleName() + ". Types must be consistent"
            );
        }
        final BodyResult bodyResult = new BodyResult<>();
        final DeviceUID myId = context.getDeviceUID();
        final java.util.Optional> localAsField = asFieldOrEmpty(localValue, localIsField);
        final java.util.Optional> initAsField = asFieldOrEmpty(initValue, localIsField);
        assert !localIsField || localAsField.isPresent() && initAsField.isPresent();
        /*
         * Three cases:
         * 1. rep. No fieldName, no field should get build
         * 2. share/classic: local is not a field, fieldName is present, build field as usual
         * 3. share/new: local is a field, fieldName is present, build a field with extractValueFromField
         */
        final Field nbr;
        if (fieldName.isPresent()) {
            if (localIsField) {
                nbr = context.buildFieldDeferred(
                        field -> extractValueFromField(initAsField.get(), (Field) field, myId),
                        localAsField.get(),
                        bodyResult::getResult
                );
            } else {
                nbr = context.buildFieldDeferred(Function.identity(), localValue, bodyResult::getResult);
            }
        } else {
            nbr = null;
        }
        ifPresent(
            localName,
            localIsField
                ? it -> context.putVariable(it, alignField(myId, initAsField.get(), localAsField.get(), nbr))
                : it -> context.putVariable(it, localValue)
        );
        ifPresent(fieldName, it -> context.putVariable(it, nbr));
        context.newCallStackFrame(SHARE_BODY.getCode());
        final Optional yieldResult;
        if (body instanceof All) {
            final All multilineBody = (All) body;
            multilineBody.forEachWithIndex((i, b) -> {
                context.newCallStackFrame(i);
                bodyResult.result = (S) b.eval(context);
            });
            yieldResult = evaluateYield(context);
            multilineBody.forEach(it -> context.returnFromCallFrame());
        } else {
            bodyResult.result = body.eval(context);
            yieldResult = evaluateYield(context);
        }
        context.returnFromCallFrame();
        saveState(context, ensureType(bodyResult.result));
        return yieldResult.or((T) bodyResult.result);
    }

    @SuppressWarnings("unchecked")
    private S ensureType(final Object o) {
        return (S) Objects.requireNonNull(o, "Share is not allowed to return, store, or get initialized to null values.");
    }

    private Optional evaluateYield(final ExecutionContext context) {
        return yield.transform(it -> context.runInNewStackFrame(SHARE_YIELD.getCode(), it::eval));
    }

    private S extractValueFromField(final Field init, final Field other, final DeviceUID id) {
        return other.containsKey(id) ? other.get(id) : init.get(other.getLocalDevice());
    }

    @Override
    public Bytecode getBytecode() {
        return fieldName.isPresent() ? SHARE : REP;
    }

    @Override
    public String getName() {
        return fieldName.isPresent() ? "share" : "rep";
    }

    /**
     * @return an {@link Optional} containing the {@link ProtelisAST} representing the yield expression,
     * or an {@link Optional#absent()} otherwise.
     */
    public Optional> getYieldExpression() {
        return yield;
    }

    @Override
    public String toString() {
        final Optional field = fieldName.transform(Reference::toString);
        return getName() + " ("
            + localName.transform(Reference::toString)
                .transform(it -> it + field.transform(f -> ", ").or("")).or("")
            + field.or("")
            + " <- "
            + stringFor(init)
            + ") { "
            + stringFor(body)
            + " }"
            + yield.transform(it -> " yield { " + stringFor(it) + '}').or("");
    }

    private static  void ifPresent(final Optional var, final Consumer todo) {
        if (var.isPresent()) {
            todo.accept(var.get());
        }
    }

    private static  Optional toGuava(final java.util.Optional origin) {
        return Optional.fromNullable(origin.orElse(null));
    }

    private static final class BodyResult {
        private S result;
        private S getResult() { // NOPMD: false positive, method used as function reference
            return result;
        }
    }
}