diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/BreakableBoundDeclaration.java b/src/main/java/com/laytonsmith/core/compiler/analysis/BreakableBoundDeclaration.java new file mode 100644 index 000000000..d03557605 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/compiler/analysis/BreakableBoundDeclaration.java @@ -0,0 +1,21 @@ +package com.laytonsmith.core.compiler.analysis; + +import com.laytonsmith.core.NodeModifiers; +import com.laytonsmith.core.constructs.Target; + +/** + * Represents a breakable (for(), while(), switch(), etc) bound declaration in a scope graph. + * This indicates a boundary where lookup for a breakable should stop. + * @author P.J.S. Kools + */ +public class BreakableBoundDeclaration extends Declaration { + + /** + * Creates a new {@link BreakableBoundDeclaration} in the {@link Namespace#BREAKABLE} namespace. + * @param modifiers The node modifiers. + * @param t The breakable target. + */ + public BreakableBoundDeclaration(NodeModifiers modifiers, Target t) { + super(Namespace.BREAKABLE, null, null, modifiers, t); + } +} diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/BreakableDeclaration.java b/src/main/java/com/laytonsmith/core/compiler/analysis/BreakableDeclaration.java new file mode 100644 index 000000000..c794eadfd --- /dev/null +++ b/src/main/java/com/laytonsmith/core/compiler/analysis/BreakableDeclaration.java @@ -0,0 +1,28 @@ +package com.laytonsmith.core.compiler.analysis; + +import com.laytonsmith.core.NodeModifiers; +import com.laytonsmith.core.constructs.Target; + +/** + * Represents a breakable (for(), while(), switch(), etc) declaration in a scope graph. + * @author P.J.S. Kools + */ +public class BreakableDeclaration extends Declaration { + + private final Scope parentScope; + + /** + * Creates a new {@link BreakableDeclaration} in the {@link Namespace#BREAKABLE} namespace. + * @param parentScope The parent scope of the breakable (that does not include this declaration). + * @param modifiers The node modifiers. + * @param t The breakable target. + */ + public BreakableDeclaration(Scope parentScope, NodeModifiers modifiers, Target t) { + super(Namespace.BREAKABLE, null, null, modifiers, t); + this.parentScope = parentScope; + } + + public Scope getParentScope() { + return this.parentScope; + } +} diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/BreakableReference.java b/src/main/java/com/laytonsmith/core/compiler/analysis/BreakableReference.java new file mode 100644 index 000000000..5260266e4 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/compiler/analysis/BreakableReference.java @@ -0,0 +1,18 @@ +package com.laytonsmith.core.compiler.analysis; + +import com.laytonsmith.core.constructs.Target; + +/** + * Represents a reference to a breakable (by the break() function) in a scope graph. + * @author P.J.S. Kools + */ +public class BreakableReference extends Reference { + + /** + * Creates a new {@link BreakableReference} in the {@link Namespace#BREAKABLE} scope. + * @param t - The target of the reference. + */ + public BreakableReference(Target t) { + super(Namespace.BREAKABLE, null, t); + } +} diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/ContinuableBoundDeclaration.java b/src/main/java/com/laytonsmith/core/compiler/analysis/ContinuableBoundDeclaration.java new file mode 100644 index 000000000..7b327fd5a --- /dev/null +++ b/src/main/java/com/laytonsmith/core/compiler/analysis/ContinuableBoundDeclaration.java @@ -0,0 +1,21 @@ +package com.laytonsmith.core.compiler.analysis; + +import com.laytonsmith.core.NodeModifiers; +import com.laytonsmith.core.constructs.Target; + +/** + * Represents a continuable (for(), while(), etc) bound declaration in a scope graph. + * This indicates a boundary where lookup for a continuable should stop. + * @author P.J.S. Kools + */ +public class ContinuableBoundDeclaration extends Declaration { + + /** + * Creates a new {@link ContinuableBoundDeclaration} in the {@link Namespace#CONTINUABLE} namespace. + * @param modifiers The node modifiers. + * @param t The continuable target. + */ + public ContinuableBoundDeclaration(NodeModifiers modifiers, Target t) { + super(Namespace.CONTINUABLE, null, null, modifiers, t); + } +} diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/ContinuableDeclaration.java b/src/main/java/com/laytonsmith/core/compiler/analysis/ContinuableDeclaration.java new file mode 100644 index 000000000..e9c9d78a1 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/compiler/analysis/ContinuableDeclaration.java @@ -0,0 +1,20 @@ +package com.laytonsmith.core.compiler.analysis; + +import com.laytonsmith.core.NodeModifiers; +import com.laytonsmith.core.constructs.Target; + +/** + * Represents a continuable (for(), while(), etc) declaration in a scope graph. + * @author P.J.S. Kools + */ +public class ContinuableDeclaration extends Declaration { + + /** + * Creates a new {@link ContinuableDeclaration} in the {@link Namespace#CONTINUABLE} namespace. + * @param modifiers The node modifiers. + * @param t The continuable target. + */ + public ContinuableDeclaration(NodeModifiers modifiers, Target t) { + super(Namespace.CONTINUABLE, null, null, modifiers, t); + } +} diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/ContinuableReference.java b/src/main/java/com/laytonsmith/core/compiler/analysis/ContinuableReference.java new file mode 100644 index 000000000..48c65cab8 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/compiler/analysis/ContinuableReference.java @@ -0,0 +1,18 @@ +package com.laytonsmith.core.compiler.analysis; + +import com.laytonsmith.core.constructs.Target; + +/** + * Represents a reference to a continuable (by the continue()) function in a scope graph. + * @author P.J.S. Kools + */ +public class ContinuableReference extends Reference { + + /** + * Creates a new {@link ContinuableReference} in the {@link Namespace#CONTINUABLE} scope. + * @param t - The target of the reference. + */ + public ContinuableReference(Target t) { + super(Namespace.CONTINUABLE, null, t); + } +} diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/IncludeReference.java b/src/main/java/com/laytonsmith/core/compiler/analysis/IncludeReference.java index a1ea4cccb..df1c72d17 100644 --- a/src/main/java/com/laytonsmith/core/compiler/analysis/IncludeReference.java +++ b/src/main/java/com/laytonsmith/core/compiler/analysis/IncludeReference.java @@ -18,7 +18,7 @@ public class IncludeReference extends Reference { * @param identifier - The include identifier (the path passed to include(), which should resolve to a file). * @param inScope - The parent scope which the include should be linked to. * @param outScope - The scope in which the include should be usable. - * @param t - The target of the declaration. + * @param t - The target of the reference. */ public IncludeReference(String identifier, Scope inScope, Scope outScope, Target t) { super(Namespace.INCLUDE, identifier, t); diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/Namespace.java b/src/main/java/com/laytonsmith/core/compiler/analysis/Namespace.java index cb0e1fdcf..92f5e3738 100644 --- a/src/main/java/com/laytonsmith/core/compiler/analysis/Namespace.java +++ b/src/main/java/com/laytonsmith/core/compiler/analysis/Namespace.java @@ -9,5 +9,8 @@ public enum Namespace { IVARIABLE, IVARIABLE_ASSIGN, PROCEDURE, - INCLUDE + INCLUDE, + RETURNABLE, + BREAKABLE, + CONTINUABLE } diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableDeclaration.java b/src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableDeclaration.java new file mode 100644 index 000000000..9040fc291 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableDeclaration.java @@ -0,0 +1,22 @@ +package com.laytonsmith.core.compiler.analysis; + +import com.laytonsmith.core.NodeModifiers; +import com.laytonsmith.core.constructs.CClassType; +import com.laytonsmith.core.constructs.Target; + +/** + * Represents a returnable declaration in a scope graph. + * @author P.J.S. Kools + */ +public class ReturnableDeclaration extends Declaration { + + /** + * Creates a new {@link ReturnableDeclaration} in the {@link Namespace#RETURNABLE} namespace. + * @param type The expected return {@link CClassType}. + * @param modifiers The node modifiers. + * @param t The returnable target. + */ + public ReturnableDeclaration(CClassType type, NodeModifiers modifiers, Target t) { + super(Namespace.RETURNABLE, null, type, modifiers, t); + } +} diff --git a/src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableReference.java b/src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableReference.java new file mode 100644 index 000000000..af63eacb3 --- /dev/null +++ b/src/main/java/com/laytonsmith/core/compiler/analysis/ReturnableReference.java @@ -0,0 +1,18 @@ +package com.laytonsmith.core.compiler.analysis; + +import com.laytonsmith.core.constructs.Target; + +/** + * Represents a reference to a returnable in a scope graph. + * @author P.J.S. Kools + */ +public class ReturnableReference extends Reference { + + /** + * Creates a new {@link ReturnableReference} in the {@link Namespace#RETURNABLE} scope. + * @param t - The target of the reference. + */ + public ReturnableReference(Target t) { + super(Namespace.RETURNABLE, null, t); + } +} diff --git a/src/main/java/com/laytonsmith/core/functions/ControlFlow.java b/src/main/java/com/laytonsmith/core/functions/ControlFlow.java index 0883b07c8..9a75e0ab3 100644 --- a/src/main/java/com/laytonsmith/core/functions/ControlFlow.java +++ b/src/main/java/com/laytonsmith/core/functions/ControlFlow.java @@ -23,6 +23,15 @@ import com.laytonsmith.core.compiler.FileOptions; import com.laytonsmith.core.compiler.SelfStatement; import com.laytonsmith.core.compiler.VariableScope; +import com.laytonsmith.core.compiler.analysis.BreakableReference; +import com.laytonsmith.core.compiler.analysis.ContinuableBoundDeclaration; +import com.laytonsmith.core.compiler.analysis.ContinuableDeclaration; +import com.laytonsmith.core.compiler.analysis.ContinuableReference; +import com.laytonsmith.core.compiler.analysis.Declaration; +import com.laytonsmith.core.compiler.analysis.BreakableBoundDeclaration; +import com.laytonsmith.core.compiler.analysis.BreakableDeclaration; +import com.laytonsmith.core.compiler.analysis.Namespace; +import com.laytonsmith.core.compiler.analysis.ReturnableReference; import com.laytonsmith.core.compiler.analysis.Scope; import com.laytonsmith.core.compiler.analysis.StaticAnalysis; import com.laytonsmith.core.compiler.signature.FunctionSignatures; @@ -717,26 +726,41 @@ public FunctionSignatures getSignatures() { @Override public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast, Environment env, Set exceptions) { + if(ast.numberOfChildren() == 0) { + return super.linkScope(analysis, parentScope, ast, env, exceptions); + } + + // Handle value in parent scope. + ParseTree val = ast.getChildAt(0); + Scope valScope = analysis.linkScope(parentScope, val, env, exceptions); + + // Add breakable declaration in case parent scope. + Scope caseParentScope = analysis.createNewScope(valScope); + caseParentScope.addDeclaration( + new BreakableDeclaration(parentScope, ast.getNodeModifiers(), ast.getTarget())); + analysis.setTermScope(ast, caseParentScope); + + // Handle switch cases scoping. List children = ast.getChildren(); - for(int i = 0; i <= children.size() - 2; i += 2) { + for(int i = 1; i <= children.size() - 2; i += 2) { ParseTree cond = children.get(i); ParseTree code = children.get(i + 1); // Handle condition in parent scope. - Scope condScope = analysis.linkScope(parentScope, cond, env, exceptions); + Scope condScope = analysis.linkScope(caseParentScope, cond, env, exceptions); // Handle code branches in separate scope. analysis.linkScope(analysis.createNewScope(condScope), code, env, exceptions); } // Handle optional default branch in separate scope. - if((children.size() & 0x01) == 0x01) { // (size % 2) == 1. + if((children.size() & 0x01) == 0x00) { // (size % 2) == 0. analysis.linkScope( - analysis.createNewScope(parentScope), children.get(children.size() - 1), env, exceptions); + analysis.createNewScope(caseParentScope), children.get(children.size() - 1), env, exceptions); } - // Return the parent scope. - return parentScope; + // Return the switch scope. + return caseParentScope; } @Override @@ -1100,7 +1124,33 @@ public Class[] thrown() { @Override public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast, Environment env, Set exceptions) { - super.linkScope(analysis, parentScope, ast, env, exceptions); + + // Fall back to default behavior for invalid usage. + if(ast.numberOfChildren() != 4) { + return super.linkScope(analysis, parentScope, ast, env, exceptions); + } + + // Link child scopes. + ParseTree assign = ast.getChildAt(0); + ParseTree cond = ast.getChildAt(1); + ParseTree exp = ast.getChildAt(2); + ParseTree code = ast.getChildAt(3); + + // Link scopes in order: assign -> cond -> (code -> exp -> cond)*. + Scope assignScope = analysis.linkScope(parentScope, assign, env, exceptions); + Scope condScope = analysis.linkScope(assignScope, cond, env, exceptions); + Scope codeParentScope = analysis.createNewScope(condScope); + Scope codeScope = analysis.linkScope(codeParentScope, code, env, exceptions); + analysis.linkScope(codeScope, exp, env, exceptions); + + // Add breakable and continuable declaration in loop code parent scope. + codeParentScope.addDeclaration( + new BreakableDeclaration(parentScope, ast.getNodeModifiers(), ast.getTarget())); + codeParentScope.addDeclaration( + new ContinuableDeclaration(ast.getNodeModifiers(), ast.getTarget())); + analysis.setTermScope(ast, codeParentScope); + + // Return parent scope. return parentScope; } @@ -1342,11 +1392,19 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope, // Order: assign -> cond -> (code -> exp -> cond)* -> elseCode?. Scope assignScope = analysis.linkScope(parentScope, assign, env, exceptions); Scope condScope = analysis.linkScope(assignScope, cond, env, exceptions); - Scope codeScope = analysis.linkScope(condScope, code, env, exceptions); + Scope codeParentScope = analysis.createNewScope(condScope); + Scope codeScope = analysis.linkScope(codeParentScope, code, env, exceptions); analysis.linkScope(codeScope, exp, env, exceptions); if(elseCode != null) { analysis.linkScope(condScope, code, env, exceptions); } + + // Add breakable and continuable declaration in loop code parent scope. + codeParentScope.addDeclaration( + new BreakableDeclaration(parentScope, ast.getNodeModifiers(), ast.getTarget())); + codeParentScope.addDeclaration( + new ContinuableDeclaration(ast.getNodeModifiers(), ast.getTarget())); + analysis.setTermScope(ast, codeParentScope); } return parentScope; } @@ -1591,7 +1649,15 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope, } Scope[] scopes = analysis.linkParamScope(keyParamScope, keyValScope, val, env, exceptions); Scope valScope = scopes[0]; // paramScope. - analysis.linkScope(valScope, code, env, exceptions); + Scope codeParentScope = analysis.createNewScope(valScope); + analysis.linkScope(codeParentScope, code, env, exceptions); + + // Add breakable and continuable declaration in loop code parent scope. + codeParentScope.addDeclaration( + new BreakableDeclaration(parentScope, ast.getNodeModifiers(), ast.getTarget())); + codeParentScope.addDeclaration( + new ContinuableDeclaration(ast.getNodeModifiers(), ast.getTarget())); + analysis.setTermScope(ast, codeParentScope); } return parentScope; } @@ -1882,8 +1948,16 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope, } Scope[] scopes = analysis.linkParamScope(keyParamScope, keyValScope, val, env, exceptions); Scope valScope = scopes[0]; // paramScope. - analysis.linkScope(valScope, code, env, exceptions); + Scope codeParentScope = analysis.createNewScope(valScope); + analysis.linkScope(codeParentScope, code, env, exceptions); analysis.linkScope(arrayScope, elseCode, env, exceptions); + + // Add breakable and continuable declaration in loop code parent scope. + codeParentScope.addDeclaration( + new BreakableDeclaration(parentScope, ast.getNodeModifiers(), ast.getTarget())); + codeParentScope.addDeclaration( + new ContinuableDeclaration(ast.getNodeModifiers(), ast.getTarget())); + analysis.setTermScope(ast, codeParentScope); } return parentScope; } @@ -2030,7 +2104,29 @@ public Mixed exec(Target t, Environment environment, Mixed... args) throws Confi @Override public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast, Environment env, Set exceptions) { - super.linkScope(analysis, parentScope, ast, env, exceptions); + + // Fall back to default behavior for invalid usage. + if(ast.numberOfChildren() != 2) { + return super.linkScope(analysis, parentScope, ast, env, exceptions); + } + + // Link child scopes. + ParseTree cond = ast.getChildAt(0); + ParseTree code = ast.getChildAt(1); + + // Link scopes in order: cond -> (code -> cond)*. + Scope condScope = analysis.linkScope(parentScope, cond, env, exceptions); + Scope codeParentScope = analysis.createNewScope(condScope); + analysis.linkScope(codeParentScope, code, env, exceptions); + + // Add breakable and continuable declaration in loop code parent scope. + codeParentScope.addDeclaration( + new BreakableDeclaration(parentScope, ast.getNodeModifiers(), ast.getTarget())); + codeParentScope.addDeclaration( + new ContinuableDeclaration(ast.getNodeModifiers(), ast.getTarget())); + analysis.setTermScope(ast, codeParentScope); + + // Return parent scope. return parentScope; } @@ -2164,7 +2260,29 @@ public Mixed execs(Target t, Environment env, Script parent, ParseTree... nodes) @Override public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast, Environment env, Set exceptions) { - super.linkScope(analysis, parentScope, ast, env, exceptions); + + // Fall back to default behavior for invalid usage. + if(ast.numberOfChildren() != 2) { + return super.linkScope(analysis, parentScope, ast, env, exceptions); + } + + // Link child scopes. + ParseTree code = ast.getChildAt(0); + ParseTree cond = ast.getChildAt(1); + + // Link scopes in order: code -> (cond -> code)*. + Scope codeParentScope = analysis.createNewScope(parentScope); + analysis.linkScope(codeParentScope, code, env, exceptions); + analysis.linkScope(parentScope, cond, env, exceptions); + + // Add breakable and continuable declaration in loop code parent scope. + codeParentScope.addDeclaration( + new BreakableDeclaration(parentScope, ast.getNodeModifiers(), ast.getTarget())); + codeParentScope.addDeclaration( + new ContinuableDeclaration(ast.getNodeModifiers(), ast.getTarget())); + analysis.setTermScope(ast, codeParentScope); + + // Return parent scope. return parentScope; } @@ -2232,11 +2350,83 @@ public String docs() { + " or a compile error will occur. An integer must be hard coded into the function."; } + @Override + public CClassType typecheck(StaticAnalysis analysis, + ParseTree ast, Environment env, Set exceptions) { + + // Typecheck function. + super.typecheck(analysis, ast, env, exceptions); + + // Get break count. + long breakCount; + if(ast.numberOfChildren() >= 1 && ast.getChildAt(0).getData() instanceof CInt breakCountInt) { + breakCount = breakCountInt.getInt(); + if(breakCount <= 0) { + breakCount = 1; + } + } else { + breakCount = 1; + } + + // Resolve this breakable reference to a breakable declaration to validate usage of break in this context. + Scope scope = analysis.getTermScope(ast); + if(scope != null) { + long remainingBreakCount = breakCount; + Set decls = scope.getDeclarations(Namespace.BREAKABLE, null); + while(decls.size() != 0) { + assert decls.size() == 1 : "Break reference resolves to multiple breakables at once."; + assert decls.iterator().next() instanceof BreakableDeclaration : "Unsupported breakable declaration."; + Declaration decl = decls.iterator().next(); + if(decl instanceof BreakableDeclaration loopDecl) { + if(--remainingBreakCount == 0) { + break; + } + decls = loopDecl.getParentScope().getDeclarations(Namespace.BREAKABLE, null); + } else if(decl instanceof BreakableBoundDeclaration) { + break; + } else { + throw new Error("Unsupported " + Namespace.BREAKABLE + " declaration."); + } + } + if(remainingBreakCount != 0) { + if(breakCount == remainingBreakCount) { + exceptions.add(new ConfigCompileException( + "Break is not valid in this context.", ast.getTarget())); + } else { + long breaksAvailable = breakCount - remainingBreakCount; + exceptions.add(new ConfigCompileException( + "Cannot break from " + breakCount + " breakables. Only " + breaksAvailable + + " nested breakable" + (breaksAvailable == 1 ? " is" : "s are") + + " available here.", ast.getTarget())); + } + } + } + + // Return void. + return CVoid.TYPE; + } + @Override public Class[] thrown() { return new Class[]{CRECastException.class}; } + @Override + public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast, + Environment env, Set exceptions) { + + // Handle children. These will execute before this break(). + Scope scope = super.linkScope(analysis, parentScope, ast, env, exceptions); + + // Add break loop reference in new scope. + scope = analysis.createNewScope(scope); + scope.addReference(new BreakableReference(ast.getTarget())); + analysis.setTermScope(ast, scope); + + // Return scope. + return scope; + } + @Override public boolean isRestricted() { return false; @@ -2336,11 +2526,55 @@ public String docs() { + " If int is set, it will skip 'int' repetitions. If no argument is specified, 1 is used."; } + @Override + public CClassType typecheck(StaticAnalysis analysis, + ParseTree ast, Environment env, Set exceptions) { + + // Typecheck function. + super.typecheck(analysis, ast, env, exceptions); + + // Resolve this continuable reference to a continuable declaration to validate usage of continue in this context. + Scope scope = analysis.getTermScope(ast); + if(scope != null) { + Set decls = scope.getDeclarations(Namespace.CONTINUABLE, null); + if(decls.size() == 0) { + exceptions.add(new ConfigCompileException( + "Continue is not valid in this context.", ast.getTarget())); + } else { + for(Declaration decl : decls) { + if(decl instanceof ContinuableBoundDeclaration) { + exceptions.add(new ConfigCompileException( + "Continue is not valid in this context.", ast.getTarget())); + } + } + } + } + + // Return void. + return CVoid.TYPE; + } + @Override public Class[] thrown() { return new Class[]{CRECastException.class}; } + @Override + public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast, + Environment env, Set exceptions) { + + // Handle children. These will execute before this break(). + Scope scope = super.linkScope(analysis, parentScope, ast, env, exceptions); + + // Add break loop reference in new scope. + scope = analysis.createNewScope(scope); + scope.addReference(new ContinuableReference(ast.getTarget())); + analysis.setTermScope(ast, scope); + + // Return scope. + return scope; + } + @Override public boolean isRestricted() { return false; @@ -2411,11 +2645,66 @@ public String docs() { + " see the docs on try/catch, particularly the finally clause for example)."; } + @Override + public CClassType typecheck(StaticAnalysis analysis, + ParseTree ast, Environment env, Set exceptions) { + + // Get value type. + CClassType valType; + Target valTarget; + if(ast.numberOfChildren() == 0) { + valType = CVoid.TYPE; + valTarget = ast.getTarget(); + } else if(ast.numberOfChildren() == 1) { + ParseTree valNode = ast.getChildAt(0); + valType = analysis.typecheck(valNode, env, exceptions); + valTarget = valNode.getTarget(); + } else { + + // Fall back to default behavior for invalid usage. + return super.typecheck(analysis, ast, env, exceptions); + } + + // Resolve this returnable reference to its returnable declaration to get its required return type. + Scope scope = analysis.getTermScope(ast); + if(scope != null) { + Set decls = scope.getDeclarations(Namespace.RETURNABLE, null); + if(decls.size() == 0) { + exceptions.add(new ConfigCompileException("Return is not valid in this context.", ast.getTarget())); + } else { + + // Type check return value for all found declared return types. + for(Declaration decl : decls) { + StaticAnalysis.requireType(valType, decl.getType(), valTarget, env, exceptions); + } + } + } + + // Return void. + return CVoid.TYPE; + } + @Override public Class[] thrown() { return null; } + @Override + public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast, + Environment env, Set exceptions) { + + // Handle children. These will execute before this return(). + Scope scope = super.linkScope(analysis, parentScope, ast, env, exceptions); + + // Add returnable reference in new scope. + scope = analysis.createNewScope(scope); + scope.addReference(new ReturnableReference(ast.getTarget())); + analysis.setTermScope(ast, scope); + + // Return scope. + return scope; + } + @Override public boolean isRestricted() { return false; diff --git a/src/main/java/com/laytonsmith/core/functions/DataHandling.java b/src/main/java/com/laytonsmith/core/functions/DataHandling.java index d07be6445..473dd74aa 100644 --- a/src/main/java/com/laytonsmith/core/functions/DataHandling.java +++ b/src/main/java/com/laytonsmith/core/functions/DataHandling.java @@ -32,6 +32,8 @@ import com.laytonsmith.core.compiler.FileOptions; import com.laytonsmith.core.compiler.SelfStatement; import com.laytonsmith.core.compiler.VariableScope; +import com.laytonsmith.core.compiler.analysis.BreakableBoundDeclaration; +import com.laytonsmith.core.compiler.analysis.ContinuableBoundDeclaration; import com.laytonsmith.core.compiler.analysis.Declaration; import com.laytonsmith.core.compiler.analysis.IVariableAssignDeclaration; import com.laytonsmith.core.compiler.analysis.IncludeReference; @@ -40,6 +42,7 @@ import com.laytonsmith.core.compiler.analysis.ProcDeclaration; import com.laytonsmith.core.compiler.analysis.ProcRootDeclaration; import com.laytonsmith.core.compiler.analysis.Reference; +import com.laytonsmith.core.compiler.analysis.ReturnableDeclaration; import com.laytonsmith.core.compiler.analysis.Scope; import com.laytonsmith.core.compiler.analysis.StaticAnalysis; import com.laytonsmith.core.compiler.signature.FunctionSignatures; @@ -1626,6 +1629,9 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast // Allow procedures to perform lookups in the decl scope. paramScope.addSpecificParent(declScope, Namespace.PROCEDURE); + // Create returnable declaration in the inner root scope. + paramScope.addDeclaration(new ReturnableDeclaration(retType, ast.getNodeModifiers(), ast.getTarget())); + // Return the declaration scope. Parameters and their default values are not accessible after the procedure. return declScope; } @@ -2703,10 +2709,16 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast return parentScope; } - // Handle optional return type argument. + // Handle optional return type argument (CClassType or CVoid, default to AUTO). int ind = 0; - if(ast.getChildAt(ind).getData().isInstanceOf(CClassType.TYPE)) { - analysis.linkScope(parentScope, ast.getChildAt(ind++), env, exceptions); + CClassType retType; + if(ast.getChildAt(ind).getData() instanceof CClassType) { + retType = (CClassType) ast.getChildAt(ind++).getData(); + } else if(ast.getChildAt(ind).getData().equals(CVoid.VOID)) { + ind++; + retType = CVoid.TYPE; + } else { + retType = CClassType.AUTO; } // Create parameter scope. Set parent scope if this closure type is allowed to resolve in the parent scope. @@ -2732,9 +2744,17 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope, ParseTree ast paramScope = scopes[0]; } + // Create returnable declaration in the inner root scope. + paramScope.addDeclaration(new ReturnableDeclaration(retType, ast.getNodeModifiers(), ast.getTarget())); + + // Create breakable and continuable bound declarations to prevent resolving to parent scope. + Scope codeParentScope = analysis.createNewScope(paramScope); + codeParentScope.addDeclaration(new BreakableBoundDeclaration(ast.getNodeModifiers(), ast.getTarget())); + codeParentScope.addDeclaration(new ContinuableBoundDeclaration(ast.getNodeModifiers(), ast.getTarget())); + // Handle closure code. ParseTree code = ast.getChildAt(ast.numberOfChildren() - 1); - analysis.linkScope(paramScope, code, env, exceptions); + analysis.linkScope(codeParentScope, code, env, exceptions); // Return the parent scope, as parameters and their default values are not accessible after the closure. return parentScope; diff --git a/src/main/java/com/laytonsmith/core/functions/EventBinding.java b/src/main/java/com/laytonsmith/core/functions/EventBinding.java index e35efe281..d3f4f1707 100644 --- a/src/main/java/com/laytonsmith/core/functions/EventBinding.java +++ b/src/main/java/com/laytonsmith/core/functions/EventBinding.java @@ -18,7 +18,10 @@ import com.laytonsmith.core.compiler.FileOptions; import com.laytonsmith.core.compiler.SelfStatement; import com.laytonsmith.core.compiler.VariableScope; +import com.laytonsmith.core.compiler.analysis.BreakableBoundDeclaration; +import com.laytonsmith.core.compiler.analysis.ContinuableBoundDeclaration; import com.laytonsmith.core.compiler.analysis.Namespace; +import com.laytonsmith.core.compiler.analysis.ReturnableDeclaration; import com.laytonsmith.core.compiler.analysis.Scope; import com.laytonsmith.core.compiler.analysis.StaticAnalysis; import com.laytonsmith.core.constructs.CArray; @@ -335,7 +338,15 @@ public Scope linkScope(StaticAnalysis analysis, Scope parentScope, paramScope = scopes[0]; valScope = scopes[1]; } - analysis.linkScope(paramScope, code, env, exceptions); + Scope codeParentScope = analysis.createNewScope(paramScope); + analysis.linkScope(codeParentScope, code, env, exceptions); + + // Create returnable declaration in the inner root scope. + codeParentScope.addDeclaration(new ReturnableDeclaration(CVoid.TYPE, ast.getNodeModifiers(), ast.getTarget())); + + // Create breakable and continuable bound declarations to prevent resolving to parent scope. + codeParentScope.addDeclaration(new BreakableBoundDeclaration(ast.getNodeModifiers(), ast.getTarget())); + codeParentScope.addDeclaration(new ContinuableBoundDeclaration(ast.getNodeModifiers(), ast.getTarget())); // Allow code after bind() to access declarations in assigned values, but not parameters themselves. return valScope;