diff --git a/doc/control/index.html b/doc/control/index.html index f70505e0..1b0d9a94 100644 --- a/doc/control/index.html +++ b/doc/control/index.html @@ -1,4 +1,4 @@ Control Flow | YAMLScript

Control Flow

YAMLScript (iow Clojure) is a functional programming language. That means that everything is a function. Running a program is just a calling a function that calls other functions.

Even though YAMLScript tries hard to look like an imperative language, you must keep in mind that it's functional and get familiar with how the flow works.

This document will cover:

  • Starting a program
  • Variable scope
  • Grouping expressions
  • Looping functions and recursion
  • Conditional expressions

Note: Some of the things that are called "functions" in in this document are actually "macros" or "special forms" in Clojure. The distinction is not particularly important here, but worth mentioning.

Starting a Program

A YAMLScript file generally is just a bunch of defn calls to define functions.

Sometimes there will be assignment expressions at the top level. Top level variables are global and can be used anywhere in the program. They belong to the file's namespace which is main by default.

If a program defines a function called main, it will be called when the program is run. Any command line arguments will be passed to the main function after being cast to numbers if they look like numbers.

program.ys:

!yamlscript/v0

defn main(word='Hello!' times=3):
each i (1 .. times):
say: "$i) $word"
$ ys program.ys
1) Hello!
2) Hello!
3) Hello!
$ ys program.ys YAMLScript 2
1) YAMLScript
2) YAMLScript

If a program does not define a main function, then nothing will happen unless you've defined a top level function call yourself. YAMLScript files that are meant to be used as libraries will not have a main function or a top level function call.

Variable Scope

One cool thing about YAMLScript (and Clojure) is that you can use any word as a variable name. Even things like if and for which are reserved words in many languages.

For example you might do this:

defn foo(list):
count =: count(list)
if count > 0:
say: 'The list is not empty'
else:
say: 'The list is empty'

Once we bind count to the result of the count function, we can't use the count function again in that scope. Often this is just fine. And it feels nice that you don't have to think up a synonym or alternative mangling for count.

Grouping Expressions

Some expression contexts allow multiple expressions to be grouped together and some only allow a single expression.

You can group multiple expressions together with a do function call when you need to to do multiple things in a context that only allows a single expression.

if a > b:
do:
say: 'a is greater than b'
say: 'a must be HUGE!'
say: 'Nothing to see here'

Note: The if function actually supports the better named then and else words for grouping, but do can also be used.

Looping Functions and Recursion

YAMLScript has a few looping functions: loop, reduce, for and each.

These will be documented in more detail in the future, but for now you can see the Clojure documentation for them:

  • loop
  • reduce
  • for
  • each is a YAMLScript function that calls to a for expression inside a doall expression. This allows you to print things in the loop body. In every other way it's the same as for.

Conditional Expressions

YAMLScript has a few common conditional expressions: if, when, cond and case.

The if Function

In YAMLScript, an if expression is a function that takes 3 arguments: a condition, a then expression and an else expression.

if a: b c  # If a is true, return b, else return c

The b and c expressions can also be mapping pairs:

if a:
say: 'yes'
say: 'no'

Sometimes you want to do more than one thing in the then or else expression:

if a:
then:
say: 'yes'
say: 'yes'
else:
say: 'no'
say: 'no'

If you use then you must also use else, but else can be used without a then:

if a:
say: 'yes'
else:
say: 'no'
say: 'no'

Since if is a function, it has a return value.

say:
if a: -'yes' 'no'

Any variable assigned in the then or else expression will only apply to that expression and not to the surrounding scope.

x =: 1
if a > b:
x =: 2
x =: 3
=>: x # => 1

What you want to do here is capture the result of the if expression:

x =:
if a > b:
then: 2
else: 3
=>: x # => 2 or 3

Note that say returns nil, so all the if expressions above would also return nil.

The when Function

YAMLScript also has a when function that is like if but without an else expression.

