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