Any time a formal language is created for computing, a compromise has to be found: whether the language is perfect for the computer but terrible for the human or vice versa. XML is very good for the computer ;-)
Using XML for the reasons mentioned above, but for programming? "Why a Markup Language?!" was what I was shouting some time playing around with a code generator in XSLT. That gave the idea.
Just writing down what I wanted to have instead of XML for a sample:
<list name="List of goods">
<head>
<columTitle>
Goods
</columnTitle>
<columnTitle>
Price
</columnTitle>
</head>
<row>
<value>
Beer
</value>
<value>
20
</value>
</row>
<row>
<value>
Wine
</value>
<value>
30
</value>
</row>
</list>
Something like that should be more easy, say, like this:
list("List of goods") {
head {
title "Goods"
title "Price"
}
row {
value "Beer"
value 20
}
row {
value "Wine"
value 30
}
}
The latter is what I call an Y language - a language specified in YML. How could this be achieved? Well, what's to do? To have the required information, how to build XML from the script above, we need:
How to do that? Let's invent a simple definition language for that information:
decl list("name") alias list
decl head() alias head
decl row() alias row
decl title("=>body") alias columnTitle
decl value("=>body") alias value
These four lines have all information we need. If you declare a parameter "name", then it's clear, that what's going in there as value will be attached to an attribute "name".
The alias keyword will connect the declared function with an XML tag name.
The notation =>body means, that what's given here as a parameter, will be put
into the value of a tag directly. Because <=body is default, you may drop this
part. The alias keyword is optional, if tag name and function name match. The
following declarations are equivalent to the ones above:
decl list("name")
decl head
decl row
decl title alias columnTitle
decl value
The difference between =>body and <=body is, that <=body
does not move a given attribute into the body, if the attribute text contains a '=' sign.
The reason for doing so is, that you can simply add plain attributes to "empty tags"
using the syntax i.e.: decl div alias div in YHTML and then calling it with
manually set attributes, i.e.
div 'style="color=red;"'
If you don't want this behaviour, you should declare using =>body.
Here you can download the complete list sample.
Hint: Every declared function generates a Python function named yml_ plus
the function name. decl head() alias head for example defines a Python
function yml_head().
It does not matter, if you're calling your function using parantheses or without, or if you want to add a semicolon to the end of line.
So these statements are all equal:
foo "hello, world"
foo("hello, world")
foo("hello, world");
Hint: What comes behind the function name (or in the parantheses) is just a comma
separated list of Python expressions. That means, text(' ' * 10)
will give you a string of ten spaces as first parameter of function text.
YML sub trees are opened and closed with braces:
foo {
bar {
something();
}
}
YML automatically escapes text of attribute values.
If you don't want this, use x(value) as attribute value. This prevents YML from
escaping your text.
// text escaped
text("> hello, world <")
// text not escaped
text(x('this is "real"'))
Three different quoting operators implement different functionality:
The '>' operator quotes into text nodes, doing XML escaping of text. An example:
> this text will be put into a text node and these angle brackets <> will be quoted
The '|' operator does the same as the '>' operator, adding a new line character to the text node.
Additionally, it can be used to implement an indention system, see YSLT below.
Then it's used together with additional '>' symbols showing the grade of indention:
| not indented
|> single indent
|>> double indent
(...)
Just like with a Unix shell, you can insert statements into text by using back quotes:
| Click `a("http://fdik.org/yml/", "this link")`, please!For abbreviation purposes, you can define a short cut insert (see YSLT below).
Here you can have a default tag for inserts using the « » operators. In YSLT you can use them for xsl:value-of tags, for example.
The following two lines are equivalent in YSLT:
| Gimme some «$value» hereby.
| Gimme some `value("$value")` hereby.They are, because there is a define special_command_name as value command
in the YSLT definition file.
Hint: If you're running into troubles because of character set problems with the '»' symbols, then use these two lines on top of your YSLT script:
define special_command_left as «
define special_command_right as »
My samples all use iso_8859-15 charset, if you're using something else you may need that.
The Apple ][ prompt operator just quotes through directly into XML what it gets.
If the first non whitespace character of a line is a '<', then quote through is applied automatically.
This is the preferred way to output XML tags directly in YML:
<output me="directly" />
] <!--
] add some comment, which then appears in XML
] -->
Because YML first is translated into Python (what I call Python YML - PYML), you can use simple Python modules together with your YML code:
import module
YML does not support name spaces. Instead, import module is being translated
into from module import *.
Additionally, you can include a second YML script file into an existing YML script file at any place using:
include something.yml
As an alternative, you can insert a Python command into the generated PYML script at any place by using the ! operator:
!print "hello, world"
Comments are written like in Java or C++:
// this is a comment
something() { // this is a comment after a tag
After quoting operators, comments are not possible. Instead, they're quoted through:
// the following line you'll find in the output document
> this text is being output // and this too
Hint: Because comments are removed with the lexical analyzer already, you should not use // in parameters when using functions without parantheses. This will cause an error, because everthing behind // will be treated as a comment:
callMe "with // in text parameter"
There is no such problem, when you're using parantheses. This line will work:
callMe("with // in text parameter")
Macros can be defined using he define keyword, i.e.:
define ABC as "ABCDEFGHIFJKLMNOPQRSTUVWXYZ"
define abc as "abcdefghifjklmnopqrstuvwxyz"
Some macros have special meanings:
quote_text is an PYML (Python) expression, how to insert plain text.
%n is a placeholder for the inserted text.
YSLT defines it as following:
define quote_text as yml_text("""%n""")
quote_indention is an YML function call, which is called by the | operator for
indenting. There is the place holder %n, which is replaced by the indention level.
YSLT defines quote_indention as following:
define quote_indention as indent("%n")
special_command_left and special_command_right are operators, which
can be used inside quoted text for special functions. By default they're defined as following:
define special_command_left as «
define special_command_right as »
They're used by YSLT for value() function calls. By YHTML they're used for <strong> text.
special_command_name is the YML function, which is called by using the operators,
which were defined with special_command_left and special_command_right.
XSLT already is a functional programming language. But it's ugly. So one of the first approaches to use YML was to define YSLT. YSLT is just a Y language for XSLT.
The hello world looks plain and simple:
include yslt.yml
stylesheet {
output "text"
template("/") {
> hello, world\n
}
}
This sample generates a table view in HTML out of the list sample of the first chapter. I'm using YHTML for it, the same Y language I'm writing this documentation in:
include yhtml.yml
include yslt.yml
stylesheet {
template ("list") {
_html {
head {
title {
> «@name»
}
style ("text/css") {
comment {
> th { border-style:solid; border-width:1; }
> td { border-style:dotted; border-width:1; }
}
}
}
body {
table {
apply "head"
apply "row"
}
}
}
}
template ("head") {
tr {
apply "columnTitle"
}
}
template ("columnTitle") {
th {
> «.»
}
}
template ("row") {
tr {
apply "value"
}
}
template ("value") {
td {
> «.»
}
}
}
This YSLT program you can download here. The result looks like this:
This works very well - see this sample program, which compiles Java interfaces out of XMI files.
For features, just read the YSLT definition and have a look on the sample above.
This documentation was written in YHTML - see the source and the Makefile as well as the YML translator script.
It was mostly generated out of the XML Schema of XHTML using the Default Compiler using YSLT and an YSLT processor. As XML tool chain, I'm using xsltproc and XMLStarlet.
YSLT is using the YPath python module as an addition to XPath and an indention system.
Because I'm lazy, I'm using Python excessively ;-) I'm just compiling to python code, which then - being executed - outputs XML code.
This is the reason why in the Makefile above the output is again put into a Python interpreter.
Have a look at the YML compiler front end and at the YML compiler back end.
Please find vim syntax coloring here. ;-)
... use this one for a compressed tar archive, or this one for a zip file.
YML is Free Software. It is under the GNU General Public License 2.0.
Updates to YML and YSLT I will announce in my blog.
Up to now, there are the following versions: