Doing proper scope control and resource management in JMP can be tricky -- the JMP objects DataTable()
, and Namespace()
are only ever explicitly released from memory and never by dereference.
For my purposes I have created a meta-programming library that can handle all the scope management and resource control necessary (as it is part of my work I cannot share the files), but something a little like it is here:
::modify display tree functions = Function( {self, display tree},
{Default Local},
box types = {"TextEditBox", "ButtonBox", "ListBoxBox"}; // Add others here
For( i = 1, i <= N Items( box types ), i++,
boxes = display tree << XPath( "//" || box types[i] );
For( j = 1, j <= N Items( boxes ), j++,
box = boxes[j];
function = Parse( Char( box << Get Function ) );
If( Try( Is Missing( function ), 0 ), Continue() ); // no function set
If( Head Name( function ) != "Function",
arg = Name Expr( function );
If( Head Name( arg ) != "Glue", // function is the right type -- a single call
new arg = Eval Expr( Glue( self = Namespace( Expr( self << Get Name ) ) ) );
Insert Into( new arg, Parse( "self:" || Char( Name Expr( arg ) ) ) );
Eval( Substitute( Expr(
box << Set Function( Local( {self}, __expr__ ) )
)
,
Expr( __expr__ ), Name Expr( new arg )
) )
)
,
arg = Arg( function, N Arg( function ) );
If( Head Name( arg ) != "Glue", // function is the right type -- a single call
new arg = Eval Expr( Glue( self = Namespace( Expr( self << Get Name ) ) ) );
Insert Into( new arg, Parse( "self:" || Char( Name Expr( arg ) ) ) );
Eval( Substitute( Expr(
box << Set Function(
Function( __args__,
{self},
__expr__
)
)
),
Expr( __args__ ), Arg( function, 1 )
,
Expr( __expr__ ), Name Expr( new arg )
) )
)
);
)
)
);
::inject self namespace = Function( {script},
{Default Local},
self = New Namespace();
script = Arg( script, 1 );
windows = {};
For( i = 1, i <= N Arg( script ), i++,
arg i = Arg( script, i );
If( Head Name( arg i ) == "Assign",
Remove From( script, i );
Insert Into( script,
Substitute(
Expr( Assign( _a_, _b_ ) )
,
Expr( _a_ ), Parse( "self:" || Char( Arg( arg i, 1 ) ) )
,
Expr( _b_ ), Arg( arg i, 2 )
)
,
i
);
arg i = Arg( script, i )
);
If( Head Name( arg i ) == "Assign" & As Name( Head Name( Arg( arg i, 2 ) ) ) == As Name( "New Window" ),
Insert Into( windows, Char( Arg( arg i, 1 ) ) )
);
If( Head Name( arg i ) == "Assign" & Head Name( Arg( arg i, 2 ) ) == "Function",
Remove From( script, i );
Insert Into( script,
Substitute( Expr(
Assign( _arg1_, Function( _args_,
{Default Local},
_script_
) )
),
Expr( _arg1_ ), Arg( arg i, 1 )
,
Expr( _args_ ), Arg( Arg( arg i, 2 ), 1 )
,
Expr( _script_ ), If( Head Name( Arg( Arg( arg i, 2 ), N Arg( Arg( arg i, 2 ) ) ) ) == "Glue",
Insert( Arg( Arg( arg i, 2 ), N Arg( Arg( arg i, 2 ) ) ),
Eval Expr( self = Namespace( Expr( self << Get Name ) ) ),
1
)
,
Eval Expr(
Glue(
self = Namespace( Expr( self << Get Name ) ),
Expr( Arg( Arg( arg i, 2 ), N Arg( Arg( arg i, 2 ) ) ) )
)
)
)
)
,
i
)
)
);
Insert Into( script, 0 );
Eval( script );
For( w = 1, w <= N Items( windows ), w++,
win = Eval( Parse( windows[w] ) );
::modify display tree functions( self, win );
Eval( Eval Expr(
win << On Close(
Local( {self = Namespace( Expr( self << Get Name ) )},
Try( self:on close );
Delete Namespaces( Force( 1 ), self )
)
)
) )
);
self
);
script = {
a = 1;
__tables = {};
add table = Function( {_table},
Insert Into( self:__tables, _table )
);
close tables = Function( {},
For( i = 1, i <= N Items( self:__tables ), i++,
Try( Close( self:__tables[i], No Save ) )
)
);
func = Function( {b},
Show( b + self:a )
);
on close = Function( {},
Print( "Closing!!" );
self:close tables;
);
run button = Function( {},
Print( "You pressed the button" );
table = Open( "$SAMPLE_DATA/Big Class.jmp" );
self:add table( table << Subset( All Rows, All Columns, Not Linked ) );
Close( table, No Save );
);
win = New Window( "teest",
List Box( {"A","B"}, <<Set Function( func( 3 ) ) )
,
List Box( {"1", "2"}, <<Set Function( Function( {this}, func( Try( Num( (Insert( {}, this << Get Selected ))[1] ), 0 ) ) ) ) )
,
Button Box( "Press Me", <<Set Function( run button ) )
);
};
self = ::inject self namespace( script );
self = ::inject self namespace( script );
If you've already loaded the two global functions then you can keep them out of the script -- they serve to modify the functions within the namespace to be aware of which namespace they're in, as well as to modify the <<Set Function( ... )
methods of the display tree to call the relevant functions within the scoping namespace. Also, this will automatically set the <<On Close()
method of the window to run the self:on close()
function (if it exists).
Note that this will, by default, delete the scoping self
namespace when the window closes. This is not always the desired behavior, but for this simple demo it will suffice.
The last two lines of the script each open a window, but there is no cross-talk between them (you can load some tables by pressing the button, and when the window is closed the tables are automatically closed as well)
Jordan