Specification

"

Introduction

Existing plain text data serialisation formats are too verbose to store meaningful objects on storage media with very limited data capacity, e.g. QR codes, NFC tags and DNS TXT records. For these storage media, character efficiency is crucial:

  • Storing more data in QR codes makes them more prone to error.
  • NFC tags with more data capacity come at a much higher cost.
  • Larger DNS packets require TCP transport and increase latency.

Often, developers are forced to roll-their-own bespoke methods of packing and unpacking data. This presents a significant barrier to entry for those looking to make use of these storage media. MODL addresses this – offering a compact data serialisation format that's easy to use and easy to customise for any use case.

Status

MODL was first published on 25th March 2018. We are currently seeking feedback from the technical community. We do not anticipate major changes to the grammar or interpreter rules.

Objectives

MODL should be, in order of priority:

  1. The most character efficient way to describe an object when minified.
  2. Unambiguous with as few surprises as possible.
  3. Human-friendly to write and read when unpacked.
  4. Easy to parse into data structures in all modern programming languages.

Rules

  1. MODL is case-sensitive
  2. Whitespace means space (0x20) or tab (0x09).
  3. Newline means LF (0x0A) or CRLF (0x0D0A).
  4. MODL ignores all leading and trailing whitespace.
  5. MODL files must be valid UTF-8 encoded Unicode documents.

Code Examples in This Document

MODL code examples are provided throughout this document, for most examples the code can be executed in the playground by clicking the run icon (run icon) where a JSON representation is output.

In case of problems retaining the formatting of code that is copied and pasted, you can click the copy icon (copy icon) to copy the MODL code to your clipboard.

Conventions in This Document

The key words "must", "must not", "required", "shall", "shall not", "should", "should not", "recommended", "may", and "optional" are to be interpreted as described in RFC2119 Content on another site..

Authors

MODL was created by Elliott Brown Content on another site. and Alex Dalitz Content on another site. with the help of Tony Walmsley Content on another site..

Input Objects and Output Objects

Input objects are UTF-8 encoded strings. The input object is processed by an interpreter which returns an object, some interpreters return a JSON string or JSON object. The output object can differ considerably from the input object due to object referencing, hidden objects and conditional evaluation – all are covered in detail in this specification.

Data Types

MODL can represent four structural types:

and four primitive types:

Machine Diagrams

MODL can be explained using machine diagrams (in the same style as JSON Content on another site.):

MODL machine diagrams

Structures

The four structures that make up MODL are: map, array, pair and conditional.

Map

A map is known in other languages as a hash table, object, record, struct, dictionary, keyed list, or associative array. A map begins with left bracket ( and ends with right bracket ), it contains zero or more pairs separated by a semi-colon:

Tip: Wherever you see the run icon (run icon) you can click the icon to run the code in a mini playground window.

run code
copy to clipboard
( make = Bentley; model = Continental GT ) ## or, more character efficient: ##(make=Bentley;model=Continental GT)
An example map.

Array

Arrays in MODL will be familiar to most – beginning with a left square bracket [ and ending with a right square bracket ]. Items are separated by a new line or semi-colon. Array items can be of any data type and data types can be mixed.

run code
copy to clipboard
[ fastback; convertible ] ## or, more character efficient: ##[fastback;convertible]
An example array.

When an array is a value of a pair, it can also be defined in a more character efficient way by splitting values with a colon (:), like this:

run code
copy to clipboard
models=fastback:convertible
An example array using colon separated values.

Pair

Pairs are the primary building block of MODL objects and consist of a value assigned to a valid key Content further down this document.. They can be used in a number of ways to increase character efficiency. Orphan pairs (not within a map) are allowed for character efficiency and will be converted into a map when parsed.

Standard Pair

The key is to the left of the equals sign and the value to the right:

run code
copy to clipboard
make = Bentley ## or, more character efficient: ## make=Bentley
An example pair.
Map Pair

When assigning a map to a key, we can omit = since the left bracket separates the key from the map contents:

run code
copy to clipboard
car( make = Bentley; model = Continental GT ) ## or, more character efficient: ## car(make=Bentley;model=Continental GT)
An example map pair.

This is simply a (1 character) more efficient way to represent the more conventional (and valid):

run code
copy to clipboard
car=( make=Bentley; model=Continental GT )
A map assigned to a key in a pair.
Array Pair

When assigning an array to a key, we can omit = since the left square bracket separates the key from the array contents:

run code
copy to clipboard
style[ fastback; convertible ] ## or, more character efficient: ## style[fastback;convertible]
An example array pair.

