stdin
Line by LineThe following examples show gradually complex uses of the EDSL.
Simple call to the exec
construct. [Try-Online]
Genspio.EDSL.(
exec ["ls"; "-la"]
)
Pretty-printed:
(exec (byte-array-to-c-string (string "ls"))
(byte-array-to-c-string (string "-la")))
Running it succeeds.
Adding comments with the %%%
operator, we can see them in the compiled output. [Try-Online]
Genspio.EDSL.(
"This is a very simple command" %%%
exec ["ls"; "-la"]
)
Pretty-printed:
(comment "This is a very simple command"
(exec (byte-array-to-c-string (string "ls"))
(byte-array-to-c-string (string "-la"))))
Compiled to POSIX (122 bytes):
export genspio_trap_3_31641=$$
trap 'exit 78' USR2
{ { ':' 'This is a very simple command' ; } ; { 'ls' '-la' ; } ; }
Running it succeeds.
When an expression is wrapped with “comments” they also appear in some error messages (compilation and run-time when using the default compiler) as “the comment stack.” [Try-Online]
Genspio.EDSL.(
"This is a very simple comment" %%% seq [
exec ["ls"; "-la"];
"This comment provides a more precise pseudo-location" %%% seq [
(* Here we use the `fail` EDSL facility: *)
fail "asserting False ☺";
];
]
)
Running it fails; returns 78.
Standard error:
Error: asserting False ☺; Comment-stack:
[`This comment provides a more precise pseudo-location`,
`This is a very simple comment`]
The call
construct is a more general version of exec
that can take any EDSL string. As with exec
the string will be checked for C-String compatibilty, hence the calls to byte-array-to-c-string
in the pretty-printed output. [Try-Online]
Genspio.EDSL.(
call [
str "echo";
Str.concat_list [str "foo"; str "bar"]; (* A concatenation at run-time. *)
]
)
Pretty-printed:
(exec (byte-array-to-c-string (string "echo"))
(byte-array-to-c-string
(byte-array-concat (list (string "foo") (string "bar")))))
Running it succeeds.
Standard output:
foobar
When a string literal cannot be converted to a “C-String” the default compiler tries to catch the error at compile-time. [Try-Online]
Genspio.EDSL.(
"A sequence that will fail" %%% seq [
call [str "ls"; str "foo\x00bar"]; (* A string containing `NUL` *)
]
)
Compilation fails with:
Error: String literal is not a valid/escapable C-string: "foo\000bar".; Code:
(exec (byte-array-to-c-string (string "ls"))
(byte-array-to-c-string …;
Comment-backtrace: ["A sequence that will fail"]
Here we use the constructs:
val get_stdout : unit t -> str t
val (||>) : unit t -> unit t -> unit t
We use let (s : …) = …
to show the types.
We then “pipe” the output to another exec
call with ||>
(which is a 2-argument shortcut for EDSL.pipe
).
Genspio.EDSL.(
let (s : str t) = get_stdout (exec ["cat"; "README.md"]) in
call [str "printf"; str "%s"; s] ||> exec ["wc"; "-l"];
)
Pretty-printed:
(pipe:
(exec (byte-array-to-c-string (string "printf"))
(byte-array-to-c-string (string "%s"))
(byte-array-to-c-string
(as-string
(exec (byte-array-to-c-string (string "cat"))
(byte-array-to-c-string (string "README.md"))))))
|
(exec (byte-array-to-c-string (string "wc"))
(byte-array-to-c-string (string "-l"))))
Running it succeeds.
Standard output:
188
The operator >>
puts any byte-array into the stdin
of any unit t
expression. [Try-Online]
Genspio.EDSL.(
(* Let's see wether `wc -l` is fine with a NUL in the middle of a “line:” *)
str "one\ntwo\nth\000ree\n" >> exec ["wc"; "-l"];
)
Pretty-printed:
((string "one\ntwo\nth\000ree\n") >>
(exec (byte-array-to-c-string (string "wc"))
(byte-array-to-c-string (string "-l"))))
Running it succeeds.
Standard output:
3
We show that str .. >> cat
is not changing anything and we try if_seq
; a version of EDSL.if_then_else
more practical for sequences/imperative code. [Try-Online]
Genspio.EDSL.(
(* With a 🐱: *)
let original = str "one\ntwo\nth\000ree\n" in
let full_cycle = original >> exec ["cat"] |> get_stdout in
if_seq
Str.(full_cycle =$= original)
~t:[
exec ["echo"; "They are the same"];
]
~e:[
exec ["echo"; "They are NOT the same"];
]
)
Pretty-printed:
(if
(string-eq
(as-string
((string "one\ntwo\nth\000ree\n") >>
(exec (byte-array-to-c-string (string "cat")))))
(string "one\ntwo\nth\000ree\n"))
then: (seq
(exec (byte-array-to-c-string (string "echo"))
(byte-array-to-c-string (string "They are the same"))))
else: (seq
(exec (byte-array-to-c-string (string "echo"))
(byte-array-to-c-string (string "They are NOT the same")))))
Running it succeeds.
Standard output:
They are the same
The default and simplest loop construct is loop_while
, the EDSL has also a simple API to manage temporary files and use them as pseudo-global-variables. [Try-Online]
Genspio.EDSL.(
let tmp = tmp_file "genspio-example" in
let body =
seq [
if_then_else Str.(tmp#get =$= str "")
(tmp#set (str "magic-"))
(if_then_else Str.(tmp#get =$= str "magic-")
(tmp#append (str "string"))
nop);
call [str "printf"; str "Currently '%s'\\n"; tmp#get];
] in
seq [
tmp#set (str "");
loop_while Str.(tmp#get <$> str "magic-string") ~body
]
)
Running it succeeds.
Standard output:
Currently 'magic-'
Currently 'magic-string'
The function EDSL.with_redirections
follows POSIX's exec
semantics.
The printf
call will output to the file /tmp/genspio-two
because redirections are set in that order:
3
is set to output to /tmp/genspio-one
,3
is then set to output to /tmp/genspio-two
(overriding the previous redirection),2
is redirected to file-descriptor 3
,1
is redirected to file-descriptor 2
,printf
outputs to 1
.Genspio.EDSL.(
seq [
with_redirections (exec ["printf"; "%s"; "hello"]) [
to_file (int 3) (str "/tmp/genspio-one");
to_file (int 3) (str "/tmp/genspio-two");
to_fd (int 2) (int 3);
to_fd (int 1) (int 2);
];
call [str "printf"; str "One: '%s'\\nTwo: '%s'\\n";
exec ["cat"; "/tmp/genspio-one"] |> get_stdout;
exec ["cat"; "/tmp/genspio-two"] |> get_stdout];
]
)
Pretty-printed:
(seq
(redirect
(exec (byte-array-to-c-string (string "printf"))
(byte-array-to-c-string (string "%s"))
(byte-array-to-c-string (string "hello")))
((int 3) > (byte-array-to-c-string (string "/tmp/genspio-one")))
((int 3) > (byte-array-to-c-string (string "/tmp/genspio-two")))
((int 2) > (int 3)) ((int 1) > (int 2)))
(exec (byte-array-to-c-string (string "printf"))
(byte-array-to-c-string (string "One: '%s'\\nTwo: '%s'\\n"))
(byte-array-to-c-string
(as-string
(exec (byte-array-to-c-string (string "cat"))
(byte-array-to-c-string (string "/tmp/genspio-one")))))
(byte-array-to-c-string
(as-string
(exec (byte-array-to-c-string (string "cat"))
(byte-array-to-c-string (string "/tmp/genspio-two")))))))
Running it succeeds.
Standard output:
One: ''
Two: 'hello'
The module EList
provides lists within the EDSL.
Genspio.EDSL.(
let l = Elist.make [
str "One";
str "Two";
] in
Elist.iter l ~f:begin fun current ->
printf (str "Current: %s\\n") [current ()];
end
)
Pretty-printed:
(list-iter list: (list (string "One") (string "Two"))
f: (fun VARIABLE ->
(exec (byte-array-to-c-string (string "printf"))
(byte-array-to-c-string (string "--"))
(byte-array-to-c-string (string "Current: %s\\n"))
(byte-array-to-c-string (raw-command "VARIABLE")))))
Running it succeeds.
Standard output:
Current: One
Current: Two
The EDSL also provides high-level utilities implemented with the API (like a standard library).
Here is an example with loop_until_true
that fails after 4 attempts (i.e. (4 - 1) × 1 = 3 seconds), unless there is line containing godot
in /etc/passwd
.
We customize the output with an ~on_failed_attempt
function that (on most terminals) erases the previous display (with \r
).
Genspio.EDSL.(
let the_condition who =
exec ["cat"; "/etc/passwd"] ||> exec ["grep"; "^" ^ who]
|> returns ~value:0
in
let the_wait who =
loop_until_true
~attempts:4
~sleep:1
~on_failed_attempt:(fun nth ->
printf (str "\rWaiting for '%s: %s-th attempt.")
[str who; Integer.to_str nth])
(the_condition who)
in
if_seq (the_wait "godot") ~t:[
printf (str "It was worth waiting\\n") [];
]
~e:[
printf (str "It was NOT worth waiting\\n") [];
]
)
Running it succeeds.
Standard output:
Waiting for 'godot: 1-th attempt.
Waiting for 'godot: 2-th attempt.
Waiting for 'godot: 3-th attempt.
Waiting for 'godot: 4-th attempt.
It was NOT worth waiting
Another function from the “extra constructs:” check_sequence
.
We customize its output with the ~verbosity
(by adding a nice prompt) and ~on_success
arguments.
Genspio.EDSL.(
check_sequence
~verbosity:(`Announce "♦ Check-seq-example → ") (* Try also `Output_all or `Silent *)
~on_success:begin fun ~step:(name, expr) ~stdout ~stderr ->
let code = Genspio.Compile.to_one_line_hum expr in
printf (str " ↳ Extra “On Success” for command `%s`\\n\
\ code: `%s`\\n\
\ stdout: `%s`\\n\
\ stderr: `%s`\\n")
[str name; str code; stdout; stderr]
end
[
"This will succeed", exec ["ls"; "/tmp"];
"This too", exec ["ls"; "/"];
"BUT NOT THIS", exec ["ls"; "/somecrazy path"];
"This won't happen", exec ["ls"; "/etc"];
]
)
Running it fails; returns 1.
Standard output:
♦ Check-seq-example → 1. This will succeed
↳ Extra “On Success” for command `1. This will succeed`
code: `(exec (byte-array-to-c-string (string "ls")) (byte-array-to-c-string (string "/tmp")))`
stdout: `/tmp/genspio-check-sequence-g-16-9344--cmd-stdout-1__This_will_succeed`
stderr: `/tmp/genspio-check-sequence-g-16-9344--cmd-stderr-1__This_will_succeed`
♦ Check-seq-example → 2. This too
↳ Extra “On Success” for command `2. This too`
code: `(exec (byte-array-to-c-string (string "ls")) (byte-array-to-c-string (string "/")))`
stdout: `/tmp/genspio-check-sequence-g-16-9344--cmd-stdout-2__This_too`
stderr: `/tmp/genspio-check-sequence-g-16-9344--cmd-stderr-2__This_too`
♦ Check-seq-example → 3. BUT NOT THIS
Step '3. BUT NOT THIS' FAILED:
``````````stdout
``````````
``````````stderr
ls: cannot access '/somecrazy path': No such file or directory
``````````
stdin
Line by LineLet's try now the on_stdin_lines
function, to read a stream of lines.
Note that for the word “lines” to really make sense, the input should be proper “text,” in the example below the '\000'
character is just silently forgotten, not counted.
Genspio.EDSL.(
printf (str "123\\n12345\\n1234\\00056\\n12\\n") []
||> on_stdin_lines begin fun line ->
printf (str "→ %s bytes\\n")
[line
>> exec ["wc"; "-c"] ||> exec ["tr"; "-d"; "\\n"]
|> get_stdout]
end
)
Running it succeeds.
Standard output:
→ 3 bytes
→ 5 bytes
→ 6 bytes
→ 2 bytes