-
Notifications
You must be signed in to change notification settings - Fork 2
Imports
TaffyScript is a tiny language with a tiny Base Class Library (BCL). If this was all there was to it, there would be no reason to use it other than for fun or maybe to learn how to write a compiler. Fortunately, that's not all there is to the language. One of the biggest helpers is the ability to import C# methods and call them from TaffyScript projects. In addition, you can also import c# objects.
Here is the syntax for importing a c# method:
import [DeclaringType].[MethodName]([ParameterTypes]) as [ImportName];
Start with the keyword import
, then write the c# Type that declares the method, then the actual method name. Next you write the parameter types (more on this later) of the overload you'd like to import. Finally, you must write as
and then the name you can use to call the method in your TaffyScript project. For a real example, this is how the BCL implements print:
import Console.WriteLine(object) as print;
There are a few constraints on what methods you can import. First, you can only import static methods. C# understand what an object in TaffyScript is, but TaffyScript has no concept of an object in C#, so there would be no way to call instance methods. Therefore, the method must be static. Second, only a few types can be used as a parameter:
- bool
- byte
- sbyte
- short
- ushort
- int
- uint
- long
- ulong
- float
- double
- char
- string
- array1d or array (TsObject[])
- array2d (TsObject[][])
- object (either a TsObject or a plain old object)
- instance (an ITsInstance)
From the C# side of things, if you'd like to write a method that will interface with TaffyScript, just make sure it only has those types in it's method signature. In addition, it must return one of those type or have no return type (void).
If you'd like to write a function that uses a variable number of arguments, things get a bit more tricky. First, you must make sure to get the TaffyScript library from nuget. Next, your method signature must look like this:
using TaffyScript;
[WeakMethod]
public static TsObject MethodName(ITsInstance inst, TsObject[] args)
{
//Insert code here.
}
It can have different names for the parameters. In addition, if you want it to have a variable number of args from c# as well, you can insert params
before TsObject[] args
.
Any method with the WeakMethod attribute will be called directly instead of being wrapped in a special TaffyScript method, but they have to have that method signature. But that does give the method direct access to the arguments passed into it. As an example, here is a method that writes all arguments to the console:
[WeakMethod]
public static TsObject WriteAll(ITsInstance inst, TsObject[] args)
{
foreach(var arg in args)
{
Console.WriteLine(args);
}
}
In addition, it gives the method access to the calling TaffyScript instance if needed. If the script was called outside of an instance, the inst parameter will be null.
Importing objects is an incredibly powerful tool. It allows you to achieve much of the functionality of c# while still gaining the benefits of TaffyScript. However, there are a few drawbacks. Imported objects can't be interfaced with dynamically. For example, you can't use the base class library function instance_create
to create a ds_list. Also, you currently cannot inherit from an imported type. Both of these are subject to change in future versions.
Imprting objects is a little more complicated than importing methods. There are three ways that an object can be imported. You can have the compiler auto generate the object for you, you can explicitly tell the compiler which fields, properties, constructor, and methods to import, or it can create a reference to any object that inherits from ITsIntance.
import object List<int> as ds_list {
//Fields and properties
Count as size;
//Constructor
new();
//Methods
Add(object) as add;
Clear() as clear;
RemoveAt(int) as delete;
//The next two methods are the list indexer methods.
get_Item(int) as get;
set_Item(int, object) as set;
Insert(int, object) as insert;
}
It starts with import object
. After that you can optionally put some import arguments inside of parentheses (more below), followed by the object type name. You can put as [type_name]
if you want and that's how the type will be represented in your TaffyScript code. It's optional if the import type does not have any invalid type characters. After that, in between braces, we have he fields and properties which are defined in the same way. First the C# name, optionally followed by an as [ts_name]
. Each imported type can only have one constructor, but you declare it using new
followedd by the constructor argument types in between parentheses. After that, you can fill out the methods in a similar way. You start with the method name, followed by the arguments for a specific overload in between parentheses, followed by an optional as [ts_name]
. You can have multiple overloads but they must have distinct ts_names.
//Version 1
import object HashSet<TsObject> as ds_set;
//Version 2
import object(typing=strong, case=snake_case) HashSet<TsObject> as ds_set;
As you can tell the syntax is mostly the same. The only difference is we end with a semi colon instead of a set of braces. As with most things in TaffyScript, the semicolon is optional, as long as it's the end of file or the next piece of code is a distinct statement. The real difference is that this type of import searches the type for all fields and methods that are compatible with TaffyScript and it will import as many of them as possible. Note that it can't import method or constructor overloads so it just gets the first compatible one it finds.
Let's take a moment to talk about the import arguments. To declare them, put the arguments in parentheses after the import object
part. It is defined by a list of options followed by an equals to it's value. Currently the possible options are case
and typing
.
The casing option only works for auto imported types. It controls how the c# method names get translated to the TaffyScript representation.
Option | Description |
---|---|
pascal_case | Converts all of the member names to PascalCase |
snake_case | Converts all of the member names to snake_case (sometimes referred to as underscore_case) |
camel_case | Converts all of the member names to camelCase |
native_case (Default) | Keeps the casing the same as it was defined in C# |
This determines if the imported type can still have members applied to it at runtime. For example, if we use our list example above, this option determines if the following is possible:
script main {
var list = new ds_list();
//We defined an add script so this will always work.
list.add(1);
//list does not have a sound field or property, so this will fail if the typing is strong, but will work if the typing is weak.
list.sound = "moo";
}
Strong casing will be marginally faster, but will lose out on some of the benefit of using a dynamic language.
Option | Description |
---|---|
weak | Makes the object dynamically typed. |
strong | Makes the object strongly typed. |
The include_std
option determines if the methods inherited from object
will be imported (ToString, GetHashCode, Equals).
Option | Description |
---|---|
true | Imports the standard methods. |
false | Ignores the standard methods. |
Any c# type that implements the interface ITsInstance can be used directly from TaffyScript code. If you want the ability to create an instance of one of those types, you can import it, which basically just lets the compiler know you're going to be creating instances of that type. In order to import it, it does have to have a constructor with the following layout:
public TypeName(TsOobject[] args) {
//Construct your instance here...
}
None of the options apply to these special imports.