This is simply a shorter way to represent the more conventional (and valid):

run code
copy to clipboard
style=[ fastback; convertible ]
An array assigned to a key in a pair.

Conditionals

Conditions are explained in detail later Content further down this document.. In the meantime, here is a simple conditional with logic based on the value of _country:

run code
copy to clipboard
_country = gb; ## The value of the "support_contact" key is set to a conditional value: support_contact = { ## Does the data in the key "country" equal "gb"? %_country=gb? ## Yes, assign the value "Joe Bloggs" to the key "support_contact" Joe Bloggs ## If not, does it equal "us"? /%_country=us? ## Yes, assign the value "John Doe" to the key "support_contact" John Doe /? ## If not, assign the value "none"? None }; ## or, more character efficient: ## _country=gb;support_contact={country=gb?John Smith/country=us?John Doe/?None}
An example conditional.

If including logic within data serialisation seems like madness, it's explained here. Content further down this document.

Primitives

The four primitives in MODL are: number, string, boolean and null.

Number

Numbers in MODL are the same as numbers in JSON (view RFC Content on another site.).

String

Strings in MODL are the same as strings in JSON (view RFC Content on another site.).

Booleans

Boolean values are represented in two ways – a conventional way and a deliberately unconventional way that is both character efficient and unambiguous.

True

True is represented as true, TRUE or 01. Since 01 is an invalid number and unlikely to be required as a string, it is the most character efficient way to represent true unambiguously.

