The Double Yielding Markup Language (DYML) aims to be a lightweight alternative to JSON/YAML while being more friendly to write text in, like AsciiDoc or Markdown, but without the weirdness and parsing complexity.
This section should give you enough information to get started writing DYML. Check out docs/markup.adoc for a complete description of the grammar, some motivation for this project and some more examples.
DYML is used to describe a tree structure with nodes and attributes. Nodes can be nodes with a name that can contain more nodes and can have attributes, or they are just text.
#item
#greeting hello world
#house @color{green} {
This is my house.
@@color{blue} #door
}
This could be translated to XML like so:
<root>
<item></item>
<greeting>hello world</greeting>
<house color="green">
This is my house.
<door color="blue"></door>
</house>
</root>
As you can see, nodes are declared like #name
and will contain all following text, until the next node is declared.
All nodes are siblings of each other, unless you declare a block with {}
that can contain child nodes.
In constrast to XML there is also no explicit root node.
Attributes for nodes are set with @key{value}
where the value can be any text.
Attributes must follow the node definition directly, but can also be written as forwarded attributes in front of the node with @@key{value}
.
DYML written in this way is text first, as anything that is not an element definition or attribute will be interpreted as text. You can also create node first elements, which have some interesting properties we will explore in an example:
#! greeting "hello world"
#! some nested elements;
#! house @color="green" {
@@color="blue"
door,
garage,
}
Which would look like this in XML:
<root>
<greeting>hello world</greeting>
<some>
<nested>
<elements></elements>
</nested>
</some>
<house color="green">
<door color="blue"></door>
<garage></garage>
</house>
</root>
Wherever an element can be started in text mode you can start an element in node mode by starting it with #!
.
In node mode all text is interpreted as the names of nodes by default and text has to be enclosed in double quotes.
Nodes are also nested into one another as you can see with #! some nested elements;
where each node is a child of the previous one.
Attributes look slightly differently (@key="value"
) but work like attributes in text mode and can be forwarded too.
Once a node in node mode is completed, nodes in text mode will follow. There are different ways for a node to be completed, all of which can be seen in the example above. The first way is to introduce text to stop nesting and therefore end the node. The second way is to end the definition with a comma or semicolon, they can be used interchangeably, but you might prefer one over the other depending on the context. The third way is to start a block with children, the node is closed at the same point the block is closed.
You can be creative with the brackets you use for blocks in node mode, all the items in the following example are equivalent except for their block types.
#! item {child}
#! item <child>
#! item [child]
There is one additional thing that can be useful for expressing some concepts: The return arrow ->
.
Inspired by some programming languages that use an arrow to denote a function’s return parameters, you can do something similar:
#! x(a, b) -> (c, d)
#! x -> option<int>
Which would correspond to the following XML:
<root>
<x>
<a></a>
<b></b>
<ret>
<c></c>
<d></d>
</ret>
</x>
<x>
<option>
<int></int>
</option>
</x>
</root>
As you can see, the return arrow must follow a node definition that can have a block. Following the arrow there must be a block that can have a name set to rename the default ret.
Note
|
Text mode is also referred to as G1 and node mode as G2. |
-
token contains the lexer that can convert an input stream into tokens.
-
parser contains logic to turn an input stream into a tree representation. You will also find the types
Visitor
andVisitable
here, which you must use if you want to create your own parser. -
encoder contains an XMLEncoder that can directly convert an input stream into an XML representation. It serves as an example as to how implement your own parser. In most cases you do not want to create your own parser, but instead use the
Unmarshal
method (defined in marshal.go) which can parse an input stream into a struct.
Run make test
to run all available tests.
Run make lint
to check the code against a list of lints with golangci-lint.