Extending the API
Adding custom builtin functions
AddyScript's inner functions are all instances of the AddyScript.Runtime.InnerFunction class. This class has a static property called Globals that contains all of its predefined instances. To extend the list of the scripting engine's built-in functions, simply create new instances of InnerFunction and add them to the InnerFunction.Globals collection. The InnerFunction constructor takes as arguments a string representing the name of the function, an array of AddyScript.Runtime.Parameter objects representing the list of parameters that the function expects, and an instance of AddyScript.Runtime.InnerFunctionLogic representing the body of the function. InnerFunctionLogic is a delegate type that takes an array of AddyScript.Runtime.DataItems.DataItem objects as a parameter and returns an object of the same DataItem type as a result. Any method in a .NET class that has this prototype can be used as the body of an inner function. Here is a C# code example that demonstrates how to add a "clrscr" function to AddyScript to clear the screen:
Note: Just make sure to call MyExtensions.RegisterFunctions(); somewhere in your code before launching the interpreter.
Adding custom builtin classes
Defining a new built-in class in AddyScript can have two meanings: it can mean adding a primitive type to the scripting engine. It can also mean adding a new object type to the scripting engine. Defining a new primitive type requires much more effort than creating a new object class. In all cases, you will need to create an instance of the AddyScript.Runtime.OOP.Class metaclass and add it to the AddyScript.Runtime.Class.OOP.Class.Predefined collection.
Object classes
For a new object class, you will make it reference AddyScript.Runtime.OOP.Class.Object directly or indirectly as its base class. The metaclass has a constructor that allows you to specify the parent class. Afterward, you will only need to provide member definitions to the new class. All members can be defined manually. For methods, this means creating their AST from scratch. But there is a shortcut that consists in creating an InnerFunction which will not be added to the InnerFunction.Globals collection but will instead be converted to AddyScript.Runtime.OOP.ClassMethod using one of the ToInstanceMethod or ToStaticMethod methods of the InnerFunction class.
Example:
This is how we could define the Exception class if it didn't exist in AddyScript
Primitive types
Creating a new primitive type goes through these same steps. But before that, you must add a new member to the AddyScript.Runtime.OOP.ClassID enumeration to represent the new type. Afterward, you will have to create a new instance of the metaclass as described above. This new class will not have a reference to a parent class, but it will have the newly defined ClassID (there is a suitable constructor in Class). After that you will need to create a new child class of AddyScript.Runtime.DataItems.DataItem to represent data of the type being defined. The Class property of this DataItem type should return the reference to the previously created Class instance. DataItem provides a whole range of virtual methods that define the behavior of an object in arithmetic operations, conversions, and property accesses. Overriding one of these methods allows you to customize the behavior of the new data type. You will probably also want to take a look at the AddyScript.Runtime.DataItems.DataItemFactory and AddyScript.Runtime.DataItems.DataItemBinder classes to add support for your data type in Marshaling operations. AddyScript.Runtime.DataItems.DataItemFactory has a CreateDataItem method that converts a .NET System.Object into a DataItem, you will certainly want to add support for your data type. AddyScript.Runtime.DataItems.DataItemBinder on its side has a Mismatch method that evaluates the degree of compatibility between .NET data types and AddyScript data types. You will also need to add support for your data type.
The last step in the process of creating a primitive type is to decide how you want to create data of this type. You may want it to have literal values or initializers or simply a static factory method. If the choice of the factory method is made, then the work is done: the factory method is probably already part of the class definition. On the other hand, for a literal value or an initializer, it will be necessary to update the analyzers so that they recognize a new category of symbols. It will also be necessary to modify the translators so that they know how to translate this new type of symbol.
Have a look at how existing primitive types are defined to better understand the whole process.