Dynamic Objects, E xpressions & S cripts in Dot-Net
DOES.NET technology allows you writing and executing expressions and scripts in NET languages without compilation. CODEDOM assembly makes DOES.NET unuseful in NET Framework. But this technology can be really useful in Silverlight (in browser) where CODEDOM is absent. You can declare and use dynamic variables (objects) right in scripts without compilation. If you used dynamic languages earlier then you be glad receiving ability to use dynamic code, macro substitutions and other powers of simple interpreter in Silverlight. DOES.NET is a minimal set of means to create any dynamic code (except type declarations).
This part of documentation describes DOES.NET technology using without internal details.
DOES.NET net components include two library for each NET platform:
Include references to these dll into your project and add using DOES; using DOESExplWPF; into code.
! DOES.NET solutions were created for internal use in our projects. Let us know about your interests and problems and we'll try develop this technology and fix bugs.
You should call Init method of Does class object to initialize it. And you can change any default settings to optimize memory use before initialization ofDoes class object. These settings depend on how many dynamic objects and functions you plan to use in your scripts.
Does.PublicObjectsDefault -initial length of table to store public objects. The default value is 5000;
Does.PrivateObjectsDefault -initial length of table to store private objects. The default value is 5000. Table of private objects will be created for each level of stack when you call DOES.NET function;
Does.PublicFunctionsDefault -initial length of table to store public functions. The default value is 1000;
Does.PrivateFunctionsDefault -initial length of table to store private functions. The default value is 1000. Table of private functions will be created for each level of stack when you call DOES.NET function;
Does.MaxStackLevels -initial length of stack. The default value is 1000;
These are just initial values. DOES.NET uses these values to create tables. But if the length of any table is exceeded when DOES.NET try to create new object, then DOES.NET increases a size of table. But this operation takes additional time and memory.
Your C# code looks like:
Error occurs if you don't call Init() method before Does class object use.
Next example demonstrates few main DOES.NET abilities and has not any sense. You can see the technique when DOES.NET dynamic objects of any types can be declared, you can assign values to them before executing of DOES.NET scripts, and take values back from DOES.NET objects after script executed.
Inside script you can also: declare objects of any types; assign values; evaluate expressions with references to DOES.NET objects, NET types, members of objects and types; can create arrays and reference their elements in expressions; can use constructions IF-ELSE-END and WHILE-CONTINUE-BREAK-END loops; can declare and call functions, use macro substitutions, and etc...
Note! Use comma or point in numeric constants to convert them to Decimal (0,00 or 0.0). Else constant will be converted to Int32.
Dynamic objects in DOES.NET can have one of three status: Public, Private, Local . And they can be declared and be accessible from NET language code or inside DOES.NET script. Object names are case insensitive.
Public objects are objects declared in root (zero) stack level and can be accessible from any function in any level of stack.
Private objects are objects declared in any stack level and can be accessible in current function and in functions called from current (in next levels of stack).
Local objects are objects declared in any stack level and can be accessible only in current function.
To declare DOES.NET objects in C# code use methods Declare and Set of Does class object.
publicbool Declare( String Name) - this method declares object without value assignment.
publicbool Declare( String Name, Object Value) - this overload of Declare method allows to declare object and to assign value of any type.
publicbool Set( String Name, Object Value) - actually equals to Declare overload.
If object with the same name already exists then two last methods just replace its value without exception creating. First method do nothing.
If dynamic objects was declared in script (in root stack level) then they also can be visible after script executed and can be accessible from C# and VB# code.
Use commands: Public, Create and Local to declare empty objects inside DOES.NET script. For example:
DOES.NET creates dynamic objects for each name listed after directive and delimited with comma.
Use function New(sType) to declare DOES.NET objects of specified type or to replace existing objects with new values of specified type.
"...; Var10=New(\"System.String\"); Var10=\"This is string\";...; Var11=String.Copy(Var10); ...;"
You must pass valid type name as string as parameter into function New. And assembly should be accessible.
Use function Array(sType,Dim1[,Dim2[,...,DimN]]) to create arrays of objects of specified type. For example:
"...; Arr1=Array(\"System.String\",10); ...;" - this script fragment creates one-dimension array String;
"...; Arr2=Array(\"System.Windows.Controls.TextBloc\k",10,3,7); ...;" - this script fragment creates three-dimension array TextBlock[10,3,7];
Refer to elements of arrays in script using square brackets:
Public objects can be accessible from anywhere. Private objects can be accessible in current function and functions called inside current. But if private object with the same name is created in nested function (sublevels of stack) , then public object and private objects of higher levels with the same names will be inaccessible from this function and functions called from this function. All local variables, declared in function, hide public variables with the same names and private variables with the same names declared in external functions (higher levels of stack), but only inside current function except nested functions.
DOES.NET clears all dynamic objects in all levels of stack except root (zero) after script execution ( oDoes.Execute(sScript) ; ). To decrease a quantity of operations with memory and to increase a speed DOES.NET doesn't erase private objects, functions and their tables after each function call, because this operation takes a time. So. If some variables prevent you work then you should clear they manually:
"...; Clear nVar1,nVar2,nVar3; Clear *Var1,nVar*,*Var*;"
Any way use * in beginning or end of mask or full object names.
After script executed, you can refer to DOES.NET object of root stack level in C# or VB code using Get method of Does class object:
Expressions in DOES.NET can consist from references, DOES.NET functions , constants and operators.
References can be to: DOES.NET objects; Types in available assemblies; members of objects; static members of types; elements of arrays.
DOES.NET uses following order when identify each identifier found in expression:
Operators can be:
The priority of operators is: (* / ) (+ -) (Comparison) (Logic)
Example of expression:
(nVar1+nVar2)*sVar1.Length > Func1(nVar3) || Func2(nVar4) == nVar5/123
Script lines can include expressions. And you can evaluate expressions in DOES.NET explorer.
Note! If expression is complex and consist of several actions - set a priority of operation explicitly using brackets ( ) ! For example:
this expression will produce error:
While Func1(nVar1,nVar2,nVar3)>1.0 && Func2(nVar1,nVar2,nVar3)>2.0 && Func3(nVar1,nVar2,nVar3)>3.0;
take members of single operation in brackets!!!
While ( Func1(nVar1,nVar2,nVar3)>1.0 && Func2(nVar1,nVar2,nVar3)>2.0 ) && Func3(nVar1,nVar2,nVar3)>3.0;
DOES.NET expression syntax allows to use constants:
Simply use DOES.NET object names to operate their values in expressions. Object names are case insensitive.
If a type has static members then you can call such static methods or refer to static fields and properties of this type. If you use type name without assembly specification then DOES.NET searches type in all available assemblies and only if dynamic object and functions with the same name aren't found before. But you can optimize this searching. Type names are case sensitive.
You can refer to members of dynamic object or static members of type with using of common syntax Object.Member.Member... (point as delimiter)
and etc... Member names are case sensitive.
Each name-identifier, found in expression, DOES.NET searches in objects tables first. Second - in functions tables. Order is from current stack level to root. The scope of visibility is the same to objects. The number of parameters can be less or equal to number of parameters declared for this function. Function names are case insensitive.
In cases when you have declared a lot of dynamic objects and functions and have loaded a lot of assemblies, the search time can grow. Expression syntax allows you optimize references by pointing a way to search name-identifier. You can use next syntax:
^ delimiters to define a full type. If name includes ^ symbol then DOES.NET replaces these symbols with points and accepts the result as full name of type. In this case DOES.NET does not search type in all assemblies but try invoking member without checking of its existence. Make sure a full type is correct! ( #System^String.Copy(\"sting\") )
You can essentially increase a speed of script executing with using of this references optimization.
Use Does class method static bool Not(Object _val) or static bool not(Object _val) to make unary inversion. It looks like "...; IF Does.Not(False); ...;"
In general DOES.NET is a technology of macro substitutions for NET languages (C# and VB). But you can use nested evaluations inside scripts. In all cases you can evaluate just completed commands and can't substitute separated clauses (like in FoxPro). And any member of expressions (except operators) can be as string subexpression stored in variable.
Eval(sExpression) function allows to use expression evaluations instead of macro substitutions in DOES.NET . For example:
If you need to run a script inside another script then you can use Exec(sScript) function. For example:
Method Execute(sScript) allows script executing.
Script is a set of commands in one string. Each command should be ended with semicolon (;) symbol. Command can be:
You can declare:
Usually commands are:
Each element of this construction should be closed with semicolon (;) too. Any quantity of any commands can be placed between elements, including nested IF ... ELSE ... END and WHILE ... END. For example:
Each element of this construction should be closed with semicolon (;) too. Any quantity of any commands can be placed between elements, including nested WHILE ... END and IF ... ELSE ... END. For example:
Class Does has a field Console of type:
If you create an object which inherits this interface and store the reference to this object to Does.Console field, then you can use directive ? to write e result of expression onto your console. See script example above.
If a command of DOES.NET script begins with // symbols then Does.Execute accepts this command as comments and does nothing.
DOES.NET functions can be public if declared in root stack level and private (if declared in stack sublevels). Private functions will be released after script executing by Does.Execute(sScript).
Public functions can be declared before scripts executions with using method of Does class:
bool DeclareFunction( String Name, String  Args, String Code)
First parameter is a function name.
Second parameter is a list of names of arguments (just names).
Third parameter is a script of function body.
Public and private functions can be declared right in script during executing. Function declarations should be placed in beginning of script before lines of script. Each function declaration should be ended with EndFunc directive. For example:
Use Return command to return value from function. For example: "Return Parm1-Parm3;"
Return value is also placed into variable with name ReturnValue in parent function or script (which calls current function).
To use DOES.NET Explorer you should:
or in C#:
!!! DOES Explorer creates new Does class object as internal field! You should pass a reference to existing Does class object as parameter into DOESExplorer(Does _ds) constructor to explore this Does class object! DOES Explorer window code looks like:
After this you can call DOESExplorer as children window from your code and can pass working oDoes instance to explore and trace.
DOES Explorer consists of four pages:
In page with objects you can create new objects of specified type and can select from list of assemblies and types ( "New..." button to call list of assemblies).
In page with functions you can just list functions and see their code.
In "Expression" page you can write any DOES.NET expression, evaluate it and watch the results of evaluations.
Main page is "Script" page. In this page you can:
If you have created DOES Explorer as children window then you can call it when error occurs during script executing and see what are problems. For example:
You can call script debugger from DOES Explorer by "Begin trace" button or in code of your NET program.
Class Does has field Trace of interface
There is a class DOESTrace declared in DOESExplWPF.dll which implements IDOESTrace and you can create object of this type and can call to debug from your code. Create object of type DOESTrace and store reference to this object into Does.Trace before Does.Execute call. Does.Execute calls trace window on each line of your script.
System.Type class and System.Reflection namespace allow to work with types and their members. We use these means as base of DOES.NET technology. Second part of technology is name tables. This part was inherited from other dynamic languages like FoxPro. And third part is a simple interpreter with minimal abilities, but a set of abilities is sufficient to create any dynamic code (scripts and expressions) except type declaration. We had to mix syntax C# and VFP to create DOES.NET script syntax and we tried to create this syntax most simple. But some internal details, described in this part of documentation, can be useful to implement more complex solutions with using DOES.NET
DOES.NET uses internal tables to store references to dynamic objects and functions and their names. We call them "name tables" (inherited from FoxPro). On each level of stack DOES.NET creates two name tables for objects and functions. A length of each type of name table depends on stack level (root or not) and Does class properties:
You can change a default sizes of table before oDoes.Init() method call. But the size of table is not fixed, and DOES.NET can expand table dimensions if size is exceeded.
When you declare new object or function then DOES.NET does:
Each element of stack (each level) has objects name table as field
Each element of objects name table keeps: a name of object, reference to object and "local" status.
To clear object DOES.NET just replace a reference to name-table-element and object with null. Further just NET FrameWork knows how and when to free memory (if there are no other references to this object). So, you have to control objects life time by yourself if needed (for example with using interface).
Function name table is similar to object name table. But element of function-name-table keeps: the name of function, list of argument names and script of function body as string.
Each element of stack (each level) has a structure.
Size of stack table is fixed! (1000 levels) If a size of stack table is exceeded during next function call, then DOES.NET doesn'texpand stack table. So if you plan more nested calls then increase
Does.MaxStackLevels property before Does.Init() call.
|Bigger or bigger||
_val1, Object _val2)
public static Object bigger(Object _val1, Object _val2)
Returns first argument if it bigger than second, else returns second.
|DeclareFunction||public bool DeclareFunction(String Name, String Args, String Code) Declares dynamic function with name Name, list of arguments Args and body script Code.|
Declares local dynamic object with name Name
and null value.
public bool DeclareLocal(String Name, Object Value) Declares local dynamic object with name Name and assigns Value.
Declares public dynamic object with name Name
and null value
public bool DeclarePublic(String Name, Object Value) Declares public dynamic object with name Name and assigns Value.
|DoCmd||public Object DoCmd(String Cmd) Executes single command. In difference to expression evaluation command can be assignment and can have left and right sides.|
|Evaluate||public Object Evaluate(String Expr) Executes expression and returns result.|
public Object Execute(String Script)
Executes script. Script is a set of commands.
|Iif or iif||
Object iftrue, Object iffalse)
public static Object iif(bool Cond, Object iftrue, Object iffalse)
Returns second argument if first argument is true else returns third argument.
|Init||public bool Init() Initializes Does class object. Should be called before Does class object using. You can change default values of properties PublicObjectsDefault , PrivateObjectsDefault, PublicFunctionsDefault, PrivateFunctionsDefault and MaxStackLevels before Init().|
|Get||public Object Get(String Name) Returns value of dynamic object by name.|
|New||public Object New(String Type) Creates and returns object of specified type Type . Type is full name of type in assembly.|
|Not or not||
public static bool not(Object _val)
Negation. Unary operation. Boolean inversion.
|Release||public void Release(String Name) Deletes dynamic object. Name can be mask like *mask, mask* or *mask*, or name of object.|
|Set||public bool Set(String Name, Object Value) Replaces value of dynamic object with name Name with reference to object Value. If dynamic object with name Name doesn't exist then private object with this name will be created.|
|Smaller or smaller||
_val1, Object _val2)
public static Object smaller(Object _val1, Object _val2)
Returns first argument if it smaller than second else returns second argument
Properties and fields:
|Console||public IDOESConsole Console Replace this field with reference to IDOESConsole object to use output values by ? directive.|
|ErrorCode||public int ErrorCode Check after Does.Execute() , Does.DoCmd() and Does.Evaluate() . If there are no errors then Does.ErrorCode is 0. Else has a code of error.|
|ErrorFunction||public String ErrorFunction The name of function where error occurred.|
|ErrorException||public Exception ErrorException Exception object if unhandled error occurred.|
|ErrorLine||public int ErrorLine Line of script or function where error occurred.|
|ErrorMessage||public String ErrorMessage Error message.|
|MaxStackLevels||public int MaxStackLevels = 1000; A length of stack. Change this value if needed but before Does.Init() call.|
|PrivateFunctionsDefault||public int PrivateFunctionsDefault = 1000; A default length of private functions name table. Change this value if needed but before Does.Init() call.|
|PrivateObjectsDefault||public int PrivateObjectsDefault = 5000; A default length of private objects name table. Change this value if needed but before Does.Init() call.|
|PublicFunctionsDefault||public int PublicFunctionsDefault = 1000; A default length of public functions name table. Change this value if needed but before Does.Init() call.|
|PublicObjectsDefault||public int PublicObjectsDefault = 5000; A default length of public objects name table. Change this value if needed but before Does.Init() call.|
|Stack||public DOESStackElement Stack; Stack table.|
|StackLevel||public int StackLevel Current level of stack.|
|Trace||public IDOESTrace Trace Replace this field with reference to DOESTrace object to begin a debugging on Does.Execute() call.|