Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add control structures to cpptraj scripts #556

Merged
merged 77 commits into from
Oct 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
99f7268
DRR - Cpptraj: Initial attempt at adding some control structures
Sep 27, 2017
e9817be
DRR - Cpptraj: Var name change
Sep 27, 2017
4bea1c2
Merge branch 'master' into loops
Sep 28, 2017
5c49602
DRR - Cpptraj: Make for specific to atom masks initially.
Sep 28, 2017
8668594
DRR - Cpptraj: Reorganization to allow nested control structures.
Sep 28, 2017
082667e
DRR - Cpptraj: Detect unterminated control blocks
Sep 28, 2017
a8db6b3
DRR - Cpptraj: Add control block description, use it to provide more …
Sep 28, 2017
3dff7d6
DRR - Cpptraj: Add mask setup
Sep 28, 2017
6d1779b
DRR - Cpptraj: Start adding logic for actually executing the control …
Sep 28, 2017
c508d5e
DRR - Cpptraj: Add logic for executing inner blocks recursively
Sep 28, 2017
aa50d9f
DRR - Cpptraj: First attempt at replacing variables.
Sep 28, 2017
360efea
DRR - Cpptraj: Make unrecognized variable a warning
Sep 28, 2017
ff1f35d
DRR - Cpptraj: Better error handling in control structure
Sep 28, 2017
d0bdd77
Merge branch 'master' into loops
Sep 28, 2017
1fadc50
DRR - Cpptraj: Come up with a way to pass variables in nested loops
Sep 28, 2017
8117771
DRR - Cpptraj: for -> formask
Sep 28, 2017
e96f75c
DRR - Cpptraj: Allow multiple masks
Sep 28, 2017
184a775
DRR - Cpptraj: Add some error checking and improve output messages.
Sep 28, 2017
13a34d7
DRR - Cpptraj: Make residues and molecules work as well
Sep 28, 2017
3532428
DRR - Cpptraj: Move debug statements. Add code docs.
Sep 28, 2017
e9bc8b1
DRR - Cpptraj: Reorganize split of Dispatch
Sep 28, 2017
3e16242
DRR - Cpptraj: Actually execute the commands.
Sep 28, 2017
8da3065
DRR - Cpptraj: Modify test to use new loop syntax
Sep 28, 2017
516eda7
DRR - Cpptraj: Hide debug info. Make outpout formatting a little nicer.
Sep 28, 2017
7c32f24
DRR - Cpptraj: Switch from 'formask in' back to 'for inmask' in prepa…
Oct 2, 2017
13e6a5c
DRR - Cpptraj: Reorganize argument parsing loop in preparation for su…
Oct 2, 2017
31b4ced
DRR - Cpptraj: Niterations => MaxIterations
Oct 2, 2017
45fa18d
DRR - Cpptraj: Generalize # iterations check
Oct 2, 2017
cefcd89
DRR - Cpptraj: Initial incarnation of the generic integer for loop va…
Oct 2, 2017
f09fb56
DRR - Cpptraj: Add description
Oct 2, 2017
10418af
DRR - Cpptraj: Enable integer for loop. Allow variable to start after…
Oct 2, 2017
4a6320f
DRR - Cpptraj: Test using integer for variable in data set name
Oct 2, 2017
af18479
DRR - Cpptraj: Add range checks. Calculate number of iterations for i…
Oct 2, 2017
e948e98
DRR - Cpptraj: Allow integer loop without end condition as long as th…
Oct 2, 2017
bafe82c
DRR - Cpptraj: Update input, no longer need end condition
Oct 2, 2017
4b8399c
DRR - Cpptraj: Fix up help
Oct 2, 2017
db7f86a
DRR - Cpptraj: Better variable name recognition in input
Oct 2, 2017
c916974
DRR - Cpptraj: Fix order of evaluations leading to small memory error…
Oct 2, 2017
40cfca1
DRR - Cpptraj: Initial incarnation of the VariableArray class.
Oct 3, 2017
5ce3b1f
DRR - Cpptraj: Change function name
Oct 3, 2017
b12be92
DRR - Cpptraj: Place current script variables in separate class. Shou…
Oct 3, 2017
5a002ae
DRR - Cpptraj: Start() does not have to add variables since this is d…
Oct 3, 2017
6275491
DRR - Cpptraj: Have ChangeArg modify ArgLine as well.
Oct 3, 2017
7e4502d
DRR - Cpptraj: Have ExecuteCommand modify commands so script variable…
Oct 3, 2017
767bbe4
DRR - Cpptraj: Script vars can be used outside of control blocks now.
Oct 3, 2017
e5d347a
DRR - Cpptraj: Properly clear control blocks when an error occurs.
Oct 3, 2017
4ed98df
DRR - Cpptraj: Print command when adding to control block. Hide debug…
Oct 3, 2017
23379ce
DRR - Cpptraj: Ensure control commands inside control blocks are prin…
Oct 3, 2017
a7637ce
DRR - Cpptraj: Add molfirstres and mollastres for mask loop types. Im…
Oct 3, 2017
53bf0c8
DRR - Cpptraj: Do not allow variables to be used as for loop start/en…
Oct 3, 2017
728b0d7
DRR - Cpptraj: Improve code docs and cleanup variable names.
Oct 3, 2017
ad6154c
DRR - Cpptraj: Fix up code docs
Oct 3, 2017
a2af2c3
DRR - Cpptraj: Add a way to set script variables
Oct 3, 2017
eea426c
DRR - Cpptraj: Code cleanup
Oct 3, 2017
f0023bb
DRR - Cpptraj: Allow any amount of whitespace in set.
Oct 4, 2017
1b6b60e
DRR - Cpptraj: Add loops test
Oct 4, 2017
43b3b59
DRR - Cpptraj: Distinguish control blocks from controls.
Oct 4, 2017
2d87483
DRR - Cpptraj: Add test requirements and enable test.
Oct 4, 2017
f2aac72
DRR - Cpptraj: Make Control inheritable
Oct 4, 2017
18c219f
DRR - Cpptraj: Add 'show' command
Oct 4, 2017
225ee81
DRR - Cpptraj: No need for separate BLOCK DispatchObject; 'for' is di…
Oct 4, 2017
21c5112
DRR - Cpptraj: Update code comments.
Oct 4, 2017
bce55c5
DRR - Cpptraj: Add routines to remove leading whitespace
Oct 6, 2017
ce842c2
DRR - Cpptraj: Add ability to set script variables to number of atoms…
Oct 6, 2017
ebe52f8
DRR - Cpptraj: Add 'trajinframes' option to set
Oct 6, 2017
a9bba08
DRR - Cpptraj: Add option to append variables.
Oct 6, 2017
281e1cc
DRR - Cpptraj: Slight change in output format of 'show' to make thing…
Oct 6, 2017
758377c
DRR - Cpptraj: First attempt at recognizing data sets as script vars
Oct 6, 2017
83ed33e
DRR - Cpptraj: Correctly recognize data set name
drroe Oct 6, 2017
8d98084
DRR - Cpptraj: Replace script variable instance with data set value
drroe Oct 10, 2017
0c874f5
DRR - Cpptraj: Improve output message and recognize dash and undersco…
Oct 10, 2017
06b5280
DRR - Cpptraj: Replace all instances of script variables in an argument.
Oct 12, 2017
ced0ad0
DRR - Cpptraj: Add test of dataset as script var
Oct 12, 2017
18d0464
DRR - Cpptraj: Hide some debug info.
Oct 12, 2017
91f4d6e
DRR - Cpptraj: Fix issue when reading old amber topology files with l…
Oct 13, 2017
97335c1
DRR - Cpptraj: Add 'quietblocks' command to suppress output within co…
Oct 13, 2017
1afc8dd
DRR - Cpptraj: Add check for valid integer in Get12I6()
Oct 16, 2017
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
8 changes: 7 additions & 1 deletion src/ArgList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@ void ArgList::RemoveFirstArg() {
marked_.erase( marked_.begin() );
}

/** Replace argument at position with given argument. Update ArgLine. */
void ArgList::ChangeArg(unsigned int idx, std::string const& arg) {
size_t pos = argline_.find( arglist_[idx], 0 );
argline_.replace(pos, arglist_[idx].size(), arg);
arglist_[idx] = arg;
}

// ArgList::Command()
/* \return pointer to the first argument
*/
Expand Down Expand Up @@ -440,7 +447,6 @@ bool ArgList::hasKey(const char *key) {
/** \param key string to search for
* \return true if key is found, false if not.
*/
// NOTE: Should this be ignoring previously marked strings?
bool ArgList::Contains(const char *key) const {
for (unsigned int arg = 0; arg < arglist_.size(); arg++)
if (!marked_[arg]) {
Expand Down
5 changes: 4 additions & 1 deletion src/ArgList.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class ArgList {
bool empty() const { return arglist_.empty(); }
/// \return the argument string
const char *ArgLine() const { return argline_.c_str(); }
std::string const& ArgLineStr() const { return argline_; }
/// \return all unmarked arguments as a string
std::string ArgString() const;
/// Clear list
Expand All @@ -60,6 +61,8 @@ class ArgList {
void PrintDebug() const;
/// Remove the first argument
void RemoveFirstArg();
/// Change argument at position to new argument
void ChangeArg(unsigned int, std::string const&);
/// \return the first argument
const char *Command() const;
/// \return true if the first argument matches key
Expand All @@ -86,7 +89,7 @@ class ArgList {
double getKeyDouble(const char*, double);
/// \return true if the key is present in the list
bool hasKey(const char*);
/// \return true if they key is in the list but do not mark.
/// \return true if they key is in the list and unmarked; do not mark.
bool Contains(const char*) const;
private:
/// Empty string to return when args not found
Expand Down
4 changes: 2 additions & 2 deletions src/Cmd.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
class Cmd {
public:
typedef std::vector< std::string > Sarray; // TODO put in common header?
/// Command destinations. EXEcute, ACTion, ANAlysis, DEPrecated.
enum DestType { EXE = 0, ACT, ANA, DEP };
/// Command destinations. EXEcute, ACTion, ANAlysis, ConTroL, BLocK, DEPrecated.
enum DestType { EXE = 0, ACT, ANA, CTL, BLK, DEP };
/// CONSTRUCTOR
Cmd() : object_(0), dest_(EXE) {}
/// CONSTRUCTOR - takes destination, DispatchObject pointer, and keywords.
Expand Down
184 changes: 171 additions & 13 deletions src/Command.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <cctype> // isalnum
#include <cstdarg>
#include <algorithm> // std::sort()
#include "Command.h"
Expand All @@ -6,6 +7,7 @@
#include "CmdInput.h" // ProcessInput()
#include "RPNcalc.h"
#include "Deprecated.h"
#include "Control.h"
// ----- GENERAL ---------------------------------------------------------------
#include "Exec_Analyze.h"
#include "Exec_Calc.h"
Expand Down Expand Up @@ -173,6 +175,12 @@ const Cmd Command::EMPTY_ = Cmd();

Command::Carray Command::names_ = Command::Carray();

Command::CtlArray Command::control_ = Command::CtlArray();

int Command::ctlidx_ = -1;

VariableArray Command::CurrentVars_ = VariableArray();

/** Initialize all commands. Should only be called once as program starts. */
void Command::Init() {
// GENERAL
Expand All @@ -193,6 +201,7 @@ void Command::Init() {
Command::AddCmd( new Exec_NoProgress(), Cmd::EXE, 1, "noprogress" );
Command::AddCmd( new Exec_Precision(), Cmd::EXE, 1, "precision" );
Command::AddCmd( new Exec_PrintData(), Cmd::EXE, 1, "printdata" );
Command::AddCmd( new Exec_QuietBlocks(), Cmd::EXE, 1, "quietblocks" );
Command::AddCmd( new Exec_Quit(), Cmd::EXE, 2, "exit", "quit" );
Command::AddCmd( new Exec_ReadData(), Cmd::EXE, 1, "readdata" );
Command::AddCmd( new Exec_ReadInput(), Cmd::EXE, 1, "readinput" );
Expand Down Expand Up @@ -364,6 +373,10 @@ void Command::Init() {
Command::AddCmd( new Analysis_Timecorr(), Cmd::ANA, 1, "timecorr" );
Command::AddCmd( new Analysis_VectorMath(), Cmd::ANA, 1, "vectormath" );
Command::AddCmd( new Analysis_Wavelet(), Cmd::ANA, 1, "wavelet" );
// CONTROL STRUCTURES
Command::AddCmd( new ControlBlock_For(), Cmd::BLK, 1, "for" );
Command::AddCmd( new Control_Set(), Cmd::CTL, 1, "set" );
Command::AddCmd( new Control_Show(), Cmd::CTL, 1, "show" );
// DEPRECATED COMMANDS
Command::AddCmd( new Deprecated_AvgCoord(), Cmd::DEP, 1, "avgcoord" );
Command::AddCmd( new Deprecated_DihScan(), Cmd::DEP, 1, "dihedralscan" );
Expand All @@ -378,8 +391,21 @@ void Command::Init() {
names_.push_back( 0 );
}

/** Free all commands. Should only be called just before program exit. */
void Command::Free() { commands_.Clear(); }
/** Clear any existing control blocks. */
void Command::ClearControlBlocks() {
for (CtlArray::iterator it = control_.begin(); it != control_.end(); ++it)
delete *it;
control_.clear();
ctlidx_ = -1;
}

/** Free all commands. Should only be called just before program exit. Also
* remove any remaining control blocks.
*/
void Command::Free() {
commands_.Clear();
ClearControlBlocks();
}

/** \param oIn Pointer to DispatchObject to add as command.
* \param dIn Command destination
Expand All @@ -403,17 +429,23 @@ void Command::AddCmd(DispatchObject* oIn, Cmd::DestType dIn, int nKeys, ...) {
}

/** Search Commands list for command with given keyword and object type. */
Cmd const& Command::SearchTokenType(DispatchObject::Otype catIn, const char* keyIn)
Cmd const& Command::SearchTokenType(DispatchObject::Otype catIn, const char* keyIn,
bool silent)
{
for (CmdList::const_iterator cmd = commands_.begin(); cmd != commands_.end(); ++cmd)
{
if (catIn != cmd->Obj().Type()) continue;
if (cmd->KeyMatches(keyIn)) return *cmd;
}
mprinterr("'%s': Command not found.\n", keyIn);
if (!silent) mprinterr("'%s': Command not found.\n", keyIn);
return EMPTY_;
}

/** Search Commands list for command with given keyword and object type. */
Cmd const& Command::SearchTokenType(DispatchObject::Otype catIn, const char* keyIn) {
return SearchTokenType(catIn, keyIn, false);
}

/** Search the Commands list for given command.
* \return the token if found, 0 if not.
*/
Expand Down Expand Up @@ -467,31 +499,159 @@ void Command::ListCommands(DispatchObject::Otype typeIn) {
ListCommandsForType( typeIn );
}

/** Search for the given command and execute it. EXE commands are executed
* immediately and then freed. ACT and ANA commands are sent to the
* CpptrajState for later execution.
/** \return true if any control blocks remain. */
bool Command::UnterminatedControl() {
if (!control_.empty()) {
mprinterr("Error: %u unterminated control block(s) detected.\n", ctlidx_+1);
for (int i = 0; i <= ctlidx_; i++)
mprinterr("Error: %i : %s\n", i, control_[i]->Description().c_str());
return true;
}
return false;
}

/** Create new control block with given Block. */
int Command::AddControlBlock(ControlBlock* ctl, CpptrajState& State, ArgList& cmdArg) {
if ( ctl->SetupBlock( State, cmdArg ) )
return 1;
if (ctlidx_ == -1) mprintf("CONTROL: Starting control block.\n");
control_.push_back( ctl );
ctlidx_++;
mprintf(" BLOCK %2i: ", ctlidx_);
for (int i = 0; i < ctlidx_; i++)
mprintf(" ");
mprintf("%s\n", ctl->Description().c_str());
//mprintf("DEBUG: Begin control block %i\n", ctlidx_);
return 0;
}

#define NEW_BLOCK "__NEW_BLOCK__"
/** Execute the specified control block. */
int Command::ExecuteControlBlock(int block, CpptrajState& State)
{
control_[block]->Start();
ControlBlock::DoneType ret = control_[block]->CheckDone(CurrentVars_);
if (State.Debug() > 0) {
mprintf("DEBUG: Start: CurrentVars:");
CurrentVars_.PrintVariables();
}
while (ret == ControlBlock::NOT_DONE) {
for (ControlBlock::const_iterator it = control_[block]->begin();
it != control_[block]->end(); ++it)
{
if (it->CommandIs(NEW_BLOCK)) {
// Execute next control block
if (ExecuteControlBlock(block+1, State)) return 1;
} else {
for (int i = 0; i < block; i++) mprintf(" ");
// Execute command
if ( ExecuteCommand(State, *it) != CpptrajState::OK ) return 1;
}
}
ret = control_[block]->CheckDone(CurrentVars_);
}
if (ret == ControlBlock::ERROR) return 1;
return 0;
}

/** Handle the given command. If inside a control block, if the command is
* a control command a new block will be created, otherwise the command will
* be added to the current control block. Once all control blocks are
* complete they will be executed. If not inside a control block, just
* execute the command.
*/
CpptrajState::RetType Command::Dispatch(CpptrajState& State, std::string const& commandIn)
{
ArgList cmdArg( commandIn );
cmdArg.MarkArg(0); // Always mark the first arg as the command
cmdArg.MarkArg(0); // Always mark the first arg as the command
// Check for control block
if (!control_.empty()) {
mprintf(" [%s]\n", cmdArg.ArgLine());
// In control block.
if ( control_[ctlidx_]->EndBlock( cmdArg ) ) {
// End the current control block.
//mprintf("DEBUG: End control block %i.\n", ctlidx_);
mprintf(" BLOCK %2i: ", ctlidx_);
for (int i = 0; i < ctlidx_; i++)
mprintf(" ");
mprintf("END\n");
ctlidx_--;
if (ctlidx_ < 0) {
// Outermost control structure is ended. Execute control block(s).
mprintf("CONTROL: Executing %u control block(s).\n", control_.size());
if (State.QuietBlocks()) SetWorldSilent(true);
int cbret = ExecuteControlBlock(0, State);
ClearControlBlocks();
if (State.QuietBlocks()) SetWorldSilent(false);
if (cbret != 0) return CpptrajState::ERR;
mprintf("CONTROL: Control block finished.\n\n");
}
} else {
// Check if this is another control block statement (silently)
Cmd const& ctlCmd = SearchTokenType(DispatchObject::CONTROL, cmdArg.Command(), true);
if (ctlCmd.Empty() || ctlCmd.Destination() != Cmd::BLK) { // TODO just check Destination?
// Add this command to current control block.
control_[ctlidx_]->AddCommand( cmdArg );
mprintf("\tAdded command '%s' to control block %i.\n", cmdArg.Command(), ctlidx_);
} else {
// Tell current block that a new block is being created
control_[ctlidx_]->AddCommand(NEW_BLOCK);
// Create new control block
DispatchObject* obj = ctlCmd.Alloc();
if (AddControlBlock( (ControlBlock*)obj, State, cmdArg )) {
delete obj;
ClearControlBlocks();
return CpptrajState::ERR;
}
}
}
return CpptrajState::OK;
}
return ExecuteCommand( State, cmdArg );
}

#undef NEW_BLOCK

/** Search for the given command and execute it. Replace any variables in
* command with their values. EXE and CTL commands are executed immediately
* and then freed. ACT and ANA commands are sent to the CpptrajState for later
* execution. BLK commands set up control blocks which will be executed when
* the outer control block is completed.
*/
CpptrajState::RetType Command::ExecuteCommand( CpptrajState& State, ArgList const& cmdArgIn ) {
// Replace variable names in command with entries from CurrentVars
ArgList cmdArg = CurrentVars_.ReplaceVariables( cmdArgIn, State.DSL(), State.Debug() );
if (cmdArg.empty()) return CpptrajState::ERR;
// Print modified command
mprintf(" [%s]\n", cmdArg.ArgLine());
// Look for command in command list.
Cmd const& cmd = SearchToken( cmdArg );
CpptrajState::RetType ret_val = CpptrajState::OK;
if (cmd.Empty()) {
// Try to evaluate the expression
// Not a command. Try to evaluate the expression
RPNcalc calc;
calc.SetDebug( State.Debug() );
if (calc.ProcessExpression( commandIn ))
if (calc.ProcessExpression( cmdArg.ArgLineStr() ))
ret_val = CpptrajState::ERR;
else {
if (calc.Evaluate( State.DSL() ))
ret_val = CpptrajState::ERR;
}
if (ret_val == CpptrajState::ERR)
mprinterr("'%s': Invalid command or expression.\n", commandIn.c_str());
mprinterr("'%s': Invalid command or expression.\n", cmdArg.ArgLine());
} else {
DispatchObject* obj = cmd.Alloc();
switch (cmd.Destination()) {
case Cmd::CTL:
ret_val = ((Control*)obj)->SetupControl(State, cmdArg, CurrentVars_);
delete obj;
break;
case Cmd::BLK:
if (AddControlBlock( (ControlBlock*)obj, State, cmdArg )) {
delete obj;
return CpptrajState::ERR;
}
break;
case Cmd::EXE:
ret_val = ((Exec*)obj)->Execute( State, cmdArg );
delete obj;
Expand Down Expand Up @@ -530,8 +690,6 @@ CpptrajState::RetType Command::ProcessInput(CpptrajState& State, std::string con
}
// Only attempt to execute if the command is not blank.
if (!input.Empty()) {
// Print the input line that will be sent to dispatch
mprintf(" [%s]\n", input.str());
# ifdef TIMER
Timer time_cmd; // DEBUG
time_cmd.Start(); // DEBUG
Expand Down
25 changes: 22 additions & 3 deletions src/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,49 @@
#define INC_COMMAND_H
#include "CmdList.h"
#include "CpptrajState.h"
#include "Control.h"
class Command {
public:
static void Init();
static void Free();
/// List commands of given type, or all if type is NONE
static void ListCommands(DispatchObject::Otype);
/// Add a command to the list of commands
static void AddCmd(DispatchObject*, Cmd::DestType, int, ...);
/// \return command corresponding to first argument in ArgList.
static Cmd const& SearchToken(ArgList&);
/// \return command of given type corresponding to given command key.
static Cmd const& SearchTokenType(DispatchObject::Otype, const char*);
/// \return true if unterminated control block(s) exist.
static bool UnterminatedControl();
/// Execute command, modifies given CpptrajState
static CpptrajState::RetType Dispatch(CpptrajState&, std::string const&);
/// Read input commands from given file, modifies given CpptrajState.
static CpptrajState::RetType ProcessInput(CpptrajState&, std::string const&);
/// \return Pointer to command name address.
static const char* CmdToken(int idx) { return names_[idx]; }
private:
/// Add a command to the list of commands
static void AddCmd(DispatchObject*, Cmd::DestType, int, ...);
/// Search for command of given type with given name; can be silent.
static Cmd const& SearchTokenType(DispatchObject::Otype, const char*, bool);
/// List all commands for given type
static void ListCommandsForType(DispatchObject::Otype);
/// Clear all existing control blocks
static void ClearControlBlocks();
/// Add a new control block
static int AddControlBlock(ControlBlock*, CpptrajState&, ArgList&);
/// Execute specified control block
static int ExecuteControlBlock(int, CpptrajState&);
/// Execute given command
static CpptrajState::RetType ExecuteCommand(CpptrajState&, ArgList const&);

static CmdList commands_; ///< Master list of commands.
static const Cmd EMPTY_; ///< Empty command.
typedef std::vector<const char*> Carray;
static Carray names_; ///< Array of pointers to all command names, for ReadLine
static Carray names_; ///< Array of pointers to all command names, for ReadLine
typedef std::vector<ControlBlock*> CtlArray;
static CtlArray control_; ///< Array of control blocks
static int ctlidx_; ///< Point to current control block
/// Array of script variables and their current values.
static VariableArray CurrentVars_;
};
#endif
Loading