ys - The YAMLScript Command Line Tool

The YAMLScript ys command line tool is the primary way to run, load and compile YAMLScript programs.

Loading is essentially the same as running, but the result is output is printed as JSON.

Here's the ys --help output:

$ ys --help

ys - The YAMLScript (YS) Command Line Tool - v0.1.84

Usage: ys [] []

Options:

--run Run a YAMLScript program file (default)
-l, --load Output (compact) JSON of YAMLScript evaluation
-e, --eval YSEXPR Evaluate a YAMLScript expression
multiple -e values joined by newline

-c, --compile Compile YAMLScript to Clojure
-b, --binary Compile to a native binary executable

-p, --print Print the result of --run in code mode
-o, --output FILE Output file for --load, --compile or --binary
-s, --stream Output all results from a multi-document stream

-T, --to FORMAT Output format for --load:
json, yaml, edn
-J, --json Output (pretty) JSON for --load
-Y, --yaml Output YAML for --load
-E, --edn Output EDN for --load
-U, --unordered Mappings don't preserve key order (faster)

-m, --mode MODE Add a mode tag: code, data, or bare (for -e)
-C, --clojure Treat input as Clojure code

-d Debug all compilation stages
-D, --debug-stage STAGE Debug a specific compilation stage:
parse, compose, resolve, build,
transform, construct, print
can be used multiple times
-S, --stack-trace Print full stack trace for errors
-x, --xtrace Print each expression before evaluation

--install Install the libyamlscript shared library
--upgrade Upgrade both ys and libyamlscript

--version Print version and exit
-h, --help Print this help and exit

Let's start with a YAML file (some.yaml) that wants to use data from another YAML file and also do some simple calculations:

!yamlscript/v0/

=>:
name =: "World"
data =: load("data1.yaml")
fruit =: data.food.fruit

num: 123
greet:: "$(data.hello.rand-nth()), $name!"
eat:: fruit.shuffle().first()
drink:: (["Bar"] * 3).join(', ').str('!!!')

Here's the other YAML file (data1.yaml):

food:
fruit:
- apple
- banana
- cherry
- date

hello:
- Aloha
- Bonjour
- Ciao
- Dzień dobry

We can "load" the YAML/YAMLScript file with the ys command and it will print the result as JSON:

$ ys -l some.yaml 
{"num":123,"greet":"Bonjour, World!","eat":"apple","drink":"Bar, Bar, Bar!!!"}

We can also format the output as YAML:

 ys -lY some.yaml 
num: 123
greet: Ciao, World!
eat: cherry
drink: Bar, Bar, Bar!!!

Here's a tiny YAMLScript program called program.ys:

!yamlscript/v0

defn main(name='world' n=3):
greet: name n

defn greet(name, times=1):
each [i (1 .. times)]:
say: "$i) Hello, $name!"

We can run this program with the ys command:

$ time ys program.ys
1) Hello, world!
2) Hello, world!
3) Hello, world!

real 0m0.021s
user 0m0.014s
sys 0m0.007s

Pretty fast, right?

We can pass in arguments:

$ ys program.ys Bob 2
ys program.ys Bob 2
1) Hello, Bob!
2) Hello, Bob!

To see what Clojure code is being generated under the hood:

$ ys -c program.ys
(declare greet)
(defn main
([name n] (greet name n))
([name] (main name 3))
([] (main "world" 3)))
(defn greet
([name times] (each [i (rng 1 times)] (say (str i ") Hello, " name "!"))))
([name] (greet name 1)))
(apply main ARGS)

You can compile the program to a native binary executable:

$ time ys -b program.ys 
* Compiling YAMLScript 'program.ys' to 'program' executable
* Setting up build env in '/tmp/tmp.xU8K3OPymt'
* This may take a few minutes...
[1/8] Initializing (2.8s @ 0.14GB)
[2/8] Performing analysis (9.1s @ 0.33GB)
[3/8] Building universe (1.2s @ 0.39GB)
[4/8] Parsing methods (1.4s @ 0.41GB)
[5/8] Inlining methods (0.9s @ 0.49GB)
[6/8] Compiling methods (10.6s @ 0.50GB)
[7/8] Layouting methods (1.0s @ 0.50GB)
[8/8] Creating image (1.5s @ 0.44GB)
* Compiled YAMLScript 'program.ys' to 'program' executable

real 0m36.340s
user 4m34.165s
sys 0m3.915s

$ time ./program Bob 2
1) Hello, Bob!
2) Hello, Bob!

real 0m0.007s
user 0m0.003s
sys 0m0.004s

As you can see, the native binary is faster than the interpreted version, but the compilation takes quite a long time.


When debugging, you can see the output of each compilation stage by adding the -d option:

$ ys -cd program.ys
*** parse output ***
({:+ "+MAP", :! "yamlscript/v0"}
{:+ "=VAL", := "defn main(name='world' n=3)"}
{:+ "+MAP"}
{:+ "=VAL", := "greet"}
{:+ "=VAL", := "name n"}
{:+ "-MAP"}
{:+ "=VAL", := "defn greet(name, times=1)"}
{:+ "+MAP"}
{:+ "=VAL", := "each [i (1 .. times)]"}
{:+ "+MAP"}
{:+ "=VAL", := "say"}
{:+ "=VAL", :$ "$i) Hello, $name!"}
{:+ "-MAP"}
{:+ "-MAP"}
{:+ "-MAP"}
{:+ "-DOC"})

