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.
Many people don't write XSLT directly, because they don't like writing programs in angle brackets. The result usually is, that people are writing Java programs, and are processing XML from Java, or are using XSLT features from Java programs.
But that is very inefficient - there are small and quick XSLT processors, and there are no advantages at all to implement that in Java or C++ usually.
So I wanted to have something like a Java or C like language, which can be easily translated into XSLT. Then a common XSLT processor can process the program, and XML can be processed very quickly.
I started this, because I saw, that code generation for Automated Software Engineering can be implemented very easily in XSLT - but writing XSLT is annoying.
You can find some sample code here how to do that with YML and YSLT.
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();
}
}
If you have defined an attribute, and want to override it setting another attribute,
you can use the attr() class for doing so. Here a sample:
// declare an attribute
decl when("test")
// using it
when ("type=1") {
// ...
// overriding it
when (attr("myAttr='something'"), "type=1")
// of course, you just can write this, too:
when "type=1", "myAttr='something'"
The latter two define myAttr="something" as XML attribute additionally to "test='type=1'".
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"'))
If you want to have both the functionality of x() and attr(), use
the combined xattr().
To make a "one-liner", you can use body(value) as a YML function parameter.
Here is a sample:
decl staff
decl director("name")
staff {
director "Herbert Meier" {
> Head of Development
}
director "Alfred E. Newman", body("Head of Sales")
}
This results in:
<?xml version="1.0" encoding="iso-8859-1"?>
<staff>
<director name="Herbert Meier">Head of Development</director>
<director name="Alfred E. Newman">Head of Sales</director>
</staff>
If you alread have XML text, you can switch off XML escaping by using xbody(value)
instead of body(value).
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
(...)
The '||' operator opens and closes a block of lines, which then are handled like if each of them would be preceeded with a Line Operator '|'.
Sample:
||
this is code being quoted through
this is the second line
||
is equivalent to:
| this is code being quoted through
| this is the second line
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.
Being in a Block Line Quote '||', you additionally can use the Line Command operator (two backquotes, '``');
This is very interesting to have in YSLT, for example:
||
some code
``apply "myTemplate"
some other code
||
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 (see next section, too):
!from module import *
!import module2
You can include a second YML script file into an existing YML script file at any place using:
include something.yml
File globbing using * and ? placeholders is allowed to include more than one file at a time:
include part*.yml
File globbing also can be used reverted; that means, the files are included in reverse order:
include reverse part*.yml
If there are the files part1.yml, part2.yml and part3.yml, part3.yml is included first now.
For including files, there are two keywords: include and section.
They're equivalent.
if you have text in a file, which you want to include between '||' operators, instead of writing:
||
include some.txt
||
you also could write:
include text some.txt
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.
If you define generate_comments as True then a comment containing the include file
name (or the empty string for the main script) and the line number are added to each line,
which is output using the pipe operator '|'.
comment_mask defines a pattern for generating the output. The default pattern is
/* %f:%n */. %f is replaced by the name of the current include file
(or an empty string if we're in the main script), and %n is replaced by the line
number of the pipe operator in the script, which generated the line.
This feature is intended for debugging purposes.
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
textstylesheet {
template "/" {
| hello, world
}
}
You can run that with:
vb@ostrich:~ % ysltproc hello.ysl
hello, world
vb@ostrich:~ %
This works very well - see this sample program, which compiles Java interfaces out of XMI files.
You can compile it using:
vb@www:~/fdik.org/yml $ ysltproc xmi2JavaInterface.ysl sample.xmi > sample.java
vb@www:~/fdik.org/yml $
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:
I added a sample for CYY, a language, which shows how to use YML/YSLT as a macro preprocessor to C++.
section cyy_templates
template "type", "enum" {
| T«@name»`if("position()=1", " = 0")`,
}
section cyy_code
#include "types.hh"
enum Types {
`` apply "types/type", "enum"
NUMBEROF_TYPES
};
class TileCache { };
template <Types SELECT, class T>
class TileCacheAdapter : public TileCache {
public:
enum { adapter_type = SELECT };
T obj;
};
class CacheController {
public:
CacheController() {
`` foreach "types/type" {
`` |>> adapter[T«@name»] = new TileCacheAdapter< T«@name», «@name» >;
`` }
}
~CacheController() {
for (int i=0; i<NUMBEROF_TYPES; i++)
delete adapter[i];
}
private:
TileCache* adapter[NUMBEROF_TYPES];
};
section cyy_end
You can use this together with this data file as input:
decl types
decl type("name")
types {
type "Street"
type "Edge"
type "Vertex"
}
So for getting C++ code out of this, use:
vb@www:~/fdik.org/yml $ ysltproc sample.cyy types.yml > sample.cc
vb@www:~/fdik.org/yml $
The content of the CYY definition files you can find here: cyy_templates, cyy_code and cyy_end.
if you don't define templates, you can use cyy_begin instead of writing two lines with cyy_templates and cyy_code.
As a result of the sample above, this C++ program is being generated.
YC is just for fun ;-) I wrote a faculty sample and Towers of Hanoi in it to demonstrate, how to compute while compile time.
For easier translating of YC code into object code, you can use this script.
With YBlog you have a sample how to hack a little Blog engine using YML.
For features, just read the YSLT definition and have a look on the samples 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.
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.
Windows users find a zip file with the needed binary tools here.
Or you can use the OpenPKG Distribution to install everything needed automatically.
YML is Free Software. It is under the GNU General Public License 2.0.
Just send me a mail ;-)
I got contributions from Alexander Bernauer, Rico Schiekel, Stefan Schlott and Markus Schaber. Thank you!
Updates to YML and YSLT I will announce in my blog.
Up to now, there are the following versions: