Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
branches:
- master
push:
branches:
- master
tags:
- '**'
schedule:
- cron: '5 14 * * *'

Expand Down Expand Up @@ -75,6 +79,7 @@ jobs:
- ubuntu-latest
ocaml-compiler:
- 4.14.x
- 5.5.x

runs-on: ${{ matrix.os }}

Expand Down
24 changes: 17 additions & 7 deletions GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ The dash AST itself is defined in `src/nodes.h` and `src/parser.h`

- `union node` in `src/nodes.h` on line 146 for commands/statements
node.type type tag

node.nbinary (AND, OR, SEMI)

- special characters and codes in `parser.h` on lines 40-64

CTL* for control codes in words
breaks multibyte characters/UTF-8 :(

VS* for variable format metadata

The parser in `src/parser.c` is not easy to read, but is a good place
Expand Down Expand Up @@ -41,13 +41,13 @@ few results:

- `Done` when EOF (dash returns the special node `neof`, not `NULL`!) has been
reached for the current input.

- `Error` when parsing failed (dash returns the special node `nerr`,
not `NULL`!).

- `Null` when there was no command, e.g., a blank line (dash returns
`NULL` here).

- `Parsed n` for some `node`, `n`. Note that `n` is a dash AST,
i.e., a ctypes structure.

Expand All @@ -69,4 +69,14 @@ an assignment).

Call `Dash.initialize`, then `Dash.parse` with the string you
have. Call `Ast.of_node` on the resulting dash AST to get a nice OCaml
structure.
structure. Look at `ocaml/rt.sh` (and the binaries it calls) and
`python/rt.py` for examples.

# Versioning

To cut a new release, you must update the version numbers in
`setup.py`, `pyproject.toml`, and `dune-workspace`. A CI lint job will
check to ensure that they are the same.

