Skip to content

Commit

Permalink
Implement closures and lexical scope lookup
Browse files Browse the repository at this point in the history
  • Loading branch information
bdw429s committed Sep 13, 2023
1 parent 25a94df commit c70fab9
Show file tree
Hide file tree
Showing 15 changed files with 828 additions and 43 deletions.
7 changes: 7 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Phase2Closure",
"request": "launch",
"mainClass": "ortus.boxlang.runtime.testing.Phase2Closure",
"projectName": "runtime"
},
{
"type": "java",
"name": "Phase2UDF",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
import java.util.Stack;

import ortus.boxlang.runtime.dynamic.BaseTemplate;
import ortus.boxlang.runtime.scopes.ArgumentsScope;
import ortus.boxlang.runtime.scopes.IScope;
import ortus.boxlang.runtime.scopes.Key;
import ortus.boxlang.runtime.types.Function;
import ortus.boxlang.runtime.types.UDF;
import ortus.boxlang.runtime.types.exceptions.KeyNotFoundException;
import ortus.boxlang.runtime.types.exceptions.ScopeNotFoundException;

/**
Expand Down Expand Up @@ -146,10 +148,8 @@ public Boolean hasParent() {
* @return Return value of the function call
*/
public Object invokeFunction( Key name, Object[] positionalArguments ) {
Function function = findFunction( name );
FunctionBoxContext fContext = new FunctionBoxContext( this, function,
function.createArgumentsScope( positionalArguments ) );
return function.invoke( fContext );
Function function = findFunction( name );
return invokeFunction( function, function.createArgumentsScope( positionalArguments ) );
}

/**
Expand All @@ -158,9 +158,35 @@ public Object invokeFunction( Key name, Object[] positionalArguments ) {
* @return Return value of the function call
*/
public Object invokeFunction( Key name, Map<Key, Object> namedArguments ) {
Function function = findFunction( name );
FunctionBoxContext fContext = new FunctionBoxContext( this, function,
function.createArgumentsScope( namedArguments ) );
Function function = findFunction( name );
return invokeFunction( function, function.createArgumentsScope( namedArguments ) );
}

/**
* Invoke a function expression such as (()=>{})() using positional args.
*
* @return Return value of the function call
*/
public Object invokeFunction( Function function, Object[] positionalArguments ) {
return invokeFunction( function, function.createArgumentsScope( positionalArguments ) );
}

/**
* Invoke a function expression such as (()=>{})() using named args.
*
* @return Return value of the function call
*/
public Object invokeFunction( Function function, Map<Key, Object> namedArguments ) {
return invokeFunction( function, function.createArgumentsScope( namedArguments ) );
}

/**
* Invoke a function expression such as (()=>{})() using named args.
*
* @return Return value of the function call
*/
public Object invokeFunction( Function function, ArgumentsScope argumentsScope ) {
FunctionBoxContext fContext = new FunctionBoxContext( getFunctionParentContext(), function, argumentsScope );
return function.invoke( fContext );
}

Expand Down Expand Up @@ -199,7 +225,7 @@ public ScopeSearchResult scopeFind( Key key, IScope defaultScope ) {
throw new UnsupportedOperationException( "Unimplemented method 'scopeFind'" );
}

public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope ) {
public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope, boolean shallow ) {
throw new UnsupportedOperationException( "Unimplemented method 'scopeFindNearby'" );
}

Expand Down Expand Up @@ -233,4 +259,26 @@ public Function findClosestFunction() {
return null;
}

/**
* Get parent context for a function execution happening in this context
*
* @return The context to use
*/
public IBoxContext getFunctionParentContext() {
return this;
}

/**
* Try to get the requested key from an unkonwn scope but overriding the parent to check if not found
*
* @param key The key to search for
*
* @return The value of the key if found
*
* @throws KeyNotFoundException If the key was not found in any scope
*/
public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope ) {
return scopeFindNearby( key, defaultScope, false );
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public CatchBoxContext( IBoxContext parent, Key exceptionKey, Throwable exceptio
*
* @throws KeyNotFoundException If the key was not found in any scope
*/
public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope ) {
public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope, boolean shallow ) {

// In Variables scope? (thread-safe lookup and get)
Object result = variablesScope.getRaw( key );
Expand All @@ -87,6 +87,10 @@ public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope ) {
return new ScopeSearchResult( variablesScope, Struct.unWrapNull( result ) );
}

if ( shallow ) {
return null;
}

return scopeFind( key, defaultScope );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import ortus.boxlang.runtime.scopes.IScope;
import ortus.boxlang.runtime.scopes.Key;
import ortus.boxlang.runtime.types.Closure;
import ortus.boxlang.runtime.types.Struct;

/**
* This context represents the execution of a closure. Closures are a simpler form of a Function which,
Expand Down Expand Up @@ -39,11 +40,38 @@ public ClosureBoxContext( IBoxContext parent, Closure function, ArgumentsScope a
/**
* Search for a variable in "nearby" scopes
*/
public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope ) {
// TODO: search getFunction().getDeclaringContext() first-- need to limit this context to ONLY the "nearby" contexts. i.e., we want to find local,
// variables, this, but not cgi, server, application, etc
public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope, boolean shallow ) {

Object result = localScope.getRaw( key );
// Null means not found
if ( result != null ) {
// Unwrap the value now in case it was really actually null for real
return new ScopeSearchResult( localScope, Struct.unWrapNull( result ) );
}

result = argumentsScope.getRaw( key );
// Null means not found
if ( result != null ) {
// Unwrap the value now in case it was really actually null for real
return new ScopeSearchResult( argumentsScope, Struct.unWrapNull( result ) );
}

// After a closure has checked local and arguments, it stops to do a shallow lookup in the declaring scope. If the declaring scope
// is also a CLosureBoxContext, it will do the same thing, and so on until it finds a non-ClosureBoxContext.
ScopeSearchResult declaringContextResult = getFunction().getDeclaringContext().scopeFindNearby( key, defaultScope, true );
if ( declaringContextResult != null ) {
return declaringContextResult;
}

// Shallow lookups don't defer to the parent
if ( shallow ) {
return null;
}

// Now we pick up where we left off at the original closure context's parent.
// A UDF is "transparent" and can see everything in the parent scope as a "local" observer, so we call scopeFindNearby() on the parent
return parent.scopeFindNearby( key, defaultScope );

return super.scopeFindNearby( key, defaultScope );
}

