From 72545d5d5cf1d59254f64b13688175e0f301e324 Mon Sep 17 00:00:00 2001 From: Florian Weikert Date: Fri, 28 Aug 2015 14:30:06 +0000 Subject: When a Skylark macro creates a native rule, it also sets the following rule attributes: generator_{function, name, location} -- MOS_MIGRATED_REVID=101774632 --- .../google/devtools/build/lib/events/Location.java | 26 +++++++++ .../build/lib/packages/PackageFactory.java | 9 +-- .../devtools/build/lib/packages/RuleFactory.java | 65 ++++++++++++++++++--- .../build/lib/rules/SkylarkRuleClassFunctions.java | 27 +-------- .../devtools/build/lib/syntax/BaseFunction.java | 34 +++-------- .../devtools/build/lib/syntax/Environment.java | 67 +++++++++++++++++++--- .../build/lib/syntax/FuncallExpression.java | 14 ++++- .../devtools/build/lib/syntax/Identifier.java | 3 +- .../build/lib/syntax/SkylarkEnvironment.java | 35 +++++------ .../build/lib/syntax/StackTraceElement.java | 26 +++++++++ .../build/lib/syntax/UserDefinedFunction.java | 5 -- 11 files changed, 213 insertions(+), 98 deletions(-) create mode 100644 src/main/java/com/google/devtools/build/lib/syntax/StackTraceElement.java (limited to 'src/main') diff --git a/src/main/java/com/google/devtools/build/lib/events/Location.java b/src/main/java/com/google/devtools/build/lib/events/Location.java index 608d307c68..74b04a913a 100644 --- a/src/main/java/com/google/devtools/build/lib/events/Location.java +++ b/src/main/java/com/google/devtools/build/lib/events/Location.java @@ -291,4 +291,30 @@ public abstract class Location implements Serializable { return null; } }; + + /** + * Returns the location in the format "filename:line". + * + *

If such a location is not defined, this method returns an empty string. + */ + public static String printPathAndLine(Location location) { + return (location == null) ? "" : location.printPathAndLine(); + } + + /** + * Returns this location in the format "filename:line". + */ + public String printPathAndLine() { + StringBuilder builder = new StringBuilder(); + PathFragment path = getPath(); + if (path != null) { + builder.append(path.getPathString()); + } + + LineAndColumn position = getStartLineAndColumn(); + if (position != null) { + builder.append(":").append(position.getLine()); + } + return builder.toString(); + } } diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java index 4fdd77edab..8fed87b539 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java +++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java @@ -899,10 +899,11 @@ public final class PackageFactory { String ruleClassName, PackageContext context, Map kwargs, - FuncallExpression ast) + FuncallExpression ast, + Environment env) throws RuleFactory.InvalidRuleException, Package.NameConflictException { RuleClass ruleClass = getBuiltInRuleClass(ruleClassName, ruleFactory); - RuleFactory.createAndAddRule(context, ruleClass, kwargs, ast); + RuleFactory.createAndAddRule(context, ruleClass, kwargs, ast, env.getStackTrace()); } private static RuleClass getBuiltInRuleClass(String ruleClassName, RuleFactory ruleFactory) { @@ -935,13 +936,13 @@ public final class PackageFactory { private static BuiltinFunction newRuleFunction( final RuleFactory ruleFactory, final String ruleClass) { return new BuiltinFunction(ruleClass, FunctionSignature.KWARGS, BuiltinFunction.USE_AST_ENV) { - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "unused"}) public Environment.NoneType invoke(Map kwargs, FuncallExpression ast, Environment env) throws EvalException { env.checkLoadingPhase(ruleClass, ast.getLocation()); try { - addRule(ruleFactory, ruleClass, getContext(env, ast), kwargs, ast); + addRule(ruleFactory, ruleClass, getContext(env, ast), kwargs, ast, env); } catch (RuleFactory.InvalidRuleException | Package.NameConflictException e) { throw new EvalException(ast.getLocation(), e.getMessage()); } diff --git a/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java b/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java index a9408ee754..930912d1e4 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java +++ b/src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java @@ -15,6 +15,7 @@ package com.google.devtools.build.lib.packages; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.events.Location; @@ -23,6 +24,7 @@ import com.google.devtools.build.lib.packages.PackageFactory.PackageContext; import com.google.devtools.build.lib.syntax.FuncallExpression; import com.google.devtools.build.lib.syntax.Label; import com.google.devtools.build.lib.syntax.Label.SyntaxException; +import com.google.devtools.build.lib.syntax.StackTraceElement; import java.util.Map; import java.util.Set; @@ -75,7 +77,8 @@ public class RuleFactory { Map attributeValues, EventHandler eventHandler, FuncallExpression ast, - Location location) + Location location, + ImmutableList stackTrace) throws InvalidRuleException, NameConflictException { Preconditions.checkNotNull(ruleClass); String ruleClassName = ruleClass.getName(); @@ -105,8 +108,9 @@ public class RuleFactory { } try { - Rule rule = ruleClass.createRuleWithLabel(pkgBuilder, label, attributeValues, - eventHandler, ast, location); + Rule rule = ruleClass.createRuleWithLabel(pkgBuilder, label, + addGeneratorAttributesForMacros(attributeValues, stackTrace), eventHandler, ast, + location); return rule; } catch (SyntaxException e) { throw new RuleFactory.InvalidRuleException(ruleClass + " " + e.getMessage()); @@ -114,7 +118,22 @@ public class RuleFactory { } /** - * Creates and returns a rule instance. + * Creates and returns a rule instance (without a stack trace). + */ + static Rule createRule( + Package.Builder pkgBuilder, + RuleClass ruleClass, + Map attributeValues, + EventHandler eventHandler, + FuncallExpression ast, + Location location) + throws InvalidRuleException, NameConflictException { + return createRule(pkgBuilder, ruleClass, attributeValues, eventHandler, ast, location, + ImmutableList.of()); + } + + /** + * Creates a rule instance, adds it to the package and returns it. * * @param pkgBuilder the under-construction package to which the rule belongs * @param ruleClass the class of the rule; this must not be null @@ -126,6 +145,8 @@ public class RuleFactory { * rule creation * @param ast the abstract syntax tree of the rule expression (optional) * @param location the location at which this rule was declared + * @param stackTrace the stack trace containing all functions that led to the creation of + * this rule (optional) * @throws InvalidRuleException if the rule could not be constructed for any * reason (e.g. no name attribute is defined) * @throws NameConflictException @@ -135,8 +156,11 @@ public class RuleFactory { Map attributeValues, EventHandler eventHandler, FuncallExpression ast, - Location location) throws InvalidRuleException, NameConflictException { - Rule rule = createRule(pkgBuilder, ruleClass, attributeValues, eventHandler, ast, location); + Location location, + ImmutableList stackTrace) + throws InvalidRuleException, NameConflictException { + Rule rule = createRule( + pkgBuilder, ruleClass, attributeValues, eventHandler, ast, location, stackTrace); pkgBuilder.addRule(rule); return rule; } @@ -144,9 +168,11 @@ public class RuleFactory { public static Rule createAndAddRule(PackageContext context, RuleClass ruleClass, Map attributeValues, - FuncallExpression ast) throws InvalidRuleException, NameConflictException { + FuncallExpression ast, + ImmutableList stackTrace) + throws InvalidRuleException, NameConflictException { return createAndAddRule(context.pkgBuilder, ruleClass, attributeValues, context.eventHandler, - ast, ast.getLocation()); + ast, ast.getLocation(), stackTrace); } /** @@ -158,4 +184,27 @@ public class RuleFactory { super(message); } } + + /** + * If the rule was created by a macro, this method sets the appropriate values for the + * attributes generator_{name, function, location} and returns all attributes. + * + *

Otherwise, it returns the given attributes without any changes. + */ + private static Map addGeneratorAttributesForMacros( + Map args, ImmutableList stackTrace) { + if (stackTrace.size() <= 1) { + // It cannot be a macro when the stack trace is empty or when it only contains the rule + // itself. + return args; + } + + StackTraceElement generator = stackTrace.get(0); + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.putAll(args); + builder.put("generator_name", args.get("name")); + builder.put("generator_function", generator.getName()); + builder.put("generator_location", Location.printPathAndLine(generator.getLocation())); + return builder.build(); + } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java index da14fc6d44..a27c6f8f85 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java @@ -321,36 +321,13 @@ public class SkylarkRuleClassFunctions { } RuleClass ruleClass = builder.build(ruleClassName); PackageContext pkgContext = (PackageContext) env.lookup(PackageFactory.PKG_CONTEXT); - return RuleFactory.createAndAddRule(pkgContext, ruleClass, - addGeneratorAttributesForMacros((Map) args[0], env), ast); + return RuleFactory.createAndAddRule( + pkgContext, ruleClass, (Map) args[0], ast, env.getStackTrace()); } catch (InvalidRuleException | NameConflictException | NoSuchVariableException e) { throw new EvalException(ast.getLocation(), e.getMessage()); } } - /** - * If the current rule was created by a macro, this method sets the appropriate values for the - * attributes generator_{name, function, location} and returns a map of all attribute values. - * - *

Otherwise, the specified map of arguments is returned without any changes. - */ - private Map addGeneratorAttributesForMacros( - Map args, Environment env) { - ImmutableList stackTrace = env.getStackTrace(); - if (stackTrace.isEmpty()) { - // If there was a macro, it would be on the stack. - return args; - } - - BaseFunction generator = stackTrace.get(0); // BaseFunction - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.putAll(args); - builder.put("generator_name", args.get("name")); - builder.put("generator_function", generator.getName()); - builder.put("generator_location", generator.getLocationPathAndLine()); - return builder.build(); - } - /** * Export a RuleFunction from a Skylark file with a given name. */ diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java index 43d27a2147..b7f562027b 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java @@ -21,9 +21,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import com.google.devtools.build.lib.events.Location; -import com.google.devtools.build.lib.events.Location.LineAndColumn; import com.google.devtools.build.lib.packages.Type.ConversionException; -import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.HashMap; @@ -61,7 +59,7 @@ import javax.annotation.Nullable; // Provide optimized argument frobbing depending of FunctionSignature and CallerSignature // (that FuncallExpression must supply), optimizing for the all-positional and all-keyword cases. // Also, use better pure maps to minimize map O(n) re-creation events when processing keyword maps. -public abstract class BaseFunction { +public abstract class BaseFunction implements StackTraceElement { // The name of the function private final String name; @@ -100,6 +98,7 @@ public abstract class BaseFunction { /** Returns the name of this function. */ + @Override public String getName() { return name; } @@ -214,7 +213,7 @@ public abstract class BaseFunction { // Note that this variable will be adjusted down if there are extra positionals, // after these extra positionals are dumped into starParam. - int numPositionalArgs = args.size(); + int numPositionalArgs = (args == null) ? 0 : args.size(); int numMandatoryPositionalParams = shape.getMandatoryPositionals(); int numOptionalPositionalParams = shape.getOptionalPositionals(); @@ -416,8 +415,8 @@ public abstract class BaseFunction { @Nullable Environment parentEnv) throws EvalException, InterruptedException { Environment env = getOrCreateChildEnvironment(parentEnv); - Preconditions.checkState(isConfigured(), "Function %s was not configured", getName()); + Preconditions.checkState(isConfigured(), "Function %s was not configured", getName()); // ast is null when called from Java (as there's no Skylark call site). Location loc = ast == null ? location : ast.getLocation(); @@ -571,26 +570,9 @@ public abstract class BaseFunction { return Objects.hash(name, location); } - /** - * Returns the location (filename:line) of the BaseFunction's definition. - * - *

If such a location is not defined, this method returns an empty string. - */ - public String getLocationPathAndLine() { - if (location == null) { - return ""; - } - - StringBuilder builder = new StringBuilder(); - PathFragment path = location.getPath(); - if (path != null) { - builder.append(path.getPathString()); - } - - LineAndColumn position = location.getStartLineAndColumn(); - if (position != null) { - builder.append(":").append(position.getLine()); - } - return builder.toString(); + @Override + @Nullable + public Location getLocation() { + return location; } } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java index 3b4342a5e5..f46538b4c1 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java @@ -142,23 +142,35 @@ public class Environment { */ @Nullable protected EventHandler eventHandler; + private ImmutableList stackTrace; + /** * Constructs an empty root non-Skylark environment. * The root environment is also the global environment. */ - public Environment() { + public Environment(ImmutableList stackTrace) { this.parent = null; this.importedExtensions = new HashMap<>(); + this.stackTrace = stackTrace; setupGlobal(); } + public Environment() { + this(ImmutableList.of()); + } + /** * Constructs an empty child environment. */ - public Environment(Environment parent) { + public Environment(Environment parent, ImmutableList stackTrace) { Preconditions.checkNotNull(parent); this.parent = parent; this.importedExtensions = new HashMap<>(); + this.stackTrace = stackTrace; + } + + public Environment(Environment parent) { + this(parent, ImmutableList.of()); } /** @@ -388,12 +400,53 @@ public class Environment { return nameSpaceFunctions != null ? nameSpaceFunctions.keySet() : ImmutableSet.of(); } + public ImmutableList getStackTrace() { + return stackTrace; + } + + /** + * Adds the given element to the stack trace (iff the stack is empty) and returns whether it was + * successful. + */ + public boolean tryAddingStackTraceRoot(StackTraceElement element) { + if (stackTrace.isEmpty()) { + stackTrace = ImmutableList.of(element); + return true; + } + return false; + } + + /** + * Removes the only remaining element from the stack trace. + * + *

This particular element describes the outer-most calling function (usually a rule). + * + *

This method is required since {@link FuncallExpression} does not create a new {@link + * Environment}, hence it has to add and remove its {@link StackTraceElement} from an existing + * one. + */ + public void removeStackTraceRoot() { + Preconditions.checkArgument(stackTrace.size() == 1); + stackTrace = ImmutableList.of(); + } + + /** + * Returns whether the given {@link BaseFunction} is part of this {@link Environment}'s stack + * trace. + */ + public boolean stackTraceContains(BaseFunction function) { + for (StackTraceElement element : stackTrace) { + if (element.equals(function)) { + return true; + } + } + return false; + } + /** - * Return the current stack trace (list of functions). + * Returns a copy of this {@link Environment}'s stack trace, including the specified element. */ - public ImmutableList getStackTrace() { - // Empty list, since this environment does not allow function definition - // (see SkylarkEnvironment) - return ImmutableList.of(); + protected ImmutableList getCopyOfUpdatedStackTrace(StackTraceElement toAdd) { + return new ImmutableList.Builder().addAll(stackTrace).add(toAdd).build(); } } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java index 2e90afd3f9..623706500c 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java @@ -481,7 +481,19 @@ public final class FuncallExpression extends Expression { @Override Object eval(Environment env) throws EvalException, InterruptedException { - return (obj != null) ? invokeObjectMethod(env) : invokeGlobalFunction(env); + // Adds the calling rule to the stack trace of the Environment if it is a BUILD environment. + // There are two reasons for this: + // a) When using aliases in load(), the rule class name in the BUILD file will differ from + // the implementation name in the bzl file. Consequently, we need to store the calling name. + // b) We need the location of the calling rule inside the BUILD file. + boolean hasAddedElement = env.isSkylark() ? false : env.tryAddingStackTraceRoot(func); + try { + return (obj != null) ? invokeObjectMethod(env) : invokeGlobalFunction(env); + } finally { + if (hasAddedElement) { + env.removeStackTraceRoot(); + } + } } /** diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java b/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java index ce68d66d60..66a09f0912 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/Identifier.java @@ -26,7 +26,7 @@ import javax.annotation.Nullable; /** * Syntax node for an identifier. */ -public final class Identifier extends Expression { +public final class Identifier extends Expression implements StackTraceElement { private final String name; @@ -37,6 +37,7 @@ public final class Identifier extends Expression { /** * Returns the name of the Identifier. */ + @Override public String getName() { return name; } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java index cc238179d8..a032b473ec 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkEnvironment.java @@ -42,8 +42,6 @@ public class SkylarkEnvironment extends Environment implements Serializable { */ private final Set readGlobalVariables = new HashSet<>(); - private ImmutableList stackTrace; - @Nullable private String fileContentHashCode; /** @@ -53,20 +51,17 @@ public class SkylarkEnvironment extends Environment implements Serializable { public static SkylarkEnvironment createEnvironmentForFunctionCalling( Environment callerEnv, SkylarkEnvironment definitionEnv, UserDefinedFunction function) throws EvalException { - if (callerEnv.getStackTrace().contains(function)) { + if (callerEnv.stackTraceContains(function)) { throw new EvalException( function.getLocation(), "Recursion was detected when calling '" + function.getName() + "' from '" + Iterables.getLast(callerEnv.getStackTrace()).getName() + "'"); } - ImmutableList stackTrace = new ImmutableList.Builder() - .addAll(callerEnv.getStackTrace()) - .add(function) - .build(); SkylarkEnvironment childEnv = // Always use the caller Environment's EventHandler. We cannot assume that the // definition Environment's EventHandler is still working properly. - new SkylarkEnvironment(definitionEnv, stackTrace, callerEnv.eventHandler); + new SkylarkEnvironment( + definitionEnv, callerEnv.getCopyOfUpdatedStackTrace(function), callerEnv.eventHandler); if (callerEnv.isLoadingPhase()) { childEnv.setLoadingPhase(); } @@ -83,9 +78,8 @@ public class SkylarkEnvironment extends Environment implements Serializable { } private SkylarkEnvironment(SkylarkEnvironment definitionEnv, - ImmutableList stackTrace, EventHandler eventHandler) { - super(definitionEnv.getGlobalEnvironment()); - this.stackTrace = stackTrace; + ImmutableList stackTrace, EventHandler eventHandler) { + super(definitionEnv.getGlobalEnvironment(), stackTrace); this.eventHandler = Preconditions.checkNotNull(eventHandler, "EventHandler cannot be null in an Environment which calls into Skylark"); } @@ -93,13 +87,17 @@ public class SkylarkEnvironment extends Environment implements Serializable { /** * Creates a global SkylarkEnvironment. */ - public SkylarkEnvironment(EventHandler eventHandler, String astFileContentHashCode) { - super(); - stackTrace = ImmutableList.of(); + public SkylarkEnvironment(EventHandler eventHandler, String astFileContentHashCode, + ImmutableList stackTrace) { + super(stackTrace); this.eventHandler = eventHandler; this.fileContentHashCode = astFileContentHashCode; } + public SkylarkEnvironment(EventHandler eventHandler, String astFileContentHashCode) { + this(eventHandler, astFileContentHashCode, ImmutableList.of()); + } + @VisibleForTesting public SkylarkEnvironment(EventHandler eventHandler) { this(eventHandler, null); @@ -107,21 +105,16 @@ public class SkylarkEnvironment extends Environment implements Serializable { public SkylarkEnvironment(SkylarkEnvironment globalEnv) { super(globalEnv); - stackTrace = ImmutableList.of(); this.eventHandler = globalEnv.eventHandler; } - @Override - public ImmutableList getStackTrace() { - return stackTrace; - } - /** * Clones this Skylark global environment. */ public SkylarkEnvironment cloneEnv(EventHandler eventHandler) { Preconditions.checkArgument(isGlobal()); - SkylarkEnvironment newEnv = new SkylarkEnvironment(eventHandler, this.fileContentHashCode); + SkylarkEnvironment newEnv = + new SkylarkEnvironment(eventHandler, this.fileContentHashCode, getStackTrace()); for (Entry entry : env.entrySet()) { newEnv.env.put(entry.getKey(), entry.getValue()); } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StackTraceElement.java b/src/main/java/com/google/devtools/build/lib/syntax/StackTraceElement.java new file mode 100644 index 0000000000..dbec94369b --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/syntax/StackTraceElement.java @@ -0,0 +1,26 @@ +// Copyright 2015 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package com.google.devtools.build.lib.syntax; + +import com.google.devtools.build.lib.events.Location; + +/** + * Represents an element of {@link Environment}'s stack trace. + */ +// TODO(fwe): maybe combine this with EvalExceptionWithStackTrace.StackTraceElement +public interface StackTraceElement { + String getName(); + + Location getLocation(); +} diff --git a/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java index f9719ae15d..6ac438b193 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/UserDefinedFunction.java @@ -14,7 +14,6 @@ package com.google.devtools.build.lib.syntax; import com.google.common.collect.ImmutableList; -import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.profiler.Profiler; import com.google.devtools.build.lib.profiler.ProfilerTask; @@ -44,10 +43,6 @@ public class UserDefinedFunction extends BaseFunction { return statements; } - Location getLocation() { - return location; - } - @Override public Object call(Object[] arguments, FuncallExpression ast, Environment env) throws EvalException, InterruptedException { -- cgit v1.2.3