Class implementation
The embedded classes
The class of Rice is roughly divided into three classes.
Basic class
User defined class
Embedded class
The basic class is the class that is defined in Rice.dll.
The user defined class is the class that is defined by the user in the Rice language.
The Embedded class is other class.
It can use the basic class just by incorporating Rice into the project. However, it is not possible to manipulate the application from the user definition class (Rice program).
You must give the user a way to manipulate the application. And embedded class is the way to do it.
Embedded classes are classes for use with Rice implemented in the application's implementation language. Its main purpose is to describe the application front end.
Currently Rice targets only the .NET Framework, so it can be said to be a class for the Rice language implemented in C# or Visual Basic.
Embedded classes do not just describe the front end of the application. For example, file IO, database access and image analysis. You can freely expand the function of Rice language by using embedded classes.
The following description is a way to write the code of the embedded class directly in your project. In other words, it is static embedding.
Dynamic embedding. In other words, how to embed classes when executing an application is explained in the next chapter.
Template for embedded class implementation
The following C# code is a template of the embedded class implementation.
1: | //C# template of embedded class |
2: | using Rice; |
3: | |
4: | namespace YourProjectNamespace { |
5: | public class Rtemplate : Rtype { |
6: | public const string TYPENAME = "template"; |
7: | public static Rtype InstanceGetter() { return new Rtemplate(); } |
8: | public Rtemplate() { _typename = TYPENAME; } |
9: | public override Rtype _replica(VirtualMachine vm) { return new Rtemplate(); } |
10: | public override Rbool Fitted(VirtualMachine vm) { return new Rbool(true); } |
11: | |
12: | public override bool _fitter(string signature, VirtualMachine vm, Queue<Rtype> argqueue) { |
13: | //A way of the fitter definition is described later. |
14: | } |
15: | |
16: | public override bool _setter(string signature, VirtualMachine vm, Rtype arg) { |
17: | //A way of the setter definition is described later. |
18: | } |
19: | |
20: | public override Rtype _getter(string signature, VirtualMachine vm) { |
21: | //A way of the getter definition is described later. |
22: | } |
23: | |
24: | public override Rtype _method(string signature, VirtualMachine vm, Queue<Rtype> argqueue) { |
25: | //A way of the method definition is described later. |
26: | } |
27: | } |
28: | } |
Let's explain the essential elements of the definition of embedded class.
using directive
It introduce the Rice namespace.
namespace
Please replace the template namespace "YourProjectNamespace" with the namespace of your project.
Class name
The class name is temporarily "Rtemplate". Please replace with the appropriate class name. The class used as an embedded class must be a derived class of Rtype type defined in Rice.dll.
I've been used an uppercase R as the prefix for the embedded class name.
Class name on Rice program
In the above template, the class name on the Rice program has been specified as the const string.
public const string TYPENAME = "template";
Please replace this string with an appropriate name. It will be used this name when registering a embedded class to Rice.
Please be careful not to duplicate other class names.
Delegate
Instantiation of embedded classes is done by delegates associated with class names unlike user-defined classes. The instance returned by the delegate is an instance which will obtain by declaring or new expression in Rice program.
In the above template, it is defined as follows.
public static Rtype InstanceGetter() { return new Rtemplate(); }
This delegate takes no arguments. Therefore, to call the default constructor in the delegate is normal way to implement.
I recommend using InstanceGetter as the name of the delegate.
This delegate is defined at the rice.cs as a delegate of type PlainProtoTypeGetter.
Constructor
Absolutely set the _typename member variable in all constructors of the embedded class.
The _typename member variable is a protected member variable of string type defined by Rtype type. The _typename member variable becomes the class name returned by the TypeName getter in the Rice program.
It is set as follows even in the above template.
public Rtemplate() { _typename = TYPENAME; }
By using the TYPENAME constant guarantees matching between registered class name and Rice's class name.
_replica() member function
Instantiation of a user defined class is done by returning a copy of the original instance. It is called the replica operation.
If your embedded class is used for a field in a user defined class, the replica operation of the user defined class will attempt the replica operation to the field.
The replica operation to embedded class will call the _replica(VirtualMachine vm) member function.
In the above template, it is defined as follows.
public override Rtype _replica(VirtualMachine vm) { return new Rtemplate(); }
The _replica(VirtualMachine vm) member function should be implemented to return the same instance as the instance returned by the delegate. In other words, you should call the default constructor.
Fitted() member function
The Fitted (VirtualMachine vm) member function is a member function called by calling Fitted getter.
If it is important whether the instance has been initialized, you can show whether the instance is initialized by this member function.
In the above template, it is defined as follows.
public override Rbool Fitted(VirtualMachine vm) { return new Rbool(true); }
Since the above template has no member variables, initialization is not necessary. Therefore, it is implemented to always return true.
If your embedded class has a member variable and it is important whether a member variable is correct status, please implement it so as to return an appropriate value depending on the status of the member variable.
_fitter() member function
public override bool _fitter(string signature, VirtualMachine vm, Queue<Rtype> argqueue) member function is a member function called by calling fitter.
As an example, let's implement the _fitter() member function of the Rtemplate class.
1: | public override bool _fitter(string signature, VirtualMachine vm, Queue<Rtype> argqueue) { |
2: | switch(signature) { |
3: | case TYPENAME + "()": { |
4: | //Default constructor. |
5: | //It is not necessary to describe it unless special initialization processing is required in the default constructor. |
6: | //If there is no default constructor, the system will automatically generate the default constructor. |
7: | //Please write Initialization process here. |
8: | return true; |
9: | } |
10: | case TYPENAME + "(" + Rint.TYPENAME + ")": { |
11: | Rint arg = (Rint)argqueue.Dequeue(); |
12: | //Please write Initialization process here. |
13: | return true; |
14: | } |
15: | case TYPENAME + "(" + Rstring.TYPENAME + "," + Rstring.TYPENAME + ")": { |
16: | Rstring firstArg = (Rstring)argqueue.Dequeue(); |
17: | Rstring secondArg = (Rstring)argqueue.Dequeue(); |
18: | //Please write Initialization process here. |
19: | return true; |
20: | } |
21: | default: |
22: | return false; |
23: | } |
24: | } |
The call name (signature) of the fitter is passed as the first argument.
For example, suppose the Rice source code has the following description.
template templateInstance = new template(10);
The new expression on the right side of this Rice source code creates an new instance of template class (C# Rtemplate type) and calls the fitter. In other words, the _fitter() member function of Rtemplate type instance is called.
At this time, the new expression creates a call name "template(int)" and passes it to the _fitter() member function as the first argument. If source code is "new template();", "template()" will be passed. If source code is "new template("string", "string");", "template(string,string)" will be passed.
In the above example, it branches to appropriate processing by the switch statement. However, any implementation is acceptable if the correct initialization can be performed using the call name.
If the initialization is successful, please return true and finish the _fitter() member function.
It indicates success of the fitter if true is returned. Returning false indicates that there is no fitter corresponding to the call name. Please throw an exception if fitter fails for some reason.
The second argument is a virtual machine. This is a virtual machine executing the current fitter call.
if you want to throw an exception specific to Rice, it obtains an instance of the exception via this virtual machine.
The actual arguments are passed in the third argument.
An instance of the Rtype derived type is stored in Queue<Rtype> type in order of the arguments of the call name (signature) and passed.
As in the above example, please take out from the Queue and cast it to an appropriate Rtype derived type.
If there is no argument, an empty Queue<Rtype> type is passed. Please note that it is not null.
You can define any number of fitters if the type, number, order of arguments are different.
_setter() member function
public override bool _setter(string signature, VirtualMachine vm, Rtype arg) member function is a member function called by calling setter.
As an example, let's implement the _setter() member function of the Rtemplate class.
1: | public override bool _setter(string signature, VirtualMachine vm, Rtype arg) { |
2: | switch(signature) { |
3: | case "A(" + Rint.TYPENAME + ")": { |
4: | Rint intArg = (Rint)arg; |
5: | //Please write setter process here. |
6: | return true; |
7: | } |
8: | case "A(" + Rstring.TYPENAME + ")": { |
9: | Rstring stringArg = (Rstring)arg; |
10: | //Please write setter process here. |
11: | return true; |
12: | } |
13: | case "B(" + Rint.TYPENAME + ")": { |
14: | Rint intArg = (Rint)arg; |
15: | //Please write setter process here. |
16: | return true; |
17: | } |
18: | default: |
19: | return false; |
20: | } |
21: | } |
The call name (signature) of the setter is passed as the first argument.
For example, suppose the Rice source code has the following description.
template templateInstance = new template(10);
templateInstance.A = 20;
Assignment statement that ends with the setter name on the left side of the second line invokes the _setter() member function of templateInstance (instance of C#'s Rtemplate type).
At this time, the assignment statement creates a call name "A(int)" and passes it to the _setter() member function as the first argument. If source code is 'templateInstance.A = "any string";', "A(string)" will be passed. If source code is "templateInstance.B = 20;", "B(int)" will be passed.
In the above example, it branches to appropriate processing by the switch statement. However, any implementation is acceptable if the correct processing can be performed using the call name.
If the processing is successful, please return true and finish the _setter() member function.
It indicates success of the setter if true is returned. Returning false indicates that there is no setter corresponding to the call name. Please throw an exception if setter fails for some reason.
The second argument is a virtual machine. This is a virtual machine executing the current setter call.
if you want to throw an exception specific to Rice, it obtains an instance of the exception via this virtual machine.
The actual argument is passed in the third argument.
An instance of the Rtype derived type as same as the call name (signature) is stored in the argument and passed.
As in the above example, please cast it to an appropriate Rtype derived type.
Setters with the same name can be defined if the type of argument is different.
_getter() member function
public override Rtype _getter(string signature, VirtualMachine vm) member function is a member function called by calling getter.
As an example, let's implement the _getter() member function of the Rtemplate class.
1: | public override Rtype _getter(string signature, VirtualMachine vm) { |
2: | switch(signature) { |
3: | case "A": { |
4: | //Please write getter process here. |
5: | return new Rint(); |
6: | } |
7: | case "B": { |
8: | Rstring stringArg = (Rstring)arg; |
9: | //Please write getter process here. |
10: | return new Rstring(); |
11: | } |
12: | default: |
13: | return null; |
14: | } |
15: | } |
The call name (signature) of the getter is passed as the first argument.
For example, suppose the Rice source code has the following description.
template templateInstance = new template(10);
int intValue = templateInstance.A;
The getter call expression on the right side of the second line invokes the _getter() member function of templateInstance (instance of C#'s Rtemplate type).
At this time, the getter call expression creates a call name "A" and passes it to the _getter() member function as the first argument. If source code is "string strValue = templateInstance.B;", "B" will be passed.
In the above example, it branches to appropriate processing by the switch statement. However, any implementation is acceptable if the correct processing can be performed using the call name.
If the processing is successful, please return an appropriate Rtype derived type and finish the _getter() member function.
It indicates success of the getter if a Rtype derived type is returned. Returning null indicates that there is no getter corresponding to the call name. Please throw an exception if getter fails for some reason.
The second argument is a virtual machine. This is a virtual machine executing the current getter call.
if you want to throw an exception specific to Rice, it obtains an instance of the exception via this virtual machine.
_metod() member function
public override Rtype _method(string signature, VirtualMachine vm, Queue<Rtype> argqueue) member function is a member function called by calling method.
As an example, let's implement the _method() member function of the Rtemplate class.
1: | public override Rtype _method(string signature, VirtualMachine vm, Queue<Rtype> argqueue) { |
2: | switch(signature) { |
3: | case "SampleMethod(" + Rstring.TYPENAME + "," + Rstring.TYPENAME + ")": { |
4: | Rstring firstArg = (Rstring)argqueue.Dequeue(); |
5: | Rstring secondArg = (Rstring)argqueue.Dequeue(); |
6: | //Please write method process here. |
7: | return new Rvoid(); |
8: | } |
9: | default: |
10: | return null; |
11: | } |
12: | } |
The call name (signature) of the method is passed as the first argument.
For example, suppose the Rice source code has the following description.
template templateInstance = new template(10);
templateInstance.SampleMethod("first str arg", "second str arg");
The method call statement of the second line invokes the _method() member function of templateInstance (instance of C#'s Rtemplate type).
At this time, the call statement creates a call name "SampleMethod(string,string)" and passes it to the _method() member function as the first argument.
In the above example, it branches to appropriate processing by the switch statement. However, any implementation is acceptable if the correct processing can be performed using the call name.
If the processing is successful, please return an appropriate Rtype derived type and finish the _method() member function.
It indicates success of the method if a Rtype derived type is returned. Returning null indicates that there is no method corresponding to the call name. Please throw an exception if method fails for some reason.
If there is no return value, please return an instance of Rvoid type.
The second argument is a virtual machine. This is a virtual machine executing the current method call.
if you want to throw an exception specific to Rice, it obtains an instance of the exception via this virtual machine.
The actual arguments are passed in the third argument.
An instance of the Rtype derived type is stored in Queue<Rtype> type in order of the arguments of the call name (signature) and passed.
As in the above example, please take out from the Queue and cast it to an appropriate Rtype derived type.
If there is no argument, an empty Queue<Rtype> type is passed. Please note that it is not null.
You can define any number of methods with same name if the type, number, order of arguments are different.
How to embed class
It explains how to register an embedded class to the Rice.
Public static void AddBuiltIn(string typename, PlainProtoTypeGetter pptg) is provided as a static member function of RiceManager class (C#).
By calling this member function by passing a class name and a delegate of PlainProtoTypeGetter type, registration of the embedded class is complete.
This is the only way to register a embedded class with the Rice.
The above Rtemplate class would be as follows.
RiceManager.AddBuiltIn(Rtemplate.TYPENAME, Rtemplate.InstanceGetter);