// scopeFind(), getScope() and getScopeNearby() do not need to be overridden, as the closure's declaring context does not affect specific scope
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ public FunctionBoxContext( IBoxContext parent, Function function, ArgumentsScope
if ( parent == null ) {
throw new IllegalArgumentException( "Parent context cannot be null for FunctionBoxContext" );
}
if ( function == null ) {
throw new IllegalArgumentException( "function cannot be null for FunctionBoxContext" );
}
this.localScope = new LocalScope();
this.argumentsScope = argumentsScope;
this.function = function;
Expand All @@ -68,7 +71,7 @@ public Function getFunction() {
/**
* Search for a variable in "nearby" scopes
*/
public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope ) {
public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope, boolean shallow ) {

Object result = localScope.getRaw( key );
// Null means not found
Expand All @@ -84,19 +87,13 @@ public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope ) {
return new ScopeSearchResult( argumentsScope, Struct.unWrapNull( result ) );
}

if ( parent != null ) {
// A UDF is "transparent" and can see everything in the parent scope as a "local" observer
return parent.scopeFindNearby( key, defaultScope );
if ( shallow ) {
return null;
}

// Default scope requested for missing keys
if ( defaultScope != null ) {
return new ScopeSearchResult( defaultScope, null );
}
// Not found anywhere
throw new KeyNotFoundException(
String.format( "The requested key [%s] was not located in any scope or it's undefined", key.getName() )
);
// A UDF is "transparent" and can see everything in the parent scope as a "local" observer
return parent.scopeFindNearby( key, defaultScope );

}

/**
Expand Down Expand Up @@ -181,4 +178,14 @@ public IScope getDefaultAssignmentScope() {
return localScope;
}

/**
* Get parent context for a function execution happening in this context
*
* @return The context to use
*/
public IBoxContext getFunctionParentContext() {
// If a function is executed inside another function, it uses the parent since there is nothing a function can "see" from inside it's calling function
return getParent();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public interface IBoxContext {
public IScope getScopeNearby( Key name ) throws ScopeNotFoundException;

/**
* Try to get the requested key from the unscoped scope
* Try to get the requested key from an unknown scope
* Meaning it needs to search scopes in order according to it's context.
* Unlike scopeFindNearby(), this version only searches trancedent scopes like
* cgi or server which are never encapsulated like variables is inside a CFC.
Expand All @@ -74,7 +74,7 @@ public interface IBoxContext {
public ScopeSearchResult scopeFind( Key key, IScope defaultScope );

/**
* Try to get the requested key from the unscoped scope
* Try to get the requested key from an unknown scope
* Meaning it needs to search scopes in order according to it's context.
* A nearby lookup is used for the closest context to the executing code
*
Expand All @@ -89,6 +89,19 @@ public interface IBoxContext {
*/
public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope );

/**
* Try to get the requested key from an unkonwn scope but not delegating to parent or default missing keys
*
* @param key The key to search for
* @param defaultScope The default scope to return if the key is not found
* @param shallow true, do not delegate to parent or default scope if not found
*
* @return The result of the search. Null if performing a shallow search and nothing was fond
*
* @throws KeyNotFoundException If the key was not found in any scope
*/
public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope, boolean shallow );

/**
* Invoke a function call such as foo() using positional args. Will check for a registered BIF first, then search known scopes for a UDF.
*
Expand Down Expand Up @@ -161,11 +174,18 @@ public interface IBoxContext {

/**
* Get the default variable assignment scope for this context
*
*
* @return The scope reference to use
*/
public IScope getDefaultAssignmentScope();

/**
* Get parent context for a function execution happening in this context
*
* @return The context to use
*/
public IBoxContext getFunctionParentContext();

/**
* Represents the results of a successful scope hunting expedition.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public ScriptingBoxContext() {
*
* @throws KeyNotFoundException If the key was not found in any scope
*/
public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope ) {
public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope, boolean shallow ) {

// In query loop?
// Need to add mechanism to keep a stack of temp scopes based on cfoutput or cfloop based on query
Expand All @@ -109,6 +109,10 @@ public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope ) {
return new ScopeSearchResult( variablesScope, Struct.unWrapNull( result ) );
}

if ( shallow ) {
return null;
}

return scopeFind( key, defaultScope );
}

Expand Down
Loading

0 comments on commit c70fab9

Please sign in to comment.