10 releases (6 breaking)
0.9.0 | Aug 11, 2023 |
---|---|
0.8.0 | Jul 12, 2022 |
0.6.0 | Jun 16, 2022 |
0.5.2 | Jun 12, 2022 |
0.2.1 | May 9, 2019 |
#1816 in Procedural macros
34 downloads per month
90KB
2.5K
SLoC
Termpose - a sensitive markup language
Termpose is an extremely flexible markup language with an elegant whitespace syntax.
Termpose data, once parsed, is very simple to work with. It doesn't have maps and numbers. It only has lists and strings. APIs are provided for type-checking-translating into types like maps and numbers, but the format itself does not impose. The only characters termpose applies special meaning to are ":()\
. The rest are yours to use as you please.
Here's an example of the kind of data I store in termpose. This is very similar to part of the save format of a game I'm working on:
mon
name leafward
affinity creation
description "
Plants healing bombs.
Standard attack.
Watch out, it's fragile!
stride 2
stamina 3
recovery 2
health 50
abilities
move
strike drain:2 damage:standard:2
bomb drain:3 effect:heal:standard:2 grenadeTimer:2 grenadeHealth:20 range:2 radius:2
In an s-expression language (in this case, woodslist), that would would break down to the following structure:
(mon
(name leafward)
(affinity creation)
(description
"Plants healing bombs.
Standard attack.
Watch out, it's fragile!")
(stride 2)
(stamina 3)
(recovery 2)
(health 50)
(abilities
move
(strike (drain 2) (damage (standard 2)))
(bomb (drain 3) (effect (heal (standard 2))) (grenadeTimer 2) (grenadeHealth 20) (range 2) (radius 2))
)
)
We were always able to use a nice, minimal, flexible S-Expressions language instead of XML, Json, Toml, Yaml or whatever else, but there was something about those old syntaxes that made the prospect unpalatable. Termpose takes that something out of the picture.
Platform support?
Warning, Rust is currently the only language that supports termpose per specification, the others do something different with items in a line with indental, because until writing the spec I hadn't thought hard about what made the most sense there.
language | spec compliant | status | further info |
---|---|---|---|
C++ | No | Very nice. | Basic API, Intro to parser combinators, termpose.ccp |
Haxe | No | Pretty good. | Haxe Source |
Scala | No | Very nice | This brief intro and the Source (start at the bottom) |
Javascript | No | There is a basic API that converts termpose to arrays of strings. | Haxe Source |
C# | No | Give me an API design and I'll do it | Haxe Source |
Java | No | Talk to me if you'd like to make a nice API, it'll be easy | Haxe Source |
Python | No | Talk to me if you'd like to make a nice API, it'll be easy | Haxe Source |
C | No | low-level char* → Term* API is implemented and without leaks | C Header |
Rust | Yes | Good support for termpose and woodslist. Has autoderive. | Rust Intro |
Format in detail
Here, I'm going to describe how data and structures can be written in termpose.
In s-expression based languages, the first term in a list is often considered special. For instance,
(f a b c)
Here, this would generally be an application of the function f
, being invoked on the variables a
, b
and c
.
In termpose, this structure can also be expressed in a way that will be more familiar to most programmers and mathematicians, one character shorter to type, and perhaps more explicit
f(a b c)
Anything that can be expressed that way can also be written
f
a
b
c
Often, in programming languages, and in data, we want an easy way to express pairs. For instance, in swift, you might define or create a rectangle with
CGRect(x: 0, y: 0, width: 320, height: 500)
In a termpose context, a structure like this could be expressed in just the same way, minus the commas. We don't need or want any commas around here.
CGRect(x: 0 y: 0 width: 320 height: 500)
Or more minimally
CGRect x:0 y:0 width:320 height:500
Or
CGRect
x:0
y:0
width:320
height:500
I should say, colons aren't a special syntax, they break down to lists as well:
(CGRect (x 0) (y 0) (width 320) (height 500))
You might say they pair things. You might also say that a colon invokes the first thing as a function of the second thing. Either interpretation is valid.
print:"maybe"
If you wanted to invoke a series of chained one-parameter invocations, you could.
print:to_lowercase:reverse:"EBYAM"
Would translate to
print(to_lowercase(reverse(EBYAM)))
You might notice the quote marks around "EBYAM" were omitted away in the translation. Quotes are for including whitespace in atoms. If you aren't using any whitespace within the atom string, they aren't needed. Here are some more typical examples of the quote syntax:
print "a string"
print "another string"
print "and then another"
But what if our hypothetical termpose-based programming language wanted to print something very long, with multiple lines?
print "
a string
another string
print "and then another"
We could simply open a string into an indent before we want it to start and unindent when we want it to end.
If we were really making a termpose-based programming language, we might want to add something extra to quoted atoms to signal that we intend them to be read as string literals. Termpose tries to make that easy by having quoted strings be invoked by anything they are directly adjacent to. For instance, if we used a '
,
print '"
a string
another string
print '"and then another"
would break down to
((print ("'" "a string\nanother string")) (print ("'" "and then another")))
Which would make it very easy to detect literals by looking for terms that start with '
.
I hope I've been able to convince you that termpose is flexible enough for whatever data you want to express. To understand how easy it is to work with termpose, take a look at some the API intros listed above.
Dependencies
~2MB
~46K SLoC