This token processor allows you to use classes and other syntax additions.
Features:
Table of contents:
this
variableThe built-in types are:
Dynamic
- the dynamic type is the implicit type compatible with any other type (often omitted)Void
- used only for return types to enforce that the function doesn't return anythingByte
- just an alias for Integer
for documentation of the intentShort
- just an alias for Integer
for documentation of the intentInteger
- 32-bit signed integer (but can also refer to 8bit and 16bit unsigned integers in arrays)Float
- 32-bit floatBoolean
- boolean type (zero = false, non-zero = true)String
- string type
You can also use array, hash and class types. Array types use brackets after the type,
for example Float[]
describes an array of floats. Hash types contains the type
of the key in the brackets, for example Integer[String]
describes a hash map
with a String
key and an Integer
value.
The syntax for constructors is just a syntax sugar for static methods that create the object and return it. In case you need more flexibility when creating an object (eg. using a different underlying type) you can use static methods directly.
Classes can either extend an existing class type or optionally extend a special common
class type named Object
. This class allows to provide automatic implementation
of to_string
methods. You can also provide your own implementations
of this method for custom string representations.
Some class types can't extend from Object, for example when they need serialization
or are backed by other type than an array. In such cases you can also register
a to_string method, albeit in a less efficient way. This is achieved by using the
Object::set_to_string(obj, func)
static method. It simply registers
the provided function to a global (self-clearing) hash map with a weak reference key.
Another alternative is to simply put a function reference as a first entry in an
array, the function must take one argument and must end with _to_string
suffix.
You can then either call the to_string
methods directly, or use
dump
or to_string
(with newlines), these are automatically
replaced with versions that know about class types. You can of course use the
original functions by using @dump
and @to_string
functions.
This functionality is completely optional. You have to put object.fix
file in the root of the scripts and extend the class from the Object
type.
Interfaces provide common type for otherwise different classes. This is achieved by creating a wrapper class that contains reference to the original class and a set of function references specific to that class so it can be called in a common way.
The original class contains some method, called as_interface
(when
the interface class is called Interface
) that returns a new instance
of this wrapper class. It can also cache it when desirable (using weak references
if the same existing interface instance should be always used).
Here is an example:
class Class1 { var @field1; var @field2; var @field3; function as_some_interface(): SomeInterface { return SomeInterface::create(this, Class1::common_method#1); } function common_method() { log("Class1"); } } class Class2 { function as_some_interface(): SomeInterface { return SomeInterface::create(this, Class2::common_method#1); } function common_method() { log("Class2"); } } class SomeInterface { var @data; var @common_method_func; constructor create(data, common_method_func) { this.data = data; this.common_method_func = common_method_func; } function common_method() { common_method_func(data); } }
this
variable
The this
variable used in instance functions and constructors is
a normal variable. It can be assigned, or it can even contain a null
value (in fact any kind of value). This is because the concept of classes is
separated from the underlying implementation type.
This can be used for various things, for example in callbacks you can pass
an array of multiple values instead of just the object and unpack it in the
code by assigning the object instance into the this
variable.
Another example is the usage of weak references (again typically in callbacks).
Some methods can check explicitly for null
s in this
.
For example comparison functions or to_string
implementations.
A slight downside of this approach is that when a null
is passed
to an instance method, there is no check at the time of method call, it will
error only when the object is accessed in the method, for some methods it can
even succeed if it's not accessed at all.
Sometimes you may want to define classes without actually using the classes token processor. For example if you're not using classes (or want it optional) but still provide the comfort of using them. Another example is the ability to make multiple versions of classes token processor (or other token processors) to work together.
The format is simple, to declare a class use a @class_SomeClass
private constant with a string value (can be empty). The string can contain
these attributes:
prefix
- function prefix (without the _
at the end)struct
- prefix for the constants (without the _
at the end)static
- specifies a list of methods that are static (the name includes a '#
' followed by the number of parameters)extend
- specifies a name of the super class
The value is delimited by '=
', multiple attributes are separated
by ',
' and when the attribute can contain multiple values they're
separated by a ':
'. No whitespace is allowed.
You can define methods by declaring a @method_SomeClass_method_name_1
private constant, where the last part is a number of parameters (including the
implied 'this
' parameter). You can either specify static methods
using the static
attribute for the class, or just use static
instead of method
in the private constant name. Undefined methods
are still recognized based on the prefix (all parameters and the return type are Dynamic
in that case).
You can define global functions by using a @global_some_func_1
private constant
to define a function named some_func
with a single parameter.
Every method uses the '(SomeType, Integer, Float): Boolean
' format to specify
the parameters. This means specifying the types of the parameters (the parameter for
'this
' is omitted for non-static methods), optionally followed by a return type.
Whitespace is allowed.
This is an example how it can look:
const @class_SomeClass = "extend=BaseClass,prefix=somecls,struct=SCLS,static=create#2:static#0"; const @field_SomeClass_field1 = "Integer"; const @field_SomeClass_field2 = "Boolean"; const @static_SomeClass_create_2 = "(Integer, Boolean): SomeClass"; const @method_SomeClass_instance_function_3 = "(Integer, Boolean)"; const @global_some_func_2 = "(SomeClass, Integer): Boolean";
The API allows other token processors to integrate with classes processing. To
get access to the API, just import classes
script and obtain the
context. Then you can register various hooks. The hooks are added based on actual
needs.
You can also use the API without classes, just use the prefixes class_context_
and class_type_
for the methods and pass the objects as a first parameter.
const { TYPE_DYNAMIC, TYPE_VOID, TYPE_INTEGER, TYPE_FLOAT, TYPE_BOOLEAN, TYPE_STRING }; const { EXT_TYPE_CLASS, EXT_TYPE_ARRAY, EXT_TYPE_HASH }; class ClassContext { static function get(fname: String): ClassContext; function register_function_call(name: String, get_types_func, adjust_call_func, data); function register_postprocess(func, data); function get_class(name: String): ClassType; function get_const_type(name: String): ClassType; function get_local_type(name: String): ClassType; function get_variable_type(name: String): ClassType; } class ClassType { static function create_array(base: ClassType): ClassType; static function create_hash(base: ClassType, index: ClassType): ClassType; function get_class_name(): String; function get_parent_class(): ClassType; function get_base(): ClassType; function get_index(): ClassType; function to_string(): String; static function dump_list(list: ClassType[]); }
The types are divided into two enumerations. The simple types are just integers
and you can directly use them (cast from/to ClassType
). The extended
types are contained in an array and the first entry is the type.
static function get(fname: String): ClassContext
function register_function_call(name: String, get_types_func, adjust_call_func, data)
null
to match all names) and the callbacks
with these signatures:function get_types(data, name: String, num_params: Integer, line: Integer): ClassType[]
null
if there is no
function for given number of parameters. The returned array is modified and passed
to the adjust_call
callback.
function adjust_call(data, name: String, types: ClassType[], tokens, src: String, start: Integer, end: Integer): Integer
function register_postprocess(func, data)
function postprocess(data, fname: String, tokens, src: String)
function get_class(name: String): ClassType
function get_const_type(name: String): ClassType
-1
if the constant
with given name is not defined. Must be called within processing.
function get_local_type(name: String): ClassType
-1
if the
local variable with given name is not defined. Must be called within
processing.
function get_variable_type(name: String): ClassType
-1
if the variable
with given name is not defined. Must be called within processing.
static function create_array(base_type: ClassType): ClassType
static function create_hash(base_type: ClassType, index_type: ClassType): ClassType
function is_class(): Boolean
function is_array(): Boolean
function is_hash(): Boolean
function is_assignable_from(other: ClassType): Boolean
function get_class_name(): String
function get_parent_class(): ClassType
function get_method_real_name(name: String, types: ClassType[], is_static: Boolean): String
null
) with given types
(the first entry is a return value).
function get_base(): ClassType
function get_index(): ClassType
function to_string(): String
static function dump_list(list: ClassType[])