Skip to content
136 changes: 81 additions & 55 deletions src/main/java/com/laytonsmith/core/Procedure.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -66,25 +67,35 @@ public Procedure(String name, CClassType returnType, List<IVariable> 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());
} catch (CloneNotSupportedException e) {
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,
Expand Down Expand Up @@ -189,92 +200,107 @@ public Mixed cexecute(List<ParseTree> 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<Mixed> args, Environment oldEnv, Target t) {
boolean prev = oldEnv.getEnv(GlobalEnv.class).getCloneVars();
oldEnv.getEnv(GlobalEnv.class).setCloneVars(false);
public Mixed execute(List<Mixed> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -379,28 +381,78 @@ public CClassType typecheck(ParseTree ast, Environment env, Set<ConfigCompileExc
} else if(cFunc.hasProcedure()) { // The function is a procedure reference.

// Type check procedure arguments.
List<CClassType> argTypes = new ArrayList<>(ast.numberOfChildren());
List<Target> 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<Declaration> 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<Declaration> decls = procRefScope.getDeclarations(Namespace.PROCEDURE, procName);
if(decls.isEmpty()) {
return CClassType.AUTO; // Proc cannot be resolved. Exception already generated.
}

// Create procedure signatures.
List<CClassType> 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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -71,11 +72,19 @@ public int process(List<ParseTree> 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.
Expand Down
Loading
Loading