-
Notifications
You must be signed in to change notification settings - Fork 1
MDL | Basics
This describes the syntax for the __M__adz __D__escription __L__anguage (MDL) which is used to describe the features provided by plugins.
Currently the language will focus on the concepts and objects exposed by a plugin, but over time will include some peripheral concerns as well.
Madz syntax follows a very basic layout:
<description-class> <description-class-description> <attributes>; #comment
Where description-class is a keyword, description-class-description is the description specific to the keyword and attributes are syntax specific to the attribute in question. Description keywords are all case insensitive.
A practical example:
typedef newName **uint8;
Where typedef is the keyword followed by the typedef-description attribute which is:
<name> <type-signature>
Generally names come first in all syntax.
To access the MDL implementation use the following syntax:
~<implementation-keyword> <implementation-keyword-description>;
Attributes are a key way of attaching additional information to descriptions. They are generally laid out as follows (where : denotes the start of an attribute):
typedef newName **uint8
# Comment about the above
: <attribute> # inline comment
: <attribute>
# Comment about the above attribute
# Some more commenting
;
Sometimes we want to talk about specific parts in our attributes, we can then use node traverse syntax (defined below) to discuss a certain part.
typedef newName **uint8
:@/type// <attribute>
# Expanded: root.children["type"].children[0]
# Applies <attribute> to the intermediate pointer.
# In c would look like: "uint8 * <attribute> * newName"
: <attribute>
;
Or you could use the builtin in-place attributer syntax (which applies the attribute to the next node):
typedef newName * @[<attribute>] *uint8
: <attribute>
;
Although many syntaxes build in a some of the relevant syntax so that it can be as simple as:
typedef newName * <attribute> *uint8
: <attribute>
;
Documentation is an attribute like any other, which uses the long escape string literal:
typedef newName **uint8
: documentation("style")
"""
Now we can write plain documentation.
With all kinds of crazy symbols ~:; and whatnot
until we hit the unfortunate
"""
: <attribute>
;
Documentation can be place on any node, but only a couple are likely going to get any use from it.
This describes default syntax which returns literal values.
Just like python for long strings:
"""
A really long string with special stuff in it~
foo @ ;
"""
This is a really basic technique for traversing nodes, like XPath it makes a lot of assumptions about the path for you. For example:
/foo # Assumes root.children["foo"]
/2/ # Assumes root.children[2]
// # Assumes root.children[0]
/bar/foo # Assumes root.children["bar"].children["foo"]
/bar/ # Assumes root.children["bar"]
/bar # Assumes root.children["bar"]
Symbols are any string of unicode characters which does not contain whitespace. Symbols are terminated by the first whitespace character encountered (and hence, can't contain whitespace). Symbols are used to name all objects declared to be part of plugins. Typically symbols will require an escape character and a namespace character. By default these are \ and . respectively.
CBaseTypes is the default set of type signatures, and the default type signature style. It uses * to describe pointers, has types of the form uint# and int#, where # is a power of 2 size starting at 8, as well as classic c types (void, char, short, int, long, u*). To guarantee access to named types use t_<type-symbol>. For example a type named * could be accessed with t_*.
Structure syntax allows for the declaration of arbitrary structures of data. They are a form of type signature:
typedef foo *
{
a_foo: * { *t_type, name2: t_someothertype }
}
# A struct containing a pointer to another structure.
:@structure-element/name2/ <element-attribute>
# Expanded: structure-elements.children["name2"]
;
Note the optional names.
Callable syntax describes how to describe a callable for MDL's consumption. Callables are a form of type signature and hence can be used in any place requiring a type signature:
typedef foo * ((*t_type, name2 t_someothertype) -> void) -> t_resulttypeyo
# A pointer to a callable which takes a callable which takes two arguments.
:@callable-argument/name2/ <argument-attribute>
# Expanded: callable-arguments.children["name2"]
;
Or in the case of method, require:
method foo (*t_type, name2: t_someothertype) -> t_resulttypeyo
:@function-argument/name2/ <argument-attribute>
;
Note the optional names.
The following keywords are the core set of provided functionality and should be provided by every programming language.
Rename a type-signature to a different name within this plugin's type namespace.
typedef <symbol> <type-signature>;
Describes a mutable piece of memory provided by a plugin with a name and a specified type.
variable <symbol> <type-signature>;
Guarantees that the attributed type is constant, in that it will not be change, and should not be changed by others.
variable foo uint8
: constant
;
# EQUIVALENT TO
variable foo uint8
:@/type/ constant
;
# EQUIVALENT TO
typedef foo uint8
: constant
;
variable foo t_foo;
Describes a uint8 whose value should not change. Compilers are allowed to assume it will not change.
To invoke an extension add the following to the front of the MDL:
~extension <extension-name>;
This will instruct the implementation to load the requested extension, and bring in all of the relevant syntax. Extensions come in two broad classes:
- Optional Implementation - Implementing this extension for each programming language is optional for it to function. Often because there exists a default implementation which convert it to the core set of functionality. Some features with optional implementations should still be implemented to get the full usefulness out of them.
- Required Implementation - Implementing this extension for each programming language is required for it to function. Often because it provides extra features not provided by default.
Extensions can rely on each other.
Each language should also provide an extension so that hints can be given to the language wrapper about how to handle certain objects. (For example, the compiled symbol to use to find the implementation of the variable in question).
This is an extension of a group of attributes to make it easier to describe functional situations.
- FUNCTION - An attribute which guarantees a callable as being functional, in that it always returns the same value given the same arguments.
- IMMUTABLE - An attribute which applies the CONSTANT attribute to children as well. (So an immutable structure would apply constant to every element, if an element was a pointer it would also apply it to the memory the pointer was pointing at. Callables do not propagate to their arguments or return values.)
Implementations should make sure to include methods for deep copying which remove the constant constraint.
Constant variables which contain functions. These allow for optimization opportunities and are easier to deal with (as they don't change value).
method <name> <type-signature-function>;
This extension provides performant datatypes, basically structs except with additional syntactic sugar. Implemented with constant function variables (through methods) and typedefed structs. Where datatype signatures have the following syntax:
datatype <symbol> {
variable <symbol> <type-signature>
: <attribute>
,
method <symbol> <type-signature-function>
: <attribute>
,
}
: <attribute>
;
Should be Implemented
Provides classes, subclassing, interfaces, and reflection.
Required Implementation
Provides exception handling. Must be implemented by languages to use it properly.