libdash 0.5.2__tar.gz → 0.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {libdash-0.5.2 → libdash-0.5.3}/MANIFEST.in +1 -1
- {libdash-0.5.2/libdash.egg-info → libdash-0.5.3}/PKG-INFO +27 -3
- {libdash-0.5.2 → libdash-0.5.3}/README.md +26 -2
- libdash-0.5.3/libdash/__init__.py +3 -0
- {libdash-0.5.2 → libdash-0.5.3}/libdash/_dash.py +140 -8
- {libdash-0.5.2 → libdash-0.5.3}/libdash/ast.py +50 -23
- {libdash-0.5.2 → libdash-0.5.3}/libdash/parser.py +5 -0
- {libdash-0.5.2 → libdash-0.5.3/libdash.egg-info}/PKG-INFO +27 -3
- {libdash-0.5.2 → libdash-0.5.3}/libdash.egg-info/SOURCES.txt +1 -0
- {libdash-0.5.2 → libdash-0.5.3}/pyproject.toml +1 -1
- libdash-0.5.3/setup.py +146 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/Makefile.am +20 -2
- {libdash-0.5.2 → libdash-0.5.3}/src/alias.c +44 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/alias.h +4 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/input.c +26 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/input.h +1 -0
- libdash-0.5.3/src/mklayout +94 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/parser.c +17 -5
- libdash-0.5.2/libdash/__init__.py +0 -2
- libdash-0.5.2/setup.py +0 -60
- {libdash-0.5.2 → libdash-0.5.3}/COPYING +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/Makefile.am +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/autogen.sh +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/configure.ac +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/libdash/printer.py +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/libdash.egg-info/dependency_links.txt +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/libdash.egg-info/top_level.txt +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/setup.cfg +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/TOUR +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/arith_yacc.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/arith_yacc.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/arith_yylex.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/bltin/bltin.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/bltin/echo.1 +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/bltin/printf.1 +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/bltin/printf.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/bltin/test.1 +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/bltin/test.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/bltin/times.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/builtins.def.in +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/cd.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/cd.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/dash.1 +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/error.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/error.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/eval.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/eval.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/exec.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/exec.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/expand.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/expand.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/funcs/cmv +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/funcs/dirs +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/funcs/kill +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/funcs/login +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/funcs/newgrp +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/funcs/popd +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/funcs/pushd +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/funcs/suspend +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/histedit.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/init.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/jobs.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/jobs.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/machdep.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/mail.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/mail.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/main.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/main.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/memalloc.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/memalloc.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/miscbltin.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/miscbltin.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/mkbuiltins +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/mkinit.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/mknodes.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/mksignames.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/mksyntax.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/mktokens +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/myhistedit.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/mystring.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/mystring.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/nodes.c.pat +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/nodetypes +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/options.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/options.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/output.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/output.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/parser.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/redir.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/redir.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/shell.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/show.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/show.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/system.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/system.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/trap.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/trap.h +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/var.c +0 -0
- {libdash-0.5.2 → libdash-0.5.3}/src/var.h +0 -0
|
@@ -5,7 +5,7 @@ exclude src/dash
|
|
|
5
5
|
exclude src/init.c src/mkinit
|
|
6
6
|
exclude src/mknodes src/mksignames src/mksyntax
|
|
7
7
|
exclude src/nodes.c src/nodes.h
|
|
8
|
-
exclude src/signames.c
|
|
8
|
+
exclude src/signames.c src/libdash_layout.c
|
|
9
9
|
exclude src/syntax.c src/syntax.h
|
|
10
10
|
exclude src/token.h
|
|
11
11
|
prune src/.deps
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: libdash
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Bindings for the dash shell as a library
|
|
5
5
|
Author: PaSh contributors
|
|
6
6
|
Author-email: Michael Greenberg <michael@greenberg.science>
|
|
@@ -124,9 +124,33 @@ For Python, see [`python/rt.py`](https://github.com/mgree/libdash/blob/master/py
|
|
|
124
124
|
|
|
125
125
|
For OCaml, see [`ocaml/shell_to_json.ml`](https://github.com/mgree/libdash/blob/master/ocaml/shell_to_json.ml), a tool that parses shell syntax and produces JSON (using the [atdgen](https://opam.ocaml.org/packages/atdgen/) bindings).
|
|
126
126
|
|
|
127
|
-
The ideal low-level interface to use is `parsecmd_safe` in `parser.c`; you'll need to ensure that dash's initialization routines have been called and that the stack marks are managed correctly. Parsing the POSIX shell is a complicated affair: beyond the usual locale issues, aliases affect the lexer, so one must use `
|
|
127
|
+
The ideal low-level interface to use is `parsecmd_safe` in `parser.c`; you'll need to ensure that dash's initialization routines have been called and that the stack marks are managed correctly. Parsing the POSIX shell is a complicated affair: beyond the usual locale issues, aliases affect the lexer, so one must use `setalias_safe` and `unalias` to manage any aliases that ought to exist. Outside of `parsecmd_safe`, call only entry points that cannot raise a dash error (e.g., `setinputfile_safe` rather than `setinputfile`).
|
|
128
128
|
|
|
129
|
-
#
|
|
129
|
+
# Locales and multibyte characters
|
|
130
|
+
|
|
131
|
+
dash's lexer decodes multibyte characters using the process's current
|
|
132
|
+
`LC_CTYPE`. In a multibyte locale (e.g., `C.UTF-8`), each multibyte character
|
|
133
|
+
is wrapped in `CTLMBCHAR` markers, and the bindings decode it to Unicode.
|
|
134
|
+
In the `C` locale, it stays as raw bytes, and any byte that collides with a
|
|
135
|
+
dash control code is escaped with `CTLESC`. The same script can therefore
|
|
136
|
+
parse to different ASTs depending on the locale in effect when you parse.
|
|
137
|
+
|
|
138
|
+
libdash never sets the locale for you:
|
|
139
|
+
|
|
140
|
+
- The OCaml binding's `Dash.initialize` calls `initialize_dash_setlocale`,
|
|
141
|
+
i.e., `setlocale(LC_ALL, "")`, so it uses the environment's locale.
|
|
142
|
+
- The Python binding uses whatever locale the interpreter set at startup.
|
|
143
|
+
Python calls `setlocale(LC_CTYPE, "")` and coerces a `C` locale to
|
|
144
|
+
`C.UTF-8` (PEP 538), so Python usually parses in UTF-8 even when the
|
|
145
|
+
environment says `C`.
|
|
146
|
+
- Programs calling the C library directly (e.g., from C or another
|
|
147
|
+
language's FFI) stay in the `C` locale unless they call `setlocale` or
|
|
148
|
+
`initialize_dash_setlocale` themselves.
|
|
149
|
+
|
|
150
|
+
To get the same ASTs from different bindings, set the same `LC_CTYPE` in
|
|
151
|
+
each process before parsing.
|
|
152
|
+
|
|
153
|
+
# How to work with parsed nodes
|
|
130
154
|
|
|
131
155
|
The general AST is described in `nodes.h`. There are some tricky invariants around the precise formatting of control codes; the OCaml code shows some examples of working with the `args` fields in `ocaml/ast.ml`, which converts the C AST to an OCaml AST.
|
|
132
156
|
|
|
@@ -52,9 +52,33 @@ For Python, see [`python/rt.py`](https://github.com/mgree/libdash/blob/master/py
|
|
|
52
52
|
|
|
53
53
|
For OCaml, see [`ocaml/shell_to_json.ml`](https://github.com/mgree/libdash/blob/master/ocaml/shell_to_json.ml), a tool that parses shell syntax and produces JSON (using the [atdgen](https://opam.ocaml.org/packages/atdgen/) bindings).
|
|
54
54
|
|
|
55
|
-
The ideal low-level interface to use is `parsecmd_safe` in `parser.c`; you'll need to ensure that dash's initialization routines have been called and that the stack marks are managed correctly. Parsing the POSIX shell is a complicated affair: beyond the usual locale issues, aliases affect the lexer, so one must use `
|
|
55
|
+
The ideal low-level interface to use is `parsecmd_safe` in `parser.c`; you'll need to ensure that dash's initialization routines have been called and that the stack marks are managed correctly. Parsing the POSIX shell is a complicated affair: beyond the usual locale issues, aliases affect the lexer, so one must use `setalias_safe` and `unalias` to manage any aliases that ought to exist. Outside of `parsecmd_safe`, call only entry points that cannot raise a dash error (e.g., `setinputfile_safe` rather than `setinputfile`).
|
|
56
56
|
|
|
57
|
-
#
|
|
57
|
+
# Locales and multibyte characters
|
|
58
|
+
|
|
59
|
+
dash's lexer decodes multibyte characters using the process's current
|
|
60
|
+
`LC_CTYPE`. In a multibyte locale (e.g., `C.UTF-8`), each multibyte character
|
|
61
|
+
is wrapped in `CTLMBCHAR` markers, and the bindings decode it to Unicode.
|
|
62
|
+
In the `C` locale, it stays as raw bytes, and any byte that collides with a
|
|
63
|
+
dash control code is escaped with `CTLESC`. The same script can therefore
|
|
64
|
+
parse to different ASTs depending on the locale in effect when you parse.
|
|
65
|
+
|
|
66
|
+
libdash never sets the locale for you:
|
|
67
|
+
|
|
68
|
+
- The OCaml binding's `Dash.initialize` calls `initialize_dash_setlocale`,
|
|
69
|
+
i.e., `setlocale(LC_ALL, "")`, so it uses the environment's locale.
|
|
70
|
+
- The Python binding uses whatever locale the interpreter set at startup.
|
|
71
|
+
Python calls `setlocale(LC_CTYPE, "")` and coerces a `C` locale to
|
|
72
|
+
`C.UTF-8` (PEP 538), so Python usually parses in UTF-8 even when the
|
|
73
|
+
environment says `C`.
|
|
74
|
+
- Programs calling the C library directly (e.g., from C or another
|
|
75
|
+
language's FFI) stay in the `C` locale unless they call `setlocale` or
|
|
76
|
+
`initialize_dash_setlocale` themselves.
|
|
77
|
+
|
|
78
|
+
To get the same ASTs from different bindings, set the same `LC_CTYPE` in
|
|
79
|
+
each process before parsing.
|
|
80
|
+
|
|
81
|
+
# How to work with parsed nodes
|
|
58
82
|
|
|
59
83
|
The general AST is described in `nodes.h`. There are some tricky invariants around the precise formatting of control codes; the OCaml code shows some examples of working with the `args` fields in `ocaml/ast.ml`, which converts the C AST to an OCaml AST.
|
|
60
84
|
|
|
@@ -36,11 +36,12 @@ NNOT = 25;
|
|
|
36
36
|
# size_t stacknleft;
|
|
37
37
|
# };
|
|
38
38
|
#
|
|
39
|
-
#
|
|
39
|
+
# The bindings use it only as an opaque buffer of this size; the fields
|
|
40
|
+
# carry their C names so that the layout check covers them too.
|
|
40
41
|
class stackmark (Structure):
|
|
41
|
-
_fields_ = [("stackp",
|
|
42
|
-
("
|
|
43
|
-
("
|
|
42
|
+
_fields_ = [("stackp", c_void_p),
|
|
43
|
+
("stacknxt", c_void_p),
|
|
44
|
+
("stacknleft", c_size_t)];
|
|
44
45
|
|
|
45
46
|
def init_stack (libdash):
|
|
46
47
|
stack = create_string_buffer (sizeof (stackmark));
|
|
@@ -75,6 +76,7 @@ def initialize_dash_errno (libdash):
|
|
|
75
76
|
|
|
76
77
|
|
|
77
78
|
def initialize (libdash):
|
|
79
|
+
require_layout (libdash);
|
|
78
80
|
initialize_dash_errno (libdash);
|
|
79
81
|
dash_init (libdash);
|
|
80
82
|
|
|
@@ -88,9 +90,24 @@ def setinputtostdin (libdash):
|
|
|
88
90
|
|
|
89
91
|
# TODO: allow push parameter
|
|
90
92
|
def setinputfile (libdash, filename):
|
|
91
|
-
libdash.
|
|
92
|
-
libdash.
|
|
93
|
-
libdash.
|
|
93
|
+
libdash.setinputfile_safe.argtypes = [c_char_p, c_int];
|
|
94
|
+
libdash.setinputfile_safe.restype = c_int;
|
|
95
|
+
if (libdash.setinputfile_safe (filename.encode ('utf-8'), 0) < 0):
|
|
96
|
+
raise OSError ("cannot open %s" % filename);
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def setalias (libdash, name, value):
|
|
100
|
+
libdash.setalias_safe.argtypes = [c_char_p, c_char_p];
|
|
101
|
+
libdash.setalias_safe.restype = c_int;
|
|
102
|
+
if (libdash.setalias_safe (name.encode ('utf-8'), value.encode ('utf-8')) != 0):
|
|
103
|
+
raise ValueError ("invalid alias name: %r" % name);
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def unalias (libdash, name):
|
|
107
|
+
"""Returns True if `name` was an alias."""
|
|
108
|
+
libdash.unalias.argtypes = [c_char_p];
|
|
109
|
+
libdash.unalias.restype = c_int;
|
|
110
|
+
return (libdash.unalias (name.encode ('utf-8')) == 0);
|
|
94
111
|
|
|
95
112
|
|
|
96
113
|
def parsecmd_safe (libdash, interactive):
|
|
@@ -261,6 +278,8 @@ class parsefile (Structure):
|
|
|
261
278
|
# /* Number of outstanding calls to pungetc. */
|
|
262
279
|
# int unget;
|
|
263
280
|
# };
|
|
281
|
+
#
|
|
282
|
+
# No lleft: configure.ac defines SMALL unless dash is built --with-libedit.
|
|
264
283
|
parsefile._fields_ = [("prev", POINTER (parsefile)),
|
|
265
284
|
("linno", c_int),
|
|
266
285
|
("fd", c_int),
|
|
@@ -271,10 +290,123 @@ parsefile._fields_ = [("prev", POINTER (parsefile)),
|
|
|
271
290
|
("strpush", POINTER (strpush)),
|
|
272
291
|
("basestrpush", strpush),
|
|
273
292
|
("spfree", POINTER (strpush)),
|
|
274
|
-
("lleft", c_int),
|
|
275
293
|
("unget", c_int)];
|
|
276
294
|
|
|
277
295
|
|
|
296
|
+
# Every struct above, by its C name. libdash.so's libdash_struct_layout
|
|
297
|
+
# (generated by src/mklayout from the same list in src/Makefile.am) holds
|
|
298
|
+
# their real sizes and field offsets; require_layout compares the two.
|
|
299
|
+
LAYOUT = [("struct stackmark", stackmark),
|
|
300
|
+
("struct nodelist", nodelist),
|
|
301
|
+
("union node", union_node),
|
|
302
|
+
("struct ncmd", ncmd),
|
|
303
|
+
("struct npipe", npipe),
|
|
304
|
+
("struct nredir", nredir),
|
|
305
|
+
("struct nbinary", nbinary),
|
|
306
|
+
("struct nif", nif),
|
|
307
|
+
("struct nfor", nfor),
|
|
308
|
+
("struct ncase", ncase),
|
|
309
|
+
("struct nclist", nclist),
|
|
310
|
+
("struct ndefun", ndefun),
|
|
311
|
+
("struct narg", narg),
|
|
312
|
+
("struct nfile", nfile),
|
|
313
|
+
("struct ndup", ndup),
|
|
314
|
+
("struct nhere", nhere),
|
|
315
|
+
("struct nnot", nnot),
|
|
316
|
+
("struct strpush", strpush),
|
|
317
|
+
("struct parsefile", parsefile)];
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class LibdashLayoutError (Exception):
|
|
321
|
+
"""The ctypes structs in libdash/_dash.py disagree with the ones
|
|
322
|
+
libdash.so was built with, so reading the AST would read the wrong
|
|
323
|
+
fields."""
|
|
324
|
+
pass;
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class _layout_row (Structure):
|
|
328
|
+
_fields_ = [("type", c_char_p),
|
|
329
|
+
("field", c_char_p), # NULL: value is the sizeof type
|
|
330
|
+
("value", c_size_t)];
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def library_layout (libdash):
|
|
334
|
+
"""libdash.so's layout table, as {C type: (sizeof, {field: offset})},
|
|
335
|
+
with fields in declaration order."""
|
|
336
|
+
first = _layout_row.in_dll (libdash, "libdash_struct_layout");
|
|
337
|
+
rows = cast (addressof (first), POINTER (_layout_row));
|
|
338
|
+
|
|
339
|
+
out = {};
|
|
340
|
+
i = 0;
|
|
341
|
+
while (rows [i].type is not None):
|
|
342
|
+
row = rows [i];
|
|
343
|
+
ctype = row.type.decode ();
|
|
344
|
+
if (row.field is None):
|
|
345
|
+
out [ctype] = (row.value, {});
|
|
346
|
+
else:
|
|
347
|
+
out [ctype][1][row.field.decode ()] = row.value;
|
|
348
|
+
i += 1;
|
|
349
|
+
|
|
350
|
+
return out;
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def layout_problems (libdash, layout):
|
|
354
|
+
"""How `layout` (rows like LAYOUT's) disagrees with libdash.so's table,
|
|
355
|
+
as a list of messages; empty if they agree."""
|
|
356
|
+
c = library_layout (libdash);
|
|
357
|
+
mine = dict (layout);
|
|
358
|
+
problems = [];
|
|
359
|
+
|
|
360
|
+
for ctype in c:
|
|
361
|
+
if ctype not in mine:
|
|
362
|
+
problems.append ("%s: in libdash.so, but has no ctypes mirror" % ctype);
|
|
363
|
+
|
|
364
|
+
for (ctype, cls) in layout:
|
|
365
|
+
if ctype not in c:
|
|
366
|
+
problems.append ("%s: has a ctypes mirror, but is not in libdash.so" % ctype);
|
|
367
|
+
continue;
|
|
368
|
+
|
|
369
|
+
(size, offsets) = c [ctype];
|
|
370
|
+
fields = [name for (name, _t) in cls._fields_];
|
|
371
|
+
|
|
372
|
+
if (sizeof (cls) != size):
|
|
373
|
+
problems.append ("%s: sizeof is %d in C, %d in ctypes"
|
|
374
|
+
% (ctype, size, sizeof (cls)));
|
|
375
|
+
|
|
376
|
+
for (name, offset) in offsets.items ():
|
|
377
|
+
if name not in fields:
|
|
378
|
+
problems.append ("%s.%s: in C (offset %d), missing from ctypes"
|
|
379
|
+
% (ctype, name, offset));
|
|
380
|
+
elif (getattr (cls, name).offset != offset):
|
|
381
|
+
problems.append ("%s.%s: offset is %d in C, %d in ctypes"
|
|
382
|
+
% (ctype, name, offset, getattr (cls, name).offset));
|
|
383
|
+
|
|
384
|
+
for name in fields:
|
|
385
|
+
if name not in offsets:
|
|
386
|
+
problems.append ("%s.%s: in ctypes, not in C" % (ctype, name));
|
|
387
|
+
|
|
388
|
+
return problems;
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
_layout_checked = False;
|
|
392
|
+
|
|
393
|
+
def require_layout (libdash, force = False):
|
|
394
|
+
"""Raises LibdashLayoutError unless LAYOUT matches libdash.so. Checks
|
|
395
|
+
once per process, unless `force`."""
|
|
396
|
+
global _layout_checked;
|
|
397
|
+
|
|
398
|
+
if (_layout_checked and not force):
|
|
399
|
+
return;
|
|
400
|
+
|
|
401
|
+
problems = layout_problems (libdash, LAYOUT);
|
|
402
|
+
if problems:
|
|
403
|
+
raise LibdashLayoutError (
|
|
404
|
+
"libdash/_dash.py's ctypes structs disagree with libdash.so:\n "
|
|
405
|
+
+ "\n ".join (problems));
|
|
406
|
+
|
|
407
|
+
_layout_checked = True;
|
|
408
|
+
|
|
409
|
+
|
|
278
410
|
# dash.ast
|
|
279
411
|
# let rec nodelist (n : nodelist structure ptr) : (node union ptr) list =
|
|
280
412
|
# if nullptr n
|
|
@@ -230,12 +230,14 @@ def of_binary (n):
|
|
|
230
230
|
return [of_node (n.nbinary.ch1), of_node (n.nbinary.ch2)]
|
|
231
231
|
|
|
232
232
|
|
|
233
|
-
def to_arg (narg):
|
|
233
|
+
def to_arg (narg, assign = False):
|
|
234
234
|
s = explode_rev (narg.text)
|
|
235
235
|
bqlist = narg.backquote
|
|
236
236
|
stack = []
|
|
237
237
|
|
|
238
|
-
|
|
238
|
+
# An assignment word starts with its name; tilde prefixes may only come
|
|
239
|
+
# after the first '=' (and after unquoted ':'s).
|
|
240
|
+
a = parse_arg (s, bqlist, stack, assign = assign, tilde_ok = not assign)
|
|
239
241
|
|
|
240
242
|
assert (len (s) == 0)
|
|
241
243
|
# assert (nullptr bqlist)
|
|
@@ -248,10 +250,26 @@ def to_arg (narg):
|
|
|
248
250
|
return (a)
|
|
249
251
|
|
|
250
252
|
|
|
251
|
-
def parse_arg (s, bqlist, stack):
|
|
253
|
+
def parse_arg (s, bqlist, stack, assign = False, tilde_ok = False):
|
|
254
|
+
"""`tilde_ok`: a tilde prefix may start here (the start of a word). In an
|
|
255
|
+
assignment (`assign`), one may also start right after the first '=' and
|
|
256
|
+
after any unquoted ':'."""
|
|
252
257
|
acc = []
|
|
258
|
+
seen_len = 0
|
|
259
|
+
seen_equals = False
|
|
253
260
|
|
|
254
261
|
while (True):
|
|
262
|
+
# after each new element, decide whether a tilde prefix may follow it
|
|
263
|
+
if (len (acc) != seen_len):
|
|
264
|
+
seen_len = len (acc)
|
|
265
|
+
last = acc [-1]
|
|
266
|
+
|
|
267
|
+
if (assign and not seen_equals and last == ["C", ORD_EQUALS]):
|
|
268
|
+
seen_equals = True
|
|
269
|
+
tilde_ok = True
|
|
270
|
+
else:
|
|
271
|
+
tilde_ok = assign and (last == ["C", ORD_COLON])
|
|
272
|
+
|
|
255
273
|
s_len = len (s)
|
|
256
274
|
# stack_len = len (stack)
|
|
257
275
|
|
|
@@ -324,7 +342,7 @@ def parse_arg (s, bqlist, stack):
|
|
|
324
342
|
|
|
325
343
|
stack.append (STACK_CTLVAR)
|
|
326
344
|
|
|
327
|
-
a = parse_arg (s, bqlist, stack)
|
|
345
|
+
a = parse_arg (s, bqlist, stack, assign = assign, tilde_ok = True)
|
|
328
346
|
|
|
329
347
|
v = ["V", [var_type (vstype), (t & 0x10 == 0x10), var_name, a]]
|
|
330
348
|
elif (len (s) >= 1):
|
|
@@ -424,7 +442,7 @@ def parse_arg (s, bqlist, stack):
|
|
|
424
442
|
|
|
425
443
|
stack.append (STACK_CTLARI)
|
|
426
444
|
|
|
427
|
-
a = parse_arg (s, bqlist, stack)
|
|
445
|
+
a = parse_arg (s, bqlist, stack, assign = assign)
|
|
428
446
|
|
|
429
447
|
# TODO: assert (stack = stack')
|
|
430
448
|
|
|
@@ -465,7 +483,7 @@ def parse_arg (s, bqlist, stack):
|
|
|
465
483
|
s.pop ()
|
|
466
484
|
stack.append (STACK_CTLQUO)
|
|
467
485
|
|
|
468
|
-
a = parse_arg (s, bqlist, stack)
|
|
486
|
+
a = parse_arg (s, bqlist, stack, assign = assign)
|
|
469
487
|
|
|
470
488
|
acc.append (["Q", a])
|
|
471
489
|
|
|
@@ -474,12 +492,17 @@ def parse_arg (s, bqlist, stack):
|
|
|
474
492
|
elif (s [-1] == ORD_TILDE):
|
|
475
493
|
s.pop ()
|
|
476
494
|
|
|
477
|
-
|
|
495
|
+
# no tilde prefix here, in arithmetic, or in double quotes
|
|
496
|
+
if ((not tilde_ok) or (STACK_CTLQUO in stack) or (STACK_CTLARI in stack)):
|
|
478
497
|
acc.append (["C", ORD_TILDE])
|
|
479
498
|
else:
|
|
480
|
-
uname = parse_tilde (s)
|
|
499
|
+
(is_prefix, uname) = parse_tilde (s, assign)
|
|
481
500
|
|
|
482
|
-
|
|
501
|
+
if (is_prefix):
|
|
502
|
+
acc.append (["T", uname])
|
|
503
|
+
else:
|
|
504
|
+
# interrupted: the ~ and what follows are ordinary
|
|
505
|
+
acc.append (["C", ORD_TILDE])
|
|
483
506
|
|
|
484
507
|
# (* ordinary character *)
|
|
485
508
|
# | c::s,_ -> arg_char (C c) s bqlist stack
|
|
@@ -496,22 +519,26 @@ def stringOrNull (acc_str):
|
|
|
496
519
|
return ["Some", acc_str]
|
|
497
520
|
|
|
498
521
|
|
|
499
|
-
def parse_tilde (s):
|
|
500
|
-
|
|
522
|
+
def parse_tilde (s, assign):
|
|
523
|
+
"""Returns (True, uname) and consumes the tilde prefix at the end of the
|
|
524
|
+
reversed `s` (uname is "None" for the bare ~), or (False, None), leaving
|
|
525
|
+
`s` untouched, if there is no tilde prefix (see exptilde in
|
|
526
|
+
src/expand.c)."""
|
|
527
|
+
i = len (s)
|
|
501
528
|
|
|
502
529
|
while (True):
|
|
503
|
-
|
|
504
|
-
return stringOrNull (acc_str)
|
|
505
|
-
else:
|
|
506
|
-
s_last = s [-1]
|
|
530
|
+
i = i - 1
|
|
507
531
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
532
|
+
if ((i < 0) or (s [i] in [CTLENDVAR, CTLENDARI, ORD_SLASH])
|
|
533
|
+
or (assign and (s [i] == ORD_COLON))):
|
|
534
|
+
break
|
|
535
|
+
elif (s [i] in [CTLESC, CTLVAR, CTLQUOTEMARK, CTLBACKQ, CTLARI]):
|
|
536
|
+
return (False, None)
|
|
537
|
+
|
|
538
|
+
acc_str = "".join (chr (c) for c in reversed (s [i + 1:]))
|
|
539
|
+
del s [i + 1:]
|
|
540
|
+
|
|
541
|
+
return (True, stringOrNull (acc_str))
|
|
515
542
|
|
|
516
543
|
|
|
517
544
|
def to_assign (a_rev):
|
|
@@ -551,7 +578,7 @@ def to_assigns (n):
|
|
|
551
578
|
sys.stdout.flush ()
|
|
552
579
|
os.abort ()
|
|
553
580
|
|
|
554
|
-
arg = to_arg (n.contents.narg)
|
|
581
|
+
arg = to_arg (n.contents.narg, assign = True)
|
|
555
582
|
|
|
556
583
|
arg.reverse ()
|
|
557
584
|
assigns.append (to_assign (arg))
|
|
@@ -16,6 +16,11 @@ def libdash_library_path():
|
|
|
16
16
|
LIBDASH_LIBRARY_PATH = os.path.join(FILE_PATH, "libdash.so")
|
|
17
17
|
return LIBDASH_LIBRARY_PATH
|
|
18
18
|
|
|
19
|
+
def check_layout():
|
|
20
|
+
"""Raises LibdashLayoutError if the bindings' ctypes structs disagree
|
|
21
|
+
with libdash.so's. parse() checks this itself before its first parse."""
|
|
22
|
+
require_layout(CDLL(libdash_library_path()), force=True)
|
|
23
|
+
|
|
19
24
|
# parsefile->eof bit 1: set by preadbuffer at EOF, never cleared by pungetc.
|
|
20
25
|
PARSEFILE_EOF = 2;
|
|
21
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: libdash
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Bindings for the dash shell as a library
|
|
5
5
|
Author: PaSh contributors
|
|
6
6
|
Author-email: Michael Greenberg <michael@greenberg.science>
|
|
@@ -124,9 +124,33 @@ For Python, see [`python/rt.py`](https://github.com/mgree/libdash/blob/master/py
|
|
|
124
124
|
|
|
125
125
|
For OCaml, see [`ocaml/shell_to_json.ml`](https://github.com/mgree/libdash/blob/master/ocaml/shell_to_json.ml), a tool that parses shell syntax and produces JSON (using the [atdgen](https://opam.ocaml.org/packages/atdgen/) bindings).
|
|
126
126
|
|
|
127
|
-
The ideal low-level interface to use is `parsecmd_safe` in `parser.c`; you'll need to ensure that dash's initialization routines have been called and that the stack marks are managed correctly. Parsing the POSIX shell is a complicated affair: beyond the usual locale issues, aliases affect the lexer, so one must use `
|
|
127
|
+
The ideal low-level interface to use is `parsecmd_safe` in `parser.c`; you'll need to ensure that dash's initialization routines have been called and that the stack marks are managed correctly. Parsing the POSIX shell is a complicated affair: beyond the usual locale issues, aliases affect the lexer, so one must use `setalias_safe` and `unalias` to manage any aliases that ought to exist. Outside of `parsecmd_safe`, call only entry points that cannot raise a dash error (e.g., `setinputfile_safe` rather than `setinputfile`).
|
|
128
128
|
|
|
129
|
-
#
|
|
129
|
+
# Locales and multibyte characters
|
|
130
|
+
|
|
131
|
+
dash's lexer decodes multibyte characters using the process's current
|
|
132
|
+
`LC_CTYPE`. In a multibyte locale (e.g., `C.UTF-8`), each multibyte character
|
|
133
|
+
is wrapped in `CTLMBCHAR` markers, and the bindings decode it to Unicode.
|
|
134
|
+
In the `C` locale, it stays as raw bytes, and any byte that collides with a
|
|
135
|
+
dash control code is escaped with `CTLESC`. The same script can therefore
|
|
136
|
+
parse to different ASTs depending on the locale in effect when you parse.
|
|
137
|
+
|
|
138
|
+
libdash never sets the locale for you:
|
|
139
|
+
|
|
140
|
+
- The OCaml binding's `Dash.initialize` calls `initialize_dash_setlocale`,
|
|
141
|
+
i.e., `setlocale(LC_ALL, "")`, so it uses the environment's locale.
|
|
142
|
+
- The Python binding uses whatever locale the interpreter set at startup.
|
|
143
|
+
Python calls `setlocale(LC_CTYPE, "")` and coerces a `C` locale to
|
|
144
|
+
`C.UTF-8` (PEP 538), so Python usually parses in UTF-8 even when the
|
|
145
|
+
environment says `C`.
|
|
146
|
+
- Programs calling the C library directly (e.g., from C or another
|
|
147
|
+
language's FFI) stay in the `C` locale unless they call `setlocale` or
|
|
148
|
+
`initialize_dash_setlocale` themselves.
|
|
149
|
+
|
|
150
|
+
To get the same ASTs from different bindings, set the same `LC_CTYPE` in
|
|
151
|
+
each process before parsing.
|
|
152
|
+
|
|
153
|
+
# How to work with parsed nodes
|
|
130
154
|
|
|
131
155
|
The general AST is described in `nodes.h`. There are some tricky invariants around the precise formatting of control codes; the OCaml code shows some examples of working with the `args` fields in `ocaml/ast.ml`, which converts the C AST to an OCaml AST.
|
|
132
156
|
|
libdash-0.5.3/setup.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from setuptools import Extension, setup
|
|
2
|
+
from setuptools.command.build_ext import build_ext
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
HERE = Path(__file__).parent.resolve()
|
|
13
|
+
long_description = (HERE / "README.md").read_text()
|
|
14
|
+
|
|
15
|
+
# What autotools needs to build the library.
|
|
16
|
+
SOURCES = ['configure.ac', 'Makefile.am', 'src']
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def git_lines(*args):
|
|
20
|
+
"""The NUL-separated output of `git args` (which must include -z) in
|
|
21
|
+
HERE, or None if HERE isn't the top of a git work tree (e.g., an
|
|
22
|
+
unpacked sdist)."""
|
|
23
|
+
try:
|
|
24
|
+
top = subprocess.run(['git', 'rev-parse', '--show-toplevel'], cwd=HERE,
|
|
25
|
+
capture_output=True, text=True)
|
|
26
|
+
if top.returncode != 0 or Path(top.stdout.strip()).resolve() != HERE:
|
|
27
|
+
return None
|
|
28
|
+
out = subprocess.run(['git', *args], cwd=HERE,
|
|
29
|
+
capture_output=True, text=True, check=True)
|
|
30
|
+
except (OSError, subprocess.CalledProcessError):
|
|
31
|
+
return None
|
|
32
|
+
return [line for line in out.stdout.split('\0') if line]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def source_files():
|
|
36
|
+
"""The files under SOURCES, relative to HERE.
|
|
37
|
+
|
|
38
|
+
In a git checkout, exactly the tracked files, so that leftovers from an
|
|
39
|
+
in-tree build (e.g., a stale src/builtins.h) can never reach the build.
|
|
40
|
+
In an sdist, everything: MANIFEST.in chose what to pack."""
|
|
41
|
+
tracked = git_lines('ls-files', '-z', '--', *SOURCES)
|
|
42
|
+
if tracked is None:
|
|
43
|
+
return [str(f.relative_to(HERE))
|
|
44
|
+
for d in SOURCES
|
|
45
|
+
for f in ([HERE / d] if (HERE / d).is_file() else (HERE / d).rglob('*'))
|
|
46
|
+
if f.is_file()]
|
|
47
|
+
|
|
48
|
+
leftovers = git_lines('ls-files', '-z', '--others', '--', *SOURCES)
|
|
49
|
+
if leftovers:
|
|
50
|
+
print(f'warning: ignoring {len(leftovers)} untracked file(s) in the source tree, '
|
|
51
|
+
'e.g., from an in-tree build (`git clean -fdX src` removes them):',
|
|
52
|
+
file=sys.stderr)
|
|
53
|
+
for f in leftovers[:10]:
|
|
54
|
+
print(f' {f}', file=sys.stderr)
|
|
55
|
+
if len(leftovers) > 10:
|
|
56
|
+
print(f' ... and {len(leftovers) - 10} more', file=sys.stderr)
|
|
57
|
+
return tracked
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def try_exec(*cmds, cwd):
|
|
61
|
+
proc = subprocess.run(cmds, cwd=cwd)
|
|
62
|
+
|
|
63
|
+
if proc.returncode != 0:
|
|
64
|
+
print('`{}` failed'.format(' '.join(cmds)), file=sys.stderr)
|
|
65
|
+
proc.check_returncode()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AutotoolsLibrary(Extension):
|
|
69
|
+
"""libdash's shared library, built by autotools and loaded with ctypes
|
|
70
|
+
(it is not a Python extension module)."""
|
|
71
|
+
|
|
72
|
+
def __init__(self, name):
|
|
73
|
+
super().__init__(name, sources=[])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class libdash_build_ext(build_ext):
|
|
77
|
+
"""Builds the library in a copy of the C sources under build_temp, so that
|
|
78
|
+
autoreconf, configure, and make never write into the source tree."""
|
|
79
|
+
|
|
80
|
+
def run(self):
|
|
81
|
+
self.build_autotools()
|
|
82
|
+
|
|
83
|
+
def package_dir(self):
|
|
84
|
+
# in place (e.g., editable installs): next to the Python sources
|
|
85
|
+
return HERE / 'libdash' if self.inplace else Path(self.build_lib) / 'libdash'
|
|
86
|
+
|
|
87
|
+
def library_names(self):
|
|
88
|
+
names = ['libdash.so']
|
|
89
|
+
if sys.platform == 'darwin':
|
|
90
|
+
names.append('libdash.dylib')
|
|
91
|
+
return names
|
|
92
|
+
|
|
93
|
+
def get_outputs(self):
|
|
94
|
+
return [str(self.package_dir() / name) for name in self.library_names()]
|
|
95
|
+
|
|
96
|
+
def get_output_mapping(self):
|
|
97
|
+
return {}
|
|
98
|
+
|
|
99
|
+
def build_autotools(self):
|
|
100
|
+
if sys.platform == 'darwin':
|
|
101
|
+
target_arch = os.environ.get("ARCHFLAGS")
|
|
102
|
+
build_arch = platform.machine()
|
|
103
|
+
if not target_arch:
|
|
104
|
+
target_arch = f"-arch {build_arch}"
|
|
105
|
+
os.environ["ARCHFLAGS"] = target_arch
|
|
106
|
+
if build_arch not in target_arch and "MACOSX_DEPLOYMENT_TARGET" not in os.environ:
|
|
107
|
+
os.environ["MACOSX_DEPLOYMENT_TARGET"] = "11.0"
|
|
108
|
+
|
|
109
|
+
# configure reads CFLAGS/LDFLAGS, not CCFLAGS; an explicit CFLAGS
|
|
110
|
+
# replaces autoconf's default -g -O2, so keep it
|
|
111
|
+
os.environ["CFLAGS"] = f'{os.environ.get("CFLAGS", "-g -O2")} {target_arch}'
|
|
112
|
+
os.environ["LDFLAGS"] = f'{os.environ.get("LDFLAGS", "")} {target_arch}'.strip()
|
|
113
|
+
|
|
114
|
+
print(f'ARCHFLAGS: {target_arch} MACOSX_DEPLOYMENT_TARGET: {os.environ.get("MACOSX_DEPLOYMENT_TARGET", "")} CFLAGS: {os.environ.get("CFLAGS")} LDFLAGS: {os.environ.get("LDFLAGS")}')
|
|
115
|
+
|
|
116
|
+
# A fresh copy every time: cibuildwheel builds every wheel from the
|
|
117
|
+
# same source tree, and objects from a previous build may target a
|
|
118
|
+
# different arch.
|
|
119
|
+
work = Path(self.build_temp).resolve() / 'autotools'
|
|
120
|
+
if work.exists():
|
|
121
|
+
shutil.rmtree(work)
|
|
122
|
+
work.mkdir(parents=True)
|
|
123
|
+
|
|
124
|
+
for f in source_files():
|
|
125
|
+
(work / f).parent.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
shutil.copy2(HERE / f, work / f)
|
|
127
|
+
|
|
128
|
+
try_exec('autoreconf', '--install', '--force', cwd=work)
|
|
129
|
+
try_exec('./configure', cwd=work)
|
|
130
|
+
try_exec('make', cwd=work)
|
|
131
|
+
|
|
132
|
+
dest = self.package_dir()
|
|
133
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
134
|
+
shutil.copy2(work / 'src/.libs/dlldash.so', dest / 'libdash.so')
|
|
135
|
+
if sys.platform == 'darwin':
|
|
136
|
+
shutil.copy2(work / 'src/.libs/libdash.dylib', dest / 'libdash.dylib')
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
setup(name='libdash',
|
|
140
|
+
packages=['libdash'],
|
|
141
|
+
ext_modules=[AutotoolsLibrary('libdash.libdash')],
|
|
142
|
+
cmdclass={'build_ext': libdash_build_ext},
|
|
143
|
+
version='0.5.3',
|
|
144
|
+
long_description=long_description,
|
|
145
|
+
long_description_content_type='text/markdown',
|
|
146
|
+
include_package_data=True)
|