RISC OS Build EnvironmentROBE

Appendix52Appendix R: DefMod file format

Overview

A DefMod input file is a declarative description of an OSLib interface. It combines module metadata, type declarations, constants, and SWI signatures in a compact syntax designed for machine processing rather than hand-written assembler or C headers.

The file is parsed as a sequence of declarations separated by semicolons. Whitespace is generally insignificant, and line comments begin with // and continue to the end of the line.

The semicolon is a separator between declarations, not a terminator on the last one. A trailing semicolon after the final top-level declaration in the file is a syntax error reported at end of file, even though the same semicolon is required between every other pair of declarations. The worked examples below are written, and should be copied, without a semicolon after their last declaration.

This format is consumed by riscos-defmod, which can generate headers, veneers, and help output from the description.

Definition language reference

Overall structure

DefMod recognises seven top-level declaration forms:

  • TITLE
  • AUTHOR
  • NEEDS
  • NEEDSATEND
  • CONST
  • TYPE
  • SWI

Declarations appear in a single stream and are separated with semicolons. An empty file is valid. The parser accepts identifiers of up to 255 characters and quoted descriptions of up to 255 characters.

TITLE ModuleName "Optional short description";
AUTHOR "Author text";
NEEDS os, wimp;
CONST ...;
TYPE ...;
SWI ...

Metadata and dependencies

DeclarationMeaning
TITLE id "description"Sets the interface title. The identifier is also used as the generated module prefix in several outputs.
AUTHOR "text"Sets the author string embedded in generated files.
NEEDS id, id, ...Names interfaces that must be included before the generated declarations.
NEEDSATEND id, id, ...Names interfaces that must be included after the generated declarations.

Constants

A constant declaration introduces one or more named numeric values, each with an associated type.

CONST ReasonCode = .int: 5 "Reason code for the SWI",
      BufferType = MyType: ExternalValue;

The right-hand value may be a literal number or the name of a previously-declared constant. Forward references are not supported for constants.

Type system

DefMod distinguishes between register-sized values and variable-sized values. Primitive register-sized types can be passed in registers directly; structures, strings, and raw data blocks generally require pointer or block handling.

TypeValue classMeaning
.intregisterSigned word-sized integer.
.shortregisterShort integer.
.byteregisterUnsigned byte-sized scalar.
.charregisterCharacter-sized scalar.
.bitsregisterBit-set or flags word, full register-word width (unsigned int).
.bytesregisterByte count or raw size value, also full register-word width: the same unsigned int typedef as .bits, despite the name suggesting something byte-sized.
.boolregisterBoolean value, full register-word width (int), not a single byte.
.ref typeregisterReference to another type.
.stringvariablePointer to string data.
.asmvariableAssembler-defined opaque data.
.datavariableRaw opaque data.
.struct ... fixed or variableStructure made from named members, optionally with a base type and optional trailing ellipsis.
.union ... variableUnion made from named members; members may also use .void.
[count] typefixedFixed-length array of a non-variable base type.
TypeNameresolvedReference to a previously-declared or externally-provided named type.

New named types are introduced with TYPE. A declaration may define an abstract name with only a description, or may assign the name to a concrete type expression.

TYPE BufferHandle "Opaque handle",
     WindowBlock = .struct (.int: window, .int: icon),
     ByteArray = [16] .byte;

Structured types

Structure members use type : name. Unions use toid : name, where toid is either a normal type or .void.

A structure may include an optional base type immediately after .struct and before the opening parenthesis:

TYPE Derived = .struct :BaseType (.int: extra);

An ellipsis inside a structure marks the structure as variable-sized:

TYPE Variable = .struct (.int: length, ...);

The parser rejects arrays whose base type is itself variable-sized.

SWI declarations

A SWI declaration introduces one or more named SWI descriptions. Each description names the SWI and assigns a parenthesised definition.