*** compose output ***
{:! "yamlscript/v0",
:%
[{:= "defn main(name='world' n=3)"}
{:% [{:= "greet"} {:= "name n"}]}
{:= "defn greet(name, times=1)"}
{:%
[{:= "each [i (1 .. times)]"}
{:% [{:= "say"} {:$ "$i) Hello, $name!"}]}]}]}

*** resolve output ***
{:xmap
[{:defn "defn main(name='world' n=3)"}
{:xmap [{:expr "greet"} {:expr "name n"}]}
{:defn "defn greet(name, times=1)"}
{:xmap
[{:expr "each [i (1 .. times)]"}
{:xmap [{:expr "say"} {:xstr "$i) Hello, $name!"}]}]}]}

*** build output ***
{:xmap
[[{:Sym defn} {:Sym main} nil]
[{:Lst
[{:Vec [{:Sym name} {:Sym n}]}
{:xmap [{:Sym greet} [{:Sym name} {:Sym n}]]}]}
{:Lst
[{:Vec [{:Sym name}]} {:Lst [{:Sym main} {:Sym name} {:Int 3}]}]}
{:Lst [{:Vec []} {:Lst [{:Sym main} {:Str "world"} {:Int 3}]}]}]
[{:Sym defn} {:Sym greet} nil]
[{:Lst
[{:Vec [{:Sym name} {:Sym times}]}
{:xmap
[[{:Sym each}
{:Vec [{:Sym i} {:Lst [{:Sym rng} {:Int 1} {:Sym times}]}]}]
{:xmap
[{:Sym say}
{:Lst
[{:Sym str}
{:Sym i}
{:Str ") Hello, "}
{:Sym name}
{:Str "!"}]}]}]}]}
{:Lst
[{:Vec [{:Sym name}]}
{:Lst [{:Sym greet} {:Sym name} {:Int 1}]}]}]]}

*** transform output ***
{:xmap
[[{:Sym defn} {:Sym main} nil]
[{:Lst
[{:Vec [{:Sym name} {:Sym n}]}
{:xmap [{:Sym greet} [{:Sym name} {:Sym n}]]}]}
{:Lst
[{:Vec [{:Sym name}]} {:Lst [{:Sym main} {:Sym name} {:Int 3}]}]}
{:Lst [{:Vec []} {:Lst [{:Sym main} {:Str "world"} {:Int 3}]}]}]
[{:Sym defn} {:Sym greet} nil]
[{:Lst
[{:Vec [{:Sym name} {:Sym times}]}
{:xmap
[[{:Sym each}
{:Vec [{:Sym i} {:Lst [{:Sym rng} {:Int 1} {:Sym times}]}]}]
{:xmap
[{:Sym say}
{:Lst
[{:Sym str}
{:Sym i}
{:Str ") Hello, "}
{:Sym name}
{:Str "!"}]}]}]}]}
{:Lst
[{:Vec [{:Sym name}]}
{:Lst [{:Sym greet} {:Sym name} {:Int 1}]}]}]]}

*** construct output ***
{:Top
[{:Lst [{:Sym declare} {:Sym greet}]}
{:Lst
[{:Sym defn}
{:Sym main}
nil
{:Lst
[{:Vec [{:Sym name} {:Sym n}]}
{:Lst [{:Sym greet} {:Sym name} {:Sym n}]}]}
{:Lst
[{:Vec [{:Sym name}]} {:Lst [{:Sym main} {:Sym name} {:Int 3}]}]}
{:Lst [{:Vec []} {:Lst [{:Sym main} {:Str "world"} {:Int 3}]}]}]}
{:Lst
[{:Sym defn}
{:Sym greet}
nil
{:Lst
[{:Vec [{:Sym name} {:Sym times}]}
{:Lst
[{:Sym each}
{:Vec [{:Sym i} {:Lst [{:Sym rng} {:Int 1} {:Sym times}]}]}
{:Lst
[{:Sym say}
{:Lst
[{:Sym str}
{:Sym i}
{:Str ") Hello, "}
{:Sym name}
{:Str "!"}]}]}]}]}
{:Lst
[{:Vec [{:Sym name}]}
{:Lst [{:Sym greet} {:Sym name} {:Int 1}]}]}]}
{:Lst [{:Sym +++} {:Lst [{:Sym apply} {:Sym main} {:Sym ARGS}]}]}]}

*** print output ***
"(declare greet)(defn main ([name n] (greet name n)) ([name] (main name 3))...

(declare greet)
(defn main
([name n] (greet name n))
([name] (main name 3))
([] (main "world" 3)))
(defn greet
([name times] (each [i (rng 1 times)] (say (str i ") Hello, " name "!"))))
([name] (greet name 1)))
(+++ (apply main ARGS))