BeOS resources can be expressed as ASCII text using the resource definition language. This language is modelled somewhat after mwbres and rez on the Macintosh. It doesn't currently support the conditional constructs and types of rez, but does support free-form data definition as in mwbres. In addition, it includes a rigid typing system, and native understanding of flattened BMessages and common Be data types such as BRect, BPoint, and rgb_color.
The basic syntax of a source file is one or more resource definition lines. A resource definition looks like:
resource( id [, name] ) data ;
The id field is required and supplies the id number of the resource. You can optionally give a name for this resource entry with the name field; alternatively, you can define a symbolic value for the id, which will implicitly supply a name if one isn't specified:
enum { R_SymbolName = 1 }; resource(R_SymbolName) data ;
The
The basic physical types you can use are integers (123 or 'abcd'), reals (1.0), strings ("string") and buffers ($"aabbccddeeff"). In addition, booleans are available as a variation of integers with the pre-defined keywords "true" and "false". In other words, the basic data types you can directly express as values are:
Physical Type | Type Code | Representation | C Type |
---|---|---|---|
integer | B_BOOL_TYPE | true or false | bool |
integer | B_INT32_TYPE | N or ' CCCC' | int32 |
real | B_FLOAT_TYPE | N. [N][+- e N] | float |
string | B_STRING_TYPE | " C*" | char*, BString |
buffer | B_RAW_TYPE | $" [0-9a-f]*" | void* |
When defining a resource, the type code of its data component also implicitly defines the type of the resource. Thus, to create a B_STRING_TYPE resource containing the text "String Resource", you simply write:
resource(1) "String Resource";
Other types of data can be created by performing a type cast.
Type casts look in some ways similar to C -- they are expressed as
a known type enclosed in parenthesis, such as (int32)
.
Each of these types are actually a combination of a physical type and
a type code. For example int32
is an integer with type
code B_INT32_TYPE. The type code specifies exactly how the physical
type will be expressed, in this case as a 4-byte signed integer.
The types that the resource parser understand are:
Type | Physical Type | Type Code | Description |
---|---|---|---|
bool | integer | B_BOOL_TYPE | 1-byte boolean |
int8 | integer | B_INT8_TYPE | 1-byte signed integer |
uint8 | integer | B_UINT8_TYPE | 1-byte unsigned integer |
int16 | integer | B_INT16_TYPE | 2-byte signed integer |
uint16 | integer | B_UINT16_TYPE | 2-byte unsigned integer |
int32 | integer | B_INT32_TYPE | 4-byte signed integer |
uint32 | integer | B_UINT32_TYPE | 4-byte unsigned integer |
int64 | integer | B_INT64_TYPE | 8-byte signed integer |
uint64 | integer | B_UINT64_TYPE | 8-byte unsigned integer |
ssize_t | integer | B_SSIZE_T_TYPE | signed status/size (4 bytes on x86/ppc) |
size_t | integer | B_SIZE_T_TYPE | unsigned size (4 bytes on x86/ppc) |
off_t | integer | B_OFF_T_TYPE | file offset (8 bytes on x86/ppc) |
time_t | integer | B_TIME_TYPE | 4 byte time value |
float | real | B_FLOAT_TYPE | 4 byte floating point |
double | real | B_DOUBLE_TYPE | 8 byte floating point |
string | string | B_STRING_TYPE | \0-terminated UTF-8 string |
buffer | buffer | B_RAW_TYPE | untyped stream of bytes |
As an example, you can create a resource containing the number 200 expressed as a signed 64 bit integer using:
resource(1) (int64)200;
For types that are not built in, you can perform a type code cast.
This looks similar to a type cast, but only changes the type code associated
with the targetted value. A type code is expressed as #'
N, where N
is any integer value.
resource(1) (#'aaaa')200; resource(2) (#200)"String"; resource(2) (#'ICON')$"001122334455";
The first line creates a new resource of type 'aaaa' whose data is a 4 byte integer with the value 200. The second creates a resource with type code 200 whose data is the given \0-terminated string. The third line creates an 'ICON' resource with the given hex data.
You can combine type casts and type code casts to get whatever data you need:
resource(1) (#'aaaa')(uint64)200;
That same resource can be expressed as short-hand with the type code and type identifier in the same cast:
resource(1) (#'aaaa' uint64)200;
The data component can also be supplied as a collection of other data values. You create compound data by placing a list of the data values in the collection inside of braces:
resource( id [, name] ) [ type-code ] compound-type { [ [ type ] name = ] data [, [ type name = ] data ] ... };
How this list of data values is stored as a byte stream in the resource is determined by the compound-type. There are two major compound types: arrays simply concatenate their data one after the other, while messages store the data in named fields in a BMessage, flattening that message to create the final byte stream.
Compound values are strictly typed, just like basic values. The default type code of an array is B_RAW_TYPE, and of a message is B_MESSAGE_TYPE. You can change the type code of the created compound value by specifing it before the value, using the same syntax as a type code cast. For example, here is a compound value with the type and structure of an rgb_color:
resource(1) #'RGBC' array { (uint8)0x00, // red (uint8)0x00, // green (uint8)0x00, // blue (uint8)0xff // alpha };
Each data value in the compound type may have a name assigned to it -- the
name is optional for raw arrays but required for messages. A name is either
a standard C identifier ([a-zA-Z_][a-zA-Z_0-9]*
) or a string or
symbol containing a string. When used in a message, these names are the fields
under which the data is stored; when used in a plain array, the names are
ignored.
As with resource data, the type code used to store data into a message is implicitly defined by the value's type. Optionally, you can explicitly specify the type of a field prior to the field's name as type identifier / type code pair.
resource(1) message('blah') { int8 "small integer" = 1, "integer" = 2, "large integer" = (int64)3, "string" = "some text" };
This produces a flattened BMessage as if you had programmatically done:
BMessage msg('blah'); msg.AddInt8("small integer", 1); msg.AddInt32("integer", 2); msg.AddInt64("large integer", 3); msg.AddString("string", "some text");
Like basic values, there are a number of built-in array types that you can use. These types result in data like a normal array, but define the structure of that array for you, so that any errors made in the data specification will be caught. They also associate a name with each entry in the array, so that you can specify the values in a different order than they will be placed in the resulting data.
The built-in array types are:
Type | C Type | Type Code | Description |
---|---|---|---|
point | BPoint | B_POINT_TYPE | float x, y; |
rect | BRect | B_RECT_TYPE | float left, top, right, bottom; |
rgb_color | rgb_color | B_RGB_COLOR_TYPE | uint8 red, green, blue, alpha; |
To use these pre-defined arrays, you supply the array type in the compound-type field, and specify data values that match the type. For example, to create a BRect structure:
resource(1) rect { 0.0, 0.0, 31.0, 31.0 };
And here is an example of using the entry names instead of order to fill in the data:
resource(1) point { y=5.0, x=1.0 };