DTU is a data format that, unlike YAML, allows to define pretty decent programming language.
🎥 Watch overview video here 👇
DTU is implemented using:
- leex for lexing
- yecc for parsing
- prettypr for pretty printing
- erlang for coding
- rebar3 for the project
- rebar3_format for code formatting
- elli as the webserver for the example
- just as command runner
To evaluate the capabilities of the format I implemented "translators" from DTU to the following languages:
- CSS
- status: pretty complete
- example: css.dtu
- HTML
- status: pretty complete
- example: html.dtu
- Javascript
- status: only the features I needed for TodoMVC
- example: js.dtu
- Erlang
- status: only the features I needed for TodoMVC
- example: erlang.dtu
To drive the testing and feature selection for each dialect I started implemented TodoMVC, you can read the frontend code here todomvc.dtu.
You can try it here: TodoMVC DTU demo
You may notice that the javascript and css are inline in their own dialects, DTU allows you to switch dialects in the same file and do the right thing.
For example, the style
node will render the CSS in the body like here.
The CSS in the style
attribute will render it inline as a string like here.
Note The whole TodoMVC is defined in a single file, embedding HTML, CSS and JS
As I achieved the objective I went for a stretch and started implementing a backend for it in a dialect of Erlang.
This one embeds HTML, CSS and JS inside Erlang as seen here, in this case
the content is rendered as a string to be served as a response to a GET /
request,
you can see the result here.
To run the server, first compile dtu:
rebar3 compile
rebar3 escriptize
mv _build/default/bin/dtu .
Then translate DTU to Erlang:
./dtu erl examples/erlang.dtu | tee dtu_backend.erl
Then start a shell to run the server:
rebar3 as test shell
Compile the module and start the server:
c(dtu_backend).
{ok, Pid} = elli:start_link([{callback, dtu_backend}, {port, 3000}]).
And try it with curl:
curl -v http://localhost:3000/hello/world
# 200
# Hello World!
curl -v http://localhost:3000/hello/notfound
# 404
# Not Found
curl -v http://localhost:3000/task -d "name=do stuff"
# 201
# Created
If you open localhost:3000 in your browser the app will load.
To get a quick idea of the possibilities of the format check erlang.dtu.
42
42.5
"string"
Any name can be used as a tag, the meaning is given by the translator
#ts 42
#kg 42.5
#[email protected] 42.5
#re "string"
#set {1, 1, 2, 3}
#date "2022-10-16T04:39:34Z"
foo
foo.bar
foo@bar
foo.Bar@baz
[email protected]
Collections by themselves don't have any meaning, this are the default names.
you may notice that all collections can have individual values and key/value pairs, it's the task of the translator to decide which values are valid.
#[]
#[1]
#[1, 2]
#[1, answer: 42]
#[1, #{2, #()}, foo]
#()
#(1)
#(1, 2)
#(1, answer: 42)
#(1, #{2, #()}, foo)
#{}
#{1}
#{1, 2}
#{1, answer: 42}
#{1, #{2, #()}, foo}
Nodes are what makes DTU better at describing logic than YAML and JSON, a node is a name
followed optionally by a head (...)
and optionally by a body {...}
.
Like with collections any value that can be in a sequence can be in the head or the body of a node.
foo ()
foo.bar () {}
foo@bar {}
foo.bar@baz (hello)
[email protected] (42) {"hi"}
foo {42}
foo (a: 42) {b: 42}
The body of a node can also be an Alt Body
which is syntax to specify alternatives.
cond {
| true: 1
| false: 0
}
switch (name) {
| "bob": "yellow"
| "patrick": "pink"
}
type (Bool) {
| True
| False
}
No operator precedence, use parenthesis.
Any token starting with one of < = > ! % & ? * - + / ~
is a symbol, symbols
have no specific meaning.
1 + 2
1 + 2 * 3
1 + (2 * 3)
1 + 2 +- #ts 32
-
dtu.erl: escript entry point
-
dtu_lexer.xrl: Lexer
-
dtu_parser.yrl: Parser
-
dtu_html.erl: HTML translator
-
dtu_css.erl: CSS translator
-
dtu_js.erl: JS translator
-
dtu_erl.erl: Erlang translator
-
dtu_pp.erl: translator utilities
MIT