when a:
say: 'yes'

The if function should only be used wen you have a then and an else expression. Otherwise, use when.

One thing about when is that its body can have multiple expressions.

when a:
say: 'yes'
say: 'yes'

The cond Function

The cond function is like a series of if expressions but with multiple conditions.

size =:
cond:
a < 20: 'small'
a < 50: 'medium'
a < 100: 'large'
else: 'huge'

The case Function

The case function is like a cond but with a single expression to compare against.

count =:
case a:
1: 'one'
2: 'two'
3: 'three'
else: 'many'
\ No newline at end of file + gtag('config', 'G-44C9DS3Q80');Control Flow | YAMLScript

Control Flow

YAMLScript (iow Clojure) is a functional programming language. That means that everything is a function. Running a program is just a calling a function that calls other functions.

Even though YAMLScript tries hard to look like an imperative language, you must keep in mind that it's functional and get familiar with how the flow works.

This document will cover:

  • Starting a program
  • Variable scope
  • Grouping expressions
  • Looping functions and recursion
  • Conditional expressions

Note: Some of the things that are called "functions" in in this document are actually "macros" or "special forms" in Clojure. The distinction is not particularly important here, but worth mentioning.

Starting a Program

A YAMLScript file generally is just a bunch of defn calls to define functions.

Sometimes there will be assignment expressions at the top level. Top level variables are global and can be used anywhere in the program. They belong to the file's namespace which is main by default.

If a program defines a function called main, it will be called when the program is run. Any command line arguments will be passed to the main function after being cast to numbers if they look like numbers.

program.ys:

!yamlscript/v0

defn main(word='Hello!' times=3)
for i (1 .. times):
say: "$i) $word"
$ ys program.ys
1) Hello!
2) Hello!
3) Hello!
$ ys program.ys YAMLScript 2
1) YAMLScript
2) YAMLScript

If a program does not define a main function, then nothing will happen unless you've defined a top level function call yourself. YAMLScript files that are meant to be used as libraries will not have a main function or a top level function call.

Variable Scope

One cool thing about YAMLScript (and Clojure) is that you can use any word as a variable name. Even things like if and for which are reserved words in many languages.

For example you might do this:

defn foo(list):
count =: count(list)
if count > 0:
say: 'The list is not empty'
else:
say: 'The list is empty'

Once we bind count to the result of the count function, we can't use the count function again in that scope. Often this is just fine. And it feels nice that you don't have to think up a synonym or alternative mangling for count.

Grouping Expressions

Some expression contexts allow multiple expressions to be grouped together and some only allow a single expression.

You can group multiple expressions together with a do function call when you need to to do multiple things in a context that only allows a single expression.

if a > b:
do:
say: 'a is greater than b'
say: 'a must be HUGE!'
say: 'Nothing to see here'

Note: The if function actually supports the better named then and else words for grouping, but do can also be used.

Looping Functions and Recursion

YAMLScript has a few looping functions: loop, reduce, for and each.

These will be documented in more detail in the future, but for now you can see the Clojure documentation for them:

  • loop
  • reduce
  • for
  • each is a YAMLScript function that calls to a for expression inside a doall expression. This allows you to print things in the loop body. In every other way it's the same as for.

Conditional Expressions

YAMLScript has a few common conditional expressions: if, when, cond and case.

The if Function

In YAMLScript, an if expression is a function that takes 3 arguments: a condition, a then expression and an else expression.

if a: b c  # If a is true, return b, else return c

The b and c expressions can also be mapping pairs:

if a:
say: 'yes'
say: 'no'

Sometimes you want to do more than one thing in the then or else expression:

if a:
then:
say: 'yes'
say: 'yes'
else:
say: 'no'
say: 'no'

If you use then you must also use else, but else can be used without a then:

if a:
say: 'yes'
else:
say: 'no'
say: 'no'

Since if is a function, it has a return value.

say:
if a: -'yes' 'no'