run code
copy to clipboard
( conventional_true = true; ## or, more character efficient: unconventional_true=01 )
An example true boolean value.
False

False is represented as false, FALSE or 00. Since 00 is an invalid number and unlikely to be required as a string it is the most character efficient way to represent false unambiguously.

run code
copy to clipboard
( conventional_false = false; ## or, more character efficient: unconventional_false=00 )
An example false boolean value.

Null

Null is represented as null, NULL or 000. Like true and false, 000 is an invalid number and unlikely to be required as a string so alongside true and false it is the most character efficient way to represent null unambiguously.

run code
copy to clipboard
( conventional_null = null; ## or, more character efficient: unconventional_null=000 )
An example null value.

Strings: Quoted and Graved

In MODL, strings can be quoted, graved or neither:

run code
copy to clipboard
key1=value1; key2="value2"; key3=`value3`
Three different ways to represent a value.

When using reserved characters Content further down this document. it's often best to use a quoted or graved string. All characters can be used in a quoted string except for the quotation mark " which must be escaped. All characters can be used in a graved string.

run code
copy to clipboard
key1="reserved characters like (brackets), [square brackets], the:colon, semi-colons; all can be used."; key2=`reserved characters like (brackets), [square brackets], the:colon, semi-colons; all can be used.`;
Using quotes in a graved value and graves in a quoted value.

A grave can be used inside a quoted value and a quote can be used inside a graved value:

run code
copy to clipboard
key1="this is a quoted value `including graves`"; key2=`this is a graved value "including quotes"`;
Using quotes in a graved value and graves in a quoted value.

Graves and quotes are also used in object referencing Content further down this document. for performing methods on strings and to pass parameters to methods.

Keys

Pair keys must be a string: they may contain numbers, may start with a number but must not be made up of solely numbers. They may not start with a percent sign (%). The question mark ? key is reserved for the object index Content further down this document.. Uppercase keys are immutable, underscore prefixed and asterisk prefixed keys are allowed but have special meaning, detailed below.

Mutable / Immutable Values

Assign a value to an uppercase key to tell the interpreter that this object can only be defined once.

run code
copy to clipboard
## This mutable value with a lower (or mixed) case key can be changed: mutable_key = 1; ## This immutable value with an uppercase key can only be set once: IMMUTABLE_KEY = 1; ## No problem: mutable_key = 2; ## Results in ERROR: IMMUTABLE_KEY = 2
An example of mutable and immutable values.

Keys must start with a letter or a number except in the following cases:

Underscore Prefixed Keys

Pairs with a key prefixed with an underscore (_) do not appear in the output object, they are typically used for object referencing Content further down this document..

Asterisk Prefixed Keys

Pairs with a key prefixed with an asterisk (*) are instructions Content further down this document..

Type Inference

MODL uses type inference to detect the data type of a value. Numbers can be forced as strings by enclosing the value in quotation marks ".

Comments

Two hash symbols mark the rest of the line as a comment.

run code
copy to clipboard
## This comment is allowed car( ## So is this one make=Bentley; ## This one too model=Continental GT; styles[ ## Inside arrays is fine too fastback; convertible ## Even at the end of lines ]; price={ ## And inside conditionals c=gb? ## At the end of tests 154400 /? ## And inside return values 210000 } )
A simple MODL object showing a variety of ways to make comments.

Comments run to the end of the line so they cannot be used in fully minified MODL – this is by design. Comments do not appear in output objects.

Object Referencing

MODL can reference any object previously described – either in the same file or in files in previous *load instructions in the same or nested files. An object can be described once and referenced multiple times. Objects are referenced using percent encapsulation (%like-this%). For character efficiency, it's possible to omit the trailing percent (%) when it's at the end of a value or followed by a space. It's also possible to omit the underscore (_) when referencing hidden objects, as shown in the second example below:

run code
copy to clipboard
_manufacturer=Bentley; car( make=%_manufacturer; model=Continental ); car( make=%manufacturer; model=Bentayga )
An example using object referencing.

In circumstances where the variable needs to be interpolated without a space, it must be fully percent encapsulated:

run code
copy to clipboard
test=foo; ## Set value 1 to use the value of the variable "test" value1=%test; ## Set value 2 to use the value of the variable "test" appended with "bar" value2=%test%bar; ## resulting in "foobar"
An example using object referencing, which is percent encapsulated.

Deep Object Referencing

Using deep object referencing, it's possible to reference values within an object using the dot (.). Here's an example referencing an item in an array:

Tip: Wherever you see the run icon (run icon) you can click the icon to run the code in a mini playground window.

run code
copy to clipboard
_test=[1;2;3]; second_value=%test.1 ## results in: 2
An example using deep object referencing on an array.

Here's an example referencing the value of a pair within a map:

run code
copy to clipboard
_test=(one=1;two=2); second_value=%test.two ## results in: 2
An example using deep object referencing on a map.

There's no limit to the level of referencing. As with any object referencing, if the reference needs to be interpolated without a space it must be percent encapsulated:

run code
copy to clipboard
_weights=(low=10;mid=20;high=30); this_weight=%weights.high%kg ## results in: 30kg
An example using deep object referencing on a map.

You can only reference the value of a pair or an item in an array. You cannot reference a nested array or map.

Object Index

The object index is set using the key ?. Values must be provided in an array and can be referenced using their position in the index. This serves as a character efficient way to reference an object. For example:

run code
copy to clipboard
## We create an object index using an array, assigning "foo" to index 0 and "bar" to index 1: ?=foo:bar; ## We can reference the values using the index test=%0; ## We've assigned "foo" to the key "test" test2=%1 ## We've assigned "bar" to the key "test2"
An example using the character efficient object index.

Scope

All objects described in MODL are global. References can only refer to objects defined earlier in the current file or a loaded file that is the subject of a *load earlier in the file. i.e. declare before use. Redefining a value after it has been used in a reference will change its value in subsequent references.

Referencing Instruction Objects

Instruction objects Content further down this document. can be referenced like this:

run code
copy to clipboard
print_classes=%*class
Assign the class instruction to the key "print_classes".

This is covered in more detail later. Content further down this document.

Methods

Methods can be applied to any string – either a graved string or a string value of an object reference. This can be useful in circumstances where a string is used multiple times but with varying case or spacing. Take the following example:

run code
copy to clipboard
( name = TESTING; description = This is an object testing variables; value1 = Testing123 )
An object with a word repeated multiple times in different case.

We can describe this object more efficiently using built-in MODL methods – u (upcase), d (downcase) and s (sentence). Here's the same object using methods to reduce the size of the object:

run code
copy to clipboard
_v=testing; ( name = %v.u; description = This is an object %v variables; value = %v.s%123 )
An object using methods to change variable case.

Methods can be applied to graved strings, this is useful for encoding data for ASCII systems Content further down this document..

The following methods are available, where %x is an object previously described:

Method ID Method Name Example Variable Transformation
u upcase %x.u Here's a REF test => HERE'S A REF TEST
d downcase %x.d Here's a REF test => here's a ref test
s sentence %x.s Here's a REF test => Here's a ref test
i initcap %x.i Here's a REF test => Here's A Ref Test
e urlencode Content on another site. %x.e Here's a REF test => Here%27s+a+REF+test
p punydecode Content further down this document. %x.p e1afmkfd => пример
r<this,that> replace %x.r<test,foo> Here's a REF test => Here's a REF foo
t<this> trim %x.t Here's a REF test => Here's a 

Methods that take parameters (replace and trim) must be called with chevrons, e.g. %string.replace<this,that>. Parameters can be graved. To replace with an empty string, use an empty graved string: %string.replace<foo,``>.

Methods can be chained, e.g. %string.replace<this,that>.upcase and it's also possible to define custom methods. Content further down this document.

Conditionals

Standard data serialisation formats contain no logic, MODL is different - allowing object authors to design logic into their object description. This means that the same object can be described differently to recipients based on client-side variables. All of this is dealt with at the interpretation layer, resulting in much less work for the developer receiving the object and most importantly: consistent logic processing.

A conditional can output any primitive or structure, including a conditional. A conditional begins with the left brace { and ends with the right brace }.

Example

Conditionals are made up of a test (e.g. country=gb) and its return value (e.g. John Smith) separated by a question mark ?. Each condition is separated by a slash /. In this example a variable called country is tested:

run code
copy to clipboard
country = gb; ## The value of the "support_contact" key is set to a conditional value: support_contact = { ## Does the data in the key "country" equal "gb"? %_country=gb? ## Yes, assign the value "John Smith" to the key "support_contact" John Smith ## If not, does it equal "us"? /%_country=us? ## Yes, assign the value "John Doe" to the key "support_contact" John Doe /? ## If not, assign the value "none"? None } ## or, more character efficient: ## country=gb;support_contact={country=gb?John Smith/country=us?John Doe/?None}
An example conditional.

As you can see from the character efficient version above, it's possible to omit % when using an object reference in a conditional test, it's also possible to omit _ from hidden objects. After interpretation, our MODL object looks like this:

run code
copy to clipboard
country = gb; support_contact = John Smith
The object after interpretation

Rationale

From a data serialisation perspective, at first glance the conditional example may seem like utter madness because we're checking a value that we defined ourselves. However, consider a case where country is prepended to the MODL string by a client and the rest of the MODL string is retrieved from elsewhere – e.g. a QR code, NFC tag or DNS TXT record. In this case the actual result depends on the prepended value of country which might not be known in advance.

Parts

Conditionals are made up of a test (e.g. country=us) and its return value (e.g. United States) separated by a question mark ?. Each condition is separated by a forward slash /.

Conditional Tests

Conditional tests are made up of an object reference and a value separated by an operator. Object references used in conditional tests are called variables.

Operators

Example user variables have been used to help demonstrate operators in the table below. The user variables are: language (l), latitude (gy), country (c) and operating system (os). These variables are typically defined by the client. The table below shows the full list of operators:

Operator Description Example Users that match condition
= Equals l=en? Users with language set to English
> Greater than gy>0? Users located in the northern hemisphere.
>= Greater than or equal to gy>=0? Users located on the equator or in the northern hemisphere.
< Less than gy<0? Users located in the southern hemisphere.
<= Less than or equal to gy<=0? Users located on the equator or in the southern hemisphere.
& AND c=be&l=fr? Users located in Belgium with language set to French.
| OR c=de|l=de? Users located in Germany or with language set to German.
! NOT !c=de|at? Users not located in Germany or Austria.
!= NOT equal to l!=en? Users with language not set to English.
* Wildcard os=*iOS*? Users using any iOS device.
Operator Precedence

This table shows operator precedence from highest to lowest:

Operator Description
! NOT
<=  <  >  >= Comparison operators
!= Not equal to
& AND
| OR
* Wildcard
Variable Assumption

Sometimes you may need to check if a variable is one of a selection of possible values. If you wanted to check if the user country was US, GB of AU using the OR operator | you could do it like this:

run code
copy to clipboard
_country=gb; { country = us | country = gb | country = au? support_number=+441270123456 /? support_number=+14161234567 }
A conditional checking a variable multiple times

Or to be more character efficient you can use variable assumption, like this:

run code
copy to clipboard
_country=gb; { country=us/gb/au? support_number=+441270123456 /? support_number=+141612345678 }
Variable assumption
Grouping Test Parts

Tests can be made up of multiple parts, these parts can be grouped by enclosing them in braces – { and }.

run code
copy to clipboard
{ { country = ca & language = fr } | country = fr? support_number=+14161234567 /? support_number=+441270123456 } ## or, more character efficient: ## {{country=ca&language=fr}|country=fr?support_number=+14161234567/?support_number=+441270123456}
An example conditional with grouped tests

Conditional Returns

A conditional return must be valid for the context that it's returned in. For example, a conditional used to assign a value to a key must have an else clause (represented by /?) to be valid. A conditional at the top level of a map (not assigned to a pair key) must return either a pair or nothing.

Boolean conditional returns

For character efficiency, a conditional used to return a value can omit the return value and return the boolean result of the conditional test itself. For example:

run code
copy to clipboard
_country=uk; british={country.u=GB|UK?} ## outputs true
A conditional without a return value.

Instructions

An instruction is set using an asterisk-prefixed key, these are processed by the interpreter first. Instructions don't appear in the output object but can be referenced Content further down this document. elsewhere in the MODL file.

Load

One or many MODL files can be loaded into another by assigning a MODL file URI to the *load or *l key. The value can be a local (to the interpreter) or remote file location with a .modl or .txt extension. The interpreter will add a .modl extension if one is not specified.

Multiple files can be loaded at the same point by providing the file URIs in an array, or at multiple different points in the file by using the instruction multiple times. Loads can also be chained. This example shows one file being loaded:

copy to clipboard
*load = example-classes.modl; example( value1 = one; value2 = two ) ## or, more character efficient: *l=example-classes.modl;example(value1=one;value2=two)
An example file loading another MODL file called example-classes.modl.

In the following example, the *LOAD / *L key is uppercase and therefore is immutable Content further up this document. – indicating that only one load is allowed and the file being loaded cannot include any further loads:

copy to clipboard
*LOAD = example-classes.modl; example( value1 = one; value2 = two ) ## or, more character efficient: ## *L=example-classes.modl;example(value1=one;value2=two)
An example file loading another MODL file called example-classes.modl.

You can pass multiple files to an uppercase load instruction in an array. It's also possible to use object references Content further up this document. in URIs, like this:

copy to clipboard
_country=gb; *LOAD = example-classes-%country%.modl; example( value1 = one; value2 = two )
An example file loading another MODL file called example-classes-gb.modl.

File caching

It's recommended that interpreters cache loaded files for one hour by default. To force an interpreter to fetch and parse the file each time the MODL is interpreted, add an exclamation mark (!) to the end of the URI. If for any reason the interpreter is unable to load the file, but it exists in cache, the cached version should be used for up to 7 days.

Files should be cached in a parsed but non-interpreted state since when they are re-used the context might have changed and the cached file will need to be re-interpreted in the new context.

Class

Classes can be created using the key *class or *c. Classes are used in object transformation and are covered in detail in the classes Content further down this document. section.

Method

Custom methods can be created using the key *m or *method.

Method Instructions

This is a list of the recognised method instructions:

Instruction ID Instruction ID Instruction Name Insutrction Name Required Instruction Description
*i *id Yes When within a method object, the ID of the method The ID of the method
*n *name No When within a method object, the name of the method The name of the method
*t *transform Yes A method or method chain showing how to transform values

For example, this method could be created to hyphenate a name:

run code
copy to clipboard
*method( ## The method can be called by it's ID or name *id=hy; *name=hyphenate; ## The value of the object that the method is called on is transformed using the following methods: *transform=replace( ,-) ) _name = John Smith; hyphenated_name = %name.hy ## or, more character efficient: ## *m(*i=hy;*n=hyphenate;*t=r( ,-));_name=John Smith;hyphenated_name=%name.hy
Defining a custom method.

Syntax Version

The MODL syntax version number can be supplied using the key *V or *VERSION – this allows the author to inform the interpreter which version of the MODL syntax has been used to describe the MODL object. If defined, the version number must be the first instruction in the file and is only recognised in uppercase (an immutable value) and so can't be redefined. All MODL interpreters will try to parse the file regardless of version.

The current version is 1, revisions will be extremely rare with very careful consideration given to any breaking change.

Working With Instructions

Any instruction can be referenced using percent prefixing, for example we can output all classes like this:

run code
copy to clipboard
*class( *id=e; *name=employee; ## *superclass tells the interpreter what type of object to create *superclass=map; ## *assign tells the interpreter how to assign the values of an array with the key e *assign=[ [title;name;job_title] ]; ## Any other pairs are automatically appended to every object with this class actions=[call;email] ); show_classes = %*class
Referencing a class

Referencing classes is useful for tools that are used to create and edit MODL records. Interpreters should output each instance of an instruction in an array, an example JSON representation is shown below:

run code
copy to clipboard
{ "show_classes": [ { "e": { "name": "employee", "superclass": "map", "assign": [ [ "title", "name", "job_title" ] ], "actions": [ "call", "email" ] } } ] }
The output when referencing a class

Classes

Classes can help make object description more efficient. A class is defined by assigning a map to the key *c or *class.

Class Instructions

Classes have their own set of instructions. If the key of a pair in a MODL object matches a class ID or class name, the pair is transformed according to the class definition. If the class has a name, it will be used as the key for the output object.

The *superclass or *s key is used within a class definition to declare a superclass. There's more about class inheritance Content further down this document.. The *assign or *a key is used within a class definition for key assignment Content further down this document..

Objects with a superclass inherit all standard (non-instructional) pairs defined in parent classes in the class hierarchy.

This is a list of the recognised class instructions, each must only be used once per class:

Instruction IDID Instruction NameName Required Instruction Description
*i *id Yes When within a class object, the ID of the class The ID of the class
*n *name No When within a class object, the name of the class The name of the class
*s *superclass No When within a class object, the parent of the class The ID or name of the parent of the class
*a *assign No Instructing how the class should assign keys to values

Example

This basic example shows how classes can be used to make object description more efficient:

run code
copy to clipboard
*class( *id=e; *name=employee; ## *superclass tells the interpreter what type of object to create *superclass=map; ## *assign tells the interpreter how to assign the values of an array with the key e *assign=[ [title;name;job_title] ]; ## Any other pairs are automatically appended to every object with this class actions=[call;email] ); ## This simple array is transformed into a map by the class defined above e=Mr:John Smith:Sales Director
An example file defining a simple class.

Here's the output object:

run code
copy to clipboard
employee( title = Mr; name = John Smith; job_title = Sales Director; actions = [ call; email ] )
The output from our example file.

We could now use this class to describe thousands of employees using as few characters as possible.

Assignment

Assignment can be used to make defining maps and arrays more efficient. The *assign (or *a) key must be an array of arrays.

Key Assignment

If a map always includes the same pairs in the same order, we can use key assignment in the class to make defining the map more character efficient. Key assignment instructs the class how to assign values that are passed to it.

copy to clipboard
*class( *id=e; *name=employee; *assign=[ [name]; [title;name]; [title;name;job_title]; [title;name;job_title;email] ]; actions=[call;email] )
Key assignment in a class.

The *assign array above shows the different permutations of values that it will accept for key assignment. If a number of values are provided that do not match one of the permutations, the interpreter will return an error. Based on the example above:

  • It can accept one value – it will assign the value to the key name.
  • It can accept two values – it will assign the values to title and name
  • It can accept three values – it will assign the values to title, name and job_title.

Now, if we define an array Content further up this document. assigned to a key that matches the class ID or name, it will be transformed according to the class, like this:

run code
copy to clipboard
*class( *id=e; *name=employee; *assign=[ [name]; [title;name]; [title;name;job_title]; [title;name;job_title;email] ]; actions=[call;email] ); e=Mr:John Smith:Sales Director:john.smith@example.com
The assign key in a class.

The output object is identical to that shown at the end of the previous section:

run code
copy to clipboard
employee( title = Mr; name = John Smith; job_title = Sales Director; actions = [ call; email ]; email = john.smith@example.com )
The output from our example file.

Only the keys that were passed values appear in the output object. If five values are passed to a class with permutations for a maximum of four values, the interpreter will return an error.

Item Assignment

If an array includes multiple items of the same type, we can use item assignment in the class to make defining the array more character efficient. To do this, use a class name in the assignment followed by an asterisk (*):

run code
copy to clipboard
*class( *id=employee; *name=employee; ## *superclass tells the interpreter what type of object to create *superclass=map; ## *assign tells the interpreter how to assign the values of an array with the key e *assign=[ [title;name;job_title] ]; ## Any other pairs are automatically appended to every object with this class actions=[call;email] ); *class( *id=e; *name=employees; *assign=[ [employee*] ] ); ## These nested arrays are transformed into maps using the class defined above e=[Mr:John Smith:Sales Director;Mrs:Jane West:Managing Director]
The assign item in a class.

This returns the following output object:

run code
copy to clipboard
employees[ ( title = Mr; name = John Smith; job_title = Sales Director; actions = [ call; email ] ); ( title = Mrs; name = Jane West; job_title = Managing Director; actions = [ call; email ] ); ]
The output from our example file.

Only class names can be used in item assignment (suffixed with an asterisk), using item assignment with anything else will result in an interpreter error.

Class Inheritance

Class inheritance can be used to make complex class definitions simpler and more efficient. For example, if we wanted to introduce a new class called customer, we could have employee and customer both inherit from a new person class:

copy to clipboard
*class( *id=p; *name=person; *superclass=map; actions=[call;email] ); *class( *id=c; *name=customer; *superclass=person; *assign=[ [title;name;email] ] ); *class( *id=e; *name=employee; *superclass=person; *assign=[ [title;name;job_title;email] ] )
New classes demonstrating class inheritance.

Due to object transformation, each customer and employee will now inherit the actions array but notice that key assignment for these two classes differ – values are assigned to different keys depending on whether an employee or customer is being defined.

Core superclasses are str, num, arr and map, if a superclass is not provided it will be determined by the interpreter using superclass inference.

Class Definition File

To aid in character efficiency, it's recommended to define classes in an external file that you can load Content further up this document.. If we move our class definitions over to a separate file called classes.modl and store it in the same local directory we can load it and describe multiple objects:

copy to clipboard
*LOAD=classes.modl; ## An employee: e=Mr:John Smith:Sales Director:john.smith@example.com; ## A customer: c=Mr:Joe Bloggs:joe.bloggs@example.com; ## Another customer: c=Mrs:Jane Wilson:jane.wilson@example.com
Loading classes from an external file.

Here's the output object:

copy to clipboard
[ employee( title=Mr; name=John Smith; job_title=Sales Director; email=john.smith@example.com; actions=[ call email ] ); customer( title=Mr; name=Joe Bloggs; email=joe.bloggs@example.com; actions=[ call email ] ); customer( title=Mrs; name=Jane Wilson; email=jane.wilson@example.com; actions=[ call email ] ) ]
The MODL output object.

Encoding For ASCII Systems

MODL is not limited to ASCII characters Content on another site. but some storage media are (e.g. DNS TXT records). Non-ASCII characters can be included in an ASCII MODL object in a number of ways.

Hex Values

Any unicode character represented by four hex digits can be used in MODL in the same way as JSON:

run code
copy to clipboard
symbol=\u03C0
An example object using a hex value to represent a non-ASCII character.

A DNS-friendly version is also available using the tilde (~) in place of the back slash (\):

run code
copy to clipboard
symbol=~u03C0
An DNS friendly example object using a hex value to represent a non-ASCII character.

Punycode

For more complex strings, it's more efficient to use Punycode Content on another site.. Use an object reference graved string and call the punydecode (or p) method. Any Unicode character can be punycode encoded, providing ASCII support for almost every language on Earth and symbols like emojis.

In the Russian language MODL object below, the name field uses the punycode conversion of пример (English translation: 'Example') and the department uses the punycode conversion of обслуживание клиентов (English: 'Customer Service'):

run code
copy to clipboard
(name=%`e1afmkfd`.p;department=%` -7sbcecqbdsccxfizhcp6b8ah`.p)
An example object using punycode to represent non-ASCII characters.

Once it's decoded it looks like this:

run code
copy to clipboard
(name=пример;department=обслуживание клиентов)
The object once it has been decoded by the interpreter.

When included in MODL objects, punycode should not include the xn-- prefix, this is added by the interpreter.

Reserved Characters

There are a range of reserved characters in MODL.

For character efficiency, you should use a quoted or graved string Content further up this document. if a value includes two or more reserved characters, otherwise use an escape character (\ or ~) for a single reserved character. The following characters have special meaning in MODL:

  • Brackets: The left bracket ( indicates the start of a map Content further up this document., the right bracket ) indicates the end.
  • Square Brackets: The left square bracket [ indicates the start of an array Content further up this document., the right square bracket ] indicates the end.
  • Braces: The left brace { indicates the start of a conditional Content further up this document., the right brace } indicates the end.
  • Semi-colon ; is used in a map Content further up this document. to separate pairs Content further up this document. and used in an array Content further up this document. to separate items.
  • Colon : is used to separate values in an array Content further up this document..
  • Equals = is used to separate the key from a value in a pair Content further up this document..
  • Grave accents and double quotes (` and " respectively) are used for quoted and graved strings Content further up this document., graves are also used in object referencing.
  • Backslash and tilde (\ and ~ respectively) are escape characters and can be used before any reserved character to escape its reserved use. To use a backlash or tilde in a MODL object, simply use two: \\, ~~, \~ or ~\.
  • Dot (.) is used for deep object referencing Content further up this document. and to transform a string using a method Content further up this document..
  • Inside conditional statements, these characters have special significance and must be escaped:
    • Question mark ? is used to split a condition test Content further up this document. from its return value in a conditional. Content further up this document.
    • Slash / is used to split the condition return value from the next condition test in a conditional. Content further up this document.
    • Pipe | is used as an OR operator within a condition test.
    • Ampersand & is used as an AND operator within a condition test.
    • Exclamation mark ! is used as a NOT operator within a condition test.
    • Asterisk * is used as a wildcard character within a condition test.
    • Less Than < is used within a condition test when evaluating one value against another.
    • Greater Than > is used within a condition test when evaluating one value against another.

Interpreter Rules

Interpreters should follow these rules:

  1. Values assigned to uppercase keys are immutable, attempts to change an immutable value must raise an error at parse time.
  2. Pairs with underscore-prefixed keys are hidden and must not appear in the interpreted object.
  3. Interpreters should use type inference for values that are not quoted, numbers can be forced as strings by quoting them.
  4. The instruction *V or *VERSION defines the MODL syntax version. If undefined, assume version 1. If defined, it must be an integer greater than zero and must be the first instruction in the MODL file, defining it after another instruction must raise an error at parse time. Interpreters should attempt to parse any version of MODL but if an error is encountered, should return e.g: "MODL Version 1 interpreter cannot process this MODL Version 2 file."
  5. The instruction *class or *c is a class definition:
    1. Classes must be defined as maps.
    2. Classes must be defined before being used.
    3. Classes must have unique IDs and names, redefining a class must raise an error at parse time.
    4. The instruction *id or *i defines the class ID.
    5. The instruction *name or *n defines the class name.
    6. The instruction *superclass or *s defines the superclass of a class using the superclass ID or name:
      1. A class must inherit all non-instructional pairs from every parent class in the class hierarchy. Pairs from a parent class are listed after the pairs in the current class, and pairs from the grandparent class are listed after the pairs from the parent class, and so on.
      2. The values of inherited mutable pairs can be changed.
      3. If a superclass if not defined, the interpreter must use superclass inference to determine whether it is str, num, arr or map.
    7. The instruction *assign or *a defines how keys should be assigned to values using an array of arrays:
      1. Permutations should be in ascending order by key count. If they are not, raise an error at parse time.
      2. If no permutation match is found, raise an error at parse time.
  6. The instruction *method or *m is a method definition:
    1. Methods must be defined before being called.
    2. Methods must have unique IDs and names, redefining a method must raise an error at parse time.
    3. The instruction *id or *i defines the method ID.
    4. The instruction *name or *n defines the method name.
    5. The instruction *transform or *t defines the method(s) to apply to the value of the object that the method is called on.
      1. Multiple chained methods must be called from left to right.
      2. Pass parameters to methods (<like,this>) parameters that include reserved characters must be graved <`like:this`,that>.
  7. A pair with a key that matches a class ID or class name must be transformed according to the class definition:
    1. In the output object, the key of the pair is set to the class name.
    2. If the value is not already a map, and one of the parent classes in the class hierarchy includes pairs, then it must be transformed to a map.
  8. The instruction with key *load or *l defines the URI of the MODL file to load, multiple files can be passed in an array.
    1. If a file does not have a .modl or .txt extension, .modl must be appended to the file name before attempting to load the file.
    2. When a URI is assigned to an uppercase *LOAD or *L key only one file can be loaded and loads cannot be chained.
    3. URIs can be local to, or remote from the interpreter.
    4. If any of the files cannot be found raise an error at parse time.
    5. Files should be fetched, parsed and cached for one hour by default, if the file name ends with an exclamation mark (!) cached files should be ignored and the new file fetched and parsed. When cached files are re-used they must be re-interpreted in the current context.
  9. An instruction with key ? defines an object index:
    1. Values must be provided in an array.
    2. Values must be assigned an index starting from zero.
  10. Dealing with strings:
    1. Find all parts of the string that are percent encapsulated, e.g %test% where neither of the percents are prefixed with an escape character (~ or \) and all parts that are percent prefixed but are followed by a space or at the end of a value.
    2. Loop through all parts found, these are object references – run "Object Referencing".
    3. Replace the strings as per "String Replacement".
  11. Punycode Decoding: Prefix the punycode encoded string with xn-- (the letters xn and two dashes) and decode using a punycode / IDN library.
  12. Object Referencing:
    1. If the reference includes one or more . (dot / full stop / period) then the reference should be split into parts separated by dots.
    2. For each part, if a referenced object is a string assume all later parts are a method call. If a referenced object corresponds to an array or map then assume all later parts refer to item positions in an array or keys in a map.
    3. If the object reference is a number and the number is within the object index bounds, replace the object reference with the value from that position in the object index.
    4. If the object is a string, replace the object reference with the value of the corresponding object. If no corresponding object is found, make no substitutions. If an object is found, this value is referred to as the subject in the following section.
    5. If a method chain is found, perform each in order from left to right. Methods must only be applied to strings, applying methods to non-strings must raise an error at parse time.
    6. Replace the part originally found with the transformed subject.
  13. String replacement
    1. The following TXT file shows which characters must be substituted within string values: substitutions file Content elsewhere on this site..
Playground
Window: 
Reduce | 
Enlarge | 
Full |