Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/development' into development
Browse files Browse the repository at this point in the history
  • Loading branch information
mctaverna committed Sep 13, 2023
2 parents cd47685 + 8c7ffb7 commit 28d3779
Show file tree
Hide file tree
Showing 18 changed files with 969 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
@@ -0,0 +1,88 @@
package ortus.boxlang.runtime.context;

import ortus.boxlang.runtime.scopes.ArgumentsScope;
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,
* unlike UDFs, do not track things like return type, output, etc. Closures also retain a reference to
* context in which they were created, which allows for lexical scoping.
*/
public class ClosureBoxContext extends FunctionBoxContext {

/**
* Creates a new execution context with a bounded function instance and parent context
*
* @param parent The parent context
* @param function The Closure being invoked with this context
*/
public ClosureBoxContext( IBoxContext parent, Closure function ) {
this( parent, function, new ArgumentsScope() );
}

/**
* Creates a new execution context with a bounded function instance and parent context and arguments scope
*
* @param parent The parent context
* @param function The Closure being invoked with this context
* @param argumentsScope The arguments scope for this context
*/
public ClosureBoxContext( IBoxContext parent, Closure function, ArgumentsScope argumentsScope ) {
super( parent, function, argumentsScope );
if ( parent == null ) {
throw new IllegalArgumentException( "Parent context cannot be null for ClosureBoxContext" );
}
}

/**
* Search for a variable in "nearby" scopes
*/
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 );

}

// scopeFind(), getScope() and getScopeNearby() do not need to be overridden, as the closure's declaring context does not affect specific scope
// lookups

/**
* Returns the function being invoked with this context, cast as a Closure
*/
@Override
public Closure getFunction() {
return ( Closure ) function;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,27 @@
import ortus.boxlang.runtime.types.exceptions.KeyNotFoundException;
import ortus.boxlang.runtime.types.exceptions.ScopeNotFoundException;

/**
* This context represents the context of any function execution in BoxLang
* It encapsulates the arguments scope and local scope and has a reference to the function being invoked.
* This context is extended for use with both UDFs and Closures as well
*/
public class FunctionBoxContext extends BaseBoxContext {

/**
* The arguments scope
*/
private IScope argumentsScope;
protected IScope argumentsScope;

/**
* The local scope
*/
private IScope localScope;
protected IScope localScope;

/**
* The Function being invoked with this context
*/
private Function function;
protected Function function;

/**
* Creates a new execution context with a bounded function instance and parent context
Expand All @@ -36,11 +41,21 @@ public FunctionBoxContext( IBoxContext parent, Function function ) {
this( parent, function, new ArgumentsScope() );
}

/**
* Creates a new execution context with a bounded function instance and parent context and arguments scope
*
* @param parent The parent context
* @param function The function being invoked with this context
* @param argumentsScope The arguments scope
*/
public FunctionBoxContext( IBoxContext parent, Function function, ArgumentsScope argumentsScope ) {
super( parent );
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 @@ -53,7 +68,10 @@ public Function getFunction() {
return function;
}

public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope ) {
/**
* Search for a variable in "nearby" scopes
*/
public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope, boolean shallow ) {

Object result = localScope.getRaw( key );
// Null means not found
Expand All @@ -69,21 +87,18 @@ 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 );

}

/**
* Search for a variable in scopes
*/
public ScopeSearchResult scopeFind( Key key, IScope defaultScope ) {

// The FunctionBoxContext has no "global" scopes, so just defer to parent
Expand All @@ -103,6 +118,9 @@ public ScopeSearchResult scopeFind( Key key, IScope defaultScope ) {
);
}

/**
* Look for a scope by name
*/
public IScope getScope( Key name ) throws ScopeNotFoundException {

// The FunctionBoxContext has no "global" scopes, so just defer to parent
Expand All @@ -117,6 +135,9 @@ public IScope getScope( Key name ) throws ScopeNotFoundException {

}

/**
* Look for a "nearby" scope by name
*/
public IScope getScopeNearby( Key name ) throws ScopeNotFoundException {
// Check the scopes I know about
if ( name.equals( localScope.getName() ) ) {
Expand Down Expand Up @@ -157,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();
}

}
Loading

0 comments on commit 28d3779

Please sign in to comment.