Module Excmd.AST

Describing structures

Some of our data-structures provide conversion functions to produce alternative formats of themselves. These are automatically generated at compile-time by PPX preprocessors in OCaml.

Unfortunately, we use different data-structures per compile-target: in native OCaml, we use {{:https://github.com/ocaml-ppx/ppx_deriving_yojson} ppx_deriving_yojson} to produce a value of type Yojson.Basic.json; but when being compiled to JavaScript, we use BuckleScript's jsConverter tooling to generate the built-in Js.t type. These generators also produce functions of different names: to_yojson is available on the native side, and tToJs on the BuckleScript side.

Due to the fact that the relevant types differ between platforms, fully generic code involving alternative-format representations like the above isn't clean and easy. Both of the above flavours of conversion-function will raise a runtime exception if called on a platform that doesn't support them; if you need to, you can catch said exception and swap implementations based on that.

exception WrongPlatform of [ `JavaScript | `Native ] * string
val unavailable_on : [ `JavaScript | `Native ] -> string -> 'a
val tOfJs : 'a -> 'b
val tToJs : 'a -> 'b
val expressionOfJs : 'a -> 'b
val expressionToJs : 'a -> 'b
val to_yojson : 'a -> 'b
val of_yojson : 'a -> 'b
val expression_to_yojson : 'a -> 'b
val expression_of_yojson : 'a -> 'b
type 'a unresolved =
| Unresolved
| Resolved of 'a
| Absent

This triple extends the usual optional type with an intermediate Unresolved state. It's used in flags to indicate the presence of a subsequent Positional argument that may-or-may-not be the payload of said flag.

val unresolved_to_yojson : a. ('a -> Yojson.Safe.json) -> 'a unresolved -> Yojson.Safe.json
type 'a or_subexpr =
| Sub of expression
| Literal of 'a

Wraps any value that may be replaced in the AST by a subexpression; i.e., in the expression echo hello world the literal hello will be wrapped in Literal. This is necessary because it could just as easily be echo (echo hello) world.

type word = string or_subexpr list

The most granular element of an excmd, like a shell command, is the "word." These are a series of parameters to a command, separated by whitespace:

echo each of these is a separate "word"

Words can also be produced by subexpressions,

echo each of these is a (echo separate) "word"

... or created via quotation:

echo "this is only a single word"

A single shell "word" can actually be composed of multiple quotations or subexpressions, as long as they are not separated by whitespace:

echo "this""is""only""a""single"(echo word)(echo also)

Such a composition is represented in this type, by a series of or_subexprs in a list.

type flag = {
name : string;
mutable payload : word unresolved;
}

A single --flag, possible with -a=payload. The flag's name is always literal; but the payload may be:

cmd --fl="a quotation,"
cmd --fl=(echo a subexpression)
cmd --fl=or"a"(echo smooshing)"thereof"

(Although this type is technically mutable, it's probably a better idea to leave the mutation up to the Expression-interface.)

type arg =
| Positional of word
| Flag of flag
type expression = {
count : int;
cmd : word;
mutable rev_args : arg list;
}
val or_subexpr_to_yojson : a. ('a -> Yojson.Safe.json) -> 'a or_subexpr -> Yojson.Safe.json
val word_to_yojson : word -> Yojson.Safe.json
val flag_to_yojson : flag -> Yojson.Safe.json
val arg_to_yojson : arg -> Yojson.Safe.json
val expression_to_yojson : expression -> Yojson.Safe.json
type t = {
expressions : expression array;
}
val to_yojson : t -> Yojson.Safe.json
val make_expression : ?⁠count:string -> cmd:word -> rev_args:arg list -> expression
val pipe_last : from:expression -> into:expression -> expression
val copy_word : word -> word
val copy_flag : flag -> flag
val copy_arg : arg -> arg
val copy_expression : expression -> expression
val copy : t -> t
val pp_bs : 'a -> 'b
val pp_native : t -> unit
val pp : t -> unit
val pp_expression_bs : 'a -> 'b
val pp_expression_native : expression -> unit
val pp_expression : expression -> unit
val is_literal : 'a or_subexpr -> bool
val get_literal_exn : 'a or_subexpr -> 'a
val get_sub_exn : 'a or_subexpr -> expression