ENAML : Enaml is Not A Markup Language (it's lame, but i was drunk) Enaml is a way to send structured data across text-based links. It's a little like ASN1 and a little like XML, but it doesn't matter if you don't know what either of those are, because Enaml is pretty simple by itself. Q: Why didn't you just use ASN1? A: ASN1 is binary. There are many reasons why it's harder to write and debug binary-based protocols (and not all of those reasons are valid). It's also complicated, and based on ancient ISO stuff, which means it's yucky. Most of all, it offends my aesthetics, since I like things to be done in a simple and obvious way so I can understand them. I think a lot of other people have similar aesthetics, because ASN1 is very rarely used anymore. Q: Why didn't you just use XML? A: XML is great, but it seems suited to larger documents. Enaml is meant to be used by line-oriented protocols where each message has very little content (usually less than 100 bytes). Think IRC. XML's structure adds a lot of redundant overhead, and to my eye, is a little less readable than Enaml, simply because Enaml encourages whitespace in strategic locations, whereas in XML, whitespace within a tag's content field is significant. For any protocol where a single protocol-message will average 1k or more, I'd suggest XML over Enaml. Enaml boils down to a simple method for encoding a block of key/value pairs, where a key's "value" can be a number, a string, a chunk of 8-bit-clean binary data, a (possibly nested) list, or another block. But an example works better: cat:{ name:"Commie" age:3 friends:[ "Gilly" "Simba" ] } This encodes a key "cat" that contains a block of info. Inside the block are keys "name", "age", and "friends". The "friends" key contains a list. Most protocol messages in Enaml consist of a single key (like "cat") that points to a block of other keys. Occasionally, a message will consist of just a single word (like "ping") because there's no need to attach any other data to it. Typically, an Enaml message is terminated with a linefeed. Each message may be fairly long (up to 8KB), but for any longer message, you should break it up into smaller pieces, or send the data through another channel. PAIR := KEY [ ":" *SP ( SIMPLE-VALUE | BLOCK-VALUE ) ] KEY := 1*32( 'a'..'z' | 'A'..'Z' | '_' | '-' ) SP := *( space(#32) | tab(#9) ) A key is 1 to 32 letters (including "_" and "-") and is NOT case sensitive: keys "cat" and "Cat" are equivalent. A key by itself is called a "flag", and is considered a key with no value. (The "ping" example above is a flag.) Otherwise, a key will be immediately followed by a ":" (colon) character, optional whitespace, and then a value. Values are numbers, strings, hex blobs, lists, or blocks. SIMPLE-VALUE := NUMBER | STRING | HEX-BLOB | LIST NUMBER := 1*( '0'..'9' ) A number is just an immediate sequence of digits. To encode the age of your cat, you can just put the digits immediately after the colon: age:3 But this can only be done with digits. Note that enaml doesn't distinguish between number and string types; everything is a string. The ability to omit the quotes around a string of digits is purely syntactic sugar, and you are free to avoid using it. Notably, this means that "age:3" and "age:03" will transmit different values for "age", because the strings are not converted to integers in transit. (It's up to the protocol to specify by convention which values are to be treated as numbers.) STRING := '"' *( PRINTABLE ) '"' PRINTABLE := characters #32-#33, #35-#36, #38-#126, #128-#254 (or: all characters except #0-#31, #34, #37, #127, #255) A string is delimited by double-quotes. All quoting within the string is done URL-style: by adding a percent ("%") and a two-character hex encoding of the missing character. For example, to quote a double-quote, you would use "%22". The characters that need to be quoted include: control characters (those below space), double-quote, percent, del, and shifted del. You are free to quote more aggressively: sometimes for debugging, it's useful to quote the #128-#160 range, because they have no printable form in Latin-1. To encode the key "compliance" with the string "100%", you would send: compliance:"100%25" If a key's value is likely to have many characters in it that need encoding (for example, if you're sending known binary data such as an image), then you may want to use a hexblob instead. HEX-BLOB := '%' 1*( HEX-DIGIT HEX-DIGIT ) HEX-DIGIT := '0'..'9' | 'A'..'F' A hex blob starts with a single "%" and then just contains pairs of hex digits, with each pair encoding a single byte. The above "compliance" example could be encoded as a hex blob, like this: compliance:%31303025 LIST := '[' *SP *( SIMPLE-VALUE 1*SP ) ']' A list starts with a "[" and contains a sequence of other simple values (numbers, strings, hex blobs, or other lists) separated by whitespace. In a list, the order is significant (unlike a block). The end of a list is marked by a "]". A list may contain zero values (it's just an empty list). The list of Commie's friends might look like this: friends:[ "Gilly" "Simba" ] In this list, Gilly is always the first friend, and Simba is always the second friend (sorry, Simba). They can't change order, because the order might be important. Lists can be nested to create N-dimensional tables: scores:[ [ 98 81 ] [ 65 84 ] ] You can nest lists up to 32 levels deep. Some Enaml parsers may allow you to nest them even deeper, but you're only guaranteed 32 levels. BLOCK-VALUE := '{' *( PAIR ) '}' A block value just contains more key/value pairs inside it. An empty block is permitted. As with lists, you may nest blocks up to 32 levels deep. Here's an example: student:{ name:"Robey" scores:{ math:"B" physics:"C" } } That's all there is to Enaml, really. ------------ PAIR := KEY [ ":" *SP ( SIMPLE-VALUE | BLOCK-VALUE ) ] KEY := 1*32( 'a'..'z' | 'A'..'Z' | '_' | '-' ) NOT case sensitive SP := *( space(#32) | tab(#9) ) SIMPLE-VALUE := NUMBER | STRING | HEX-BLOB | LIST BLOCK-VALUE := '{' *( PAIR ) '}' NUMBER := 1*( '0'..'9' ) STRING := DQUOT-STRING | SQUOT-STRING DQUOT-STRING := '"' *( DQUOT-PRINTABLE ) '"' SQUOT-STRING := '\'' *( SQUOT-PRINTABLE ) '\'' DQUOT-PRINTABLE := PRINTABLE | squot(#39) SQUOT-PRINTABLE := PRINTABLE | dquot(#34) PRINTABLE := characters #32-#33, #35-#36, #38, #40-#126, #161-#254 (or: all characters except #0-#31, #34, #37, #39, #127-#160, #255) HEX-BLOB := '%' 1*( HEX-DIGIT HEX-DIGIT ) HEX-DIGIT := '0'..'9' | 'A'..'F' LIST := '[' *SP *( SIMPLE-VALUE 1*SP ) ']' BLOCK-VALUE := '{' *( PAIR ) '}' Protocol lines normally consist of a single PAIR, up to 8KB long, terminated by a linefeed. Lists can be nested up to 32 levels deep. Blocks can be nested up to 32 levels deep.