PyPI releases are automatic via GH CI. OCaml releases are via
`dune-release`.
2 changes: 1 addition & 1 deletion dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
(package
(name libdash)
(synopsis "Bindings to the dash shell's parser")
(version 0.4.0)
(version 0.4.1)
(depends
("ctypes" (>= "0.21.1"))
("ctypes-foreign" (>= "0.21.1"))
Expand Down
2 changes: 2 additions & 0 deletions libdash.opam
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# This file is generated by dune, edit dune-project instead
opam-version: "2.0"
version: "0.4.0"
synopsis: "Bindings to the dash shell's parser"
maintainer: ["michael@greenberg.science"]
authors: ["Michael Greenberg"]
Expand All @@ -11,6 +12,7 @@ depends: [
"ctypes" {>= "0.21.1"}
"ctypes-foreign" {>= "0.21.1"}
"atdgen" {>= "2.15.0"}
"atdgen-runtime" {>= "2.15.0"}
"conf-autoconf" {>= "0.1"}
"conf-aclocal" {>= "2"}
"conf-automake" {>= "1"}
Expand Down
48 changes: 22 additions & 26 deletions ocaml/ast.ml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
type linno = int

exception ParseException of string

type t =
| Command of (linno * assign list * args * redirection list) (* assign, args, redir *)
| Pipe of (bool * t list) (* background?, commands *)
Expand Down Expand Up @@ -70,14 +70,14 @@ let string_of_var_type = function
| TrimR -> "%"
| TrimRMax -> "%%"
| TrimL -> "#"
| TrimLMax -> "##"
| Length -> "#"
| TrimLMax -> "##"
| Length -> "#"

(* Some possible further simplifications:

* Drop bool from pipe
dash *always* forks for a pipe, but sometimes it waits
* Drop redirection from Command, etc.
* Drop redirection from Command, etc.
Just use Redir... though this may affect subshell behavior.
NCMD: expredir, pushredir, redirectsafe REDIR_PUSH|REDIR_SAVEFD2
NREDIR: expredir, pushredir, redirectsafe REDIR_PUSH
Expand Down Expand Up @@ -203,9 +203,9 @@ and redirs (n : node union ptr) =
| 19 -> mk_file FromTo
(* NAPPEND *)
| 20 -> mk_file Append
(* NTOFD *)
(* NTOFD *)
| 21 -> mk_dup ToFD
(* NFROMFD *)
(* NFROMFD *)
| 22 -> mk_dup FromFD
(* NHERE quoted heredoc---no expansion)*)
| 23 -> mk_here Here
Expand All @@ -225,7 +225,7 @@ and to_arg (n : narg structure) : arg =
assert (s = []);
assert (nullptr bqlist);
assert (stack = []);
a
a

and parse_arg ?tilde_ok:(tilde_ok=false) ~assign:(assign:bool) (s : char list) (bqlist : nodelist structure ptr) stack =
match s,stack with
Expand Down Expand Up @@ -316,10 +316,10 @@ and parse_tilde acc s =
if acc = [] then (None, s) else (Some (implode acc), s)
(* ordinary char *)
(* TODO 2019-01-03 only characters from the portable character set *)
| c::s' -> parse_tilde (acc @ [c]) s'
| c::s' -> parse_tilde (acc @ [c]) s'

and arg_char assign c s bqlist stack =
let tilde_ok =
let tilde_ok =
match c with
| C _ -> assign && (match last s with
| Some ':' -> true
Expand Down Expand Up @@ -351,17 +351,17 @@ and to_assign (n : narg structure) : (string * arg) =
assert (nullptr bqlist);
assert (stack = []);
(v,a)
and to_assigns n =

and to_assigns n =
if nullptr n
then []
then []
else (assert (n @-> node_type = 15);
let n = n @-> node_narg in
to_assign n::to_assigns (getf n narg_next))

and to_args (n : node union ptr) : args =
if nullptr n
then []
then []
else (assert (n @-> node_type = 15);
let n = n @-> node_narg in
to_arg n::to_args (getf n narg_next))
Expand All @@ -377,7 +377,7 @@ let background s = "{ " ^ s ^ " & }"

let lines = Str.split (Str.regexp "[\n]+")

let fresh_marker heredoc =
let fresh_marker heredoc =
let eofs_in_line line =
if String.length line > 2 && String.get line 0 = 'E' && String.get line 1 == 'O'
then
Expand All @@ -391,7 +391,7 @@ let fresh_marker heredoc =
| line::lines -> find_eofs lines (max max_fs (eofs_in_line line))
in
"EOF" ^ String.make (find_eofs heredoc 0) 'F'

let rec to_string = function
| Command (_,assigns,cmds,redirs) ->
separated string_of_assign assigns ^
Expand All @@ -403,7 +403,7 @@ let rec to_string = function
| Redir (_,a,redirs) ->
to_string a ^ string_of_redirs redirs
| Background (_,a,redirs) ->
(* we translate
(* we translate
cmds... &
to
{ cmds & }
Expand Down Expand Up @@ -434,15 +434,15 @@ let rec to_string = function
"case " ^ string_of_arg a ^ " in " ^
separated string_of_case cs ^ " esac"
| Defun (_,name,body) -> name ^ "() {\n" ^ to_string body ^ "\n}"

and string_of_if c t e =
"if " ^ to_string c ^
"; then " ^ to_string t ^
(match e with
| Command (-1,[],[],[]) -> "; fi" (* one-armed if *)
| If (c,t,e) -> "; el" ^ string_of_if c t e
| _ -> "; else " ^ to_string e ^ "; fi")

and string_of_arg_char ?quote_mode:(quote_mode=QUnquoted) = function
| E c ->
(* removed ! from chars_to_escape to have the right behavior in non-interactive shells *)
Expand Down Expand Up @@ -476,12 +476,8 @@ and string_of_arg ?quote_mode:(quote_mode=QUnquoted) = function
then "\\$" ^ string_of_arg ~quote_mode a
else char ^ string_of_arg ~quote_mode a

and next_is_escaped = function
| E _ :: _ -> true
| _ -> false

and string_of_assign (v,a) = v ^ "=" ^ string_of_arg a

and string_of_case c =
let pats = List.map string_of_arg c.cpattern in
intercalate "|" pats ^ ") " ^ to_string c.cbody ^ ";;"
Expand All @@ -499,7 +495,7 @@ and string_of_redir = function
let marker = fresh_marker (lines heredoc) in
show_unless 0 fd ^ "<<" ^
(if t = XHere then marker else "'" ^ marker ^ "'") ^ "\n" ^ heredoc ^ marker ^ "\n"

and string_of_redirs rs =
let ss = List.map string_of_redir rs in
(if List.length ss > 0 then " " else "") ^ intercalate " " ss
Loading
Loading