Lua logo

Introduction

This manual describes the functions provided by LuaMoulds. It is assumed that the reader is familiar with the Lua programming language.[1]

What is LuaMoulds

LuaMoulds is a Lua module that provides means to define C-like structured types in Lua.

It has been designed for the definition of formats of messages (in particular, of signals exchanged by LunaSDL agents), but it is a standalone module that can be used as a general purpose type system as well.

Getting and installing

The official repository of LuaMoulds is on GitHub at the following link: https://github.com/stetre/luamoulds .

LuaMoulds requires Lua version 5.2 or greater, and LPeg.

Since it is written in plain Lua, no compiling is needed.

To install LuaMoulds, download the latest release and extract it somewhere on your system.

To use LuaMoulds, make sure that the base directory containing the moulds.lua script is in Lua’s package.path (for this purpose, there is an example configure shell script in the base directory.)

For example, on a GNU/Linux system, you may do this:

# ... download luamoulds-0.1.tar.gz ...
[ ]$ tar -zxpvf luamoulds-0.1.tar.gz
[ ]$ cd luamoulds-0.1
[luamoulds-0.1]$ . configure

Some basic examples can be found in the examples/ directory and are described in the Examples section of this manual.

License

LuaMoulds is released under the MIT/X11 license (same as Lua, and with the same only requirement to give proper credits to the original author). The copyright notice is in the LICENSE file in the base directory of the official repository on GitHub.

Overview

The LuaMoulds module is loaded with Lua’s require( ), as shown in the example below. It returns a function that, each time it is called, creates a new database of type definitions (which we will call a 'mould set').

moulds = require("moulds")

ms = moulds() -- creates a new mould set (i.e. a database of types)

A mould set, when created, has a few primitive types defined in it.

Additional custom primitive types may be added with the primitive( ) method.

Derived types can then be defined on top of primitives, and added to the mould set using the typedef( ) and the ftypedef( ) methods. Derived types are defined with a C-like typedef syntax that supports the struct and union constructs, as well as arrays and nesting. For example:

-- defines a few types in the mould set ms:
ms:typedef([[

-- this is a comment (C and C++ style comments are also supported)
#include <stdio.h> /* and lines starting with '#' are ignored */

-- some aliases of primitive types:
typedef boolean myboolean
typedef string mystring
typedef void myvoid

-- a derived structured type:
typedef struct {
   boolean b
   string  s;  -- optional separators ';' or ',' can be used
   number  arr[5]
} mytype

]])