Any variable assigned in the then or else expression will only apply to that expression and not to the surrounding scope.

x =: 1
if a > b:
x =: 2
x =: 3
=>: x # => 1

What you want to do here is capture the result of the if expression:

x =:
if a > b:
then: 2
else: 3
=>: x # => 2 or 3

Note that say returns nil, so all the if expressions above would also return nil.

The when Function

YAMLScript also has a when function that is like if but without an else expression.

when a:
say: 'yes'

The if function should only be used wen you have a then and an else expression. Otherwise, use when.

One thing about when is that its body can have multiple expressions.

when a:
say: 'yes'
say: 'yes'

The cond Function

The cond function is like a series of if expressions but with multiple conditions.

size =:
cond:
a < 20: 'small'
a < 50: 'medium'
a < 100: 'large'
else: 'huge'

The case Function

The case function is like a cond but with a single expression to compare against.

count =:
case a:
1: 'one'
2: 'two'
3: 'three'
else: 'many'
\ No newline at end of file diff --git a/doc/run-ys/index.html b/doc/run-ys/index.html index dd552ada..e1a8e105 100644 --- a/doc/run-ys/index.html +++ b/doc/run-ys/index.html @@ -1,4 +1,4 @@ Self Installation Scripts | YAMLScript

Self Installation Scripts

Note: If you just ran a program with bash that printed a URL to this page, click the arrow below for more information on "What just happened?".

What just happened?

If you are reading this you probably just ran a YAMLScript program with bash. The first time you do that, the program installed the ys interpreter under the /tmp/ directory for you and then ran the program with it. Subsequent runs of the program will use that installed ys interpreter.

You may continue to run the program this way, but there will be a slight delay at the start each time while the run-ys auto-installer script is downloaded.

It is very easy to install the ys interpreter permanently on your system so that you can run the program with ys instead of bash.

$ curl -s https://yamlscript.org/install-ys | bash

See the YAMLScript Installation page for more information.


YAMLScript has a way to publish programs that people can run immediately without having installed the ys interpreter first.

Warning: See the Security Considerations below before using this technique.

Just begin your YAMLScript program with these lines:

#!/usr/bin/env ys-0
source <(curl '-s' 'https://yamlscript.org/run-ys') "$@" :

Then anyone can run your program using Bash with a command like this: bash script.ys arg1 arg2 ....

The first time they do so, the ys interpreter will be downloaded and installed under the /tmp/ directory.

The ys interpreter will be downloaded only once, and it will be used for all subsequent runs of the script.

Note: The curl command will still download and evaluate the run-ys Bash script on subsequent runs so the user will need to have internet access.

The program can also be run with the ys interpreter if the user installs it. In that case the Bash installer line will be ignored.

Since the program has a shebang line, it can also be run as a PATH command if the file is marked as executable.

Example

Here's a small YAMLScript program that program that prints the ROT13 encoding of its arguments:

#!/usr/bin/env ys-0

source <(curl '-s' 'https://yamlscript.org/run-ys') "$@" :

alphabet =: set((\\A .. \\Z) + (\\a .. \\z))
rot13 =: cycle(alphabet).drop(13 * 2).zipmap(alphabet)

defn main(*input):
say: str/escape(input.join(' ') rot13)

If we run it with ys:

$ ys rot13.ys I Love YS
V Ybir LF

If we run it with bash:

$ bash rot13.ys I Love YS
Installing YAMLScript CLI '/tmp/yamlscript-run-ys/bin/ys-0.1.86' now...
Ctl-C to abort
See https://yamlscript.org/doc/run-ys for more information.

Installed /tmp/yamlscript-run-ys/bin/ys - version 0.1.86
--------------------------------------------------------------------------------
V Ybir LF

and again:

$ bash rot13.ys I Love YS
V Ybir LF

How It Works

The program is both valid YAMLScript and valid Bash.

YAMLScript programs are required to start with a YAML tag like this:

!yamlscript/v0