SWI Example_Call = (NUMBER &12345 "Example call",
                    ENTRY (R0 = .int: value, R1 # ReasonCode),
                    EXIT (R0! = .int: result, R2 ?));

The definition begins with NUMBER followed by a numeric literal. An optional description may follow. The remainder may contain any of:

  • ENTRY (...) for input conditions;
  • EXIT (...) for output conditions; and
  • ABSENT to mark a described but non-callable SWI.

Entry and exit clauses may appear individually. If both are present they are separated by commas inside the outer SWI definition.

Entry conditions

FormMeaning
Rn = type:nameRegister contains a value of the given type.
Rn -> type:nameRegister contains a reference to the given type.
Rn # valueRegister is loaded with the constant value before the SWI call.
Rn | type:nameRegister value is bitwise ORed with the supplied argument before the call.
Rn + type:nameRegister value is added to the supplied argument before the call.
Rn ^ type:nameRegister value is exclusive-ORed with the supplied argument before the call.
FLAGSInput PSR flags are significant.

The parser recognises & in this position, but rejects it with an explicit error.

Exit conditions

FormMeaning
Rn = type:nameRegister returns a value of the given type.
Rn! = type:nameRegister returns both an output parameter and the function result value.
Rn -> type:nameRegister returns a reference result.
Rn! -> type:nameRegister returns a reference result and is also the function result value.
Rn ?Register is corrupted by the SWI or veneer.
Rn !Register is the primary result value.
FLAGSOutput PSR flags are returned.
FLAGS !Output PSR flags are returned and are also the primary result value.

The exclamation mark therefore has two distinct roles: inside a typed exit condition it marks the returned value, and as a standalone suffix it selects a register or the flags word as the primary result.

Descriptions and starred values

A quoted description attached immediately after the NUMBER field marks the SWI as having a named numeric constant in generated headers. Alternatively a bare * may appear there to request the same treatment without adding text. The same mechanism applies to constant entry conditions using Rn # value "description" or Rn # value *.

These starred values are used by the C and assembler header generators to emit manifest constants for SWI numbers and reason codes.

Literals and lexical rules

Identifiers begin with a letter and may contain letters, digits, and underscores. Keywords are matched case-insensitively by the lexer.

Numeric literals may be written in decimal, hexadecimal, binary, or character-packed form:

FormMeaning
123Decimal integer.
-123Negative decimal integer.
0x1234Hexadecimal integer.
&1234Alternative hexadecimal form.
0b1010Binary integer.
%1010Alternative binary form.
'AB'Packed character constant, with up to four characters.

Quoted descriptions support escaped apostrophe, escaped quotation mark, escaped reverse solidus, \n, \0, and two-digit hexadecimal escapes of the form \xHH.

Worked examples

A small Def file

This example shows metadata, a type, a constant, and a single SWI definition.

TITLE Example "Example OSLib interface";
AUTHOR "Example Author";
NEEDS os;
CONST ExampleReason = .int: 1 "Primary reason code";
TYPE ExampleBlock = .struct (.int: value, .bool: enabled);
SWI Example_DoThing = (NUMBER &54321 "DoThing",
                       ENTRY (R0 -> ExampleBlock: block,
                              R1 # ExampleReason),
                       EXIT (R0! = .int: result, R1 ?))

Verified against the live DefMod parser, this produces (among other things) the SWI numbers, the exampleblock structure, the examplereason constant, and the example_do_thing()/xexample_do_thing() function declarations.

Absent SWI

An absent SWI can still be documented, but no veneer is generated for it.

SWI Example_Reserved = (NUMBER &54322 "Reserved call", ABSENT)

Common errors

Mistakes beyond the trailing semicolon

Beyond the trailing-semicolon-at-end-of-file bug described above, two other mistakes are easy to make:

  • A missing semicolon between two declarations is reported as a syntax error at the line following the missing semicolon, which is a reasonably useful pointer back to the real mistake.
  • An undefined or misspelled type name used as a structure or union member type is not caught by DefMod at all. It passes straight through into the generated C header as a verbatim, lower-cased identifier, so the error only surfaces later when the C compiler fails to find that type -- far removed, in both location and wording, from the actual mistake in the Def file. If a generated header fails to compile because of an unknown type, check the corresponding member's type name in the Def file before looking elsewhere.