Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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