But if they start with a shebang line like this:

#!/usr/bin/env ys-0

then the !yamlscript/v0 tag is optional.

When you run the program with bash, the shebang line is merely a comment and ignored by Bash.

The source line is a Bash command that reads and evaluates the contents of the <(...) process substitution file. The curl command inside downloads the run-ys script and installs the ys interpreter under the /tmp/ directory if it is not already installed.

It then execs the installed ys interpreter with your original program and any arguments you provided.

The source line is also a valid YAMLScript command. It calls the YAMLScript source macro which ignores all of its arguments (much like the comment macro does).

Note: The source macro was added in YAMLScript version 0.1.86. This technique will not work with earlier versions of YAMLScript.

Use Cases and Security Considerations

This technique is may be useful in situations where you want to share a YAMLScript program with people who are not yet familiar with YAMLScript.

Since the program is run with Bash which gets more Bash code from the internet, it is subject to the many security risks of running arbitrary code from the internet.

Caveat yamlscriptor!

** Note**: A more secure way to distribute a YAMLScript program is to compile it to a binary executable and distribute the binary instead.

There is at least one use case where this Bash technique is safe and useful:

You can easily run a YAMLScript program that you are developing with a particular version of the ys interpreter without having to install it first. Just use the YS_VERSION environment variable to specify the version you want:

$ YS_VERSION=0.1.86 bash my-program.ys arg1 arg2 ...

This might be useful for testing a reported bug with an older version of the interpreter, for example.

There may be other development and testing use cases for this technique as well. If you find one, please let us know!

\ No newline at end of file + gtag('config', 'G-44C9DS3Q80');Self Installation Scripts | YAMLScript

Self Installation Scripts

Note: If you just ran a program with bash that printed a URL to this page, click the arrow below for more information on "What just happened?".

What just happened?

If you are reading this you probably just ran a YAMLScript program with bash. The first time you do that, the program installed the ys interpreter under the /tmp/ directory for you and then ran the program with it. Subsequent runs of the program will use that installed ys interpreter.

You may continue to run the program this way, but there will be a slight delay at the start each time while the run-ys auto-installer script is downloaded.

It is very easy to install the ys interpreter permanently on your system so that you can run the program with ys instead of bash.

$ curl -s https://yamlscript.org/install-ys | bash

See the YAMLScript Installation page for more information.


YAMLScript has a way to publish programs that people can run immediately without having installed the ys interpreter first.

Warning: See the Security Considerations below before using this technique.

Just begin your YAMLScript program with these lines:

#!/usr/bin/env ys-0
source <(curl '-s' 'https://yamlscript.org/run-ys') "$@" :

Then anyone can run your program using Bash with a command like this: bash script.ys arg1 arg2 ....

The first time they do so, the ys interpreter will be downloaded and installed under the /tmp/ directory.

The ys interpreter will be downloaded only once, and it will be used for all subsequent runs of the script.

Note: The curl command will still download and evaluate the run-ys Bash script on subsequent runs so the user will need to have internet access.

The program can also be run with the ys interpreter if the user installs it. In that case the Bash installer line will be ignored.

Since the program has a shebang line, it can also be run as a PATH command if the file is marked as executable.

Example

Here's a small YAMLScript program that program that prints the ROT13 encoding of its arguments:

#!/usr/bin/env ys-0

source <(curl '-s' 'https://yamlscript.org/run-ys') "$@" :

alphabet =: set((\\A .. \\Z) + (\\a .. \\z))
rot13 =: cycle(alphabet).drop(13 * 2).zipmap(alphabet)

defn main(*input):
say: str/escape(input.join(' ') rot13)

If we run it with ys:

$ ys rot13.ys I Love YS
V Ybir LF

If we run it with bash:

$ bash rot13.ys I Love YS
Installing YAMLScript CLI '/tmp/yamlscript-run-ys/bin/ys-0.1.87' now...
Ctl-C to abort
See https://yamlscript.org/doc/run-ys for more information.

