001/* 002 * Copyright (c) 2012-2018 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others 003 * 004 * This program and the accompanying materials are made available under the 005 * terms of the Eclipse Public License 2.0 which is available at 006 * http://www.eclipse.org/legal/epl-2.0. 007 * 008 * SPDX-License-Identifier: EPL-2.0 009 */ 010 011package gololang; 012 013import org.eclipse.golo.compiler.GoloClassLoader; 014import org.eclipse.golo.compiler.GoloCompilationException; 015 016import java.io.ByteArrayInputStream; 017import java.io.IOException; 018import java.io.InputStream; 019import java.nio.charset.StandardCharsets; 020import java.lang.reflect.InvocationTargetException; 021import java.util.*; 022 023/** 024 * An evaluation environment offers facilities for dynamic code compilation, loading and execution from Golo code as 025 * strings. 026 * <p> 027 * An evaluation environment is reusable across several executions. The only exception is when using {@code asModule()}, 028 * as attempts to load a module with the same name as an already loaded one fails. 029 * <p> 030 * Each instance of this class uses a dedicated {@link GoloClassLoader}, hence usual rules about classloader delegation 031 * and isolation apply to evaluation environments. 032 * <p> 033 * While dynamic code evaluation is useful, it shall still be used with care and parsimony. It is especially important 034 * not to abuse {@code run()}, as each invocation triggers the generation of a one-shot class. 035 * <p> 036 * Here is an example usage of this API: 037 * <pre class="listing"><code class="lang-golo" data-lang="golo"> 038 * let env = EvaluationEnvironment() 039 * let code = 040 * """ 041 * function a = -> "a." 042 * function b = -> "b." 043 * """ 044 * let mod = env: anonymousModule(code) 045 * let a = fun("a", mod) 046 * let b = fun("b", mod) 047 * println(a()) 048 * println(b()) 049 * </code></pre> 050 * <p> 051 * While this class is expected to be used from Golo code, it can also be used as a convenient way to embed Golo into 052 * polyglot JVM applications. 053 */ 054public class EvaluationEnvironment { 055 056 private final GoloClassLoader goloClassLoader; 057 private final List<String> imports = new LinkedList<>(); 058 059 private static String anonymousFilename() { 060 return "$Anonymous$_" + System.nanoTime() + ".golo"; 061 } 062 063 private static String anonymousModuleName() { 064 return "module anonymous" + System.nanoTime(); 065 } 066 067 /** 068 * Creates an evaluation environment using the current thread context classloader. 069 */ 070 public EvaluationEnvironment() { 071 this(Thread.currentThread().getContextClassLoader()); 072 } 073 074 /** 075 * Creates an evaluation environment using a parent classloader. 076 * 077 * @param parentClassLoader the parent classloader. 078 */ 079 public EvaluationEnvironment(ClassLoader parentClassLoader) { 080 goloClassLoader = new GoloClassLoader(parentClassLoader); 081 } 082 083 /** 084 * Imports symbols. 085 * <p> 086 * Each symbol generates an equivalent {@code import} statement in the corresponding Golo code. Calling 087 * {@code imports("foo.Bar", "bar.Baz")} means that the subsequent code evaluations have {@code import foo.Bar} and 088 * {@code import bar.Baz} statements. 089 * <p> 090 * Note that this has no effect for {@link #asModule(String)}. Also, calling this method several times accumulates 091 * the imports, in order. 092 * 093 * @param head the first imported symbol. 094 * @param tail the next imported symbols. 095 * @return this evaluation environment. 096 */ 097 public EvaluationEnvironment imports(String head, String... tail) { 098 imports.add(head); 099 Collections.addAll(imports, tail); 100 return this; 101 } 102 103 /** 104 * Clears all import symbols for the next code evaluation requests. 105 * 106 * @return this evaluation environment. 107 */ 108 public EvaluationEnvironment clearImports() { 109 imports.clear(); 110 return this; 111 } 112 113 /** 114 * Evaluates a complete module string. 115 *<p> 116 * For instance: 117 * <pre class="listing"><code class="lang-golo" data-lang="golo"> 118 * let code = 119 * """ 120 * module foo 121 * 122 * function a = -> "a!" 123 * function b = -> "b!" 124 * """ 125 * let mod = env: asModule(code) 126 * let a = fun("a", mod) 127 * let b = fun("b", mod) 128 * println(a()) 129 * println(b()) 130 * </code></pre> 131 * 132 * @param source the module Golo source code as a string. 133 * @return the corresponding module, as a {@link Class}. 134 * @see gololang.Predefined#fun(Class, Object, Object) 135 */ 136 public Object asModule(String source) { 137 try (InputStream in = new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8))) { 138 return goloClassLoader.load(anonymousFilename(), in); 139 } catch (IOException e) { 140 throw new RuntimeException(e); 141 } catch (GoloCompilationException e) { 142 e.setSourceCode(source); 143 throw e; 144 } 145 } 146 147 /** 148 * Loads an anonymous module. This is the same as {@link #asModule(String)}, except that the code does not contain 149 * a {@code module} declaration. 150 * 151 * <pre class="listing"><code class="lang-golo" data-lang="golo"> 152 * let code = 153 * """ 154 * function a = -> "a!" 155 * function b = -> "b!" 156 * """ 157 * let mod = env: anonymousModule(code) 158 * let a = fun("a", mod) 159 * let b = fun("b", mod) 160 * println(a()) 161 * println(b()) 162 * </code></pre> 163 * 164 * @param source the module Golo source code as a string. 165 * @return the corresponding module, as a {@link Class}. 166 * @see gololang.Predefined#fun(Class, Object, Object) 167 */ 168 public Object anonymousModule(String source) { 169 return asModule(anonymousModuleName() + "\n\n" + source); 170 } 171 172 /** 173 * Defines a function, and returns it. 174 * 175 * <pre class="listing"><code class="lang-golo" data-lang="golo"> 176 * let code = "|a, b| -> (a + b) * 2" 177 * let f = env: def(code) 178 * println(f(10, 20)) 179 * </code></pre> 180 * 181 * @param source the function code. 182 * @return the function as a {@link gololang.FunctionReference} instance. 183 */ 184 public Object def(String source) { 185 return loadAndRun("return " + source, "$_code"); 186 } 187 188 /** 189 * Evaluates some code as the body of a function and returns it. 190 * 191 * <pre class="listing"><code class="lang-golo" data-lang="golo"> 192 * let code = "return (a + b) * 2" 193 * let f = env: asFunction(code, "a", "b") 194 * println(f(10, 20)) 195 * </code></pre> 196 * 197 * @param source the function body source code. 198 * @param argumentNames the argument names. 199 * @return the function as a {@link gololang.FunctionReference} instance. 200 */ 201 public Object asFunction(String source, String... argumentNames) { 202 return loadAndRun(source, "$_code_ref", argumentNames); 203 } 204 205 /** 206 * Runs some code as the body of a function and returns the value. The code shall use {@code return} statements 207 * to provide return values, if any. 208 * 209 * <pre class="listing"><code class="lang-golo" data-lang="golo"> 210 * let code = """println(">>> run") 211 * foreach (i in range(0, 3)) { 212 * println("w00t") 213 * } 214 * return 666""" 215 * env: run(code) 216 * 217 * </code></pre> 218 * 219 * @param source the source to run. 220 * @return the return value, or {@code null} if no {@code return} statement is used. 221 */ 222 public Object run(String source) { 223 return loadAndRun(source, "$_code"); 224 } 225 226 /** 227 * Runs some code as the body of a function and returns the value. This is the same as {@link #run(String)}, but it 228 * takes a set of reference bindings in a map. Each reference is equivalent to a {@code let} statement. 229 * 230 * <pre class="listing"><code class="lang-golo" data-lang="golo"> 231 * let code = """println(">>> run_map") 232 * println(a) 233 * println(b) 234 * """ 235 * let values = java.util.TreeMap(): add("a", 1): add("b", 2) 236 * env: run(code, values) 237 * </code></pre> 238 * 239 * @param source the source to run. 240 * @param context a map of bindings from name to values. 241 * @return the return value, or {@code null} if no {@code return} statement is used. 242 */ 243 public Object run(String source, Map<String, Object> context) { 244 StringBuilder builder = new StringBuilder(); 245 for (String param : context.keySet()) { 246 builder 247 .append("let ") 248 .append(param) 249 .append(" = $_env: get(\"") 250 .append(param) 251 .append("\")\n"); 252 } 253 builder.append(source); 254 return loadAndRun(builder.toString(), "$_code", new String[]{"$_env"}, new Object[]{context}); 255 } 256 257 private Class<?> wrapAndLoad(String source, String... argumentNames) { 258 StringBuilder builder = new StringBuilder() 259 .append(anonymousModuleName()) 260 .append("\n"); 261 for (String importSymbol : imports) { 262 builder.append("import ").append(importSymbol).append("\n"); 263 } 264 builder.append("\nfunction $_code = "); 265 if (argumentNames.length > 0) { 266 builder.append("| "); 267 final int lastIndex = argumentNames.length - 1; 268 for (int i = 0; i < argumentNames.length; i++) { 269 builder.append(argumentNames[i]); 270 if (i < lastIndex) { 271 builder.append(", "); 272 } 273 } 274 builder.append(" |"); 275 } 276 builder 277 .append(" {\n") 278 .append(source) 279 .append("\n}\n\n") 280 .append("function $_code_ref = -> ^$_code\n\n"); 281 return (Class<?>) asModule(builder.toString()); 282 } 283 284 private Object loadAndRun(String source, String target, String... argumentNames) { 285 try { 286 Class<?> module = wrapAndLoad(source, argumentNames); 287 return module.getMethod(target).invoke(null); 288 } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 289 throw new RuntimeException(e); 290 } 291 } 292 293 private Object loadAndRun(String source, String target, String[] argumentNames, Object[] arguments) { 294 try { 295 Class<?> module = wrapAndLoad(source, argumentNames); 296 Class<?>[] type = new Class<?>[argumentNames.length]; 297 Arrays.fill(type, Object.class); 298 return module.getMethod(target, type).invoke(null, arguments); 299 } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { 300 throw new RuntimeException(e); 301 } 302 } 303}