diff --git a/alchemist/alchemist-incarnation-protelis/build.gradle.kts b/alchemist/alchemist-incarnation-protelis/build.gradle.kts
index 8b25b3c326..39fa275426 100644
--- a/alchemist/alchemist-incarnation-protelis/build.gradle.kts
+++ b/alchemist/alchemist-incarnation-protelis/build.gradle.kts
@@ -15,7 +15,7 @@ dependencies {
* Check with:
* ./gradlew dependencyInsight --dependency org.eclipse.emf:org.eclipse.emf.ecore --configuration runtimeClasspath
*/
- implementation("org.eclipse.emf:org.eclipse.emf.ecore:2.12.0") {
+ implementation(Libs.org_eclipse_emf_ecore) {
isForce = true
}
}
diff --git a/alchemist/alchemist-incarnation-protelis/src/test/resources/tomacs.yml b/alchemist/alchemist-incarnation-protelis/src/test/resources/tomacs.yml
index 798efe38e2..f56e7768a1 100644
--- a/alchemist/alchemist-incarnation-protelis/src/test/resources/tomacs.yml
+++ b/alchemist/alchemist-incarnation-protelis/src/test/resources/tomacs.yml
@@ -22,16 +22,16 @@ variables:
bp:
formula: 0.5
retain: &retain
- formula: 1 / $minfreq
+ formula: 1 / minfreq
fc:
- formula: "$time ? ($small ? $sp : $bp) : 0"
+ formula: "time ? (small ? sp : bp) : 0"
minfreq: &minfreq
- formula: "$freq * (1 - $fc)"
+ formula: "freq * (1 - fc)"
maxfreq: &maxfreq
- formula: "$freq * (1 + $fc)"
+ formula: "freq * (1 + fc)"
speed: &speed
language: scala
- formula: "if ($time) 0 else 1"
+ formula: "if (time.asInstanceOf[Boolean]) 0 else 1"
seed: &seed
min: 0
max: 199
diff --git a/alchemist/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/SAPEREIncarnation.java b/alchemist/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/SAPEREIncarnation.java
index d3f938fe5f..87f80616ca 100644
--- a/alchemist/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/SAPEREIncarnation.java
+++ b/alchemist/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/SAPEREIncarnation.java
@@ -7,17 +7,6 @@
*/
package it.unibo.alchemist.model;
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.apache.commons.math3.random.RandomGenerator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import it.unibo.alchemist.expressions.implementations.Type;
import it.unibo.alchemist.expressions.interfaces.IExpression;
@@ -42,6 +31,16 @@
import it.unibo.alchemist.model.interfaces.Position;
import it.unibo.alchemist.model.interfaces.Reaction;
import it.unibo.alchemist.model.interfaces.TimeDistribution;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
*
@@ -134,13 +133,8 @@ private double sapereProperty(final ILsaNode node, final ILsaMolecule molecule,
@Override
public ILsaMolecule createMolecule(final String s) {
- try {
- final String param = s.startsWith("{") && s.endsWith("}") ? s.substring(1, s.length() - 1) : s;
- return new LsaMolecule(param);
- } catch (RuntimeException e) {
- L.info("Unable to load the requested molecule:\n" + e);
- }
- return null;
+ final String param = s.trim().startsWith("{") && s.endsWith("}") ? s.substring(1, s.length() - 1) : s;
+ return new LsaMolecule(param);
}
@Override
diff --git a/alchemist/alchemist-influence-sphere/src/main/kotlin/it/unibo/alchemist/model/influencesphere/sensory/FieldOfView2D.kt b/alchemist/alchemist-influence-sphere/src/main/kotlin/it/unibo/alchemist/model/influencesphere/sensory/FieldOfView2D.kt
index 6570769670..a82caf7156 100644
--- a/alchemist/alchemist-influence-sphere/src/main/kotlin/it/unibo/alchemist/model/influencesphere/sensory/FieldOfView2D.kt
+++ b/alchemist/alchemist-influence-sphere/src/main/kotlin/it/unibo/alchemist/model/influencesphere/sensory/FieldOfView2D.kt
@@ -1,10 +1,10 @@
package it.unibo.alchemist.model.influencesphere.sensory
-import it.unibo.alchemist.model.interfaces.Position2D
import it.unibo.alchemist.model.influencesphere.shapes.GeometricShape2D
+import it.unibo.alchemist.model.interfaces.Position2D
import java.awt.geom.Arc2D
-// To be better implemented following https://legends2k.github.io/2d-fov/design.html
+// To be better implemented following http://archive.fo/Cwy38
class FieldOfView2D
>(
originX: Double,
originY: Double,
diff --git a/alchemist/alchemist-loading/build.gradle.kts b/alchemist/alchemist-loading/build.gradle.kts
index 6a4b3f8b22..245d254ce6 100644
--- a/alchemist/alchemist-loading/build.gradle.kts
+++ b/alchemist/alchemist-loading/build.gradle.kts
@@ -12,11 +12,15 @@ dependencies {
implementation(project(":alchemist-time"))
implementation(project(":alchemist-maps"))
implementation(Libs.commons_lang3)
- implementation(Libs.groovy)
implementation(Libs.guava)
implementation(Libs.jirf)
implementation(Libs.snakeyaml)
+ runtimeOnly(Libs.groovy_jsr223)
+ runtimeOnly(Libs.kotlin_scripting_jsr223_embeddable)
+// runtimeOnly(Libs.kotlin_script_runtime)
+// runtimeOnly("org.jetbrains.kotlin:kotlin-script-util:1.3.40")
+
testImplementation(project(":alchemist-engine"))
testImplementation(project(":alchemist-maps"))
testImplementation(Libs.gson)
diff --git a/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/Loader.java b/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/Loader.java
index 66056d7959..84485d79f1 100644
--- a/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/Loader.java
+++ b/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/Loader.java
@@ -8,6 +8,7 @@
package it.unibo.alchemist.loader;
import it.unibo.alchemist.loader.export.Extractor;
+import it.unibo.alchemist.loader.variables.DependentVariable;
import it.unibo.alchemist.loader.variables.Variable;
import it.unibo.alchemist.model.interfaces.Environment;
import it.unibo.alchemist.model.interfaces.Position;
@@ -31,6 +32,15 @@ public interface Loader extends Serializable {
*/
> Environment getDefault();
+ /**
+ * Allows to access the currently defined dependent variable (those variables whose value can be determined given a
+ * valid set of values for the free variables).
+ *
+ * @return a {@link Map} between variable names and their actual
+ * representation
+ */
+ Map> getDependentVariables();
+
/**
* @return a {@link Map} between variable names and their actual
* representation
@@ -51,6 +61,14 @@ public interface Loader extends Serializable {
*/
> Environment getWith(Map values);
+ /**
+ * Allows to access the currently defined constants, namely variables defined in the simulation file whose value is
+ * constant and does not depend on the value of any free variable (directly or indirectly).
+ *
+ * @return a {@link Map} between variable names and their computed value
+ */
+ Map getConstants();
+
/**
* @return The data extractors
*/
diff --git a/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/YamlLoader.java b/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/YamlLoader.java
index c63f5f58e8..d422d2bc8b 100644
--- a/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/YamlLoader.java
+++ b/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/YamlLoader.java
@@ -28,12 +28,9 @@
import it.unibo.alchemist.loader.shapes.Shape;
import it.unibo.alchemist.loader.variables.ArbitraryVariable;
import it.unibo.alchemist.loader.variables.DependentVariable;
-import it.unibo.alchemist.loader.variables.GroovyVariable;
-import it.unibo.alchemist.loader.variables.JavascriptVariable;
+import it.unibo.alchemist.loader.variables.JSR223Variable;
import it.unibo.alchemist.loader.variables.LinearVariable;
import it.unibo.alchemist.loader.variables.NumericConstant;
-import it.unibo.alchemist.loader.variables.ScalaVariable;
-import it.unibo.alchemist.loader.variables.ScriptVariable;
import it.unibo.alchemist.loader.variables.Variable;
import it.unibo.alchemist.model.implementations.environments.Continuous2DEnvironment;
import it.unibo.alchemist.model.implementations.linkingrules.NoLinks;
@@ -149,12 +146,6 @@ public final class YamlLoader implements Loader {
private static final String VALUE_FILTER = SYNTAX.getString("value-filter");
private static final String VALUES = SYNTAX.getString("values");
private static final String VARIABLES = SYNTAX.getString("variables");
- @SuppressWarnings("deprecation")
- private static final Map>> SUPPORTED_LANGUAGES = ImmutableMap.of(
- "groovy", GroovyVariable::new,
- "javascript", JavascriptVariable::new,
- "scala", ScalaVariable::new
- );
private static final Map, Map>> DEFAULT_MANDATORY_PARAMETERS = ImmutableMap., Map>>builder()
.put(Layer.class, ImmutableMap.of(TYPE, CharSequence.class, MOLECULE, CharSequence.class))
.build();
@@ -172,8 +163,7 @@ public final class YamlLoader implements Loader {
final Object formula = m.get(FORMULA);
return formula instanceof Number
? new NumericConstant((Number) formula)
- : SUPPORTED_LANGUAGES.get(m.getOrDefault(LANGUAGE, "groovy").toString().toLowerCase(Locale.ENGLISH))
- .apply(formula.toString());
+ : new JSR223Variable<>(m.getOrDefault(LANGUAGE, "groovy").toString().toLowerCase(Locale.ENGLISH), formula.toString());
}));
private static final BuilderConfiguration FILTERING_CONFIG = new BuilderConfiguration<>(
ImmutableMap.of(NAME, CharSequence.class), ImmutableMap.of(), makeBaseFactory(), m -> CommonFilters.fromString(m.get(NAME).toString()));
@@ -393,6 +383,11 @@ public YamlLoader(final String yaml) {
this(new StringReader(yaml));
}
+ @Override
+ public ImmutableMap getConstants() {
+ return constants;
+ }
+
@Override
public List getDataExtractors() {
return Collections.unmodifiableList(extractors);
@@ -404,8 +399,13 @@ public > Environment getDefault() {
}
@Override
- public Map> getVariables() {
- return Collections.unmodifiableMap(variables);
+ public ImmutableMap> getDependentVariables() {
+ return depVariables;
+ }
+
+ @Override
+ public ImmutableMap> getVariables() {
+ return variables;
}
@Override
diff --git a/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/variables/JavascriptVariable.java b/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/variables/JavascriptVariable.java
deleted file mode 100644
index effaa242e9..0000000000
--- a/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/variables/JavascriptVariable.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2010-2019, Danilo Pianini and contributors listed in the main project's alchemist/build.gradle file.
- *
- * This file is part of Alchemist, and is distributed under the terms of the
- * GNU General Public License, with a linking exception,
- * as described in the file LICENSE in the Alchemist distribution's top directory.
- */
-package it.unibo.alchemist.loader.variables;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
-import javax.script.ScriptException;
-
-/**
- * Interpreter that relies on the Nashorn Javascript engine shipped with the
- * standard JDK. Variables are substituted with their provided values using a
- * simple regular expression, and then the resulting {@link String} is fed to
- * the Javascript interpereter.
- *
- * @deprecated This class uses Nashorn, which is scheduled to be removed in future JDKs
- */
-@Deprecated
-public final class JavascriptVariable extends ScriptVariable {
-
- private static final long serialVersionUID = 1L;
- private static final ScriptEngineManager MANAGER = new ScriptEngineManager();
- private static final ScriptEngine ENGINE = MANAGER.getEngineByName("nashorn");
- private static final Logger L = LoggerFactory.getLogger(JavascriptVariable.class);
-
- /**
- * @param formula
- * the formula
- */
- public JavascriptVariable(final String formula) {
- super(formula);
- L.warn("Javascript variables are deprecated and will get dropped from Alchemist as soon as Nashorn gets "
- + "dropped from the JDK as per JEP 335. See https://openjdk.java.net/jeps/335.");
- }
-
- @Override
- protected Object interpret(final String s) {
- try {
- return ENGINE.eval(s);
- } catch (final ScriptException e) {
- throw new IllegalStateException("«" + s + "» is not a valid Javascript fragment", e);
- }
- }
-
-}
diff --git a/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/variables/ScalaVariable.java b/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/variables/ScalaVariable.java
deleted file mode 100644
index 0309ca1e3c..0000000000
--- a/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/variables/ScalaVariable.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2010-2019, Danilo Pianini and contributors listed in the main project's alchemist/build.gradle file.
- *
- * This file is part of Alchemist, and is distributed under the terms of the
- * GNU General Public License, with a linking exception,
- * as described in the file LICENSE in the Alchemist distribution's top directory.
- */
-package it.unibo.alchemist.loader.variables;
-
-import it.unibo.alchemist.scala.ScalaInterpreter;
-
-/**
- * Interpreter that relies on the Scala Toolbox for interpreting the formula
- * String.
- *
- * @param return type
- */
-public final class ScalaVariable extends ScriptVariable {
-
- private static final long serialVersionUID = 1L;
-
- /**
- * @param formula the Scala script
- */
- public ScalaVariable(final String formula) {
- super(formula);
- }
-
- @Override
- protected R interpret(final String s) {
- try {
- return ScalaInterpreter.apply(s);
- } catch (Throwable t) { // NOPMD
- throw new IllegalStateException("«" + s + "» is not a valid Scala script.", t);
- }
- }
-
-}
diff --git a/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/variables/ScriptVariable.java b/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/variables/ScriptVariable.java
deleted file mode 100644
index 66ce91251a..0000000000
--- a/alchemist/alchemist-loading/src/main/java/it/unibo/alchemist/loader/variables/ScriptVariable.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2010-2019, Danilo Pianini and contributors listed in the main project's alchemist/build.gradle file.
- *
- * This file is part of Alchemist, and is distributed under the terms of the
- * GNU General Public License, with a linking exception,
- * as described in the file LICENSE in the Alchemist distribution's top directory.
- */
-package it.unibo.alchemist.loader.variables;
-
-import org.danilopianini.util.Hashes;
-
-import java.util.Map;
-import java.util.stream.Stream;
-
-/**
- * This variable can be initialized by providing a formula where other variables
- * are prefixed with the $ symbol.
- *
- * Subclasses must implement an interpreter able to compute on the string
- * produced by this class.
- *
- * @param Evaluation return type
- */
-public abstract class ScriptVariable implements DependentVariable {
-
- /**
- *
- */
- private static final long serialVersionUID = 1L;
- private static final String RANDOM = "RANDOM";
- private static final String RANDOM_REGEX = "\\$" + RANDOM;
- private final String script;
-
- /**
- * @param formula
- * a valid Javascript expression, where variable names are
- * prefixed with $, and where $RANDOM can be used to generate
- * controlled random values
- */
- public ScriptVariable(final String formula) {
- this.script = formula;
- }
-
- @Override
- public final R getWith(final Map variables) {
- /*
- * 1) Sort variable names by decreasing length.
- * 2) Replace each name with with value in the script
- * 3) Run the script in the engine
- */
- final String[] keys = variables.keySet().stream()
- .sorted((v1, v2) -> Integer.compare(v2.length(), v1.length()))
- .toArray(i -> new String[i]);
- String formula = script;
- if (script.contains(RANDOM)) {
- final Object[] hash = Stream.concat(Stream.of(script), variables.values().stream()).toArray();
- final double random = Math.abs((double) Hashes.hash32(hash)) / Integer.MAX_VALUE;
- formula = script.replaceAll(RANDOM_REGEX, Double.toString(random));
- }
- for (final String var : keys) {
- formula = formula.replaceAll("\\$" + var, variables.get(var).toString());
- }
- return interpret(formula);
- }
-
- /**
- * @param s
- * the string where variables are replaced by their string value
- * @return the result of the interpretation
- */
- protected abstract R interpret(String s);
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String toString() {
- return script;
- }
-
-}
diff --git a/alchemist/alchemist-loading/src/main/kotlin/it/unibo/alchemist/loader/variables/GroovyVariable.kt b/alchemist/alchemist-loading/src/main/kotlin/it/unibo/alchemist/loader/variables/GroovyVariable.kt
deleted file mode 100644
index d47ce9c9f8..0000000000
--- a/alchemist/alchemist-loading/src/main/kotlin/it/unibo/alchemist/loader/variables/GroovyVariable.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2010-2019, Danilo Pianini and contributors listed in the main project's alchemist/build.gradle file.
- *
- * This file is part of Alchemist, and is distributed under the terms of the
- * GNU General Public License, with a linking exception,
- * as described in the file LICENSE in the Alchemist distribution's top directory.
- */
-
-package it.unibo.alchemist.loader.variables
-
-import groovy.util.Eval
-
-/*
- * A variable written as Groovy script
- */
-class GroovyVariable(formula: String) : ScriptVariable(formula) {
- override fun interpret(s: String): R {
- try {
- @Suppress("UNCHECKED_CAST")
- return Eval.me(s) as R
- } catch (e: Exception) {
- throw IllegalStateException("«$s» is not a valid Groovy script", e)
- }
- }
-}
\ No newline at end of file
diff --git a/alchemist/alchemist-loading/src/main/kotlin/it/unibo/alchemist/loader/variables/JSR223Variable.kt b/alchemist/alchemist-loading/src/main/kotlin/it/unibo/alchemist/loader/variables/JSR223Variable.kt
new file mode 100644
index 0000000000..e341143222
--- /dev/null
+++ b/alchemist/alchemist-loading/src/main/kotlin/it/unibo/alchemist/loader/variables/JSR223Variable.kt
@@ -0,0 +1,45 @@
+package it.unibo.alchemist.loader.variables
+
+import javax.script.Bindings
+import javax.script.ScriptEngineManager
+import javax.script.ScriptException
+
+/**
+ * This variable loads any [JSR-233](http://archive.fo/PGdk8) language available in the classpath.
+ *
+ * @param R return type of the variable
+ * @constructor builds a new JSR223Variable given a language name and a script.
+ *
+ * @param language can be the name of the language, the file extension, or its mime type
+ * @param formula the script that will get interpreted
+ */
+data class JSR223Variable(val language: String, val formula: String) : DependentVariable {
+
+ private val engine = with(ScriptEngineManager()) {
+ getEngineByName(language) ?: getEngineByExtension(language) ?: getEngineByMimeType(language)
+ ?: throw IllegalArgumentException("$language is not an available language. Your environment supports the following languages:" +
+ engineFactories.map {
+ " - ${it.languageName }, aka ${it.extensions + it.mimeTypes} (${it.languageVersion} on ${it.engineName} ${it.engineVersion})"
+ }.joinToString(separator = System.lineSeparator(), prefix = System.lineSeparator()))
+ }
+
+ /**
+ * Given the current controlled variables, computes the current values for
+ * this variable.
+ *
+ * @param variables
+ * a mapping between variable names and values
+ * @return the value for this value
+ * @throws IllegalStateException
+ * if the value can not be computed, e.g. because there are
+ * unassigned required variables
+ */
+ @Suppress("UNCHECKED_CAST")
+ override fun getWith(variables: Map): R = try {
+ engine.eval(formula, variables.asBindings()) as R
+ } catch (e: ScriptException) {
+ throw IllegalStateException(e)
+ }
+
+ fun Map.asBindings(): Bindings = object : Bindings, MutableMap by this.toMutableMap() { }
+}
\ No newline at end of file
diff --git a/alchemist/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestKtVariable.kt b/alchemist/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestKtVariable.kt
new file mode 100644
index 0000000000..2de48ae99c
--- /dev/null
+++ b/alchemist/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestKtVariable.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010-2019, Danilo Pianini and contributors listed in the main project's alchemist/build.gradle file.
+ *
+ * This file is part of Alchemist, and is distributed under the terms of the
+ * GNU General Public License, with a linking exception,
+ * as described in the file LICENSE in the Alchemist distribution's top directory.
+ */
+
+package it.unibo.alchemist.test
+
+import io.kotlintest.specs.StringSpec
+import it.unibo.alchemist.loader.YamlLoader
+import it.unibo.alchemist.model.interfaces.Position
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertNotNull
+import org.kaikikm.threadresloader.ResourceLoader
+
+class TestKtVariable> : StringSpec({
+ "test loading a kotlin variable" {
+ val file = ResourceLoader.getResourceAsStream("testktvar.yml")
+ assertNotNull(file)
+ val loader = YamlLoader(file)
+ assertNotNull(loader.getWith(emptyMap()))
+ loader.constants.let { variable ->
+ assertEquals(23, variable["a"])
+ assertEquals(listOf("a", 5.5), variable["test2"])
+ assertEquals(listOf(23, 5.5), variable["test"])
+ }
+ }
+})
diff --git a/alchemist/alchemist-loading/src/test/resources/isac/14-yaml-vars.yml b/alchemist/alchemist-loading/src/test/resources/isac/14-yaml-vars.yml
index 31930fb153..c707bb543b 100644
--- a/alchemist/alchemist-loading/src/test/resources/isac/14-yaml-vars.yml
+++ b/alchemist/alchemist-loading/src/test/resources/isac/14-yaml-vars.yml
@@ -10,11 +10,11 @@ variables:
step: 1
default: 5
mSize: &mSize
- formula: -$size
+ formula: -size
sourceStart: &sourceStart
- formula: $mSize / 10
+ formula: mSize / 10
sourceSize: &sourceSize
- formula: $size / 5
+ formula: size / 5
network-model:
type: ConnectWithinDistance
diff --git a/alchemist/alchemist-loading/src/test/resources/isac/15-move.yml b/alchemist/alchemist-loading/src/test/resources/isac/15-move.yml
index 28ba90e1ab..7b43805bab 100644
--- a/alchemist/alchemist-loading/src/test/resources/isac/15-move.yml
+++ b/alchemist/alchemist-loading/src/test/resources/isac/15-move.yml
@@ -10,11 +10,11 @@ variables:
step: 1
default: 5
mSize: &mSize
- formula: -$size
+ formula: -size
sourceStart: &sourceStart
- formula: $mSize / 10
+ formula: mSize / 10
sourceSize: &sourceSize
- formula: $size / 5
+ formula: size / 5
network-model:
type: ConnectWithinDistance
@@ -30,11 +30,11 @@ send: &grad
type: DiracComb
parameters: [0.1]
program: >
- {token, def: N>0, L} --> {token, def: N + 1, L}
+ {token, def: N>0, L} --> {token, N + 1, L}
# Cleanup old information
- program: >
+ - program: >
{token, def: N>30, L} -->
-
+
move: &move
- time-distribution: 0.5
type: Event
diff --git a/alchemist/alchemist-loading/src/test/resources/synthetic/testlist.yml b/alchemist/alchemist-loading/src/test/resources/synthetic/testlist.yml
index 5fb3635af3..ca0189587e 100644
--- a/alchemist/alchemist-loading/src/test/resources/synthetic/testlist.yml
+++ b/alchemist/alchemist-loading/src/test/resources/synthetic/testlist.yml
@@ -4,7 +4,7 @@ variables:
myvar: &myvar
formula: 1
myvar2: &myvar2
- formula: $myvar * 100
+ formula: myvar * 100
environment:
type: Continuous2DEnvironment
diff --git a/alchemist/alchemist-loading/src/test/resources/testgps.yml b/alchemist/alchemist-loading/src/test/resources/testgps.yml
index bab316e64d..0c236a363c 100755
--- a/alchemist/alchemist-loading/src/test/resources/testgps.yml
+++ b/alchemist/alchemist-loading/src/test/resources/testgps.yml
@@ -17,4 +17,4 @@ displacements:
type: FromGPSTrace
parameters: [7, "gpsTrace", true, "AlignToSimulationTime"]
programs:
- - *move
\ No newline at end of file
+ - *move
diff --git a/alchemist/alchemist-loading/src/test/resources/testktvar.yml b/alchemist/alchemist-loading/src/test/resources/testktvar.yml
new file mode 100644
index 0000000000..d34ade6812
--- /dev/null
+++ b/alchemist/alchemist-loading/src/test/resources/testktvar.yml
@@ -0,0 +1,17 @@
+variables:
+ a:
+ formula: 22 + 1
+ language: kts
+ test:
+ formula: |
+ import it.unibo.alchemist.loader.YamlLoader
+ import kotlin.collections.listOf
+ val b = 5.5
+ listOf(bindings["a"], b)
+ language: kotlin
+ test2:
+ formula: |
+ val b = 5.5
+ listOf("a", b)
+ language: kotlin
+incarnation: sapere
diff --git a/alchemist/buildSrc/src/main/kotlin/Libs.kt b/alchemist/buildSrc/src/main/kotlin/Libs.kt
index 6de0065856..fdfa7a9e9a 100644
--- a/alchemist/buildSrc/src/main/kotlin/Libs.kt
+++ b/alchemist/buildSrc/src/main/kotlin/Libs.kt
@@ -204,7 +204,7 @@ object Libs {
/**
* https://groovy-lang.org */
- const val groovy: String = "org.codehaus.groovy:groovy:" + Versions.groovy
+ const val groovy_jsr223: String = "org.codehaus.groovy:groovy-jsr223:" + Versions.groovy_jsr223
/**
* http://www.controlsfx.org/ */
@@ -248,6 +248,11 @@ object Libs {
"org.danilopianini:thread-inheritable-resource-loader:" +
Versions.thread_inheritable_resource_loader
+ /**
+ * https://www.eclipse.org/emf */
+ const val org_eclipse_emf_ecore: String = "org.eclipse.emf:org.eclipse.emf.ecore:" +
+ Versions.org_eclipse_emf_ecore
+
const val org_jetbrains_dokka_gradle_plugin: String =
"org.jetbrains.dokka:org.jetbrains.dokka.gradle.plugin:" +
Versions.org_jetbrains_dokka_gradle_plugin
@@ -267,6 +272,12 @@ object Libs {
"org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:" +
Versions.org_jetbrains_kotlin
+ /**
+ * https://kotlinlang.org/ */
+ const val kotlin_scripting_jsr223_embeddable: String =
+ "org.jetbrains.kotlin:kotlin-scripting-jsr223-embeddable:" +
+ Versions.org_jetbrains_kotlin
+
/**
* https://kotlinlang.org/ */
const val kotlin_stdlib: String = "org.jetbrains.kotlin:kotlin-stdlib:" +
diff --git a/alchemist/buildSrc/src/main/kotlin/Versions.kt b/alchemist/buildSrc/src/main/kotlin/Versions.kt
index 221dbe7946..63e50ef62c 100644
--- a/alchemist/buildSrc/src/main/kotlin/Versions.kt
+++ b/alchemist/buildSrc/src/main/kotlin/Versions.kt
@@ -1,3 +1,5 @@
+import kotlin.String
+
/**
* Find which updates are available by running
* `$ ./gradlew buildSrcVersions`
@@ -13,7 +15,7 @@ object Versions {
const val com_github_cb372: String = "0.9.3" // available: "0.28.0"
- const val rtree: String = "0.8.7"
+ const val rtree: String = "0.8.7"
const val com_github_maiflai_scalatest_gradle_plugin: String = "0.25"
@@ -23,7 +25,7 @@ object Versions {
const val gson: String = "2.8.5"
- const val guava: String = "28.0-jre"
+ const val guava: String = "28.0-jre"
const val concurrentlinkedhashmap_lru: String = "1.4.2"
@@ -37,7 +39,7 @@ object Versions {
const val miglayout_swing: String = "5.2"
- const val ktlint: String = "0.32.0" // available: "0.33.0"
+ const val ktlint: String = "0.33.0"
const val konf: String = "0.13.3"
@@ -51,13 +53,13 @@ object Versions {
const val de_fayard_buildsrcversions_gradle_plugin: String = "0.3.2"
- const val classgraph: String = "4.8.37" // available: "4.8.41"
+ const val classgraph: String = "4.8.37" // available: "4.8.43"
const val io_github_javaeden_orchid: String = "0.17.1"
const val jpx: String = "1.4.0"
- const val kotlintest_runner_junit5: String = "3.3.2"
+ const val kotlintest_runner_junit5: String = "3.3.2" // available: "3.3.3"
const val scafi_core_2_12: String = "0.3.2" // available: "53ddebd1"
@@ -73,7 +75,7 @@ object Versions {
const val org_apache_ignite: String = "2.7.0" // available: "2.7.5"
- const val groovy: String = "2.5.7"
+ const val groovy_jsr223: String = "2.5.7"
const val controlsfx: String = "9.0.0" // available: "11.0.0"
@@ -95,21 +97,23 @@ object Versions {
const val thread_inheritable_resource_loader: String = "0.3.0"
+ const val org_eclipse_emf_ecore: String = "2.12.0" // available: "2.18.0"
+
const val org_jetbrains_dokka_gradle_plugin: String = "0.9.17" // available: "0.9.18"
- const val org_jetbrains_kotlin_jvm_gradle_plugin: String = "1.3.40"
+ const val org_jetbrains_kotlin_jvm_gradle_plugin: String = "1.3.40"
- const val org_jetbrains_kotlin: String = "1.3.40"
+ const val org_jetbrains_kotlin: String = "1.3.40"
const val annotations: String = "17.0.0"
const val jgrapht_core: String = "1.3.1"
- const val org_jlleitschuh_gradle_ktlint_gradle_plugin: String = "8.1.0"
+ const val org_jlleitschuh_gradle_ktlint_gradle_plugin: String = "8.1.0"
const val jool_java_8: String = "0.9.14"
- const val org_junit_jupiter: String = "5.4.2"
+ const val org_junit_jupiter: String = "5.4.2" // available: "5.5.0"
const val mapsforge_map_awt: String = "0.11.0"
@@ -119,7 +123,7 @@ object Versions {
const val pegdown: String = "1.6.0"
- const val org_protelis: String = "12.2.0"
+ const val org_protelis: String = "12.2.0"
const val org_scala_lang: String = "2.12.2" // available: "2.13.0"
@@ -137,10 +141,10 @@ object Versions {
object Gradle {
const val runningVersion: String = "5.4.1"
- const val currentVersion: String = "5.4.1"
+ const val currentVersion: String = "5.5"
- const val nightlyVersion: String = "5.6-20190627000039+0000"
+ const val nightlyVersion: String = "5.6-20190702000118+0000"
- const val releaseCandidate: String = "5.5-rc-4"
+ const val releaseCandidate: String = ""
}
}
diff --git a/alchemist/src/orchid/resources/wiki/usage/yaml.md b/alchemist/src/orchid/resources/wiki/usage/yaml.md
index 3a5c5a2bfe..5100ac26fc 100644
--- a/alchemist/src/orchid/resources/wiki/usage/yaml.md
+++ b/alchemist/src/orchid/resources/wiki/usage/yaml.md
@@ -164,6 +164,134 @@ If the simulation is not executed as batch, then the default value is used
### Dependent variables
+Some variables are combination of free parameters.
+Let's suppose that we want to deploy on a circle, but for some reason (e.g. because it is required by the constructor of some action) we need to compute and have available radius and perimeter.
+We don't need to control both of them: the perimeter can be computed.
+Alchemist provides support for performing computation over variables.
+Let's first define our radius.
+We want it to be a free variable, ranging geometrically from 0.1 to 10 in nine steps, and defaulting to 1.
+```yaml
+variables:
+ radius: &radius
+ type: GeometricVariable
+ parameters: [1, 0.1, 10, 9]
+```
+Now we want to compute the diameter.
+We can do so by using the `formula` syntax:
+```yaml
+variables:
+ radius: &radius
+ type: GeometricVariable
+ parameters: [1, 0.1, 10, 9]
+ diameter: &diam
+ formula: Math.PI * 2 * radius
+```
+How does it work?
+Alchemist feeds the formula String to an interpreter and takes the result of the interpretation.
+By default, [Groovy](https://groovy-lang.org/) is used as language to interpret the formula, but other languages can be used.
+
+Variables can be defined in any order.
+Alchemist figures out the dependencies automatically, as far as there are no cyclic dependencies (e.g. variable `a` requires `b`, and `b` requires `a`).
+Please note that the simulator variable dependency resolution system is not designed to solve mathematical systems,
+so even though the problem has a well formed mathematical solution the actual variable resolution may fail;
+e.g. if `a` is defined as `2 * b + 1`, and `b` is defined as `4 - a`, the system **won't** bind `a` to `3` and `b` to `1`,
+but will simply fail complaining about circular dependencies.
+
+### Using different languages
+
+In order to use a language different than Groovy, the user may specify it explicitly by using the `language` keyword.
+For example, Scala can be used:
+```yaml
+variables:
+ radius: &radius
+ type: GeometricVariable
+ parameters: [1, 0.1, 10, 9]
+ diameter: &diam
+ formula: Math.PI * 2 * radius
+ language: scala
+```
+Or Kotlin:
+```yaml
+variables:
+ radius: &radius
+ type: GeometricVariable
+ parameters: [1, 0.1, 10, 9]
+ diameter: &diam
+ formula: listOf(Math.PI, 2.0, 0.3).fold(1.0) { a, b -> a * b }
+ language: kotlin
+```
+
+The system is [JSR-233](http://archive.fo/PGdk8)-compatible, as such, every language with a valid JSR-233 implementation could be used.
+The only requirement for the language to be available is the availability in the runtime classpath of a JSR-233 compatible version of the desired language.
+If Alchemist is being used (as recommended) in conjunction with Gradle, and you want to embed your favorite JSR-233 compatible scripting language, you should have a dependency declaration similar to:
+
+```kotlin
+dependencies {
+ ...
+ runtimeOnly("my.favorite.scripting.language:supporting-jsr233:0.1.0")
+ ...
+}
+```
+
+For instance, Alchemist supports Kotlin and Groovy natively by simply providing in its `build.gradle.kts` something similar to:
+```kotlin
+dependencies {
+ ...
+ runtimeOnly("org.codehaus.groovy:groovy-jsr223:2.5.7")
+ runtimeOnly("org.jetbrains.kotlin:kotlin-scripting-jsr223-embeddable:1.3.40")
+ runtimeOnly("org.jetbrains.kotlin:kotlin-script-runtime:1.3.40")
+ runtimeOnly("org.jetbrains.kotlin:kotlin-script-util:1.3.40")}
+ ...
+```
+Alchemist provides a number of ready-to use interpreters. Besides Groovy (used by default) it includes:
+
+* [Scala](https://www.scala-lang.org/)
+* [Kotlin](https://kotlinlang.org/)
+
+Moreover, several implementations of the Java Virtual Machine feature internal interpreters for
+[ECMAScript](https://www.ecma-international.org/publications/standards/Ecma-262.htm)/
+[Javascript](https://en.wikipedia.org/wiki/JavaScript).
+In case they are provided, such engines can be used without any additional effort.
+Javascript used to be the default for Alchemist, but it has been replaced by Groovy since
+[Nashorn, the interpreter embedded in OpenJDK, is deprecated](https://openjdk.java.net/jeps/335).
+
+
+#### Multiline programs
+
+Sometimes data manipulation can get tricky and trivial scripting may no longer be enough.
+In such cases, and especially with modern languages that allow for a reduced usage of cerimonial semicolons (such as Kotlin and Scala), it can be useful to write multiline programs.
+This can be achieved in YAML by using the pipe `|` operator, as exemplified in the following snippet:
+
+```yaml
+variables:
+ a:
+ formula: 22 + 1
+ language: kt
+ test:
+ formula: |
+ import com.google.common.reflect.TypeToken
+ import com.google.gson.Gson
+ Gson().fromJson>>>>(
+ ClassLoader.getSystemResourceAsStream("explorable-area.json")?.reader(),
+ object: TypeToken>>>>() {}.type
+ )
+ .get("coordinates")!!
+ .first()
+ .map { Pair(it.last(), it.first()) }
+ language: kotlin
+```
+If the string begins with a `|`, its contents preserve newlines, thus allowing for multiline scripts of arbitrary complexity.
+
+#### Known issues
+
+Alchemist exploit JSR-233's variable binding system to let the scripts use variables defined elsewhere.
+Not all languages support this system properly.
+In particular, Kotlin does not (yet) support variable injection and requires a workaround.
+In order for a script to access a variable named `myVar`, the programmer should write instead `bindings["myVar"]`.
+The issue is being tracked as [KT-15125](https://youtrack.jetbrains.com/issue/KT-15125).
+Once it gets solved (if ever), and as soon as Alchemist incorporates the version of Kotlin including the fix,
+the workaround will no longer be necessary.
+
### Using variables
## Controlling the reproducibility