Installed /tmp/yamlscript-run-ys/bin/ys - version 0.1.87
--------------------------------------------------------------------------------
V Ybir LF

and again:

$ bash rot13.ys I Love YS
V Ybir LF

How It Works

The program is both valid YAMLScript and valid Bash.

YAMLScript programs are required to start with a YAML tag like this:

!yamlscript/v0

But if they start with a shebang line like this:

#!/usr/bin/env ys-0

then the !yamlscript/v0 tag is optional.

When you run the program with bash, the shebang line is merely a comment and ignored by Bash.

The source line is a Bash command that reads and evaluates the contents of the <(...) process substitution file. The curl command inside downloads the run-ys script and installs the ys interpreter under the /tmp/ directory if it is not already installed.

It then execs the installed ys interpreter with your original program and any arguments you provided.

The source line is also a valid YAMLScript command. It calls the YAMLScript source macro which ignores all of its arguments (much like the comment macro does).

Note: The source macro was added in YAMLScript version 0.1.87. This technique will not work with earlier versions of YAMLScript.

Use Cases and Security Considerations

This technique is may be useful in situations where you want to share a YAMLScript program with people who are not yet familiar with YAMLScript.

Since the program is run with Bash which gets more Bash code from the internet, it is subject to the many security risks of running arbitrary code from the internet.

Caveat yamlscriptor!

** Note**: A more secure way to distribute a YAMLScript program is to compile it to a binary executable and distribute the binary instead.

There is at least one use case where this Bash technique is safe and useful:

You can easily run a YAMLScript program that you are developing with a particular version of the ys interpreter without having to install it first. Just use the YS_VERSION environment variable to specify the version you want:

$ YS_VERSION=0.1.87 bash my-program.ys arg1 arg2 ...

This might be useful for testing a reported bug with an older version of the interpreter, for example.

There may be other development and testing use cases for this technique as well. If you find one, please let us know!

\ No newline at end of file diff --git a/doc/ys/index.html b/doc/ys/index.html index 07ff77a8..2a8db8d7 100644 --- a/doc/ys/index.html +++ b/doc/ys/index.html @@ -1,4 +1,4 @@ ys - The YAMLScript Command Line Tool | YAMLScript

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.86

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))
\ No newline at end of file + gtag('config', 'G-44C9DS3Q80');ys - The YAMLScript Command Line Tool | YAMLScript

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.87

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))
\ No newline at end of file diff --git a/feed.xml b/feed.xml index e9aaa5a9..ad10f68a 100644 --- a/feed.xml +++ b/feed.xml @@ -770,7 +770,7 @@ In fact, even if you have them installed, the build will ignore them.

Try:

$ ys --help

It should display:

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

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

-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
+
ys - The YAMLScript (YS) Command Line Tool - v0.1.87

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

-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

In the next day or two we'll go over all of these options in detail.

Here's a quick example of how to run YAMLScript to process a file from the internet that Google just told me about:

@@ -902,9 +902,9 @@ Today we'll learn about all the things you can do with it.

Welcome to Day 7 of the YAMLScript Advent Calendar

On Tuesday you learned how to install YAMLScript. Reminder, here's the quick way to install the latest version:

-
$ curl https://yamlscript.org/install | PREFIX=~/.yamlscript bash
$ export PATH=$HOME/.yamlscript/bin:$PATH
$ ys --version
YAMLScript v0.1.86
+
$ curl https://yamlscript.org/install | PREFIX=~/.yamlscript bash
$ export PATH=$HOME/.yamlscript/bin:$PATH
$ ys --version
YAMLScript v0.1.87

The best first command to run is ys --help:

-
$ ys --help

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

Usage: ys [<option...>] [<file>]

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

-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
+
$ ys --help

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

Usage: ys [<option...>] [<file>]

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

-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

Ready, set, actions!

The first thing to notice is that ys has 3 "actions":