ys - The YS CLI Tool
The YS ys command line tool is the primary way to run, load and compile YS
programs.
Note
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 YS Command Line Tool - v0.3.1
Usage: ys [<option...>] [<file>]
Options:
-e, --eval YSEXPR Evaluate a YS expression
multiple -e values are joined by newline
-l, --load Output the (compact) JSON of YS evaluation
-f, --file FILE Explicitly indicate input file
-c, --compile Compile YS to source or an artifact
-p, --print Print the final evaluation result value
-o, --output FILE Output file for --load or --compile
-s, --stream Output all results from a multi-document stream
-T, --to FORMAT Output format for --load:
json, yaml, csv, tsv, edn
or target for --compile:
bb, clj, clj+, bin, go, dir, lib, so, dylib, dll, h, js, html, wasm
-J, --json Output (pretty) JSON for --load
-Y, --yaml Output YAML 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 libys shared library
--upgrade Upgrade both ys and libys
--install-m2 Install the ys.v0 jars into ~/.m2
--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:
!ys-0:
::
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/YS 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 YS program called program.ys:
!ys-0
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)
When debugging, you can see the output of each compilation stage by adding the
-d option:
$ ys -cd program.ys
*** parse output ***
({:+ "+MAP", :! "ys-0"}
{:+ "=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 ***
{:! "ys-0",
:%
[{:= "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))
Standard Modules in Expressions🔗
YAMLScript expressions supplied with -e have the standard module aliases
available automatically, including fs, http, json, and yaml.
Positional expression shorthand has the same behavior:
ys -pe 'json/dump({})'
This is runtime setup in the current namespace.
It preserves existing aliases and lets leading ns and use declarations
establish their own aliases first.
The input YAML, its mode and tags, and the generated compilation output are
unchanged; nothing is wrapped in =>: or inserted into the document stream.
Raw Clojure evaluation with -C keeps its existing behavior.
Script files and compiled programs opt in explicitly:
!ys-0
use: v0
say: json/dump({})
use: ys::v0 is equivalent to use: v0.
The umbrella import adds aliases for available public standard modules without
referring their functions into the current namespace.
It skips modules disabled by YS_MODULES or unavailable in the runtime,
including restricted WASI modules.
Explicit individual imports continue to report errors for those modules.
Repeating the umbrella import is harmless; modifiers are not supported.
When a file is followed by -e, automatic imports apply only while evaluating
the expressions, after the file has run.
Compiling Programs🔗
ys -c writes Clojure to standard output.
With --output, the filename selects the compilation target:
| Output | Target | Result |
|---|---|---|
foo or foo.exe |
bin |
Native executable |
foo.go |
go |
Generated Go source |
foo/ |
dir |
Buildable Go project |
foo.so, foo.dylib, foo.dll |
lib |
Shared library |
foo.h |
h |
FFI header |
foo.js |
js |
Browser-target Wasm bytes |
foo.html |
html |
HTML runner and companion foo.js |
foo.wasm |
wasm |
WASI preview 1 module |
foo.clj |
clj |
Clojure source |
foo.bb |
bb |
Executable Babashka script |
An explicit --to selects the target regardless of the filename and implies
--compile.
Unknown extensions require --to.
Binary, library, header, directory, and Wasm targets require an output path.
For artifact targets, a .ys input supplies a default output in the current
directory: its basename with the final .ys replaced by the target extension.
--to=bin removes .ys; --to=dir creates a directory with that basename.
--to=wasm writes .wasm, and --to=js writes .js.
--to=so, --to=dylib, and --to=dll are aliases for --to=lib that select
the corresponding default filename extension.
--to=lib chooses .so, .dylib, or .dll for the target platform.
Add ,h to a library target to also write its matching header, for example
-Tso,h or -Tdylib,h,darwin/amd64.
An explicit header path in --output takes precedence over the matching name.
--to=h writes .h; --to=html writes .html and a companion .js.
Use -Tjs,html to request the same pair with .js as the primary output.
This also applies to cross-compilation.
For example, ys sample/rosetta-code/99-bottles-of-beer.ys -cTbin writes
./99-bottles-of-beer.
Using -cTwasm instead writes ./99-bottles-of-beer.wasm.
Text targets go, clj, clj+, and bb continue to use standard output when
no output path is supplied.
Existing output files, directories (even empty ones), and symlinks cause an
error.
Normal data output without --compile retains its existing behavior.
Artifact compilation shows a progress line and elapsed time on standard error.
In a terminal, a dot appears each second and the final status replaces the line.
The success mark is green and the failure mark is red; NO_COLOR disables color.
Gloat's build diagnostics are shown only on failure.
ys foo.ys -c -o foo
ys foo.ys --to=bin -o foo.xyz
ys -ce 'say: 42' -o answer
ys -c - -o answer < foo.ys
ys foo.ys -c -o foo,darwin/amd64
ys foo.ys --to=bin,darwin/amd64 -o foo.xyz
ys foo.ys -c -o lib/foo.so,include/foo.h
ys foo.ys -c -o foo.so,.h,darwin/amd64
ys foo.ys -c -o foo.js,.html
ys foo.ys -c -Tjs,html
ys foo.ys -c -o assets/foo.js,pages/foo.html
ys foo.ys -c -Thtml,-Xserve
ys foo.ys -c -Tbin,-Xprune
ys foo.ys -c -o foo,-Xprune
Compilation specifications have the form PRIMARY[,MODIFIER...].
Modifiers can select a companion output, an OS/ARCH platform, or a Gloat
processing extension written as -Xname or -Xname=value.
Modifiers can follow either --to or --output.
The old semicolon form is not supported.
Multiple Gloat extensions must each include -X, for example
-Twasm,-Xprune,-Xgzip.
They are passed unchanged to Gloat, which validates their names, values, and
target compatibility.
-Xserve and -Xopen imply an HTML companion.
Without an explicit output, they write persistent foo/index.html and
foo/index.js files and serve http://localhost:8000/foo/index.html.
An explicit output retains its exact name and writes its companion beside it.
Serving rejects explicitly selected JS and HTML files in different directories.
Program arguments belong in the page URL query, separated by commas.
Each argument is percent-decoded, so ?one,two passes two arguments and
?one%2Ctwo passes one argument containing a comma.
The -Xhtml, -Xserve, and -Xopen extensions do not accept values.
YAMLScript publishes only the primary artifact and any declared companion.
Use the companion syntax when an additional generated file must be retained.
An extension-only companion replaces the primary extension and keeps its
location.
Shared libraries publish a header only when requested.
A standalone .h request builds a temporary shared library and retains its
header, using Gloat's EXPORT declarations and ABI.
Browser .js files contain Wasm, not JavaScript source.
Serve the generated HTML and Wasm through an HTTP server to run them.
HTML companions reference the final relative Wasm location.
Compilation uses Gloat's Glojure engine, from either native ys engine.
YS_GLOAT can select a specific Gloat executable.
Otherwise ys looks beside its executable and on PATH.
If Gloat is missing, ys must be installed under a writable PREFIX/bin/
directory; it installs Gloat into that prefix through https://in-1.cc.
The prefix restriction applies only when Gloat needs installation.
Gloat manages build dependencies; shared-library and header cross-compilation
also require the target C toolchain.
For development, make -C ys test-compile runs the compiler wrapper tests.
make -C ys test-compile-real also builds real artifacts, calls the shared
library, checks cross-compilation, and runs browser-target Wasm under Node
and WASI under Wasmtime.
Makes provisions the test tools.
Use YAMLSCRIPT_ENGINE=graalvm to select the GraalVM CLI for these checks.
Generated project Makefiles inherit Makes' restriction on paths containing
spaces; choose a directory without spaces when building through that Makefile.