An instance of a LuaMoulds type is a Lua (sequence whose first array-element is a string denoting the type, and the elements that follow are the values of the terminal fields. The special value '?' is assigned to uninitialized terminal fields. This special value is configurable.

To instantiate a type, one can use the new( ) method. For example:

x = ms:new('mytype') -- create an uninitialized instance of mytype
--> x = { 'mytype', '?', '?', '?', '?', '?', '?', '?' }

The set( ) and get( ) methods can then be used to set or get the values of the fields. With these methods, fields may be accessed at any level of nesting, including the outermost:

-- set some values:
ms:set(x, 'b', true)          -- set the x.b field
ms:set(x, 's', 'this is s')   -- set the x.s field
ms:set(x, 'arr', 12, 25, 41)  -- set (part of) the x.arr field
--> x = { 'mytype', true, 'this is s', 12, 25, 41, '?', '?' }

-- get some values:
print(ms:get(x, '*'))     --> true  'this is s'  12  25  41  ?  ?  (the whole type)
print(ms:get(x, 'b'))     --> true
print(ms:get(x, 's'))     --> 'this is s'
print(ms:get(x, 'arr'))   --> 12  25  41  ?  ?  (the whole array)
print(ms:get(x, 'arr.2')  --> 25  (only the 2nd element)

-- copy a whole array from an instance to another:
y = ms:new('mytype')
ms:set(y, 'arr', ms:get(x, 'arr'))
--> y = { 'mytype', '?', '?', 12, 25, 41, '?', '?' }

Fields are referred to with a dot notation similar to the notation used in C, but with a couple of differences: elements of arrays are named with their index, starting from 1 (e.g., the elements of the arr array in the above example are named 'arr.1', 'arr.2', …​, 'arr.5'), and the special name '*' is used to denote the whole type.

Besides having a structure encoded in the type name, LuaMoulds instances are still regular Lua tables, and as such, their fields are dynamically typed. That is, any valid Lua value may be assigned to a terminal field, and values of different types may be assigned to it at different times.

The set( ) and get( ) methods don’t do type-checking on the values they write in or read from type instances. For safe access, LuaMoulds provides the two additional methods tset( ) and tget( ), where the 't' stands for 'terminal' as these methods can only be used to access terminal fields. It provides also two methods, pset( ) and pget( ), to conveniently access instances of primitive types without the need to specify a field name.

Mould sets

A mould set is a database of types, created with the moulds( ) function returned by the LuaMoulds module when it is loaded with Lua’s require( ) as in the Overview example.

(One can, of course, assign the function to a different name than moulds. For clarity, in this manual we will however stick to that name.)

  • moulds ([ np ])
    → ms

    Creates the new mould set ms, having only the pre-defined primitive types. The optional parameter np (a string), if supplied, overrides the default NP value ('not present'), which is the question mark character ('?').

With the exception of the moulds( ) function, all other functions described in this manual are methods of the 'mould set' object (i.e. they can be called either as ms.func(ms, args) or as ms:func(args), where ms is a mould set).

Multiple mould sets can be created in the same Lua application, with different types defined in them (this can be useful, for example, to create separated interfaces when types are used to describe formats of messages).

Primitive types

Primitive types are types for terminal fields, that can be combined using the typedef constructs to define derived types.

With the exception of the pre-defined void type, which has size 0, all primitive types have size 1, where the size of a type is the number of terminal fields it has.

Pre-defined primitive types

A mould set, when created, has the following primitive types defined in it:

  • Lua’s native basic types (with the exception of nil),

  • the void type (a type having no values),

  • a few constrained numeric types (bit, char, short, int, etc.), and

  • the bitstr and hexstr types (strings representing binary and hexadecimal data).

The full list of pre-defined primitive types, and a description of the values they admit, can be found in the tables below.

Table 1. The void primitive type
Type Size Values

void

0

none

Table 2. Lua’s native primitive types
Type Size Values

boolean

1

any Lua boolean

number

1

any Lua number

string

1

any Lua string

function

1

any Lua function

table

1

any Lua table

thread

1

any Lua thread

userdata

1

any Lua userdata

Table 3. Constrained numeric primitive types
Type Size Values

bit

1

a Lua number that can be either 0 or 1

char

1

any Lua number representing an 8-bit signed integer (-128 ≤ x ≤ 127)

uchar

1

any Lua number representing an 8-bit unsigned integer (0 ≤ x ≤ 255)

short

1

any Lua number representing a 16-bit signed integer (-215 ≤ x ≤ 215-1)

ushort

1

any Lua number representing a 16-bit unsigned integer (0 ≤ x ≤ 216-1)

int

1

any Lua number representing a 32-bit signed integer (-231 ≤ x ≤ 231-1)

uint

1

any Lua number representing a 32-bit unsigned integer (0 ≤ x ≤ 232-1)

long

1

any Lua number representing a 64-bit signed integer (-263 ≤ x ≤ 263-1)

ulong

1

any Lua number representing a 64-bit unsigned integer (0 ≤ x ≤ 264-1)

float

1

any Lua number representing a single precision floating point

double

1

any Lua number (representing a double precision floating point)

Table 4. String-data primitive types
Type Size Values

bitstr

1

any Lua string containing only the characters '0' and '1'

hexstr

1

any Lua string, of even length, containing only characters from the ranges '0-9', 'a-f' and 'A-F'

Custom primitive types

Additional primitive types may be added to a mould set by means of the primitive( ) method described below. A primitive type must have size 1, and a check function must be provided for it.

  • primitive (ms, name, checkfunc)

    Defines, in the mould set ms, an additional primitive type named name (a string). The checkfunc argument must be a function accepting a value and returning true if it is a valid value for the newly defined type, or false otherwise.

Example: a custom primitive numeric type, constrained between 0 and 1
ms = moulds.new()

local function check_mynumber(x)
   return type(x)=="number" and x>=0 and x<1
end

ms:primitive("mynumber", check_mynumber)

Derived types

Derived types are types defined by combining already defined types (primitive or derived as well) with the constructs provided by the C-like typedef syntax described in this section.

In order to add types to a mould set, their definitions are to be supplied to the typedef( ) or to the ftypedef( ) method. The former accepts definitions in a string, while the latter accepts the name of a file containing them.

Typedef methods

  • typedef (ms, defs)
    → name1, name2, …​

    Parses the string defs and adds to the mould set ms the types it defines. Returns the list of names of the types.


  • ftypedef (ms, filename)
    → name1, name2, …​

    Same as typedef( ), but it gets the type definitions from a file instead.

Multiple typedefs can be bundled in the same string or file.

Definitions can contain one-line Lua comments (-- …​), C comments (/* …​ */) and C++ comments (// …​). They can also contain lines starting with a #, like for example C preprocessor directives. All this is stripped by LuaMoulds before parsing the typedefs.

The syntax supports the struct and union constructs, arrays, and nesting.

Typedefs, and fields in structs and unions, can be separated by just one or more whitespaces (sequences of spaces, tabs and newlines), but the separators , and ; may optionally be used.

Type declaration syntax

Below is the PEG description of the type declaration syntax.

typedecl       <-  'typedef' sp+ basetype sp+ Name array* sep?
basetype       <-  'void' / structorunion / Type
array          <-  sp* '[' sp* Size sp* ']'
structorunion  <-  ('struct' / 'union') sp* fieldlist
fieldlist      <-  '{' sp* field (sep field)* sep? '}'
field          <-  basetype sp+ FieldName array?
sp             <-  ' ' / '\t' / '\n'
sep            <-  (sp* (',' / ';') sp*) / sp+

Size is a positive integer, while Name, FieldName and Type are identifiers composed only of letters, digits and underscores, and not starting with a digit (Type must be the name of an already defined type, primitive or derived as well).

Type instances

An instance of a LuaMoulds type is a Lua sequence whose first array-element is a string denoting the type, and the elements that follow are the values of the terminal fields. A special NP value ('not present'), which by default is '?', is assigned to uninitialized (or absent) terminal fields.

Instances can be created with the new( ) or the clone( ) methods.

Fields can be accessed with the set( ) and get( ) methods, or with the tset( ) and tget( ) methods that provide safe access to terminal fields (that is, they check that the values are consistent with the fields types).

For instances of primitive types (or of aliases of primitives), the pset( ) and pget( ) methods are also provided in order to access the only value they contain without the need to specify the field name. These methods are also safe.

Fields are referred to with a dot notation similar to the notation used in C to access fields in nested structures, but with a couple of differences: elements of arrays are named with their index, starting from 1 (e.g.,'arr.1', 'arr.2', …​), and the special name '*' denotes the whole type (that is, the outermost-level field).

Creating instances

  • new (ms, typename)
    → s

    Creates a new instance s of the type named typename from the mould set ms. The terminal fields of the new instance are initialized to the NP value ('not present').


  • clone (ms, s)
    → s'

    Creates a new instance s' by cloning the already existing instance s.

Accessing fields

  • set (ms, s, fieldname, …​)

    Writes the values supplied in the variable arguments part in the fieldname field of the instance s.

    Assuming fieldsz is the size of the field named fieldname, and nargs is the number of supplied arguments, then:

    • if nargs < fieldsz, only the first nargs terminal fields are written, while the remaining ones are left untouched;

    • if nargs >= fieldsz, the entire field is written with the first fieldsz arguments, and any extra value is ignored;

    • if nargs = 0 (no values are supplied), all the terminal fields of fieldname are reset to the NP value ('not present').


  • get (ms, s, fieldname)
    → value1, value2, …​

    Returns the terminal values of the field named fieldname of the instance s.


  • tset (ms, s, fieldname [, value])

    Safely writes value in the fieldname terminal field of the instance s. The supplied value must be a valid value for the terminal field’s type, or the NP value ('not present'). If value is not supplied, the field is reset to the NP value.


  • tget (ms, s, fieldname)
    → value

    Safely reads the terminal field named fieldname of the instance s.


  • pset (ms, s [, value])

    Same as a tset( ) call with fieldname = '*', on an instance s of a primitive type (or of an alias).


  • pget (ms, s)
    → value

    Same as a tget( ) call with fieldname = '*', on an instance s of a primitive type (or of an alias).

Additional functions

This section describes some additional methods of the mould set object.

  • np (ms)
    → np

    Returns the NP value ('not present') for the mould set ms.


  • sizeof (ms, typename [, fieldname])
    → size, first, last, descr

    Returns the size of the field named fieldname in the type named typename, the indices of its first and last terminals in a type instance, and a string describing its structure.

    If fieldname is not supplied, it applies to the outermost field (same as ms:sizeof(typename,'*')).

    The typename argument may also be a type instance, in which case its type name is used.


  • __tostring (ms)
    → string

    Returns a string describing the types currently defined in the mould set ms (metamethod for Lua’s tostring( )).


  • tostring (ms, s [, fieldname [, sep]])
    → string

    Returns a string with the full name of the field named fieldname of the instance s, followed by its terminal values. If fieldname is not supplied, or if it supplied as nil or as '*', the string refers to outermost field (i.e. to the whole s). The optional sep parameter is the separator to be used when concatenating the elements contained in the string (by default, a single space is used).


  • print (ms, s [, fieldname [, sep]])

    Prints the contents of an instance s or of its field name fieldname (this function just passes its arguments to tostring( ) and prints the strings it returns).


  • version (ms)
    → version

    Returns a string with the version of the LuaMoulds module (e.g. "LuaMoulds v1.0").

Examples

The scripts for the examples listed here can be found in the examples/ directory of the official LuaMoulds release.

Basic

Just a basic example, with the creation of a mould set and of a type instance, and the access of some of its fields.

The script for this example is in the examples/basic/ directory.

-- LuaMoulds example: basic.lua
local moulds = require("moulds")

-- create a mould set:
local ms = moulds()

-- prepare a string with some type definitions:
local mydefs = [[
-- these are just aliases of primitive types:
typedef char mychar
typedef int myint

-- a structured type:
typedef struct {
   boolean  b
   myint    i
   mychar   c[6]
   double   d
} mystruct
]]

-- add the types to the mould set:
ms:typedef(mydefs)

-- create an instance of mystruct:
local x = ms:new('mystruct')

-- some prints...
ms:print(x)       --> mystruct ? ? ? ? ? ? ? ? ?
ms:print(x,'c')   --> mystruct.c ? ? ? ? ? ?
print(ms:sizeof(x))    --> 9 2 10 struct{ ... }
print(#x)     --> 10 (the size + 1 for the type name)

-- write some fields:
ms:set(x, 'b', true)
ms:set(x, 'i', 123)
ms:set(x, 'c', 10, 20, 30)
ms:set(x, 'd', 1.0e20)

-- read some fields:
print(ms:get(x, '*'))   --> mystruct true 123 10 20 30 ? ? ? 1e+20
print(ms:get(x, 'b'))   --> true
print(ms:get(x, 'i'))   --> 123
print(ms:get(x, 'c.1')) --> 10
print(ms:get(x, 'c.6')) --> ?

Definitions from file

Another basic example, where the definitions are supplied in a file instead of a string.

The script for this example is in the examples/fromfile/ directory.

-- LuaMoulds example: fromfile.lua
local moulds = require("moulds")

-- create a mould set:
local ms = moulds()

-- add the types to the mould set:
ms:ftypedef("definitions.lua")

-- create an instance of mytype:
local x = ms:new('mytype')

ms:set(x, "e2", 12, 34)
ms:print(x)
ms:print(x, "e2")
ms:print(x, "e2.a")
ms:print(x, "e2.b")
ms:print(x, "e2.c")

-- copy field e2 to field e1
ms:set(x, "e1", ms:get(x, "e2"))
ms:print(x)
ms:print(x, "e1")

Safe access

In this example, an error is deliberately caused by trying to write an invalid value in a terminal field with the tset( ) method. This method is 'safe', in that it checks that the value is consistent with the field’s type and if it is not, it raises an error.

The script for this example is in the examples/safe/ directory.

-- LuaMoulds example: safe.lua
local moulds = require("moulds")

-- create a mould set:
local ms = moulds()

-- add a type to the mould set:
ms:typedef([[
typedef struct {
   boolean  b
   int      i
} mytype
]])

-- create an instance:
local x = ms:new('mytype')

-- safely write values in the terminal fields:
ms:tset(x, "b", true)
ms:tset(x, "i", 10)

ms:print(x)

-- this should cause an error, because the value is not
-- valid for the field's type:
ms:tset(x, "i", true)

Primitive types

This short example shows how the pset( ) and pget( ) methods can be conveniently used instead of tset( ) and tget( ), when operating on instances of primitive types.

Since primitive types (except for the void type, which can not be accessed) have only one terminal field in them, the two methods do not require the caller to specify a field name.

The example also shows how to define custom primitive types with the primitive( ) method.

The script for this example is in the examples/primitives/ directory.

-- LuaMoulds example: primitives.lua
local moulds = require("moulds")

-- create a mould set:
local ms = moulds()

-- create an instance of the primitive type 'char':
local c = ms:new('char')

-- set its value:
ms:pset(c, 123)
ms:print(c)       --> char 123
print(ms:pget(c)) --> 123

-- reset its value:
ms:pset(c)
ms:print(c)       --> char ?
print(ms:pget(c)) --> ?
print(ms:pget(c) == ms:np()) --> true

-- this would cause an error because the value is out of range:
-- ms:pset(c, 300)

-- define a custom primitive type:
ms:primitive("mynumber",
              function(x) return type(x)=="number" and x>=0 and x<1 end)

local n = ms:new('mynumber')
ms:pset(n, 0.5)
ms:print(n)
print(ms:pget(n))

-- this would cause an error because the value is out of range:
-- ms:pset(n, 2)

Multidimensional arrays

This example shows how LuaMoulds can be used to create multi-dimensional arrays.

Of course this method is not suited for large and sparse arrays (because instances of LuaMoulds types always have a value for each terminal field), but otherwise it has some advantages like, for example, built-in boundary checks and type-checking.

The script for this example is in the examples/matrix/ directory.

-- LuaMoulds example: matrix.lua
local moulds = require("moulds")

local ms = moulds()

local nrows = 3 -- number of rows
local ncols = 4 -- number of columns

-- define the type 'nrows x ncols matrix of strings':
ms:typedef(string.format("typedef string matrix[%u][%u]",ncols,nrows))

-- instantiate a matrix:
local M = ms:new('matrix')

-- write in each element a string with its indices:
for i = 1, nrows do
   for j = 1, ncols do
      ms:tset(M,string.format('%s.%s',i,j),string.format('(%u,%u)',i,j))
   end
end

ms:print(M)

-- write something in M(2,3):
ms:tset(M,'2.3', "hello!")

ms:print(M)

Summary of LuaMoulds functions

The tables below summarize the functions and methods provided by LuaMoulds.

The behavior of all the functions when an error occurs is to call Lua’s error( ) function with a string message.

Table 5. LuaMoulds functions
Function Return values Description

moulds ([ np ])

ms

Create a new mould set.

ms:primitive (name, checkfunc)

-

Define a custom primitive type.

ms:typedef (defs)

name1, name2, …​

Add derived types getting their definitions from a string.

ms:ftypedef (filename)

name1, name2, …​

Add derived types getting their definitions from a file.

ms:new (typename)

s

Create a new instance of a type.

ms:clone (s)

s'

Clone an instance of a type.

ms:set (s, fieldname, …​)

-

Set or reset the value(s) of a field.

ms:get (s, fieldname)

value1, value2, …​

Get the value(s) of a field.

ms:tset (s, fieldname [, value])

-

Safely set or reset the value of a terminal field.

ms:tget (s, fieldname)

value

Safely get the value of a terminal field.

ms:pset (s [, value])

-

Safely set or reset the value in an instance of a primitive type.

ms:pget (s)

value

Safely get the value from an instance of a primitive type.

ms:np ( )

np

Get the NP value for a mould set.

ms:sizeof (typename [, fieldname])

size, first, last, descr

Get the size and other informations about a type or one of its fields.

ms:__tostring ( )

string

__tostring metamethod.

ms:tostring (s [, fieldname [, sep]])

string

Get a string with the name and values of a field.

ms:print (s [, fieldname [, sep]])

-

Print a string with the name and values of a field.

ms:version ( )

version

Get the version of the LuaMoulds module.


1. This manual is written in AsciiDoc, rendered with AsciiDoctor and a CSS from the AsciiDoctor Stylesheet Factory. The PDF version is produced with AsciiDoctor-Pdf.