A Ruby DSL for generating marshalable structured data easily compatable with statically allocated C systems. Currently, it prefers simplicity and predictability over speed.
Basically, malloc
is a huge pain when you only have 16K of RAM.
There's currently a (single) example in this project. To run it, do this:
git clone git://github.com/sw17ch/cauterize.git
cd cauterize
bundle install
cd example
sh build.sh
If this completes without error, then you should find a bunch of generated code
in cauterize_output. Look at the structures and enumerations defined in the
example_project.h
file. Also look at how the Pack_*
and Unpack_*
functions are organized and named.
Once you've looked at this, take a look at example_project.c
. This will show
you the exact mechanism used to package and unpackage different structures.
cauterize.h
and cauterize.c
are used as iterators over C buffers. They are
used to abstract the process of packaging and unpackaging different elements.
There are 7 fundamental classes of types in Cauterize. These types have several characteristics:
- They can be copied with
memcpy
. - They do not attempt to cover the concept of indirection or pointers.
- They are simple.
- They cannot be defined recursively.
- 1 Byte Primitives
:bool
- a boolean value:int8
- a signed, 8 bit value:uint8
- an unsigned, 8 bit value
- 2 Byte Primitives
:int16
- a signed, 16 bit value:uint16
- an unsigned, 16 bit value
- 4 Byte Primitives
:int32
- a signed, 32 bit value:uint32
- an unsigned, 32 bit value:float32
- a 32 bit floating point value
- 8 Byte Primitives
:int64
- a signed, 64 bit value:uint64
- an unsigned, 64 bit value:float64
- a 64 bit floating point value
Scalars are any type that corresponds to a C scalar value. That is, something
that can be defined with the native types (int
, long
, short
, etc). It is
highly recommended that these ONLY ever use values from stdint.h
. This
ensures that the sizes of these scalars will be consistent across platforms.
Scalars can be defined simply by giving them a name that corresponds to a type
from stdint.h
.
scalar(:uint8_t)
scalar(:uint32_t)
Enumerations correspond almost exactly to C enumerations. They are a list of names. When appropriate, a specific scalar value may also be specified. If no scalar is specified, enumerations will be represented in order from 0.
enumeration(:color) do |e|
e.value :red
e.value :blue
e.value :green
end
enumeration(:days_of_week) do |e|
e.value :sunday, 100
e.value :monday, 101
e.value :tuesday, 102
e.value :wednesday, 103
e.value :thursday, 104
e.value :friday, 105
e.value :saturday, 106
end
Fixed arrays are arrays that only ever make sense when they are full. An example of an array with this property is a MAC Address. MAC addresses are always 6 bytes. Never more. Never less.
fixed_array(:mac_address) do |a|
a.array_type :uint8_t # the type held by the array
a.array_size 6 # the number of elements in the array
end
Variable Arrays are arrays that have a maximum length, but may not be entirely utilized.
# a way to represent some number of dates/times as 32-bit values
variable_array(:datetimes) do |a|
a.size_type :uint8_t # WILL BE DEPRECATED
a.array_type :int32_t
a.array_size 128
end
# a string, represented as `int_8`'s, with a maximum length of 32 bytes
variable_array(:string_32) do |a|
a.size_type :uint8_t # WILL BE DEPRECATED
a.array_type :int8_t
a.array_size 32
end
Composites are very similar to C structures. They are collections of other types. Each field has a name and may correspond to any other defined type.
composite(:person) do |c|
c.field :name, :string_32
c.field :age, :uint8_t
c.field :date_of_birth, :uint32_t
end
Groups are similar to C unions with one major difference. Each group is comprised of a type tag and a union of the types the union is capable of representing. This is known as a tagged union.
The tag is used to inform the user application what type the union is currently representing. The tag is a special enumeration that is automatically defined.
group(:requests) do |g|
c.field :add_user, :add_user_request
c.field :get_user, :get_user_request
c.field :delete_user, :delete_user_request
end
group(:responses) do |g|
c.field :add_user, :add_user_response
c.field :get_user, :get_user_response
c.field :delete_user, :delete_user_response
end
Groups don't have to specify a data type in all of their fields.
group(:complex) do |g|
g.field :a_number, :int32
g.dataless :a_state # no associated data with this alternative
end