diff --git a/src/main/java/com/laytonsmith/core/Procedure.java b/src/main/java/com/laytonsmith/core/Procedure.java index 8e62f139c..483221a56 100644 --- a/src/main/java/com/laytonsmith/core/Procedure.java +++ b/src/main/java/com/laytonsmith/core/Procedure.java @@ -7,6 +7,7 @@ import com.laytonsmith.core.constructs.CClassType; import com.laytonsmith.core.constructs.CFunction; import com.laytonsmith.core.constructs.CNull; +import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.CVoid; import com.laytonsmith.core.constructs.Construct; import com.laytonsmith.core.constructs.IVariable; @@ -66,10 +67,12 @@ public Procedure(String name, CClassType returnType, List varList, Sm this.definedAt = t; this.varList = new HashMap<>(); this.procComment = procComment; + boolean optionalParamDetected = false; for(int i = 0; i < varList.size(); i++) { IVariable var = varList.get(i); if(var.getDefinedType().isVariadicType() && i != varList.size() - 1) { - throw new CREFormatException("Varargs can only be added to the last argument.", t); + throw new CREFormatException( + "Varargs can only be added to the last parameter in a procedure.", var.getTarget()); } try { this.varList.put(var.getVariableName(), var.clone()); @@ -77,14 +80,22 @@ public Procedure(String name, CClassType returnType, List varList, Sm this.varList.put(var.getVariableName(), var); } this.varIndex.add(var); - if(var.getDefinedType().isVariadicType() && var.ival() != CNull.UNDEFINED) { - throw new CREFormatException("Varargs may not have default values", t); + boolean paramOptional = (var.ival() != CNull.UNDEFINED); + if(paramOptional) { + if(var.getDefinedType().isVariadicType()) { + throw new CREFormatException("Varargs may not have default values.", var.getTarget()); + } + optionalParamDetected = true; + } else if(optionalParamDetected && !var.getDefinedType().isVariadicType()) { + throw new CREFormatException( + "Procedure parameters after optional parameters must be optional or varargs.", var.getTarget()); } this.originals.put(var.getVariableName(), var.ival()); } this.tree = tree; if(!PROCEDURE_NAME_REGEX.matcher(name).matches()) { - throw new CREFormatException("Procedure names must start with an underscore, and may only contain letters, underscores, and digits. (Found " + this.name + ")", t); + throw new CREFormatException("Procedure names must start with an underscore," + + " and may only contain letters, underscores, and digits. (Found " + this.name + ")", t); } //Let's look through the tree now, and see if this is possibly constant or not. //If it is, it may or may not help us during compilation, but if it's not, @@ -189,92 +200,107 @@ public Mixed cexecute(List args, Environment env, Target t) { * Executes this procedure, with the arguments that were passed in * * @param args The arguments passed to the procedure call. - * @param oldEnv The environment to be cloned. + * @param parentEnv The environment to be cloned. * @param t * @return */ - public Mixed execute(List args, Environment oldEnv, Target t) { - boolean prev = oldEnv.getEnv(GlobalEnv.class).getCloneVars(); - oldEnv.getEnv(GlobalEnv.class).setCloneVars(false); + public Mixed execute(List args, Environment parentEnv, Target t) { + boolean prevCloneVars = parentEnv.getEnv(GlobalEnv.class).getCloneVars(); + parentEnv.getEnv(GlobalEnv.class).setCloneVars(false); Environment env; try { - env = oldEnv.clone(); + env = parentEnv.clone(); env.getEnv(GlobalEnv.class).setCloneVars(true); } catch (CloneNotSupportedException ex) { throw new RuntimeException(ex); } - oldEnv.getEnv(GlobalEnv.class).setCloneVars(prev); + parentEnv.getEnv(GlobalEnv.class).setCloneVars(prevCloneVars); Script fakeScript = Script.GenerateScript(tree, env.getEnv(GlobalEnv.class).GetLabel(), null); // Create container for the @arguments variable. CArray arguments = new CArray(Target.UNKNOWN, this.varIndex.size()); + env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(CArray.TYPE, "@arguments", arguments, t)); + + // Initialize varargs parameter if present. + CArray vararg = null; + IVariable varargsVar = (!this.varIndex.isEmpty() + && this.varIndex.get(this.varIndex.size() - 1).getDefinedType().isVariadicType() + ? this.varIndex.get(this.varIndex.size() - 1) : null); + if(varargsVar != null) { + vararg = new CArray(t); // TODO - Add type once generics are implemented. + Target varargsTarget = (args.size() >= this.varIndex.size() + ? args.get(this.varIndex.size() - 1).getTarget() : t); + env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(CArray.TYPE, + varargsVar.getVariableName(), vararg, varargsTarget)); + } // Handle passed procedure arguments. int varInd; - CArray vararg = null; for(varInd = 0; varInd < args.size(); varInd++) { - Mixed c = args.get(varInd); - arguments.push(c, t); - if(this.varIndex.size() > varInd - || (!this.varIndex.isEmpty() - && this.varIndex.get(this.varIndex.size() - 1).getDefinedType().isVariadicType())) { - IVariable var; - if(varInd < this.varIndex.size() - 1 - || !this.varIndex.get(this.varIndex.size() - 1).getDefinedType().isVariadicType()) { - var = this.varIndex.get(varInd); - } else { - var = this.varIndex.get(this.varIndex.size() - 1); - if(vararg == null) { - // TODO: Once generics are added, add the type - vararg = new CArray(t); - env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(CArray.TYPE, - var.getVariableName(), vararg, c.getTarget())); - } - } - // Type check "void" value. - if(c instanceof CVoid - && !(var.getDefinedType().equals(Auto.TYPE) || var.getDefinedType().equals(CVoid.TYPE))) { - throw new CRECastException("Procedure \"" + name + "\" expects a value of type " - + var.getDefinedType().val() + " in argument " + (varInd + 1) + ", but" - + " a void value was found instead.", c.getTarget()); - } + // Get argument value. + Mixed argVal = args.get(varInd); - // Type check vararg parameter. - if(var.getDefinedType().isVariadicType()) { - if(InstanceofUtil.isInstanceof(c.typeof(), var.getDefinedType().getVarargsBaseType(), env)) { - vararg.push(c, t); - continue; - } else { - throw new CRECastException("Procedure \"" + name + "\" expects a value of type " - + var.getDefinedType().val() + " in argument " + (varInd + 1) + ", but" - + " a value of type " + c.typeof() + " was found instead.", c.getTarget()); - } - } + // Add argument value to @arguments array. + arguments.push(argVal, t); - // Type check non-vararg parameter. - if(InstanceofUtil.isInstanceof(c.typeof(), var.getDefinedType(), env)) { - env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(var.getDefinedType(), - var.getVariableName(), c, c.getTarget())); + // Get parameter to assign argument value to. + IVariable var; + if(this.varIndex.size() > varInd) { + var = this.varIndex.get(varInd); + } else if(varargsVar != null) { + var = varargsVar; + } else { + continue; // No parameters remaining and no varargs present. Ignore excessive arguments. + } + + // Type check "void" argument/parameter. + if(argVal instanceof CVoid + && !(var.getDefinedType().equals(Auto.TYPE) || var.getDefinedType().equals(CVoid.TYPE))) { + throw new CRECastException("Procedure \"" + name + "\" expects a value of type " + + var.getDefinedType().val() + " in argument " + (varInd + 1) + ", but" + + " a void value was found instead.", argVal.getTarget()); + } + + // Type check vararg argument/parameter. + if(var.getDefinedType().isVariadicType()) { + if(InstanceofUtil.isInstanceof(argVal.typeof(), var.getDefinedType().getVarargsBaseType(), env)) { + vararg.push(argVal, t); continue; } else { throw new CRECastException("Procedure \"" + name + "\" expects a value of type " + var.getDefinedType().val() + " in argument " + (varInd + 1) + ", but" - + " a value of type " + c.typeof() + " was found instead.", c.getTarget()); + + " a value of type " + argVal.typeof() + " was found instead.", argVal.getTarget()); } } + + // Type check non-vararg argument/parameter. + if(InstanceofUtil.isInstanceof(argVal.typeof(), var.getDefinedType(), env)) { + env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(var.getDefinedType(), + var.getVariableName(), argVal, argVal.getTarget())); + continue; + } else { + throw new CRECastException("Procedure \"" + name + "\" expects a value of type " + + var.getDefinedType().val() + " in argument " + (varInd + 1) + ", but" + + " a value of type " + argVal.typeof() + " was found instead.", argVal.getTarget()); + } } // Assign default values to remaining proc arguments. - while(varInd < this.varIndex.size()) { - String varName = this.varIndex.get(varInd++).getVariableName(); + int nonVarargVarSize = (vararg == null ? this.varIndex.size() : this.varIndex.size() - 1); + while(varInd < nonVarargVarSize) { + IVariable paramVar = this.varIndex.get(varInd++); + String varName = paramVar.getVariableName(); Mixed defVal = this.originals.get(varName); + if(defVal == CNull.UNDEFINED) { + defVal = new CString("", paramVar.getTarget()); // Fill in empty string for undefined parameters. + } env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(Auto.TYPE, varName, defVal, defVal.getTarget())); arguments.push(defVal, t); } - env.getEnv(GlobalEnv.class).GetVarList().set(new IVariable(CArray.TYPE, "@arguments", arguments, t)); + // Execute procedure body. StackTraceManager stManager = env.getEnv(GlobalEnv.class).GetStackTraceManager(); stManager.addStackTraceElement(new ConfigRuntimeException.StackTraceElement("proc " + name, getTarget())); try { diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java b/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java index eacf81b14..7ab7cafb0 100644 --- a/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java +++ b/src/main/java/com/laytonsmith/core/compiler/analysis/StaticAnalysis.java @@ -14,6 +14,8 @@ import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Static; import com.laytonsmith.core.compiler.CompilerEnvironment; +import com.laytonsmith.core.compiler.signature.FunctionSignatures; +import com.laytonsmith.core.compiler.signature.SignatureBuilder; import com.laytonsmith.core.constructs.Auto; import com.laytonsmith.core.constructs.CClassType; import com.laytonsmith.core.constructs.CFunction; @@ -379,28 +381,78 @@ public CClassType typecheck(ParseTree ast, Environment env, Set argTypes = new ArrayList<>(ast.numberOfChildren()); + List argTargets = new ArrayList<>(ast.numberOfChildren()); for(ParseTree child : ast.getChildren()) { - this.typecheck(child, env, exceptions); + argTypes.add(this.typecheck(child, env, exceptions)); + argTargets.add(child.getTarget()); } - // Return procedure return type. + // Get procedure declaration. String procName = cFunc.val(); - Scope scope = this.getTermScope(ast); - if(scope != null) { - Set decls = scope.getDeclarations(Namespace.PROCEDURE, procName); - if(decls.isEmpty()) { - return CClassType.AUTO; // Proc cannot be resolved. Exception for this is already generated. - } else { - // TODO - Get the most specific type when multiple declarations exist. - return decls.iterator().next().getType(); - } - } else { + Target procTarget = cFunc.getTarget(); + Scope procRefScope = this.getTermScope(ast); + if(procRefScope == null) { + // If this runs, then a proc reference was created without setting its Scope using setTermScope(). exceptions.add(new ConfigCompileException("Procedure cannot be resolved (missing procedure scope," + " this is an internal error that should never happen): " - + procName, cFunc.getTarget())); + + procName, procTarget)); return CClassType.AUTO; } + Set decls = procRefScope.getDeclarations(Namespace.PROCEDURE, procName); + if(decls.isEmpty()) { + return CClassType.AUTO; // Proc cannot be resolved. Exception already generated. + } + + // Create procedure signatures. + List procReturnTypes = new ArrayList<>(1); + for(Declaration decl : decls) { + if(decl instanceof ProcDeclaration procDecl) { + + // Create new procedure signature. + SignatureBuilder signatureBuilder = new SignatureBuilder(procDecl.getType()); + boolean optionalParamDetected = false; + boolean varargsParamDetected = false; + for(ParamDeclaration paramDecl : procDecl.getParameters()) { + CClassType paramType = paramDecl.getType(); + if(varargsParamDetected) { + return CClassType.AUTO; // Invalid procedure signature. Exception(s) already generated. + } + if(paramType.isVariadicType()) { + signatureBuilder.varParam( + paramType.getVarargsBaseType(), paramDecl.getIdentifier(), null); + varargsParamDetected = true; + } else { + boolean paramOptional = paramDecl.getDefaultValue() != null; + signatureBuilder.param(paramType, paramDecl.getIdentifier(), null, paramOptional); + if(paramOptional) { + optionalParamDetected = true; + } else if(optionalParamDetected) { + return CClassType.AUTO; // Invalid procedure signature. Exception(s) already generated. + } + } + } + + // Typecheck arguments against new procedure signature. + FunctionSignatures procSignature = signatureBuilder.build(); + procReturnTypes.add(procSignature.getReturnType( + procTarget, argTypes, argTargets, env, exceptions)); + } else { + + // If this runs, then a wrong declaration type proc declaration was created. + exceptions.add(new ConfigCompileException("Procedure resolves to non-procedure declaration" + + " (this is an internal error that should never happen): " + + procName + " -> " + decl.getIdentifier(), procTarget)); + } + } + if(procReturnTypes.isEmpty()) { + return CClassType.AUTO; // No ProcDeclarations found. Exception already generated. + } + + // Return procedure return type. + // TODO - Get the most specific type when multiple declarations exist. + return procReturnTypes.get(0); } else { throw new Error("Unsupported " + CFunction.class.getSimpleName() + " type in type checking for node with value: " + cFunc.val()); diff --git a/src/main/java/com/laytonsmith/core/compiler/keywords/ProcKeyword.java b/src/main/java/com/laytonsmith/core/compiler/keywords/ProcKeyword.java index 394e4ec8b..65414a31c 100644 --- a/src/main/java/com/laytonsmith/core/compiler/keywords/ProcKeyword.java +++ b/src/main/java/com/laytonsmith/core/compiler/keywords/ProcKeyword.java @@ -12,8 +12,9 @@ import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.exceptions.ConfigCompileException; +import com.laytonsmith.core.exceptions.CRE.CREInvalidProcedureException; import com.laytonsmith.core.functions.DataHandling; -import com.laytonsmith.core.functions.Meta; +import com.laytonsmith.core.functions.Exceptions; import com.laytonsmith.core.functions.Compiler; import java.util.List; @@ -71,11 +72,19 @@ public int process(List list, int keywordPosition) throws ConfigCompi list.remove(keywordPosition); // Remove __cbrace__ function node. } else { - // Define as forward declaration by add a "noop()" as code block. + // Define as forward declaration by adding an exception throw as code block. + FileOptions forwardImplFileOptions = list.get(keywordPosition + 1).getFileOptions(); + ParseTree throwNode = new ParseTree(new CFunction(Exceptions._throw.NAME, Target.UNKNOWN), + forwardImplFileOptions, true); + throwNode.addChild(new ParseTree( + new CString(CREInvalidProcedureException.TYPE.getSimpleName(), Target.UNKNOWN), + options, true)); + throwNode.addChild(new ParseTree( + new CString("Cannot invoke procedure forward declaration.", Target.UNKNOWN), + options, true)); ParseTree statement = new ParseTree(new CFunction(Compiler.__statements__.NAME, Target.UNKNOWN), - list.get(keywordPosition + 1).getFileOptions(), true); - statement.addChild(new ParseTree(new CFunction(Meta.noop.NAME, Target.UNKNOWN), - list.get(keywordPosition + 1).getFileOptions(), true)); + forwardImplFileOptions, true); + statement.addChild(throwNode); procNode.addChild(statement); // Remove processed nodes from AST. diff --git a/src/main/java/com/laytonsmith/core/compiler/signature/FunctionSignature.java b/src/main/java/com/laytonsmith/core/compiler/signature/FunctionSignature.java index 73dedf58a..53c67cec4 100644 --- a/src/main/java/com/laytonsmith/core/compiler/signature/FunctionSignature.java +++ b/src/main/java/com/laytonsmith/core/compiler/signature/FunctionSignature.java @@ -20,21 +20,17 @@ public class FunctionSignature { private final ReturnType returnType; private final List params; private final List throwsList; - private boolean noneIsAllowed; /** * Creates a new {@link FunctionSignature} with the given properties. * @param returnType - The function return type. * @param params - The function parameters. * @param throwsList - The list of possibly thrown exceptions by the function. - * @param noneIsAllowed - If the none (Java {@code null}) type is allowed and should be treated as - * {@link CClassType.AUTO}. */ - public FunctionSignature(ReturnType returnType, List params, List throwsList, boolean noneIsAllowed) { + public FunctionSignature(ReturnType returnType, List params, List throwsList) { this.returnType = returnType; this.params = params; this.throwsList = throwsList; - this.noneIsAllowed = noneIsAllowed; } /** @@ -43,7 +39,7 @@ public FunctionSignature(ReturnType returnType, List params, List * @param returnType - The function return type. */ public FunctionSignature(ReturnType returnType) { - this(returnType, new ArrayList<>(), new ArrayList<>(), false); + this(returnType, new ArrayList<>(), new ArrayList<>()); } /** @@ -61,10 +57,6 @@ protected void addThrows(Throws throwsObj) { this.throwsList.add(throwsObj); } - protected void setNoneIsAllowed(boolean noneIsAllowed) { - this.noneIsAllowed = noneIsAllowed; - } - /** * Gets the function's return type. * @return The return type. @@ -109,7 +101,8 @@ public boolean matches(List argTypes, Environment env, boolean allow // Match normal or optional parameter. if(argIndex < argTypes.size() - && InstanceofUtil.isInstanceof(argTypes.get(argIndex), param.getType(), env)) { + && ((param.getNoneTypeAllowed() && argTypes.get(argIndex) == null) + || InstanceofUtil.isInstanceof(argTypes.get(argIndex), param.getType(), env))) { // Keep track of the optional parameter match. if(param.isOptional()) { @@ -130,7 +123,7 @@ public boolean matches(List argTypes, Environment env, boolean allow // Match as many arguments as possible with this varparam. int numMatches = 0; while(argIndex < argTypes.size() - && ((argTypes.get(argIndex) == null && this.noneIsAllowed) + && ((param.getNoneTypeAllowed() && argTypes.get(argIndex) == null) || InstanceofUtil.isInstanceof(argTypes.get(argIndex), param.getType(), env))) { argIndex++; numMatches++; @@ -172,7 +165,8 @@ public boolean matches(List argTypes, Environment env, boolean allow */ public String getParamTypesString() { return "(" + StringUtils.Join(this.params, ", ", null, null, null, (Param param) -> { - String ret = (param.getType() == null ? "any" : param.getType().getSimpleName()); + String ret = (param.getType() == null ? "any" : (param.getNoneTypeAllowed() + ? param.getType().getSimpleName() + "|none" : param.getType().getSimpleName())); if(param.isVarParam()) { ret += "..."; } diff --git a/src/main/java/com/laytonsmith/core/compiler/signature/Param.java b/src/main/java/com/laytonsmith/core/compiler/signature/Param.java index 9a1642ead..a74189111 100644 --- a/src/main/java/com/laytonsmith/core/compiler/signature/Param.java +++ b/src/main/java/com/laytonsmith/core/compiler/signature/Param.java @@ -13,6 +13,7 @@ public class Param { private final String desc; private final boolean isVarParam; private final boolean isOptional; + private final boolean noneTypeAllowed; /** * Creates a new {@link Param} with the given properties. @@ -24,14 +25,34 @@ public class Param { * of the given type, or one argument of type {@code array}. {@code false} otherwise. * Note that a varparam is only usable as type {@code array}. * @param isOptional - {@code true} if the parameter is optional, {@code false} otherwise. + * @param noneTypeAllowed - {@code true} if the none (Java {@code null}) type is allowed to match this parameter in + * addition to what the parameter type already matches. + * {@code false} otherwise. If the parameter type is {@code null}, then this boolean parameter is ignored. */ - public Param(CClassType type, String name, String desc, boolean isVarParam, boolean isOptional) { + public Param(CClassType type, String name, String desc, + boolean isVarParam, boolean isOptional, boolean noneTypeAllowed) { assert !isVarParam || !isOptional : "A parameter cannot be variadic and optional at the same time."; this.type = type; this.name = name; this.desc = desc; this.isVarParam = isVarParam; this.isOptional = isOptional; + this.noneTypeAllowed = noneTypeAllowed; + } + + /** + * Creates a new {@link Param} with the given properties. + * Parameters cannot be variadic and optional at the same time, as varparams already imply optionality. + * @param type - The (parent) type of the parameter. + * @param name - The name of the parameter. + * @param desc - The description of the parameter. + * @param isVarParam - {@code true} if the parameter is a varparam, meaning that it matches zero or more arguments + * of the given type, or one argument of type {@code array}. {@code false} otherwise. + * Note that a varparam is only usable as type {@code array}. + * @param isOptional - {@code true} if the parameter is optional, {@code false} otherwise. + */ + public Param(CClassType type, String name, String desc, boolean isVarParam, boolean isOptional) { + this(type, name, desc, isVarParam, isOptional, false); } /** @@ -85,4 +106,15 @@ public boolean isVarParam() { public boolean isOptional() { return this.isOptional; } + + /** + * Gets whether the none type is allowed to match this parameter in addition to what the parameter type matches. + * When this returns {@code false}, the none type can still match this parameter if {@ref #getType()} returns + * {@code null}. + * @return {@code true} if a none type argument can match this parameter in addition to what the parameter type + * matches, {@code false} otherwise. + */ + public boolean getNoneTypeAllowed() { + return this.noneTypeAllowed; + } } diff --git a/src/main/java/com/laytonsmith/core/compiler/signature/SignatureBuilder.java b/src/main/java/com/laytonsmith/core/compiler/signature/SignatureBuilder.java index 41a6b490c..77603585a 100644 --- a/src/main/java/com/laytonsmith/core/compiler/signature/SignatureBuilder.java +++ b/src/main/java/com/laytonsmith/core/compiler/signature/SignatureBuilder.java @@ -56,6 +56,22 @@ public SignatureBuilder(CClassType returnType, String returnValDesc, MatchType m this.signature = new FunctionSignature(new ReturnType(returnType, returnValDesc)); } + /** + * Adds a normal function parameter. Parameters should be added from left to right. + * @param paramType - The {@link CClassType} of the parameter. + * @param paramName - The name of the parameter. + * @param paramDesc - The description of the parameter. + * @param isOptional - Whether the parameter is optional or not. + * @param noneTypeAllowed - Whether a none type (Java {@code null}) is allowed to match this parameter in addition + * to what the parameter type already matches. + * @return This {@link SignatureBuilder}, for chaining builder methods. + */ + public SignatureBuilder param( + CClassType paramType, String paramName, String paramDesc, boolean isOptional, boolean noneTypeAllowed) { + this.signature.addParam(new Param(paramType, paramName, paramDesc, false, isOptional, noneTypeAllowed)); + return this; + } + /** * Adds a normal function parameter. Parameters should be added from left to right. * @param paramType - The {@link CClassType} of the parameter. @@ -80,6 +96,21 @@ public SignatureBuilder param(CClassType paramType, String paramName, String par return this.param(paramType, paramName, paramDesc, false); } + /** + * Adds a variadic function parameter (varparam). Parameters should be added from left to right. + * @param paramType - The {@link CClassType} of the parameter (the 'paramType' in 'paramType... paramName'). + * @param paramName - The name of the parameter. + * @param paramDesc - The description of the parameter. + * @param noneTypeAllowed - Whether a none type (Java {@code null}) is allowed to match an entry in this parameter + * in addition to what the parameter type already matches. + * @return This {@link SignatureBuilder}, for chaining builder methods. + */ + public SignatureBuilder varParam( + CClassType paramType, String paramName, String paramDesc, boolean noneTypeAllowed) { + this.signature.addParam(new Param(paramType, paramName, paramDesc, true, false, noneTypeAllowed)); + return this; + } + /** * Adds a variadic function parameter (varparam). Parameters should be added from left to right. * @param paramType - The {@link CClassType} of the parameter (the 'paramType' in 'paramType... paramName'). @@ -103,11 +134,6 @@ public SignatureBuilder throwsEx(Class exception, String return this; } - public SignatureBuilder setNoneIsAllowed(boolean allowed) { - this.signature.setNoneIsAllowed(allowed); - return this; - } - /** * Finalizes the last function signature and starts a new function signature with the given return type. * @param returnType - The return type of the new function signature. diff --git a/src/main/java/com/laytonsmith/core/functions/BasicLogic.java b/src/main/java/com/laytonsmith/core/functions/BasicLogic.java index 064028027..e5844cbd4 100644 --- a/src/main/java/com/laytonsmith/core/functions/BasicLogic.java +++ b/src/main/java/com/laytonsmith/core/functions/BasicLogic.java @@ -16,7 +16,6 @@ import com.laytonsmith.core.compiler.analysis.StaticAnalysis; import com.laytonsmith.core.compiler.signature.FunctionSignatures; import com.laytonsmith.core.compiler.signature.SignatureBuilder; -import com.laytonsmith.core.constructs.Auto; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CBoolean; import com.laytonsmith.core.constructs.CClassType; @@ -28,6 +27,7 @@ import com.laytonsmith.core.constructs.CString; import com.laytonsmith.core.constructs.CSymbol; import com.laytonsmith.core.constructs.CVoid; +import com.laytonsmith.core.constructs.InstanceofUtil; import com.laytonsmith.core.constructs.Target; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.environments.GlobalEnv; @@ -1275,7 +1275,37 @@ public FunctionSignatures getSignatures() { * Note that getReturnType could be overridden, not using this signature for typechecking. * That implementation is not yet possible until A OR B OR ... types can be described. */ - return new SignatureBuilder(CClassType.AUTO).varParam(Mixed.TYPE, "vals", null).build(); + return new SignatureBuilder(CClassType.AUTO) + .varParam(Mixed.TYPE, "vals", "The values.") + .newSignature(CClassType.AUTO) + .param(Mixed.TYPE, "val", "The first value.") + .varParam(Mixed.TYPE, "vals", "The values.") + .param(Mixed.TYPE, "termVal", "The final terminating (non-returning) value.", true, true).build(); + } + + @Override + public CClassType getReturnType(Target t, List argTypes, + List argTargets, Environment env, Set exceptions) { + + // Get return type based on the function signatures. This generates all necessary compile errors. + CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions); + + // Return an occurring argument type if all argument types extend that type. + // TODO - Make this return a multitype instead as soon as all typechecking code supports multitypes. + search: + for(CClassType type1 : argTypes) { + if(type1 != null) { + for(CClassType type2 : argTypes) { + if(type2 != null && !InstanceofUtil.isInstanceof(type2, type1, env)) { + continue search; + } + } + return type1; + } + } + + // Return super result. + return retType; } @Override @@ -1570,8 +1600,35 @@ public FunctionSignatures getSignatures() { * Note that getReturnType could be overridden, not using this signature for typechecking. * That implementation is not yet possible until A OR B OR ... types can be described. */ - return new SignatureBuilder(Auto.TYPE).varParam(Mixed.TYPE, "vals", null) - .setNoneIsAllowed(true).build(); + return new SignatureBuilder(CClassType.AUTO) + .param(Mixed.TYPE, "val", "The first value.") + .varParam(Mixed.TYPE, "vals", "The values.") + .param(Mixed.TYPE, "termVal", "The final terminating (non-returning) value.", true, true).build(); + } + + @Override + public CClassType getReturnType(Target t, List argTypes, + List argTargets, Environment env, Set exceptions) { + + // Get return type based on the function signatures. This generates all necessary compile errors. + CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions); + + // Return an occurring argument type if all argument types extend that type. + // TODO - Make this return a multitype instead as soon as all typechecking code supports multitypes. + search: + for(CClassType type1 : argTypes) { + if(type1 != null) { + for(CClassType type2 : argTypes) { + if(type2 != null && !InstanceofUtil.isInstanceof(type2, type1, env)) { + continue search; + } + } + return type1; + } + } + + // Return super result. + return retType; } @Override diff --git a/src/main/java/com/laytonsmith/core/functions/ControlFlow.java b/src/main/java/com/laytonsmith/core/functions/ControlFlow.java index b571627e0..ba1b28807 100644 --- a/src/main/java/com/laytonsmith/core/functions/ControlFlow.java +++ b/src/main/java/com/laytonsmith/core/functions/ControlFlow.java @@ -1,5 +1,6 @@ package com.laytonsmith.core.functions; +import com.laytonsmith.PureUtilities.MathUtils; import com.laytonsmith.PureUtilities.TermColors; import com.laytonsmith.PureUtilities.Common.StreamUtils; import com.laytonsmith.PureUtilities.Version; @@ -84,6 +85,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -135,14 +137,22 @@ public Mixed exec(Target t, Environment env, Mixed... args) @Override public FunctionSignatures getSignatures() { /* - * TODO - Decide how to define the ternary return value. - * Note that getReturnType is overridden, so these signatures are not used for typechecking. + * TODO - Decide how to define the ternary return value. */ return new SignatureBuilder(CClassType.AUTO, MatchType.MATCH_FIRST) .param(Booleanish.TYPE, "cond", "The condition.") .param(Mixed.TYPE, "ifValue", "The value that is returned when the condition is true.") .param(Mixed.TYPE, "elseValue", "The value that is returned when the condition is false.") - .newSignature(CVoid.TYPE).param(Booleanish.TYPE, "cond", "The condition.") + .newSignature(CClassType.AUTO) + .param(Booleanish.TYPE, "cond", "The condition.") + .param(Mixed.TYPE, "ifValue", "The value that is returned when the condition is true.") + .param(null, "elseCode", "The optional code that runs when the condition is false. This code must be terminating.") + .newSignature(CClassType.AUTO) + .param(Booleanish.TYPE, "cond", "The condition.") + .param(null, "ifCode", "The code that runs when the condition is true. This code must be terminating.") + .param(Mixed.TYPE, "elseValue", "The value that is returned when the condition is false.") + .newSignature(CVoid.TYPE) + .param(Booleanish.TYPE, "cond", "The condition.") .param(null, "ifCode", "The code that runs when the condition is true.") .param(null, "elseCode", "The optional code that runs when the condition is false.", true).build(); } @@ -154,9 +164,9 @@ public CClassType getReturnType(Target t, List argTypes, // Get return type based on the function signatures. This generates all necessary compile errors. CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions); - // When void is returned, ternary usage could still be possible when a branch is terminating. - // It is also possible that both branches are terminating, in which case this should return null as well. - if(retType == CVoid.TYPE && argTypes.size() == 3) { + // Return the type of the other code branch / value if one branch is terminating. + // Return null if both branches are terminating. + if(argTypes.size() == 3) { // Return the type of the other branch if one branch is terminating (ternary, terminating or void). if(argTypes.get(1) == null) { @@ -181,7 +191,7 @@ public CClassType getReturnType(Target t, List argTypes, } } - // Return the super result. + // Return super result. return retType; } @@ -431,11 +441,69 @@ public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes) @Override public FunctionSignatures getSignatures() { + /* - * TODO - Implement a way to define [cond, code]* using signatures, and use it here. - * Also check switch() and switch_ic(), as they need the same feature. + * TODO - Implement a way to define [cond, code]*, [elseCode] using signatures, and use it here. + * Also check complex_try(), switch() and switch_ic(), as they need the same feature. + * Implement Booleanish typecheck for all conditions. These are currently checked in getReturnType(). */ - return super.getSignatures(); + return new SignatureBuilder(CVoid.TYPE) + .varParam(null, "[cond, code]*, [elseCode]", "The conditional code blocks.").build(); + } + + @Override + public CClassType getReturnType(Target t, List argTypes, + List argTargets, Environment env, Set exceptions) { + + // Get return type based on the function signatures. This generates all necessary compile errors. + CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions); + + // Override return type with null if all code branches (including the else code branch) are terminating. + if(argTypes.size() >= 1) { + + // Get all code branch return types. + boolean allBranchesTerminate = true; + Set returnTypes = new HashSet<>(); + for(int i = 1; i < argTypes.size(); i += 2) { + + // Typecheck condition arguments. + // TODO - Remove this typecheck when it is implemented through function signatures. + CClassType condType = argTypes.get(i - 1); + StaticAnalysis.requireType(condType, Booleanish.TYPE, argTargets.get(i - 1), env, exceptions); + + CClassType codeType = argTypes.get(i); + returnTypes.add(codeType); + allBranchesTerminate &= (codeType == null); + } + if(MathUtils.isOdd(argTypes.size())) { + CClassType defaultArgType = argTypes.get(argTypes.size() - 1); + returnTypes.add(defaultArgType); + allBranchesTerminate &= (defaultArgType == null); + } + + // Return null if all branches (including the else code branch) are terminating. + if(allBranchesTerminate) { + return null; + } + + // Return an occurring return type if all return types extend that type. + // TODO - Make this return a multitype instead as soon as all typechecking code supports multitypes. + search: + for(CClassType type1 : returnTypes) { + if(type1 != null) { + for(CClassType type2 : returnTypes) { + if(type2 != null && !InstanceofUtil.isInstanceof(type2, type1, env)) { + continue search; + } + } + return type1; + } + } + return CClassType.AUTO; + } + + // Return super result. + return retType; } @Override @@ -462,7 +530,7 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope, } // Handle optional else branch in separate scope. - if((ast.numberOfChildren() & 0x01) == 0x01) { // (size % 2) == 1. + if(MathUtils.isOdd(ast.numberOfChildren())) { analysis.linkScope(analysis.createNewScope(parentScope), ast.getChildAt(ast.numberOfChildren() - 1), env, exceptions); } @@ -719,11 +787,63 @@ public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes) @Override public FunctionSignatures getSignatures() { + /* * TODO - Implement a way to define [case, code]* using signatures, and use it here. - * Also check ifelse() and switch_ic(), as they need the same feature. + * Also check complex_try(), ifelse() and switch_ic(), as they need the same feature. */ - return super.getSignatures(); + return new SignatureBuilder(CVoid.TYPE) + .param(Mixed.TYPE, "value", "The switch value.") + .varParam(null, "[caseVal, caseCode]*, [defaultCode]", "The case blocks.").build(); + } + + @Override + public CClassType getReturnType(Target t, List argTypes, + List argTargets, Environment env, Set exceptions) { + + // Get return type based on the function signatures. This generates all necessary compile errors. + CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions); + + // Override return type with null if all cases (including the default case) are terminating. + if(argTypes.size() >= 2) { + + // Get all case/default code branch return types. + boolean allBranchesTerminate = true; + Set returnTypes = new HashSet<>(); + for(int i = 2; i < argTypes.size(); i += 2) { + CClassType argType = argTypes.get(i); + returnTypes.add(argType); + allBranchesTerminate &= (argType == null); + } + if(MathUtils.isEven(argTypes.size())) { + CClassType defaultArgType = argTypes.get(argTypes.size() - 1); + returnTypes.add(defaultArgType); + allBranchesTerminate &= (defaultArgType == null); + } + + // Return null if all cases (including the default case) are terminating. + if(allBranchesTerminate) { + return null; + } + + // Return an occurring return type if all return types extend that type. + // TODO - Make this return a multitype instead as soon as all typechecking code supports multitypes. + search: + for(CClassType type1 : returnTypes) { + if(type1 != null) { + for(CClassType type2 : returnTypes) { + if(type2 != null && !InstanceofUtil.isInstanceof(type2, type1, env)) { + continue search; + } + } + return type1; + } + } + return CClassType.AUTO; + } + + // Return super result. + return retType; } @Override @@ -757,7 +877,7 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope, } // Handle optional default branch in separate scope. - if((children.size() & 0x01) == 0x00) { // (size % 2) == 0. + if(MathUtils.isEven(children.size())) { analysis.linkScope( analysis.createNewScope(caseParentScope), children.get(children.size() - 1), env, exceptions); } @@ -2708,8 +2828,8 @@ public CClassType typecheck(StaticAnalysis analysis, } } - // Return void. - return CVoid.TYPE; + // Return null to indicate that this call does not result in a value. + return null; } @Override diff --git a/src/main/java/com/laytonsmith/core/functions/DataHandling.java b/src/main/java/com/laytonsmith/core/functions/DataHandling.java index f0dfc4296..611c98b7a 100644 --- a/src/main/java/com/laytonsmith/core/functions/DataHandling.java +++ b/src/main/java/com/laytonsmith/core/functions/DataHandling.java @@ -1594,8 +1594,7 @@ public static Procedure getProcedure(Target t, Environment env, Script parent, P env.getEnv(GlobalEnv.class).ClearFlag(GlobalEnv.FLAG_VAR_ARGS_ALLOWED); } if(paramDefaultValues[i] instanceof IVariable ivar) { - paramDefaultValues[i] = env.getEnv(GlobalEnv.class) - .GetVarList().get(ivar.getVariableName(), t, true, env).ival(); + paramDefaultValues[i] = originalList.get(ivar.getVariableName(), t, env).ival(); } // Mark proc as not constant if a default parameter is not a constant. @@ -1678,7 +1677,7 @@ public static Procedure getProcedure(Target t, Environment env, Script parent, P varNames.add(varName); // Get IVariable value. - Mixed ivarVal = (paramDefaultValue != null ? paramDefaultValue : new CString("", t)); + Mixed ivarVal = (paramDefaultValue != null ? paramDefaultValue : CNull.UNDEFINED); // Store IVariable object clone. vars.add(new IVariable(ivar.getDefinedType(), ivar.getVariableName(), ivarVal, ivar.getTarget())); @@ -1700,6 +1699,34 @@ public Mixed exec(Target t, Environment env, Mixed... args) throws ConfigRuntime return CVoid.VOID; } + @Override + public CClassType typecheck(StaticAnalysis analysis, + ParseTree ast, Environment env, Set exceptions) { + + // Fall back to super implementation for invalid usage. + if(ast.numberOfChildren() < 2) { + return super.typecheck(analysis, ast, env, exceptions); + } + + // Get procedure declared return type (not the proc() return type). + CClassType returnType = (ast.getChildAt(0).getData() instanceof CClassType type ? type : CClassType.AUTO); + + // Typecheck arguments excluding code block. + for(int i = 0; i < ast.numberOfChildren() - 1; i++) { + analysis.typecheck(ast.getChildAt(i), env, exceptions); + } + + // Typecheck code block. + CClassType codeBlockType = analysis.typecheck(ast.getChildAt(ast.numberOfChildren() - 1), env, exceptions); + if(returnType != CClassType.AUTO && returnType != CVoid.TYPE && codeBlockType != null) { + exceptions.add(new ConfigCompileException( + "Missing return statement; Not all code paths return a value.", ast.getTarget())); + } + + // Return void. + return CVoid.TYPE; + } + @Override public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast, Environment env, Set exceptions) { diff --git a/src/main/java/com/laytonsmith/core/functions/Exceptions.java b/src/main/java/com/laytonsmith/core/functions/Exceptions.java index 916aab22e..862028a2b 100644 --- a/src/main/java/com/laytonsmith/core/functions/Exceptions.java +++ b/src/main/java/com/laytonsmith/core/functions/Exceptions.java @@ -28,6 +28,8 @@ import com.laytonsmith.core.compiler.VariableScope; import com.laytonsmith.core.compiler.analysis.Scope; import com.laytonsmith.core.compiler.analysis.StaticAnalysis; +import com.laytonsmith.core.compiler.signature.FunctionSignatures; +import com.laytonsmith.core.compiler.signature.SignatureBuilder; import com.laytonsmith.core.constructs.CArray; import com.laytonsmith.core.constructs.CClassType; import com.laytonsmith.core.constructs.CClosure; @@ -209,6 +211,40 @@ public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommand return CVoid.VOID; } + @Override + public FunctionSignatures getSignatures() { + return new SignatureBuilder(CVoid.TYPE) + .param(null, "tryCode", "The try code block.") + .param(null, "catchCode", "The catch code block.", true) + .newSignature(CVoid.TYPE) + .param(null, "tryCode", "The try code block.") + .param(null, "exVar", "The exception variable.") + .param(null, "catchCode", "The catch code block.") + .param(CString.TYPE, "exceptionType", "The exception type to catch.") + .newSignature(CVoid.TYPE) + .param(null, "tryCode", "The try code block.") + .param(null, "exVar", "The exception variable.") + .param(null, "catchCode", "The catch code block.") + .param(CArray.TYPE, "exceptionTypes", "Array of exception types to catch.", true).build(); + } + + @Override + public CClassType getReturnType(Target t, List argTypes, + List argTargets, Environment env, Set exceptions) { + + // Get return type based on the function signatures. This generates all necessary compile errors. + CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions); + + // Return null if both try and catch block do not return. + if((argTypes.size() == 2 && argTypes.get(0) == null && argTypes.get(1) == null) + || (argTypes.size() >= 3 && argTypes.get(0) == null && argTypes.get(2) == null)) { + return null; + } + + // Return return type based on function signatures. + return retType; + } + @Override @SuppressWarnings({"checkstyle:fallthrough", "checkstyle:defaultcomeslast"}) // Intended for control flow. public Scope linkScope(StaticAnalysis analysis, Scope parentScope, @@ -282,9 +318,11 @@ public List isScope(List children) { @seealso({_try.class, com.laytonsmith.tools.docgen.templates.Exceptions.class}) public static class _throw extends AbstractFunction { + public static final String NAME = "throw"; + @Override public String getName() { - return "throw"; + return NAME; } @Override @@ -375,6 +413,29 @@ public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommand throw throwable; } } + + @Override + public FunctionSignatures getSignatures() { + return new SignatureBuilder(null).param(CArray.TYPE, "exception", "The exception to throw.") + .newSignature(null).param(CREThrowable.TYPE, "exception", "The exception to throw.") + .newSignature(null) + .param(CString.TYPE, "exceptionType", "The exception type string.") + .param(CString.TYPE, "msg", "The exception message.") + .param(CArray.TYPE, "causedBy", "The causing exception object.", true) + .newSignature(null) + .param(CString.TYPE, "exceptionType", "The exception type string.") + .param(CString.TYPE, "msg", "The exception message.") + .param(CREThrowable.TYPE, "causedBy", "The causing exception object.", true) + .newSignature(null) + .param(CClassType.TYPE, "exceptionType", "The exception type.") + .param(CString.TYPE, "msg", "The exception message.") + .param(CArray.TYPE, "causedBy", "The causing exception object.", true) + .newSignature(null) + .param(CClassType.TYPE, "exceptionType", "The exception type.") + .param(CString.TYPE, "msg", "The exception message.") + .param(CREThrowable.TYPE, "causedBy", "The causing exception object.", true) + .build(); + } } @api @@ -558,6 +619,43 @@ public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes) return CVoid.VOID; } + @Override + public FunctionSignatures getSignatures() { + + /* + * TODO - Implement a way to define [catchVariable, catchCode]*, [catchCode] using signatures, and use it here. + * Also check ifelse(), switch() and switch_ic(), as they need the same feature. + */ + return new SignatureBuilder(CVoid.TYPE) + .param(null, "tryCode", "The try code block.") + .varParam(null, "[catchVariable, catchCode]*, [catchCode]", "The catch blocks.").build(); + } + + @Override + public CClassType getReturnType(Target t, List argTypes, + List argTargets, Environment env, Set exceptions) { + + // Get return type based on the function signatures. This generates all necessary compile errors. + CClassType retType = super.getReturnType(t, argTypes, argTargets, env, exceptions); + + // Return null if both try and all catch blocks do not return. + if(argTypes.size() > 1 && argTypes.get(0) == null) { + search: { + for(int i = 2; i < argTypes.size(); i += 2) { + if(argTypes.get(i) != null) { + break search; + } + } + if(!MathUtils.isEven(argTypes.size()) || argTypes.get(argTypes.size() - 1) == null) { + return null; + } + } + } + + // Return return type based on function signatures. + return retType; + } + @Override public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast, Environment env, Set exceptions) { diff --git a/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java b/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java index 1a34c8796..fc293338b 100644 --- a/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java +++ b/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java @@ -1444,4 +1444,126 @@ public void testSoftCastSyntaxCompiles() throws Exception { sa.setLocalEnable(true); MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, env, null, true), env, env.getEnvClasses(), sa); } + + @Test + public void testNoFalseMissingReturnInProcErrors() throws Exception { + String script = """ + + int proc _a() { + return 1; + } + _a(); + int proc _b(@a, @b) { + if(@a) { + return 1; + } else if(@b) { + return 1; + } else { + return 1; + } + } + _b(1, 2); + int proc _c(@a) { + switch(@a) { + case 1: return 1; + case 2: return 1; + default: return 1; + } + } + _c(1); + int proc _d(@a) { + switch_ic(@a) { + case 'a': return 1; + case 'b': return 1; + default: return 1; + } + } + _d('str'); + int proc _e() { + try(return(1), return(1)); + } + _e(); + int proc _f() { + try { + return 1; + } catch(RangeException @ex) { + return 1; + } catch(CastException @ex) { + return 1; + } + } + _f(); + """; + Environment env = Static.GenerateStandaloneEnvironment(); + StaticAnalysis sa = new StaticAnalysis(true); + sa.setLocalEnable(true); + MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, env, null, true), env, env.getEnvClasses(), sa); + } + + @Test + public void testNoFalseNoneTypeErrorsInBranchAssigns() throws Exception { + String script = """ + + boolean @cond = dyn(true); + boolean @cond2 = dyn(true); + string @str = dyn(''); + + int @a1 = if(@cond, 1, 2); + int @a2 = if(@cond, die(), 2); + int @a3 = if(@cond, 1, die()); + + int @b1 = if(@cond) {1} else if(@cond2) {2} else {3} + int @b2 = if(@cond) {die()} else if(@cond2) {2} else {3} + int @b3 = if(@cond) {1} else if(@cond2) {die()} else {3} + int @b4 = if(@cond) {1} else if(@cond2) {2} else {die()} + int @b5 = if(@cond) {die()} else if(@cond2) {die()} else {3} + int @b6 = if(@cond) {die()} else if(@cond2) {2} else {die()} + int @b7 = if(@cond) {1} else if(@cond2) {die()} else {die()} + + int @c1 = switch(@str) { + default: 1 + } + int @c2 = switch(@str) { + case 1: 1 + default: 1 + } + int @c3 = switch(@str) { + case 1: die() + default: 1 + } + int @c4 = switch(@str) { + case 1: 1 + default: die() + } + int @c5 = switch(@str) { + case 1: 1 + case 2: 1 + default: 1 + } + int @c6 = switch(@str) { + case 1: die() + case 2: 1 + default: 1 + } + int @c7 = switch(@str) { + case 1: 1 + case 2: die() + default: 1 + } + int @c8 = switch(@str) { + case 1: 1 + case 2: 1 + default: die() + } + + int @d1 = if(@cond, 1, try(die(), die())); + + int @e1 = if(@cond, 1, try {die()} catch(Exception @ex) {die()}); + int @e2 = if(@cond, 1, try {die()} catch(RangeException @ex) {die()} catch(CastException @ex) {die()}); + """; + Environment env = Static.GenerateStandaloneEnvironment(); + StaticAnalysis sa = new StaticAnalysis(true); + sa.setLocalEnable(true); + MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, env, null, true), env, env.getEnvClasses(), sa); + } }