summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog228
-rw-r--r--LICENSE22
-rw-r--r--Makefile75
-rw-r--r--extract.h37
-rw-r--r--extract.l678
-rw-r--r--extract.y1620
-rw-r--r--gc.c368
-rw-r--r--gc.h36
-rw-r--r--lib.c1609
-rw-r--r--lib.h331
-rw-r--r--regex.c631
-rw-r--r--regex.h107
-rw-r--r--tests/001/data87
-rw-r--r--tests/001/query-1.expected688
-rw-r--r--tests/001/query-1.txr7
-rw-r--r--tests/001/query-2.expected696
-rw-r--r--tests/001/query-2.txr7
-rw-r--r--tests/001/query-3.expected87
-rw-r--r--tests/001/query-3.txr21
-rw-r--r--tests/002/etc/passwd32
-rw-r--r--tests/002/proc/1/status31
-rw-r--r--tests/002/proc/1/task/1/stat1
-rw-r--r--tests/002/proc/103/status20
-rw-r--r--tests/002/proc/103/task/103/stat1
-rw-r--r--tests/002/proc/1068/status31
-rw-r--r--tests/002/proc/1068/task/1068/stat1
-rw-r--r--tests/002/proc/1235/status20
-rw-r--r--tests/002/proc/1235/task/1235/stat1
-rw-r--r--tests/002/proc/1236/status20
-rw-r--r--tests/002/proc/1236/task/1236/stat1
-rw-r--r--tests/002/proc/15812/status31
-rw-r--r--tests/002/proc/15812/task/15812/stat1
-rw-r--r--tests/002/proc/16/status20
-rw-r--r--tests/002/proc/16/task/16/stat1
-rw-r--r--tests/002/proc/1620/status31
-rw-r--r--tests/002/proc/1620/task/1620/stat1
-rw-r--r--tests/002/proc/1624/status31
-rw-r--r--tests/002/proc/1624/task/1624/stat1
-rw-r--r--tests/002/proc/16248/status31
-rw-r--r--tests/002/proc/16248/task/16248/stat1
-rw-r--r--tests/002/proc/16249/status31
-rw-r--r--tests/002/proc/16249/task/16249/stat1
-rw-r--r--tests/002/proc/1645/status31
-rw-r--r--tests/002/proc/1645/task/1645/stat1
-rw-r--r--tests/002/proc/1665/status31
-rw-r--r--tests/002/proc/1665/task/1665/stat1
-rw-r--r--tests/002/proc/1698/status31
-rw-r--r--tests/002/proc/1698/task/1698/stat1
-rw-r--r--tests/002/proc/17/status20
-rw-r--r--tests/002/proc/17/task/17/stat1
-rw-r--r--tests/002/proc/175/status20
-rw-r--r--tests/002/proc/175/task/175/stat1
-rw-r--r--tests/002/proc/1766/status31
-rw-r--r--tests/002/proc/1766/task/1766/stat1
-rw-r--r--tests/002/proc/1790/status20
-rw-r--r--tests/002/proc/1790/task/1790/stat1
-rw-r--r--tests/002/proc/1791/status20
-rw-r--r--tests/002/proc/1791/task/1791/stat1
-rw-r--r--tests/002/proc/1821/status31
-rw-r--r--tests/002/proc/1821/task/1821/stat1
-rw-r--r--tests/002/proc/1821/task/1822/stat1
-rw-r--r--tests/002/proc/1821/task/1826/stat1
-rw-r--r--tests/002/proc/1839/status31
-rw-r--r--tests/002/proc/1839/task/1839/stat1
-rw-r--r--tests/002/proc/1851/status31
-rw-r--r--tests/002/proc/1851/task/1851/stat1
-rw-r--r--tests/002/proc/186/status20
-rw-r--r--tests/002/proc/186/task/186/stat1
-rw-r--r--tests/002/proc/1887/status31
-rw-r--r--tests/002/proc/1887/task/1887/stat1
-rw-r--r--tests/002/proc/1902/status31
-rw-r--r--tests/002/proc/1902/task/1902/stat1
-rw-r--r--tests/002/proc/1921/status31
-rw-r--r--tests/002/proc/1921/task/1921/stat1
-rw-r--r--tests/002/proc/1925/status20
-rw-r--r--tests/002/proc/1925/task/1925/stat1
-rw-r--r--tests/002/proc/1926/status20
-rw-r--r--tests/002/proc/1926/task/1926/stat1
-rw-r--r--tests/002/proc/1927/status20
-rw-r--r--tests/002/proc/1927/task/1927/stat1
-rw-r--r--tests/002/proc/1928/status20
-rw-r--r--tests/002/proc/1928/task/1928/stat1
-rw-r--r--tests/002/proc/1929/status20
-rw-r--r--tests/002/proc/1929/task/1929/stat1
-rw-r--r--tests/002/proc/1930/status20
-rw-r--r--tests/002/proc/1930/task/1930/stat1
-rw-r--r--tests/002/proc/1931/status20
-rw-r--r--tests/002/proc/1931/task/1931/stat1
-rw-r--r--tests/002/proc/1932/status20
-rw-r--r--tests/002/proc/1932/task/1932/stat1
-rw-r--r--tests/002/proc/1936/status31
-rw-r--r--tests/002/proc/1936/task/1936/stat1
-rw-r--r--tests/002/proc/1963/status31
-rw-r--r--tests/002/proc/1963/task/1963/stat1
-rw-r--r--tests/002/proc/1989/status31
-rw-r--r--tests/002/proc/1989/task/1989/stat1
-rw-r--r--tests/002/proc/2/status20
-rw-r--r--tests/002/proc/2/task/2/stat1
-rw-r--r--tests/002/proc/2008/status31
-rw-r--r--tests/002/proc/2008/task/2008/stat1
-rw-r--r--tests/002/proc/2027/status31
-rw-r--r--tests/002/proc/2027/task/2027/stat1
-rw-r--r--tests/002/proc/2027/task/2032/stat1
-rw-r--r--tests/002/proc/2041/status31
-rw-r--r--tests/002/proc/2041/task/2041/stat1
-rw-r--r--tests/002/proc/2052/status31
-rw-r--r--tests/002/proc/2052/task/2052/stat1
-rw-r--r--tests/002/proc/2062/status31
-rw-r--r--tests/002/proc/2062/task/2062/stat1
-rw-r--r--tests/002/proc/2124/status31
-rw-r--r--tests/002/proc/2124/task/2124/stat1
-rw-r--r--tests/002/proc/2184/status31
-rw-r--r--tests/002/proc/2184/task/2184/stat1
-rw-r--r--tests/002/proc/2354/status31
-rw-r--r--tests/002/proc/2354/task/2354/stat1
-rw-r--r--tests/002/proc/2551/status31
-rw-r--r--tests/002/proc/2551/task/2551/stat1
-rw-r--r--tests/002/proc/2579/status31
-rw-r--r--tests/002/proc/2579/task/2579/stat1
-rw-r--r--tests/002/proc/2625/status31
-rw-r--r--tests/002/proc/2625/task/2625/stat1
-rw-r--r--tests/002/proc/2626/status31
-rw-r--r--tests/002/proc/2626/task/2626/stat1
-rw-r--r--tests/002/proc/2626/task/2627/stat1
-rw-r--r--tests/002/proc/2631/status31
-rw-r--r--tests/002/proc/2631/task/2631/stat1
-rw-r--r--tests/002/proc/2634/status31
-rw-r--r--tests/002/proc/2634/task/2634/stat1
-rw-r--r--tests/002/proc/2636/status31
-rw-r--r--tests/002/proc/2636/task/2636/stat1
-rw-r--r--tests/002/proc/2638/status31
-rw-r--r--tests/002/proc/2638/task/2638/stat1
-rw-r--r--tests/002/proc/2644/status31
-rw-r--r--tests/002/proc/2644/task/2644/stat1
-rw-r--r--tests/002/proc/2661/status31
-rw-r--r--tests/002/proc/2661/task/2661/stat1
-rw-r--r--tests/002/proc/2685/status31
-rw-r--r--tests/002/proc/2685/task/2685/stat1
-rw-r--r--tests/002/proc/2689/status31
-rw-r--r--tests/002/proc/2689/task/2689/stat1
-rw-r--r--tests/002/proc/2691/status31
-rw-r--r--tests/002/proc/2691/task/2691/stat1
-rw-r--r--tests/002/proc/2691/task/2696/stat1
-rw-r--r--tests/002/proc/2691/task/2708/stat1
-rw-r--r--tests/002/proc/2691/task/2709/stat1
-rw-r--r--tests/002/proc/2691/task/2710/stat1
-rw-r--r--tests/002/proc/2691/task/2711/stat1
-rw-r--r--tests/002/proc/2691/task/2712/stat1
-rw-r--r--tests/002/proc/2691/task/2713/stat1
-rw-r--r--tests/002/proc/2691/task/2714/stat1
-rw-r--r--tests/002/proc/2691/task/2715/stat1
-rw-r--r--tests/002/proc/2693/status31
-rw-r--r--tests/002/proc/2693/task/2693/stat1
-rw-r--r--tests/002/proc/2695/status31
-rw-r--r--tests/002/proc/2695/task/2695/stat1
-rw-r--r--tests/002/proc/2698/status31
-rw-r--r--tests/002/proc/2698/task/2698/stat1
-rw-r--r--tests/002/proc/2698/task/2699/stat1
-rw-r--r--tests/002/proc/2701/status31
-rw-r--r--tests/002/proc/2701/task/2701/stat1
-rw-r--r--tests/002/proc/2707/status31
-rw-r--r--tests/002/proc/2707/task/2707/stat1
-rw-r--r--tests/002/proc/27121/status20
-rw-r--r--tests/002/proc/27121/task/27121/stat1
-rw-r--r--tests/002/proc/2717/status31
-rw-r--r--tests/002/proc/2717/task/2717/stat1
-rw-r--r--tests/002/proc/2718/status31
-rw-r--r--tests/002/proc/2718/task/2718/stat1
-rw-r--r--tests/002/proc/2720/status31
-rw-r--r--tests/002/proc/2720/task/2720/stat1
-rw-r--r--tests/002/proc/2722/status31
-rw-r--r--tests/002/proc/2722/task/2722/stat1
-rw-r--r--tests/002/proc/27243/status20
-rw-r--r--tests/002/proc/27243/task/27243/stat1
-rw-r--r--tests/002/proc/2726/status31
-rw-r--r--tests/002/proc/2726/task/2726/stat1
-rw-r--r--tests/002/proc/2728/status31
-rw-r--r--tests/002/proc/2728/task/2728/stat1
-rw-r--r--tests/002/proc/27682/status31
-rw-r--r--tests/002/proc/27682/task/27682/stat1
-rw-r--r--tests/002/proc/27684/status31
-rw-r--r--tests/002/proc/27684/task/27684/stat1
-rw-r--r--tests/002/proc/27685/status31
-rw-r--r--tests/002/proc/27685/task/27685/stat1
-rw-r--r--tests/002/proc/28/status20
-rw-r--r--tests/002/proc/28/task/28/stat1
-rw-r--r--tests/002/proc/29/status20
-rw-r--r--tests/002/proc/29/task/29/stat1
-rw-r--r--tests/002/proc/29840/status31
-rw-r--r--tests/002/proc/29840/task/29840/stat1
-rw-r--r--tests/002/proc/3/status20
-rw-r--r--tests/002/proc/3/task/3/stat1
-rw-r--r--tests/002/proc/30737/status31
-rw-r--r--tests/002/proc/30737/task/30737/stat1
-rw-r--r--tests/002/proc/31905/status31
-rw-r--r--tests/002/proc/31905/task/31905/stat1
-rw-r--r--tests/002/proc/31907/status31
-rw-r--r--tests/002/proc/31907/task/31907/stat1
-rw-r--r--tests/002/proc/31908/status31
-rw-r--r--tests/002/proc/31908/task/31908/stat1
-rw-r--r--tests/002/proc/32672/status31
-rw-r--r--tests/002/proc/32672/task/32672/stat1
-rw-r--r--tests/002/proc/32674/status31
-rw-r--r--tests/002/proc/32674/task/32674/stat1
-rw-r--r--tests/002/proc/32675/status31
-rw-r--r--tests/002/proc/32675/task/32675/stat1
-rw-r--r--tests/002/proc/4/status20
-rw-r--r--tests/002/proc/4/task/4/stat1
-rw-r--r--tests/002/proc/5/status20
-rw-r--r--tests/002/proc/5/task/5/stat1
-rw-r--r--tests/002/proc/870/status31
-rw-r--r--tests/002/proc/870/task/870/stat1
-rw-r--r--tests/002/query-1.expected90
-rw-r--r--tests/002/query-1.txr38
-rw-r--r--txr.11741
-rw-r--r--unwind.c88
-rw-r--r--unwind.h66
217 files changed, 11977 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 00000000..b5279410
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,228 @@
+2009-09-25 Kaz Kylheku <kkylheku@gmail.com>
+
+ Version 011
+
+ New @(maybe) clause optionally matches (does not fail if none of
+ its clauses match anything).
+
+ New blocks feature: allows a query or subquery to be
+ abruptly terminated by invoking an exit to a named or anonymous
+ block. @(collect) and @(skip) have implicit anonymous blocks now.
+
+ The @(skip) directive takes a numeric argument now, which limits
+ how many lines are searched.
+
+ * Makefile, extract.l, extract.y, extract.h, gc.c, gc.h, lib.c, lib.h,
+ regex.c, regex.h, txr.1, unwind.c, unwind.h: Copyright notice and
+ license text updated or added, and version bumped up to 011.
+ * tests/001/query-1.txr, tests/001/query-2.txr, tests/001/query-3.txr,
+ tests/002/query-1.txr: Assigned to public domain.
+
+2009-09-25 Kaz Kylheku <kkylheku@gmail.com>
+
+ New features:
+ - named blocks;
+ - maybe clause;
+ - optional iteration bound on skip.
+
+ * extract.y: includes added: "unwind.h", <setjmp.h>.
+ (MAYBE, OR): New grammar tokens.
+ (maybe_clause): New nonterminal grammar symbol.
+ (expr): A NUMBER can be an expression now, so that @(skip 42)
+ is valid syntax.
+ (match_files): Support for numeric argument in skip directive
+ to bound the search to a maximum number of lines.
+ Anonymous block established around skip.
+ New directives implemented: maybe, block, accept and fail.
+ Anonymous block established around collect.
+ * txr.1: Documentation updated with new features.
+ * Makefile: new object file unwind.o, and associated rules.
+ * extract.l (yybadtoken): New cases for MAYBE and OR.
+ (grammar): Likewise.
+ * lib.c (block, fail, accept): New symbol variables.
+ (obj_init): New symbols interned.
+ * lib.h (block, fail, accept): Declared.
+ (if2, if3): Macros fixed so test expression is not compared to nil,
+ but implicitly tested as boolean.
+ * unwind.c, unwind.h: New source files.
+
+2009-09-24 Kaz Kylheku <kkylheku@gmail.com>
+
+ Stability fixes.
+
+ * extract.y (match_files): Fixed invalid string("-") to
+ string(chk_strdup("-")) which caused a freeing of
+ a non-malloced string at gc finalization time.
+ * regex.c (nfa_state_shallow_free): New function: does not
+ free satellite objects, just the structure itself.
+ (nfa_combine): Use nfa_state_shallow_free instead of nfa_state_free,
+ because the merged state inherits ownership of objects from the state
+ being spliced out.
+ (nfa_state_set): Fix lack of initialization of s.visited member of the
+ state structure.
+
+2009-09-24 Kaz Kylheku <kkylheku@gmail.com>
+
+ Version 010
+
+ A file specs can start with $, which means read a directory.
+
+ Data sources are not into memory at once, but on demand,
+ which can reduce memory for many queries.
+
+ Regular expressions are now compiled once, when the
+ query is parsed.
+
+ Character escapes are now supported in regular expressions,
+ and as a special syntax.
+
+ * extract.l (version): Bumped to 010.
+ (grammar): 8 and 9 are not octal digits; handle all regex
+ backslash escaping in lexical grammar.
+ * extract.y (grammar): Get rid of backslash handling from
+ regex grammar. Lexer returns a REGCHAR for every escaped
+ item. In situations where an operator character is implicily
+ literal, like * in a character class, we use the grammar
+ to include that alongside REGCHAR. Bugfixes: the character ], when not
+ closing a class, is not a syntax error but stands for itself;
+ the character - stands for itself outside of character class;
+ the | character is literal in a character class.
+ * txr.1: Updated version. Documented character escapes.
+
+2009-09-24 Kaz Kylheku <kkylheku@gmail.com>
+
+ Lazy stream list improvement: no extra NIL element caused
+ by end-of-file. Requires push-back support in streams.
+ To avoid introducing a new structure member into streams,
+ we extend the semantics of the label member, and rename
+ it to label_pushback.
+
+ * lib.c (stdio_line_stream, pipe_line_stream,
+ dirent_stream): Follow rename of struct stream member;
+ assert that label is an atom.
+ (stream_get): Check pushback stack first and get item from there.
+ (stream_pushback): New function.
+ (lazy_stream_func): Pull one more item from the stream and
+ use /that/ to decide whether to continue the lazy stream.
+ The extra item is pushed back, if valid.
+ (lazy_stream_cons): Simplified: no hack involving regular cons. Starts
+ the induction by peeking into the stream. If something is there, it is
+ pushed back, and a lazy cons is constructed which will fetch it.
+ (obj_print): Made aware of the pushback, which must be skipped
+ to get to the terminating label.
+ * lib.h (struct stream): Member renamed from label to label_pushback.
+ (stream_pushback): New function declaration.
+
+2009-09-23 Kaz Kylheku <kkylheku@gmail.com>
+
+ Escape syntax in regexes, and text. The
+ standard seven character escapes are supported,
+ namely \a, \b, \t, \n, \v, \f, and \r,
+ as well as hex and octal escapes, plus
+ the code \e for ASCII ESC.
+
+ * extract.l (char_esc, num_esc): New functions.
+ (grammar): New lex cases.
+ * lib.c (obj_print): Support all character escapes in printing.
+ Bugfix: backslash printed as two backslashes, not one.
+
+2009-09-23 Kaz Kylheku <kkylheku@gmail.com>
+
+ * tests/002/query-1.txr: Modified to use $ to scan thread
+ subdirectories.
+ * tests/002/query-1.expected: Updated.
+
+2009-09-23 Kaz Kylheku <kkylheku@gmail.com>
+
+ New COBJ type for wrapping arbitrary C objects into the
+ Lisp-like framework. Compiled regexes are objects now.
+ Regexes in a query are now compiled just once.
+
+ * extract.y (grammar): Regexes compiled while parsing.
+ (match_line): Modify with respect to the abstract syntax
+ tree change, and the interface changes in the match_regex,
+ and search_regex functions.
+ * gc.c (mark_obj, finalize): Handle marking and finalization
+ of COBJ objects.
+ * lib.c (typeof, equal, obj_print): Handle COBJ.
+ (cobj, cobj_print_op): New functions.
+ * lib.h (type_t): New enum element, COBJ.
+ (struct cobj, struct subj_ops): New types.
+ (union obj): New member, co.
+ (cobj, cobj_print_op): New functions declared.
+ * regex.c (regex_equal, regex_destroy, regex_compile, regex_nfa): New
+ functions.
+ (regex_obj_ops): New static struct.
+ (search_regex, match_regex): Interface change. Regex arguments
+ are now compiled regexes. Functions won't handle raw regexes.
+ * regex.h (regex_compile, regex_nfa): New functions declared.
+
+2009-09-23 Kaz Kylheku <kkylheku@gmail.com>
+
+ New feature: file specs that start with $ read directories.
+ Reading from an ``ls'' pipe is too slow.
+
+ Streams and lazy conses implemented. Lazy conses allow us to treat a
+ file or other kind of stream exactly as if it were a list.
+ We can use car and cdr, etc. But only the parts of the list
+ that we actually touch are instantiated on-the-fly by
+ reading from the underlying stream.
+
+ * extract.l: inclusion of <dirent.h> added.
+ * extract.l: inclusion of <dirent.h> added.
+ * extract.y (fpip_closedir): new enumeration in struct fpip,
+ and fpip_noclose removed.
+ (complex_open): Check for leading $, use opendir.
+ (complex_open_failed): New function.
+ (complex_close): Handle fpip_closedir case. Not closing
+ stdin and stdout is handled by explicit comparison now.
+ (complex_snarf): New function, constructs stream of
+ a suitable type, over object returned from complex_close,
+ wraps it in a lazy list.
+ (match_files): Use complex_snarf instead of snarf to get a lazy list.
+ * gc.c: Handle LCONS and STREAM cases.
+ * lib.c (stream_t, lcons_t): New variables holding symbols.
+ (typeof, equal, obj_print): Handle LCONS and STREAM.
+ (car, cdr, car_l, cdr_l, consp, atom, listp): Rewritten to handle
+ LCONS.
+ (chk_strdup, stdio_line_read, stdio_line_write, stdio_close
+ stdio_line_stream, pipe_close, pipe_line_stream,
+ dirent_read, dirent_close, dirent_stream,
+ stream_get, stream_put, stream_close,
+ make_lazycons, lazy_stream_func, lazy_stream_cons): New functions.
+ (stdio_line_stream_ops, pipe_line_stream_ops,
+ dirent_stream_ops): New static structs.
+ (obj_init): Intern new symbols lstream, lcons, and dir.
+ * lib.h (type_t): New enum members STREAM and LCONS.
+ (struct stream, struct stream_ops, struct lazy_cons): New types.
+ (union obj): New members sm and lc.
+ (chk_strdup, stdio_line_stream, pipe_line_stream,
+ dirent_stream, stream_get, stream_put, stream_close,
+ lazy_stream_cons): New function declarations.
+ * regex.c: inclusion of <dirent.h> added
+
+2009-09-23 Kaz Kylheku <kkylheku@gmail.com>
+
+ Version 009
+
+ User-friendly error messages from parser.
+ Fixed -q option.
+
+ * extract.l (version): Bumped to 009.
+ * txr.1: Updated version.
+
+2009-09-22 Kaz Kylheku <kkylheku@gmail.com>
+
+ * Makefile (LIBLEX): New variable.
+ Refer to lex library as -lfl, using variable
+ that can be overridden.
+
+2009-09-22 Kaz Kylheku <kkylheku@gmail.com>
+
+ * extract.h (yybadtoken): New function declaration.
+ * extract.l (yybadtoken): New function.
+ (main): Fixed -q option.
+ * extract.y (grammar): Lots of new error productions, some
+ phrase rules refactored, resulting in much more user-friendly
+ error diagnosis.
+ * txr.1: -q option semantics clarified.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..ae4276e0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (C) 2009, Kaz Kylheku <kkylheku@gmail.com>.
+All rights reserved.
+
+BSD License:
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ 3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..2b0973b5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,75 @@
+# Copyright 2009
+# Kaz Kylheku <kkylheku@gmail.com>
+# Vancouver, Canada
+# All rights reserved.
+#
+# BSD License:
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+# 3. The name of the author may not be used to endorse or promote
+# products derived from this software without specific prior
+# written permission.
+#
+# THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+
+# Test data in the tests/ directory is in the public domain,
+# unless it contains notices to the contrary.
+OPT_FLAGS := -O2
+LANG_FLAGS := -ansi -D_GNU_SOURCE
+DIAG_FLAGS := -Wall
+DBG_FLAGS := -g
+LEXLIB := fl
+
+CFLAGS := $(LANG_FLAGS) $(DIAG_FLAGS) $(OPT_FLAGS) $(DBG_FLAGS)
+
+txr: lex.yy.o y.tab.o lib.o regex.o gc.o unwind.o
+ $(CC) $(CFLAGS) -o $@ $^ -l$(LEXLIB)
+
+lex.yy.o y.tab.o: y.tab.h extract.h lib.h gc.h
+
+y.tab.o: regex.h
+
+lib.o: lib.h gc.h
+
+regex.o: regex.h lib.h gc.h
+
+gc.o: gc.h lib.h gc.h
+
+unwind.o: unwind.h lib.h
+
+lex.yy.c: extract.l
+ $(LEX) $<
+
+y.tab.c y.tab.h: extract.y
+ if $(YACC) -v -d $< ; then true ; else rm $@ ; false ; fi
+
+clean:
+ rm -f txr lex.yy.o y.tab.o lib.o regex.o gc.o unwind.o \
+ y.tab.c lex.yy.c y.tab.h y.output $(TESTS:.ok=.out)
+
+TESTS := $(patsubst %.txr,%.ok,$(shell find tests -name '*.txr' | sort))
+
+tests: txr $(TESTS)
+ @echo "** tests passed!"
+
+tests/001/%: TXR_ARGS := tests/001/data
+tests/002/%: TXR_OPTS := -DTESTDIR=tests/002
+
+%.ok: %.txr
+ ./txr $(TXR_OPTS) $^ $(TXR_ARGS) > $(@:.ok=.out)
+ diff $(@:.ok=.expected) $(@:.ok=.out)
+
+%.expected: %.txr
+ ./txr $(TXR_OPTS) $^ $(TXR_ARGS) > $@
+
diff --git a/extract.h b/extract.h
new file mode 100644
index 00000000..cff5d432
--- /dev/null
+++ b/extract.h
@@ -0,0 +1,37 @@
+/* Copyright 2009
+ * Kaz Kylheku <kkylheku@gmail.com>
+ * Vancouver, Canada
+ * All rights reserved.
+ *
+ * BSD License:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <stdio.h>
+long lineno;
+extern int opt_loglevel;
+extern int opt_nobindings;
+extern int opt_arraydims;
+int yyparse(void);
+obj_t *get_spec(void);
+int extract(obj_t *spec, obj_t *filenames, obj_t *bindings);
+void yyerrorf(int level, const char *s, ...);
+void yyerrorlf(int level, long spec_lineno, const char *s, ...);
+void yybadtoken(int tok, const char *context);
diff --git a/extract.l b/extract.l
new file mode 100644
index 00000000..4c15476d
--- /dev/null
+++ b/extract.l
@@ -0,0 +1,678 @@
+/* Copyright 2009
+ * Kaz Kylheku <kkylheku@gmail.com>
+ * Vancouver, Canada
+ * All rights reserved.
+ *
+ * BSD License:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+%{
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+#include <dirent.h>
+#include "y.tab.h"
+#include "lib.h"
+#include "gc.h"
+#include "extract.h"
+
+#define YY_NO_UNPUT
+
+const char *version = "011";
+const char *progname = "txr";
+const char *spec_file = "stdin";
+long lineno = 1;
+int opt_loglevel = 1; /* 0 - quiet; 1 - normal; 2 - verbose */
+int opt_nobindings = 0;
+int opt_arraydims = 1;
+
+static int nesting;
+static int closechar;
+static int errors;
+
+/*
+ * Can implement an emergency allocator here from a fixed storage
+ * pool, which sets an OOM flag. Program can check flag
+ * and gracefully terminate instead of aborting like this.
+ */
+void *oom_realloc_handler(void *old, size_t size)
+{
+ fprintf(stderr, "%s: out of memory\n", progname);
+ puts("false");
+ abort();
+}
+
+void yyerror(const char *s)
+{
+ yyerrorlf(1, lineno, "%s", s);
+ errors++;
+}
+
+void yyerrorf(int level, const char *s, ...)
+{
+ if (opt_loglevel >= level) {
+ va_list vl;
+ va_start (vl, s);
+ fprintf(stderr, "%s: (%s:%ld): ", progname, spec_file, lineno);
+ vfprintf(stderr, s, vl);
+ putc('\n', stderr);
+ va_end (vl);
+ }
+
+ if (level < 2)
+ errors++;
+}
+
+void yyerrorlf(int level, long spec_lineno, const char *s, ...)
+{
+ if (opt_loglevel >= level) {
+ va_list vl;
+ va_start (vl, s);
+ fprintf(stderr, "%s: (%s:%ld): ", progname, spec_file, spec_lineno);
+ vfprintf(stderr, s, vl);
+ putc('\n', stderr);
+ va_end (vl);
+ }
+
+ if (level < 2)
+ errors++;
+}
+
+void yybadtoken(int tok, const char *context)
+{
+ const char *problem = 0;
+
+ switch (tok) {
+ case TEXT: problem = "text"; break;
+ case IDENT: problem = "identifier"; break;
+ case ALL: problem = "\"all\""; break;
+ case SOME: problem = "\"some\""; break;
+ case NONE: problem = "\"none\""; break;
+ case MAYBE: problem = "\"maybe\""; break;
+ case AND: problem = "\"and\""; break;
+ case OR: problem = "\"or\""; break;
+ case END: problem = "\"end\""; break;
+ case COLLECT: problem = "\"collect\""; break;
+ case UNTIL: problem = "\"until\""; break;
+ case COLL: problem = "\"coll\""; break;
+ case OUTPUT: problem = "\"output\""; break;
+ case REPEAT: problem = "\"repeat\""; break;
+ case REP: problem = "\"rep\""; break;
+ case SINGLE: problem = "\"single\""; break;
+ case FIRST: problem = "\"first\""; break;
+ case LAST: problem = "\"last\""; break;
+ case EMPTY: problem = "\"empty\""; break;
+ case NUMBER: problem = "\"number\""; break;
+ case REGCHAR: problem = "regular expression character"; break;
+ }
+
+ if (problem != 0)
+ if (context)
+ yyerrorlf(1, lineno, "misplaced %s in %s", problem, context);
+ else
+ yyerrorlf(1, lineno, "unexpected %s", problem);
+ else
+ if (context)
+ yyerrorlf(1, lineno, "unterminated %s", context);
+ else
+ yyerrorlf(1, lineno, "unexpected end of input");
+}
+
+static int char_esc(int letter)
+{
+ switch (letter) {
+ case 'a': return '\a';
+ case 'b': return '\b';
+ case 't': return '\t';
+ case 'n': return '\n';
+ case 'v': return '\v';
+ case 'f': return '\f';
+ case 'r': return '\r';
+ case 'e': return 27;
+ }
+
+ abort();
+}
+
+static int num_esc(char *num)
+{
+ if (num[0] == 'x') {
+ if (strlen(num) > 3)
+ yyerror("too many digits in hex character escape");
+ return strtol(num + 1, 0, 16);
+ } else {
+ if (strlen(num) > 3)
+ yyerror("too many digits in octal character escape");
+ return strtol(num, 0, 8);
+ }
+}
+
+%}
+
+TOK [a-zA-Z_][a-zA-Z0-9_]*|[+-]?[0-9]+
+WS [\t ]*
+%x SPECIAL REGEX REGCLASS
+
+%%
+
+<SPECIAL>{TOK} {
+ long val;
+ char *errp;
+
+ errno = 0;
+
+ val = strtol(yytext, &errp, 10);
+
+ if (nesting == 0)
+ BEGIN(INITIAL);
+
+ if (*errp != 0) {
+ /* not a number */
+ yylval.lexeme = strdup(yytext);
+ return IDENT;
+ }
+
+ if ((val == LONG_MAX || val == LONG_MIN) && errno == ERANGE)
+ yyerror("numeric overflow in token");
+
+ yylval.num = val;
+ return NUMBER;
+ }
+
+
+<SPECIAL>\({WS}all{WS}\) {
+ BEGIN(INITIAL);
+ return ALL;
+ }
+
+<SPECIAL>\({WS}some{WS}\) {
+ BEGIN(INITIAL);
+ return SOME;
+ }
+
+<SPECIAL>\({WS}none{WS}\) {
+ BEGIN(INITIAL);
+ return NONE;
+ }
+
+<SPECIAL>\({WS}maybe{WS}\) {
+ BEGIN(INITIAL);
+ return MAYBE;
+ }
+
+<SPECIAL>\({WS}and{WS}\) {
+ BEGIN(INITIAL);
+ return AND;
+ }
+
+<SPECIAL>\({WS}or{WS}\) {
+ BEGIN(INITIAL);
+ return OR;
+ }
+
+<SPECIAL>\({WS}end{WS}\) {
+ BEGIN(INITIAL);
+ return END;
+ }
+
+<SPECIAL>\({WS}collect{WS}\) {
+ BEGIN(INITIAL);
+ return COLLECT;
+ }
+
+<SPECIAL>\({WS}coll{WS}\) {
+ BEGIN(INITIAL);
+ return COLL;
+ }
+
+<SPECIAL>\({WS}until{WS}\) {
+ BEGIN(INITIAL);
+ return UNTIL;
+ }
+
+<SPECIAL>\({WS}output{WS}\) {
+ BEGIN(INITIAL);
+ return OUTPUT;
+ }
+
+<SPECIAL>\({WS}repeat{WS}\) {
+ BEGIN(INITIAL);
+ return REPEAT;
+ }
+
+
+<SPECIAL>\({WS}rep{WS}\) {
+ BEGIN(INITIAL);
+ return REP;
+ }
+
+<SPECIAL>\({WS}single{WS}\) {
+ BEGIN(INITIAL);
+ return SINGLE;
+ }
+
+<SPECIAL>\({WS}first{WS}\) {
+ BEGIN(INITIAL);
+ return FIRST;
+ }
+
+<SPECIAL>\({WS}last{WS}\) {
+ BEGIN(INITIAL);
+ return LAST;
+ }
+
+<SPECIAL>\({WS}empty{WS}\) {
+ BEGIN(INITIAL);
+ return EMPTY;
+ }
+
+<SPECIAL>\{|\( {
+ nesting++;
+ if (yytext[0] == '{')
+ closechar = '}';
+ else
+ closechar = ')';
+ return yytext[0];
+ }
+
+<SPECIAL>\}|\) {
+ if (yytext[0] != closechar) {
+ yyerror("paren mismatch");
+ BEGIN(INITIAL);
+ } else {
+ if (--nesting == 0)
+ BEGIN(INITIAL);
+ return yytext[0];
+ }
+ }
+
+<SPECIAL>[\t ]+ {
+ /* Eat whitespace in directive */
+ }
+
+<SPECIAL>@ {
+ if (nesting == 0) {
+ BEGIN(INITIAL);
+ yylval.lexeme = strdup("@");
+ return TEXT;
+ } else {
+ yyerrorf(0, "bad character in directive: %c", yytext[0]);
+ }
+ }
+
+<SPECIAL>\n {
+ lineno++;
+ yyerror("newline in directive");
+ }
+
+<SPECIAL>[/] {
+ BEGIN(REGEX);
+ return '/';
+ }
+
+<SPECIAL>\. {
+ yylval.chr = '.';
+ return '.';
+ }
+
+<SPECIAL>[\\][abtnvfre] {
+ char lexeme[2];
+ lexeme[0] = char_esc(yytext[1]);
+ lexeme[1] = 0;
+ yylval.lexeme = strdup(lexeme);
+ BEGIN(INITIAL);
+ return TEXT;
+ }
+
+<SPECIAL>[\\](x[0-9a-fA-F]+|[0-7]+) {
+ char lexeme[2];
+ lexeme[0] = num_esc(yytext + 1);
+ lexeme[1] = 0;
+ yylval.lexeme = strdup(lexeme);
+ BEGIN(INITIAL);
+ return TEXT;
+ }
+
+<SPECIAL>. {
+ yyerrorf(0, "bad character in directive: '%c'", yytext[0]);
+ }
+
+<REGEX>[/] {
+ if (nesting == 0)
+ BEGIN(INITIAL);
+ else
+ BEGIN(SPECIAL);
+ yylval.chr = '/';
+ return '/';
+ }
+
+
+<REGEX>[\\][abtnvfre] {
+ yylval.chr = char_esc(yytext[1]);
+ return REGCHAR;
+ }
+
+<REGEX>[\\](x[0-9a-fA-F]+|[0-9]+) {
+ yylval.chr = num_esc(yytext + 1);
+ return REGCHAR;
+ }
+
+<REGEX>\n {
+ lineno++;
+ yyerror("newline in regex");
+ }
+
+<REGEX>[.*?+^] {
+ yylval.chr = yytext[0];
+ return yytext[0];
+ }
+
+
+<REGEX>[\[\]\-] {
+ yylval.chr = yytext[0];
+ return yytext[0];
+ }
+
+<REGEX>[()|] {
+ yylval.chr = yytext[0];
+ return yytext[0];
+ }
+
+<REGEX>[\\]. {
+ yylval.chr = yytext[1];
+ return REGCHAR;
+ }
+
+<REGEX>. {
+ yylval.chr = yytext[0];
+ return REGCHAR;
+ }
+
+<INITIAL>[^@\n]+ {
+ yylval.lexeme = strdup(yytext);
+ return TEXT;
+ }
+
+<INITIAL>\n {
+ lineno++;
+ return '\n';
+ }
+
+<INITIAL>@{WS}\* {
+ BEGIN(SPECIAL);
+ return '*';
+ }
+
+<INITIAL>@ {
+ BEGIN(SPECIAL);
+ }
+
+<INITIAL>^@#.*\n {
+ /* eat whole line comment */
+ lineno++;
+ }
+
+<INITIAL>@#.* {
+ /* comment to end of line */
+ }
+
+%%
+
+void help(void)
+{
+ const char *text =
+"\n"
+"txr version %s\n"
+"\n"
+"copyright 2009, Kaz Kylheku <kkylheku@gmail.com>\n"
+"\n"
+"usage:\n"
+"\n"
+" %s [ options ] query-file { data-file }*\n"
+"\n"
+"The query-file or data-file arguments may be specified as -, in which case\n"
+"standard input is used. If these arguments end with a | character, then\n"
+"they are treated as command pipes. Leading arguments which begin with a -\n"
+"followed by one or more characters, and which are not arguments to options\n"
+"are interpreted as options. The -- option indicates the end of the options.\n"
+"\n"
+"If no data-file arguments sare supplied, then the query itself must open a\n"
+"a data source prior to attempting to make any pattern match, or it will\n"
+"simply fail due to a match which has run out of data.\n"
+"\n"
+"options:\n"
+"\n"
+"-Dvar=value Pre-define variable var, with the given value.\n"
+" A list value can be specified using commas.\n"
+"-Dvar Predefine variable var, with empty string value.\n"
+"-q Quiet: don't report errors during query matching.\n"
+"-v Verbose: extra logging from matcher.\n"
+"-b Don't dump list of bindings.\n"
+"-a num Generate array variables up to num-dimensions.\n"
+" Default is 1. Additional dimensions are fudged\n"
+" by generating numeric suffixes\n"
+"--help You already know!\n"
+"--version Display program version\n"
+"\n"
+"Options that take no argument can be combined. The -q and -v options\n"
+"are mutually exclusive; the right-most one dominates.\n"
+"\n"
+ ;
+ fprintf(stdout, text, version, progname);
+}
+
+void hint(void)
+{
+ fprintf(stderr, "%s: incorrect arguments: try --help\n", progname);
+}
+
+int main(int argc, char **argv)
+{
+ obj_t *stack_top = nil;
+ obj_t *spec = nil;
+ obj_t *bindings = nil;
+ int match_loglevel = opt_loglevel;
+ progname = argv[0] ? argv[0] : progname;
+
+ gc_stack_top = &stack_top;
+
+ if (argc <= 1) {
+ hint();
+ return EXIT_FAILURE;
+ }
+
+ argc--, argv++;
+
+ while (argc > 0 && (*argv)[0] == '-') {
+ if (!strcmp(*argv, "--")) {
+ argv++, argc--;
+ break;
+ }
+
+ if (!strcmp(*argv, "-"))
+ break;
+
+ if (!strncmp(*argv, "-D", 2)) {
+ char *var = *argv + 2;
+ char *equals = strchr(var, '=');
+ char *has_comma = (equals != 0) ? strchr(equals, ',') : 0;
+
+ if (has_comma) {
+ char *val = equals + 1;
+ obj_t *list = nil;
+
+ *equals = 0;
+
+ for (;;) {
+ size_t piece = strcspn(val, ",");
+ char comma_p = val[piece];
+
+ val[piece] = 0;
+
+ list = cons(string(strdup(val)), list);
+
+ if (!comma_p)
+ break;
+
+ val += piece + 1;
+ }
+
+ list = nreverse(list);
+ bindings = cons(cons(intern(string(strdup(var))), list), bindings);
+ } else if (equals) {
+ char *val = equals + 1;
+ *equals = 0;
+ bindings = cons(cons(intern(string(strdup(var))),
+ string(strdup(val))), bindings);
+ } else {
+ bindings = cons(cons(intern(string(strdup(var))),
+ null_string), bindings);
+ }
+
+ argc--, argv++;
+ continue;
+ }
+
+ if (!strcmp(*argv, "--version")) {
+ printf("%s: version %s\n", progname, version);
+ return 0;
+ }
+
+ if (!strcmp(*argv, "--help")) {
+ help();
+ return 0;
+ }
+
+ if (!strcmp(*argv, "-a")) {
+ long val;
+ char *errp;
+ char opt = (*argv)[1];
+
+ if (argc == 1) {
+ fprintf(stderr, "%s: option %c needs argument\n", progname, opt);
+
+ return EXIT_FAILURE;
+ }
+
+ argv++, argc--;
+
+ switch (opt) {
+ case 'a':
+ val = strtol(*argv, &errp, 10);
+ if (*errp != 0) {
+ fprintf(stderr, "%s: option %c needs numeric argument, not %s\n",
+ progname, opt, *argv);
+ return EXIT_FAILURE;
+ }
+
+ opt_arraydims = val;
+ break;
+ }
+
+ argv++, argc--;
+ continue;
+ }
+
+ if (!strcmp(*argv, "--gc-debug")) {
+ opt_gc_debug = 1;
+ argv++, argc--;
+ continue;
+ }
+
+ {
+ char *popt;
+ for (popt = (*argv)+1; *popt != 0; popt++) {
+ switch (*popt) {
+ case 'v':
+ match_loglevel = 2;
+ break;
+ case 'q':
+ match_loglevel = 0;
+ break;
+ case 'b':
+ opt_nobindings = 1;
+ break;
+ default:
+ fprintf(stderr, "%s: unrecognized option: %c\n", progname, *popt);
+ return EXIT_FAILURE;
+ }
+ }
+
+ argc--, argv++;
+ }
+ }
+
+ if (argc < 1) {
+ hint();
+ return EXIT_FAILURE;
+ }
+
+ if (strcmp(*argv, "-") != 0) {
+ yyin = fopen(*argv, "r");
+ if (yyin == 0) {
+ fprintf(stderr, "%s: unable to open %s\n", progname, *argv);
+ return EXIT_FAILURE;
+ }
+ spec_file = *argv;
+ }
+
+ argc--, argv++;
+
+ {
+ int gc;
+ init(progname, oom_realloc_handler);
+
+ gc = gc_state(0);
+ yyparse();
+ gc_state(gc);
+
+ if (errors)
+ return EXIT_FAILURE;
+ spec = get_spec();
+
+
+ opt_loglevel = match_loglevel;
+
+ if (opt_loglevel >= 2) {
+ fputs("spec:\n", stderr);
+ dump(spec, stderr);
+
+ fputs("bindings:\n", stderr);
+ dump(bindings, stderr);
+ }
+
+ {
+ int retval;
+ list_collect_decl(filenames, iter);
+
+ while (*argv)
+ list_collect(iter, string(*argv++));
+
+ retval = extract(spec, filenames, bindings);
+
+ return errors ? EXIT_FAILURE : retval;
+ }
+ }
+}
diff --git a/extract.y b/extract.y
new file mode 100644
index 00000000..594b341e
--- /dev/null
+++ b/extract.y
@@ -0,0 +1,1620 @@
+/* Copyright 2009
+ * Kaz Kylheku <kkylheku@gmail.com>
+ * Vancouver, Canada
+ * All rights reserved.
+ *
+ * BSD License:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+%{
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <limits.h>
+#include <errno.h>
+#include <dirent.h>
+#include <setjmp.h>
+#include "lib.h"
+#include "gc.h"
+#include "unwind.h"
+#include "regex.h"
+#include "extract.h"
+
+int yylex(void);
+void yyerror(const char *);
+
+obj_t *repeat_rep_helper(obj_t *sym, obj_t *main, obj_t *parts);
+
+static obj_t *parsed_spec;
+static int output_produced;
+
+%}
+
+%union {
+ char *lexeme;
+ union obj *obj;
+ char chr;
+ long num;
+}
+
+%token <lexeme> TEXT IDENT ALL SOME NONE MAYBE AND OR END COLLECT UNTIL COLL
+%token <lexeme> OUTPUT REPEAT REP SINGLE FIRST LAST EMPTY
+%token <num> NUMBER
+%token <chr> REGCHAR
+
+%type <obj> spec clauses clause all_clause some_clause none_clause maybe_clause
+%type <obj> collect_clause clause_parts additional_parts output_clause
+%type <obj> line elems_opt elems elem var var_op list exprs expr
+%type <obj> out_clauses out_clauses_opt out_clause
+%type <obj> repeat_clause repeat_parts_opt o_line
+%type <obj> o_elems_opt o_elems_opt2 o_elems o_elem rep_elem rep_parts_opt
+%type <obj> regex regexpr regbranch
+%type <obj> regterm regclass regclassterm regrange
+%type <chr> regchar
+
+%nonassoc ALL SOME NONE MAYBE AND OR END COLLECT UNTIL COLL
+%nonassoc OUTPUT REPEAT REP FIRST LAST EMPTY
+%nonassoc '{' '}' '[' ']' '(' ')'
+%right IDENT TEXT NUMBER
+%left '|' '/'
+%right '*' '?' '+'
+%right '^' '.' '\\' REGCHAR
+
+%%
+
+spec : clauses { parsed_spec = $1; }
+ | { parsed_spec = nil; }
+ | error { parsed_spec = nil;
+ yybadtoken(yychar, 0); }
+ ;
+
+clauses : clause { $$ = cons($1, nil); }
+ | clause clauses { $$ = cons($1, $2); }
+ ;
+
+clause : all_clause { $$ = list(num(lineno - 1), $1, nao); }
+ | some_clause { $$ = list(num(lineno - 1), $1, nao); }
+ | none_clause { $$ = list(num(lineno - 1), $1, nao); }
+ | maybe_clause { $$ = list(num(lineno - 1), $1, nao); }
+ | collect_clause { $$ = list(num(lineno - 1), $1, nao); }
+ | output_clause { $$ = list(num(lineno - 1), $1, nao); }
+ | line { $$ = $1; }
+ | repeat_clause { $$ = nil;
+ yyerror("repeat outside of output"); }
+ ;
+
+all_clause : ALL newl clause_parts { $$ = cons(all, $3); }
+ | ALL newl error { $$ = nil;
+ yybadtoken(yychar,
+ "all clause"); }
+ | ALL newl END { $$ = nil;
+ yyerror("empty all clause"); }
+
+ ;
+
+some_clause : SOME newl clause_parts { $$ = cons(some, $3); }
+ | SOME newl error { $$ = nil;
+ yybadtoken(yychar,
+ "some clause"); }
+ | SOME newl END { $$ = nil;
+ yyerror("empty some clause"); }
+ ;
+
+none_clause : NONE newl clause_parts { $$ = cons(none, $3); }
+ | NONE newl error { $$ = nil;
+ yybadtoken(yychar,
+ "none clause"); }
+ | NONE newl END { $$ = nil;
+ yyerror("empty none clause"); }
+ ;
+
+maybe_clause : MAYBE newl clause_parts { $$ = cons(maybe, $3); }
+ | MAYBE newl error { $$ = nil;
+ yybadtoken(yychar,
+ "maybe clause"); }
+ | MAYBE newl END { $$ = nil;
+ yyerror("empty maybe clause"); }
+ ;
+
+collect_clause : COLLECT newl clauses END newl { $$ = list(collect, $3, nao); }
+ | COLLECT newl clauses
+ UNTIL newl clauses END newl { $$ = list(collect, $3,
+ $6, nao); }
+ | COLLECT newl error { $$ = nil;
+ if (yychar == UNTIL || yychar == END)
+ yyerror("empty collect");
+ else
+ yybadtoken(yychar,
+ "collect clause"); }
+ ;
+
+clause_parts : clauses additional_parts { $$ = cons($1, $2); }
+ ;
+
+additional_parts : END newl { $$ = nil; }
+ | AND newl clauses additional_parts { $$ = cons($3, $4); }
+ | OR newl clauses additional_parts { $$ = cons($3, $4); }
+ ;
+
+line : elems_opt '\n' { $$ = $1; }
+ ;
+
+elems_opt : elems { $$ = cons(num(lineno - 1), $1); }
+ | { $$ = nil; }
+ ;
+
+elems : elem { $$ = cons($1, nil); }
+ | elem elems { $$ = cons($1, $2); }
+ | rep_elem { $$ = nil;
+ yyerror("rep outside of output"); }
+ ;
+
+elem : TEXT { $$ = string($1); }
+ | var { $$ = $1; }
+ | list { $$ = $1; }
+ | regex { $$ = cons(regex_compile($1), $1); }
+ | COLL elems END { $$ = list(coll, $2, nao); }
+ | COLL elems
+ UNTIL elems END { $$ = list(coll, $2, $4, nao); }
+ | COLL error { $$ = nil;
+ yybadtoken(yychar, "coll clause"); }
+ ;
+
+output_clause : OUTPUT o_elems '\n'
+ out_clauses
+ END newl { $$ = list(output, $4, $2, nao); }
+ | OUTPUT newl
+ out_clauses
+ END newl { $$ = list(output, $3, nao); }
+ | OUTPUT o_elems '\n'
+ error { $$ = nil;
+ yybadtoken(yychar, "output clause"); }
+ | OUTPUT newl
+ error { $$ = nil;
+ yybadtoken(yychar, "output clause"); }
+ ;
+
+out_clauses : out_clause { $$ = cons($1, nil); }
+ | out_clause out_clauses { $$ = cons($1, $2); }
+ ;
+
+out_clause : repeat_clause { $$ = list(num(lineno - 1), $1, nao); }
+ | o_line { $$ = $1; }
+ | all_clause { $$ = nil;
+ yyerror("match clause in output"); }
+ | some_clause { $$ = nil;
+ yyerror("match clause in output"); }
+ | none_clause { $$ = nil;
+ yyerror("match clause in output"); }
+ | maybe_clause { $$ = nil;
+ yyerror("match clause in output"); }
+ | collect_clause { $$ = nil;
+ yyerror("match clause in output"); }
+ | output_clause { $$ = nil;
+ yyerror("match clause in output"); }
+ ;
+
+repeat_clause : REPEAT newl
+ out_clauses
+ repeat_parts_opt
+ END newl { $$ = repeat_rep_helper(repeat, $3, $4); }
+ | REPEAT newl
+ error { $$ = nil;
+ yybadtoken(yychar, "repeat clause"); }
+ ;
+
+repeat_parts_opt : SINGLE newl
+ out_clauses_opt
+ repeat_parts_opt { $$ = cons(cons(single, $3), $4); }
+ | FIRST newl
+ out_clauses_opt
+ repeat_parts_opt { $$ = cons(cons(frst, $3), $4); }
+ | LAST newl
+ out_clauses_opt
+ repeat_parts_opt { $$ = cons(cons(lst, $3), $4); }
+ | EMPTY newl
+ out_clauses_opt
+ repeat_parts_opt { $$ = cons(cons(empty, $3), $4); }
+ | /* empty */ { $$ = nil; }
+ ;
+
+
+out_clauses_opt : out_clauses { $$ = $1; }
+ | /* empty */ { $$ = null_list; }
+
+o_line : o_elems_opt '\n' { $$ = $1; }
+ ;
+
+o_elems_opt : o_elems { $$ = cons(num(lineno - 1), $1); }
+ | { $$ = nil; }
+ ;
+
+o_elems_opt2 : o_elems { $$ = $1; }
+ | { $$ = null_list; }
+ ;
+
+o_elems : o_elem { $$ = cons($1, nil); }
+ | o_elem o_elems { $$ = cons($1, $2); }
+ ;
+
+o_elem : TEXT { $$ = string($1); }
+ | var { $$ = $1; }
+ | rep_elem { $$ = $1; }
+ ;
+
+rep_elem : REP o_elems
+ rep_parts_opt END { $$ = repeat_rep_helper(rep, $2, $3); }
+ | REP error { $$ = nil; yybadtoken(yychar, "rep clause"); }
+ ;
+
+rep_parts_opt : SINGLE o_elems_opt2
+ rep_parts_opt { $$ = cons(cons(single, $2), $3); }
+ | FIRST o_elems_opt2
+ rep_parts_opt { $$ = cons(cons(frst, $2), $3); }
+ | LAST o_elems_opt2
+ rep_parts_opt { $$ = cons(cons(lst, $2), $3); }
+ | EMPTY o_elems_opt2
+ rep_parts_opt { $$ = cons(cons(empty, $2), $3); }
+ | /* empty */ { $$ = nil; }
+ ;
+
+
+/* This sucks, but factoring '*' into a nonterminal
+ * that generates an empty phrase causes reduce/reduce conflicts.
+ */
+var : IDENT { $$ = list(var, intern(string($1)), nao); }
+ | IDENT elem { $$ = list(var, intern(string($1)), $2, nao); }
+ | '{' IDENT '}' { $$ = list(var, intern(string($2)), nao); }
+ | '{' IDENT '}' elem { $$ = list(var, intern(string($2)), $4, nao); }
+ | '{' IDENT regex '}' { $$ = list(var, intern(string($2)),
+ nil, cons(regex_compile($3), $3),
+ nao); }
+ | '{' IDENT NUMBER '}' { $$ = list(var, intern(string($2)),
+ nil, num($3), nao); }
+ | var_op IDENT { $$ = list(var, intern(string($2)),
+ nil, $1, nao); }
+ | var_op IDENT elem { $$ = list(var, intern(string($2)),
+ $3, $1, nao); }
+ | var_op '{' IDENT '}' { $$ = list(var, intern(string($3)),
+ nil, $1, nao); }
+ | var_op '{' IDENT '}' elem { $$ = list(var, intern(string($3)),
+ $5, $1, nao); }
+ | IDENT error { $$ = nil;
+ yybadtoken(yychar, "variable spec"); }
+ | var_op error { $$ = nil;
+ yybadtoken(yychar, "variable spec"); }
+ ;
+
+var_op : '*' { $$ = t; }
+ ;
+
+list : '(' exprs ')' { $$ = $2; }
+ | '(' ')' { $$ = nil; }
+ | '(' error { $$ = nil;
+ yybadtoken(yychar, "list expression"); }
+ ;
+
+exprs : expr { $$ = cons($1, nil); }
+ | expr exprs { $$ = cons($1, $2); }
+ | expr '.' expr { $$ = cons($1, $3); }
+ ;
+
+expr : IDENT { $$ = intern(string($1)); }
+ | NUMBER { $$ = num($1); }
+ | list { $$ = $1; }
+ | regex { $$ = cons(regex_compile($1), $1); }
+ ;
+
+regex : '/' regexpr '/' { $$ = $2; }
+ | '/' '/' { $$ = nil; }
+ | '/' error { $$ = nil;
+ yybadtoken(yychar, "regex"); }
+ ;
+
+regexpr : regbranch { $$ = $1; }
+ | regbranch '|' regbranch { $$ = list(list(or, $1,
+ $3, nao), nao); }
+ ;
+
+regbranch : regterm { $$ = cons($1, nil); }
+ | regterm regbranch { $$ = cons($1, $2); }
+ ;
+
+regterm : '[' regclass ']' { $$ = cons(set, $2); }
+ | '[' '^' regclass ']' { $$ = cons(cset, $3); }
+ | '.' { $$ = wild; }
+ | '^' { $$ = chr('^'); }
+ | ']' { $$ = chr(']'); }
+ | '-' { $$ = chr('-'); }
+ | regterm '*' { $$ = list(zeroplus, $1, nao); }
+ | regterm '+' { $$ = list(oneplus, $1, nao); }
+ | regterm '?' { $$ = list(optional, $1, nao); }
+ | REGCHAR { $$ = chr($1); }
+ | '(' regexpr ')' { $$ = cons(compound, $2); }
+ | '(' error { $$ = nil;
+ yybadtoken(yychar, "regex subexpression"); }
+ | '[' error { $$ = nil;
+ yybadtoken(yychar, "regex character class"); }
+ ;
+
+regclass : regclassterm { $$ = cons($1, nil); }
+ | regclassterm regclass { $$ = cons($1, $2); }
+ ;
+
+regclassterm : regrange { $$ = $1; }
+ | regchar { $$ = chr($1); }
+ ;
+
+regrange : regchar '-' regchar { $$ = cons(chr($1), chr($3)); }
+
+regchar : '?' { $$ = '?'; }
+ | '.' { $$ = '.'; }
+ | '*' { $$ = '*'; }
+ | '+' { $$ = '+'; }
+ | '(' { $$ = '('; }
+ | ')' { $$ = ')'; }
+ | '^' { $$ = '^'; }
+ | '|' { $$ = '|'; }
+ | REGCHAR { $$ = $1; }
+ ;
+
+newl : '\n'
+ | error '\n' { yyerror("newline expected after directive");
+ yyerrok; }
+ ;
+
+%%
+
+obj_t *repeat_rep_helper(obj_t *sym, obj_t *main, obj_t *parts)
+{
+ obj_t *single_parts = nil;
+ obj_t *first_parts = nil;
+ obj_t *last_parts = nil;
+ obj_t *empty_parts = nil;
+ obj_t *iter;
+
+ for (iter = parts; iter != nil; iter = cdr(iter)) {
+ obj_t *part = car(iter);
+ obj_t *sym = car(part);
+ obj_t *clauses = cdr(part);
+
+ if (sym == single)
+ single_parts = nappend2(single_parts, clauses);
+ else if (sym == frst)
+ first_parts = nappend2(first_parts, clauses);
+ else if (sym == lst)
+ last_parts = nappend2(last_parts, clauses);
+ else if (sym == empty)
+ empty_parts = nappend2(empty_parts, clauses);
+ else
+ abort();
+ }
+
+ return list(sym, main, single_parts, first_parts,
+ last_parts, empty_parts, nao);
+}
+
+obj_t *get_spec(void)
+{
+ return parsed_spec;
+}
+
+void dump_shell_string(const char *str)
+{
+ int ch;
+
+ putchar('"');
+ while ((ch = *str++) != 0) {
+ switch (ch) {
+ case '"': case '`': case '$': case '\\': case '\n':
+ putchar('\\');
+ /* fallthrough */
+ default:
+ putchar(ch);
+ }
+ }
+ putchar('"');
+}
+
+void dump_var(const char *name, char *pfx1, size_t len1,
+ char *pfx2, size_t len2, obj_t *value, int level)
+{
+ if (len1 >= 112 || len2 >= 112)
+ abort();
+
+ if (stringp(value)) {
+ fputs(name, stdout);
+ fputs(pfx1, stdout);
+ fputs(pfx2, stdout);
+ putchar('=');
+ dump_shell_string(c_str(value));
+ putchar('\n');
+ } else {
+ obj_t *iter;
+ int i;
+ size_t add1 = 0, add2 = 0;
+
+ for (i = 0, iter = value; iter; iter = cdr(iter), i++) {
+ if (level < opt_arraydims) {
+ add2 = sprintf(pfx2 + len2, "[%d]", i);
+ add1 = 0;
+ } else {
+ add1 = sprintf(pfx1 + len1, "_%d", i);
+ add2 = 0;
+ }
+
+ dump_var(name, pfx1, len1 + add1, pfx2, len2 + add2, car(iter), level + 1);
+ }
+ }
+}
+
+void dump_bindings(obj_t *bindings)
+{
+ if (opt_loglevel >= 2) {
+ fputs("raw_bindings:\n", stderr);
+ dump(bindings, stderr);
+ }
+
+ while (bindings) {
+ char pfx1[128], pfx2[128];
+ obj_t *var = car(car(bindings));
+ obj_t *value = cdr(car(bindings));
+ const char *name = c_str(symbol_name(var));
+ *pfx1 = 0; *pfx2 = 0;
+ dump_var(name, pfx1, 0, pfx2, 0, value, 0);
+ bindings = cdr(bindings);
+ }
+}
+
+obj_t *depth(obj_t *obj)
+{
+ obj_t *dep = zero;
+
+ if (obj == nil)
+ return one;
+
+ if (atom(obj))
+ return zero;
+
+ while (obj) {
+ dep = max2(dep, depth(first(obj)));
+ obj = rest(obj);
+ }
+
+ return plus(dep, one);
+}
+
+obj_t *merge(obj_t *left, obj_t *right)
+{
+ obj_t *left_depth = depth(left);
+ obj_t *right_depth = depth(right);
+
+ while (lt(left_depth, right_depth) || zerop(left_depth)) {
+ left = cons(left, nil);
+ left_depth = plus(left_depth, one);
+ }
+
+ while (lt(right_depth, left_depth) || zerop(right_depth)) {
+ right = cons(right, nil);
+ right_depth = plus(right_depth, one);
+ }
+
+ return append2(left, right);
+}
+
+obj_t *dest_bind(obj_t *bindings, obj_t *pattern, obj_t *value)
+{
+ if (nullp(pattern))
+ return bindings;
+
+ if (symbolp(pattern)) {
+ obj_t *existing = assoc(bindings, pattern);
+ if (existing) {
+ if (tree_find(value, cdr(existing)))
+ return bindings;
+ if (tree_find(cdr(existing), value))
+ return bindings;
+ yyerrorf(2, "bind variable mismatch: %s", c_str(symbol_name(pattern)));
+ return t;
+ }
+ return cons(cons(pattern, value), bindings);
+ }
+
+ if (consp(pattern)) {
+ obj_t *piter = pattern, *viter = value;
+
+ while (consp(piter) && consp(viter))
+ {
+ bindings = dest_bind(bindings, car(piter), car(viter));
+ if (bindings == t)
+ return t;
+ piter = cdr(piter);
+ viter = cdr(viter);
+ } while (consp(piter) && consp(viter));
+
+ if (symbolp(piter)) {
+ bindings = dest_bind(bindings, piter, viter);
+ if (bindings == t)
+ return t;
+ }
+ }
+
+ return bindings;
+}
+
+obj_t *match_line(obj_t *bindings, obj_t *specline, obj_t *dataline,
+ obj_t *pos, obj_t *spec_lineno, obj_t *data_lineno,
+ obj_t *file)
+{
+#define LOG_MISMATCH(KIND) \
+ yyerrorlf(2, c_num(spec_lineno), \
+ "%s mismatch, position %ld (%s:%ld)", (KIND), c_num(pos), \
+ c_str(file), c_num(data_lineno)); \
+ yyerrorlf(2, c_num(spec_lineno), " %s", c_str(dataline)); \
+ if (c_num(pos) < 77) \
+ yyerrorlf(2, c_num(spec_lineno), " %*s^", (int) c_num(pos), "")
+
+#define LOG_MATCH(KIND, EXTENT) \
+ yyerrorlf(2, c_num(spec_lineno), \
+ "%s matched, position %ld-%ld (%s:%ld)", (KIND), \
+ c_num(pos), c_num(EXTENT), c_str(file), \
+ c_num(data_lineno)); \
+ yyerrorlf(2, c_num(spec_lineno), " %s", c_str(dataline)); \
+ if (c_num(EXTENT) < 77) \
+ yyerrorlf(2, c_num(spec_lineno), " %*s%-*s^", (int) c_num(pos), \
+ "", (int) (c_num(EXTENT) - c_num(pos)), "^")
+ for (;;) {
+ obj_t *elem;
+
+ if (specline == nil)
+ break;
+
+ elem = first(specline);
+
+ switch (elem ? elem->t.type : 0) {
+ case CONS: /* directive */
+ {
+ obj_t *directive = first(elem);
+
+ if (directive == var) {
+ obj_t *sym = second(elem);
+ obj_t *pat = third(elem);
+ obj_t *modifier = fourth(elem);
+ obj_t *pair = assoc(bindings, sym); /* var exists already? */
+
+ if (pair) {
+ /* If the variable already has a binding, we replace
+ it with its value, and treat it as a string match.
+ The spec looks like ((var <sym> <pat>) ...)
+ and it must be transformed into
+ (<sym-substituted> <pat> ...) */
+ if (pat) {
+ specline = cons(cdr(pair), cons(pat, rest(specline)));
+ } else if (nump(modifier)) {
+ obj_t *past = plus(pos, modifier);
+
+ if (c_num(past) > c_num(length_str(dataline)) ||
+ c_num(past) < c_num(pos))
+ {
+ LOG_MISMATCH("fixed field size");
+ return nil;
+ }
+
+ if (!tree_find(trim_str(sub_str(dataline, pos, past)),
+ cdr(pair)))
+ {
+ LOG_MISMATCH("fixed field contents");
+ return nil;
+ }
+
+ LOG_MATCH("fixed field", past);
+ pos = past;
+ specline = cdr(specline);
+ } else {
+ specline = cons(cdr(pair), rest(specline));
+ }
+ continue;
+ } else if (pat == nil) { /* match to end of line or with regex */
+ if (consp(modifier)) {
+ obj_t *past = match_regex(dataline, car(modifier), pos);
+ if (nullp(past)) {
+ LOG_MISMATCH("var positive regex");
+ return nil;
+ }
+ LOG_MATCH("var positive regex", past);
+ bindings = acons_new(bindings, sym, sub_str(dataline, pos, past));
+ pos = past;
+ } else if (nump(modifier)) {
+ obj_t *past = plus(pos, modifier);
+ if (c_num(past) > c_num(length_str(dataline)) ||
+ c_num(past) < c_num(pos))
+ {
+ LOG_MISMATCH("count based var");
+ return nil;
+ }
+ LOG_MATCH("count based var", past);
+ bindings = acons_new(bindings, sym, trim_str(sub_str(dataline, pos, past)));
+ pos = past;
+ } else {
+ bindings = acons_new(bindings, sym, sub_str(dataline, pos, nil));
+ pos = length_str(dataline);
+ }
+ } else if (pat->t.type == STR) {
+ obj_t *find = search_str(dataline, pat, pos, modifier);
+ if (!find) {
+ LOG_MISMATCH("var delimiting string");
+ return nil;
+ }
+ LOG_MATCH("var delimiting string", find);
+ bindings = acons_new(bindings, sym, sub_str(dataline, pos, find));
+ pos = plus(find, length_str(pat));
+ } else if (consp(pat) && typeof(first(pat)) == regex) {
+ obj_t *find = search_regex(dataline, first(pat), pos, modifier);
+ obj_t *fpos = car(find);
+ obj_t *flen = cdr(find);
+ if (!find) {
+ LOG_MISMATCH("var delimiting regex");
+ return nil;
+ }
+ LOG_MATCH("var delimiting regex", fpos);
+ bindings = acons_new(bindings, sym, sub_str(dataline, pos, fpos));
+ pos = plus(fpos, flen);
+ } else if (consp(pat) && first(pat) == var) {
+ /* Unbound var followed by var: the following one must be bound. */
+ obj_t *second_sym = second(pat);
+ obj_t *next_pat = third(pat);
+ obj_t *pair = assoc(bindings, second_sym); /* var exists already? */
+
+ if (!pair) {
+ yyerrorlf(1, c_num(spec_lineno), "consecutive unbound variables");
+ return nil;
+ }
+
+ /* Re-generate a new spec with an edited version of
+ the element we just processed, and repeat. */
+ {
+ obj_t *new_elem = list(var, sym, cdr(pair), modifier, nao);
+
+ if (next_pat)
+ specline = cons(new_elem, cons(next_pat, rest(specline)));
+ else
+ specline = cons(new_elem, rest(specline));
+ }
+
+ continue;
+ } else if (consp(pat) && (consp(first(pat)) || stringp(first(pat)))) {
+ cons_bind (find, len, search_str(dataline, pat, pos, modifier));
+ if (!find) {
+ LOG_MISMATCH("string");
+ return nil;
+ }
+ bindings = acons_new(bindings, sym, sub_str(dataline, pos, find));
+ pos = plus(find, len);
+ } else {
+ yyerrorlf(0, c_num(spec_lineno), "variable followed by invalid element");
+ return nil;
+ }
+ } else if (typeof(directive) == regex) {
+ obj_t *past = match_regex(dataline, directive, pos);
+ if (nullp(past)) {
+ LOG_MISMATCH("regex");
+ return nil;
+ }
+ LOG_MATCH("regex", past);
+ pos = past;
+ } else if (directive == coll) {
+ obj_t *coll_specline = second(elem);
+ obj_t *until_specline = third(elem);
+ obj_t *bindings_coll = nil;
+ obj_t *iter;
+
+ for (;;) {
+ cons_bind (new_bindings, new_pos,
+ match_line(bindings, coll_specline, dataline, pos,
+ spec_lineno, data_lineno, file));
+
+ if (new_pos) {
+ LOG_MATCH("coll", new_pos);
+
+ for (iter = new_bindings; iter && iter != bindings;
+ iter = cdr(iter))
+ {
+ obj_t *binding = car(iter);
+ obj_t *existing = assoc(bindings_coll, car(binding));
+
+ bindings_coll = acons_new(bindings_coll, car(binding),
+ cons(cdr(binding), cdr(existing)));
+ }
+ }
+
+ if (until_specline) {
+ cons_bind (until_bindings, until_pos,
+ match_line(bindings, until_specline, dataline, pos,
+ spec_lineno, data_lineno, file));
+
+ (void) until_bindings;
+ if (until_pos) {
+ /* The until specline matched. Special behavior:
+ We throw away its bindings, and run it again.
+ We run it again by incorporating it into the
+ surrouding specline, just behind the collect
+ item, which will be popped off. */
+ LOG_MATCH("until", until_pos);
+ (void) new_bindings;
+ specline = cons(first(specline),
+ append2(until_specline, rest(specline)));
+ break;
+ }
+ LOG_MISMATCH("until");
+ }
+
+ if (new_pos && !equal(new_pos, pos)) {
+ pos = new_pos;
+ assert (c_num(pos) <= c_num(length_str(dataline)));
+ } else {
+ pos = plus(pos, one);
+ }
+
+ if (c_num(pos) >= c_num(length_str(dataline)))
+ break;
+ }
+
+
+ if (!bindings_coll)
+ yyerrorlf(2, c_num(spec_lineno), "nothing was collected");
+
+ for (iter = bindings_coll; iter; iter = cdr(iter)) {
+ obj_t *pair = car(iter);
+ obj_t *rev = cons(car(pair), nreverse(cdr(pair)));
+ bindings = cons(rev, bindings);
+ }
+ } else if (consp(directive) || stringp(directive)) {
+ cons_bind (find, len, search_str_tree(dataline, elem, pos, nil));
+ obj_t *newpos;
+
+ if (find == nil || !equal(find, pos)) {
+ LOG_MISMATCH("string tree");
+ return nil;
+ }
+
+ newpos = plus(find, len);
+ LOG_MATCH("string tree", newpos);
+ pos = newpos;
+ } else {
+ yyerrorlf(0, c_num(spec_lineno), "unknown directive: %s",
+ c_str(symbol_name(directive)));
+ }
+ }
+ break;
+ case STR:
+ {
+ obj_t *find = search_str(dataline, elem, pos, nil);
+ obj_t *newpos;
+ if (find == nil || !equal(find, pos)) {
+ LOG_MISMATCH("string");
+ return nil;
+ }
+ newpos = plus(find, length_str(elem));
+ LOG_MATCH("string", newpos);
+ pos = newpos;
+ break;
+ }
+ default:
+ yyerrorlf(0, c_num(spec_lineno), "unsupported object in spec");
+ }
+
+ specline = cdr(specline);
+ }
+
+ return cons(bindings, pos);
+}
+
+obj_t *format_field(obj_t *string_or_list, obj_t *spec)
+{
+ if (!stringp(string_or_list))
+ return string_or_list;
+
+ {
+ obj_t *right = lt(spec, zero);
+ obj_t *width = if3(lt(spec, zero), neg(spec), spec);
+ obj_t *diff = minus(width, length_str(string_or_list));
+
+ if (le(diff, zero))
+ return string_or_list;
+
+ if (ge(length_str(string_or_list), width))
+ return string_or_list;
+
+ {
+ obj_t *padding = mkstring(diff, chr(' '));
+
+ return if3(right,
+ cat_str(list(padding, string_or_list, nao), nil),
+ cat_str(list(string_or_list, padding, nao), nil));
+ }
+ }
+}
+
+obj_t *subst_vars(obj_t *spec, obj_t *bindings)
+{
+ list_collect_decl(out, iter);
+
+ while (spec) {
+ obj_t *elem = first(spec);
+
+ if (consp(elem) && first(elem) == var) {
+ obj_t *sym = second(elem);
+ obj_t *pat = third(elem);
+ obj_t *modifier = fourth(elem);
+ obj_t *pair = assoc(bindings, sym);
+
+ if (pair) {
+ if (pat)
+ spec = cons(cdr(pair), cons(pat, rest(spec)));
+ else if (nump(modifier))
+ spec = cons(format_field(cdr(pair), modifier), rest(spec));
+ else
+ spec = cons(cdr(pair), rest(spec));
+ continue;
+ }
+ }
+
+ list_collect(iter, elem);
+ spec = cdr(spec);
+ }
+
+ return out;
+}
+
+typedef struct fpip {
+ FILE *f;
+ DIR *d;
+ enum { fpip_fclose, fpip_pclose, fpip_closedir } close;
+} fpip_t;
+
+fpip_t complex_open(obj_t *name, obj_t *output)
+{
+ fpip_t ret = { 0 };
+
+ const char *namestr = c_str(name);
+ long len = c_num(length_str(name));
+
+ if (len == 0)
+ return ret;
+
+ if (!strcmp(namestr, "-")) {
+ ret.close = fpip_fclose;
+ ret.f = output ? stdout : stdin;
+ output_produced = output ? 1 : 0;
+ } else if (namestr[0] == '!') {
+ ret.close = fpip_pclose;
+ ret.f = popen(namestr+1, output ? "w" : "r");
+ } else if (namestr[0] == '$') {
+ if (output)
+ return ret;
+ ret.close = fpip_closedir;
+ ret.d = opendir(namestr+1);
+ } else {
+ ret.close = fpip_fclose;
+ ret.f = fopen(namestr, output ? "w" : "r");
+ }
+
+ return ret;
+}
+
+int complex_open_failed(fpip_t fp)
+{
+ return fp.f == 0 && fp.d == 0;
+}
+
+void complex_close(fpip_t fp)
+{
+ if (fp.f == 0)
+ return;
+ switch (fp.close) {
+ case fpip_fclose:
+ if (fp.f != stdin && fp.f != stdout)
+ fclose(fp.f);
+ return;
+ case fpip_pclose:
+ pclose(fp.f);
+ return;
+ case fpip_closedir:
+ closedir(fp.d);
+ return;
+ }
+
+ abort();
+}
+
+obj_t *complex_snarf(fpip_t fp, obj_t *name)
+{
+ switch (fp.close) {
+ case fpip_fclose:
+ return lazy_stream_cons(stdio_line_stream(fp.f, name));
+ case fpip_pclose:
+ return lazy_stream_cons(pipe_line_stream(fp.f, name));
+ case fpip_closedir:
+ return lazy_stream_cons(dirent_stream(fp.d, name));
+ }
+
+ abort();
+}
+
+obj_t *robust_length(obj_t *obj)
+{
+ if (obj == nil)
+ return zero;
+ if (atom(obj))
+ return negone;
+ return length(obj);
+}
+
+obj_t *bind_car(obj_t *bind_cons)
+{
+ return if3(consp(cdr(bind_cons)),
+ cons(car(bind_cons), car(cdr(bind_cons))),
+ bind_cons);
+}
+
+obj_t *bind_cdr(obj_t *bind_cons)
+{
+ return if3(consp(cdr(bind_cons)),
+ cons(car(bind_cons), cdr(cdr(bind_cons))),
+ bind_cons);
+}
+
+obj_t *extract_vars(obj_t *output_spec)
+{
+ list_collect_decl (vars, tai);
+
+ if (consp(output_spec)) {
+ if (first(output_spec) == var) {
+ list_collect (tai, second(output_spec));
+ } else {
+ for (; output_spec; output_spec = cdr(output_spec))
+ list_collect_nconc(tai, extract_vars(car(output_spec)));
+ }
+ }
+
+ return vars;
+}
+
+obj_t *extract_bindings(obj_t *bindings, obj_t *output_spec)
+{
+ list_collect_decl (bindings_out, tail);
+ obj_t *var_list = extract_vars(output_spec);
+
+ for (; bindings; bindings = cdr(bindings))
+ if (memq(car(car(bindings)), var_list))
+ list_collect(tail, car(bindings));
+
+ return bindings_out;
+}
+
+void do_output_line(obj_t *bindings, obj_t *specline,
+ obj_t *spec_lineno, FILE *out)
+{
+ for (; specline; specline = rest(specline)) {
+ obj_t *elem = first(specline);
+
+ switch (elem ? elem->t.type : 0) {
+ case CONS:
+ {
+ obj_t *directive = first(elem);
+
+ if (directive == var) {
+ obj_t *str = cat_str(subst_vars(cons(elem, nil), bindings), nil);
+ if (str == nil) {
+ yyerrorlf(1, c_num(spec_lineno), "bad substitution: %s",
+ c_str(symbol_name(second(elem))));
+ continue;
+ }
+ fputs(c_str(str), out);
+ } else if (directive == rep) {
+ obj_t *main_clauses = second(elem);
+ obj_t *single_clauses = third(elem);
+ obj_t *first_clauses = fourth(elem);
+ obj_t *last_clauses = fifth(elem);
+ obj_t *empty_clauses = sixth(elem);
+ obj_t *bind_cp = extract_bindings(bindings, elem);
+ obj_t *max_depth = reduce_left(func_n2(max2),
+ bind_cp, zero,
+ chain(list(func_n1(cdr),
+ func_n1(robust_length),
+ nao)));
+
+ if (equal(max_depth, zero) && empty_clauses) {
+ do_output_line(bindings, empty_clauses, spec_lineno, out);
+ } else if (equal(max_depth, one) && single_clauses) {
+ obj_t *bind_a = mapcar(func_n1(bind_car), bind_cp);
+ do_output_line(bind_a, single_clauses, spec_lineno, out);
+ } else if (!zerop(max_depth)) {
+ long i;
+
+ for (i = 0; i < c_num(max_depth); i++) {
+ obj_t *bind_a = mapcar(func_n1(bind_car), bind_cp);
+ obj_t *bind_d = mapcar(func_n1(bind_cdr), bind_cp);
+
+ if (i == 0 && first_clauses) {
+ do_output_line(bind_a, first_clauses, spec_lineno, out);
+ } else if (i == c_num(max_depth) - 1 && last_clauses) {
+ do_output_line(bind_a, last_clauses, spec_lineno, out);
+ } else {
+ do_output_line(bind_a, main_clauses, spec_lineno, out);
+ }
+
+ bind_cp = bind_d;
+ }
+ }
+
+ } else {
+ yyerrorlf(0, c_num(spec_lineno), "unknown directive: %s",
+ c_str(symbol_name(directive)));
+ }
+ }
+ break;
+ case STR:
+ fputs(c_str(elem), out);
+ break;
+ case 0:
+ break;
+ default:
+ yyerrorlf(0, c_num(spec_lineno), "unsupported object in output spec");
+ }
+ }
+}
+
+void do_output(obj_t *bindings, obj_t *specs, FILE *out)
+{
+ if (equal(specs, null_list))
+ return;
+
+ for (; specs; specs = cdr(specs)) {
+ cons_bind (spec_lineno, specline, first(specs));
+ obj_t *first_elem = first(specline);
+
+ if (consp(first_elem)) {
+ obj_t *sym = first(first_elem);
+
+ if (sym == repeat) {
+ obj_t *main_clauses = second(first_elem);
+ obj_t *single_clauses = third(first_elem);
+ obj_t *first_clauses = fourth(first_elem);
+ obj_t *last_clauses = fifth(first_elem);
+ obj_t *empty_clauses = sixth(first_elem);
+ obj_t *bind_cp = extract_bindings(bindings, first_elem);
+ obj_t *max_depth = reduce_left(func_n2(max2),
+ bind_cp, zero,
+ chain(list(func_n1(cdr),
+ func_n1(robust_length),
+ nao)));
+
+ if (equal(max_depth, zero) && empty_clauses) {
+ do_output(bind_cp, empty_clauses, out);
+ } else if (equal(max_depth, one) && single_clauses) {
+ obj_t *bind_a = mapcar(func_n1(bind_car), bind_cp);
+ do_output(bind_a, single_clauses, out);
+ } else if (!zerop(max_depth)) {
+ long i;
+
+ for (i = 0; i < c_num(max_depth); i++) {
+ obj_t *bind_a = mapcar(func_n1(bind_car), bind_cp);
+ obj_t *bind_d = mapcar(func_n1(bind_cdr), bind_cp);
+
+ if (i == 0 && first_clauses) {
+ do_output(bind_a, first_clauses, out);
+ } else if (i == c_num(max_depth) - 1 && last_clauses) {
+ do_output(bind_a, last_clauses, out);
+ } else {
+ do_output(bind_a, main_clauses, out);
+ }
+
+ bind_cp = bind_d;
+ }
+ }
+ continue;
+ }
+ }
+
+ do_output_line(bindings, specline, spec_lineno, out);
+ putc('\n', out);
+ }
+}
+
+obj_t *match_files(obj_t *spec, obj_t *files,
+ obj_t *bindings, obj_t *first_file_parsed,
+ obj_t *data_linenum)
+{
+ obj_t *data = nil;
+ long data_lineno = 0;
+
+ if (first_file_parsed) {
+ data = first_file_parsed;
+ data_lineno = c_num(data_linenum);
+ first_file_parsed = nil;
+ } else if (files) {
+ obj_t *name = first(files);
+ fpip_t fp = (errno = 0, complex_open(name, nil));
+
+ yyerrorf(2, "opening data source %s", c_str(name));
+
+ if (complex_open_failed(fp)) {
+ if (errno != 0)
+ yyerrorf(2, "could not open %s: %s", c_str(name), strerror(errno));
+ else
+ yyerrorf(2, "could not open %s", c_str(name));
+ return nil;
+ }
+
+ if ((data = complex_snarf(fp, name)) != nil)
+ data_lineno = 1;
+ }
+
+ for (; spec; spec = rest(spec), data = rest(data), data_lineno++)
+repeat_spec_same_data:
+ {
+ obj_t *specline = rest(first(spec));
+ obj_t *dataline = first(data);
+ obj_t *spec_linenum = first(first(spec));
+ obj_t *first_spec = first(specline);
+ long spec_lineno = spec_linenum ? c_num(spec_linenum) : 0;
+
+ if (consp(first_spec)) {
+ obj_t *sym = first(first_spec);
+
+ if (sym == skip) {
+ obj_t *max = first(rest(first_spec));
+ long cmax = nump(max) ? c_num(max) : 0;
+ long reps = 0;
+
+ if (rest(specline))
+ yyerrorlf(1, spec_lineno, "material after skip directive ignored");
+
+ if ((spec = rest(spec)) == nil)
+ break;
+
+ {
+ uw_block_begin(nil, result);
+
+ while (dataline && (!max || reps++ < cmax)) {
+ cons_bind (new_bindings, success,
+ match_files(spec, files, bindings,
+ data, num(data_lineno)));
+
+ if (success) {
+ yyerrorlf(2, spec_lineno, "skip matched %s:%ld",
+ c_str(first(files)), data_lineno);
+ result = cons(new_bindings, cons(data, num(data_lineno)));
+ break;
+ }
+
+ yyerrorlf(2, spec_lineno, "skip didn't match %s:%ld",
+ c_str(first(files)), data_lineno);
+ data = rest(data);
+ data_lineno++;
+ dataline = first(data);
+ }
+
+ uw_block_end;
+
+ if (result)
+ return result;
+ }
+
+ yyerrorlf(2, spec_lineno, "skip failed");
+ return nil;
+ } else if (sym == block) {
+ obj_t *name = first(rest(first_spec));
+ if (rest(specline))
+ yyerrorlf(1, spec_lineno, "material after block directive ignored");
+ if ((spec = rest(spec)) == nil)
+ break;
+ uw_block_begin(name, result);
+ result = match_files(spec, files, bindings, data, num(data_lineno));
+ uw_block_end;
+ return result;
+ } else if (sym == fail || sym == accept) {
+ obj_t *target = first(rest(first_spec));
+
+ if (rest(specline))
+ yyerrorlf(1, spec_lineno, "material after %s ignored",
+ c_str(symbol_name(sym)));
+
+ uw_block_return(target,
+ if2(sym == accept,
+ cons(bindings,
+ if3(data, cons(data, num(data_lineno)), t))));
+ if (target)
+ yyerrorlf(1, spec_lineno, "%s: no block named %s in scope",
+ c_str(symbol_name(sym)), c_str(symbol_name(target)));
+ else
+ yyerrorlf(1, spec_lineno, "%s: not anonymous block in scope",
+ c_str(symbol_name(sym)));
+
+ return nil;
+ } else if (sym == next) {
+ if (rest(first_spec))
+ yyerrorlf(0, spec_lineno, "next takes no args");
+
+ if ((spec = rest(spec)) == nil)
+ break;
+
+ if (rest(specline)) {
+ obj_t *sub = subst_vars(rest(specline), bindings);
+ obj_t *str = cat_str(sub, nil);
+ if (str == nil) {
+ yyerrorlf(2, spec_lineno, "bad substitution in next file spec");
+ continue;
+ }
+ files = cons(str, files);
+ } else {
+ files = rest(files);
+ }
+
+ /* We recursively process the file list, but the new
+ data position we return to the caller must be in the
+ original file we we were called with. Hence, we can't
+ make a straight tail call here. */
+ {
+ cons_bind (new_bindings, success,
+ match_files(spec, files, bindings, nil, nil));
+ if (success)
+ return cons(new_bindings,
+ if3(data, cons(data, num(data_lineno)), t));
+ return nil;
+ }
+ } else if (sym == some || sym == all || sym == none || sym == maybe) {
+ obj_t *specs;
+ obj_t *all_match = t;
+ obj_t *some_match = nil;
+ obj_t *max_line = zero;
+ obj_t *max_data = nil;
+
+ for (specs = rest(first_spec); specs != nil; specs = rest(specs))
+ {
+ obj_t *nested_spec = first(specs);
+ obj_t *data_linenum = num(data_lineno);
+
+ cons_bind (new_bindings, success,
+ match_files(nested_spec, files, bindings,
+ data, data_linenum));
+
+ if (success) {
+ bindings = new_bindings;
+ some_match = t;
+
+ if (success == t) {
+ max_data = t;
+ } else if (consp(success) && max_data != t) {
+ cons_bind (new_data, new_line, success);
+ if (gt(new_line, max_line)) {
+ max_line = new_line;
+ max_data = new_data;
+ }
+ }
+ } else {
+ all_match = nil;
+ }
+ }
+
+ if (sym == all && !all_match) {
+ yyerrorlf(2, spec_lineno, "all: some clauses didn't match");
+ return nil;
+ }
+
+ if (sym == some && !some_match) {
+ yyerrorlf(2, spec_lineno, "some: no clauses matched");
+ return nil;
+ }
+
+ if (sym == none && some_match) {
+ yyerrorlf(2, spec_lineno, "none: some clauses matched");
+ return nil;
+ }
+
+ /* No check for maybe, since it always succeeds. */
+
+ if (consp(max_data)) {
+ data_lineno = c_num(max_line);
+ data = max_data;
+ } else if (max_data == t) {
+ data = nil;
+ }
+
+ if ((spec = rest(spec)) == nil)
+ break;
+
+ goto repeat_spec_same_data;
+ } else if (sym == collect) {
+ obj_t *coll_spec = second(first_spec);
+ obj_t *until_spec = third(first_spec);
+ obj_t *bindings_coll = nil;
+ obj_t *iter;
+
+ uw_block_begin(nil, result);
+
+ result = t;
+
+ while (data) {
+ cons_bind (new_bindings, success,
+ match_files(coll_spec, files, bindings,
+ data, num(data_lineno)));
+
+ if (success) {
+ yyerrorlf(2, spec_lineno, "collect matched %s:%ld",
+ c_str(first(files)), data_lineno);
+
+ for (iter = new_bindings; iter && iter != bindings;
+ iter = cdr(iter))
+ {
+ obj_t *binding = car(iter);
+ obj_t *existing = assoc(bindings_coll, car(binding));
+
+ bindings_coll = acons_new(bindings_coll, car(binding),
+ cons(cdr(binding), cdr(existing)));
+ }
+ }
+
+ /* Until clause sees un-collated bindings from collect. */
+ if (until_spec)
+ {
+ cons_bind (discarded_bindings, success,
+ match_files(until_spec, files, new_bindings,
+ data, num(data_lineno)));
+
+ if (success) {
+ /* The until spec matched. Special behavior:
+ We throw away its bindings, and run it again.
+ We run it again by incorporating it into the
+ surrouding spec, just behind the topmost one.
+ When we bail out of this loop, the first(spec)
+ will be popped, exposing the until_spec,
+ and then the main loop is repeated. */
+ (void) discarded_bindings;
+ spec = cons(first(spec), append2(until_spec, rest(spec)));
+ break;
+ }
+ }
+
+ if (success) {
+ if (consp(success)) {
+ yyerrorlf(2, spec_lineno,
+ "collect advancing from line %ld to %ld",
+ data_lineno, c_num(cdr(success)));
+ data = car(success);
+ data_lineno = c_num(cdr(success));
+ } else {
+ yyerrorlf(2, spec_lineno, "collect consumed entire file");
+ data = nil;
+ break;
+ }
+ } else {
+ data = rest(data);
+ data_lineno++;
+ }
+ }
+
+ uw_block_end;
+
+ if (!result) {
+ yyerrorlf(2, spec_lineno, "collect explicitly failed");
+ return nil;
+ }
+
+ if (!bindings_coll)
+ yyerrorlf(2, spec_lineno, "nothing was collected");
+
+ for (iter = bindings_coll; iter; iter = cdr(iter)) {
+ obj_t *pair = car(iter);
+ obj_t *rev = cons(car(pair), nreverse(cdr(pair)));
+ bindings = cons(rev, bindings);
+ }
+
+ if ((spec = rest(spec)) == nil)
+ break;
+
+ goto repeat_spec_same_data;
+ } else if (sym == flattn) {
+ obj_t *iter;
+
+ for (iter = rest(first_spec); iter; iter = rest(iter)) {
+ obj_t *sym = first(iter);
+
+ if (!symbolp(sym)) {
+ yyerrorlf(1, spec_lineno, "non-symbol in flatten directive");
+ continue;
+ } else {
+ obj_t *existing = assoc(bindings, sym);
+
+ if (existing)
+ *cdr_l(existing) = flatten(cdr(existing));
+ }
+ }
+
+ if ((spec = rest(spec)) == nil)
+ break;
+
+ goto repeat_spec_same_data;
+ } else if (sym == forget) {
+ bindings = alist_remove(bindings, rest(first_spec));
+
+ if ((spec = rest(spec)) == nil)
+ break;
+
+ goto repeat_spec_same_data;
+ } else if (sym == mrge) {
+ obj_t *target = first(rest(first_spec));
+ obj_t *args = rest(rest(first_spec));
+ obj_t *exists = assoc(bindings, target);
+ obj_t *merged = nil;
+
+ if (!target || !symbolp(target))
+ yyerrorlf(1, spec_lineno, "bad merge directive");
+
+ if (exists)
+ yyerrorlf(1, spec_lineno, "merge: symbol %s already bound",
+ c_str(symbol_name(target)));
+
+ for (; args; args = rest(args)) {
+ obj_t *other_sym = first(args);
+
+ if (other_sym) {
+ obj_t *other_lookup = assoc(bindings, other_sym);
+
+ if (!symbolp(other_sym))
+ yyerrorlf(1, spec_lineno, "non-symbol in merge directive");
+ else if (!other_lookup)
+ yyerrorlf(1, spec_lineno, "merge: nonexistent symbol %s",
+ c_str(symbol_name(sym)));
+
+ if (merged)
+ merged = merge(merged, cdr(other_lookup));
+ else
+ merged = cdr(other_lookup);
+ }
+ }
+
+ bindings = acons_new(bindings, target, merged);
+
+ if ((spec = rest(spec)) == nil)
+ break;
+
+ goto repeat_spec_same_data;
+ } else if (sym == bind) {
+ obj_t *args = rest(first_spec);
+ obj_t *pattern = first(args);
+ obj_t *var = second(args);
+ obj_t *lookup = assoc(bindings, var);
+
+ if (!var || !symbolp(var))
+ yyerrorlf(1, spec_lineno, "bind: bad variable spec");
+ else if (!lookup)
+ yyerrorlf(1, spec_lineno, "bind: unbound source variable");
+
+ bindings = dest_bind(bindings, pattern, cdr(lookup));
+
+ if (bindings == t)
+ return nil;
+
+ if ((spec = rest(spec)) == nil)
+ break;
+
+ goto repeat_spec_same_data;
+ } else if (sym == cat) {
+ obj_t *iter;
+
+ for (iter = rest(first_spec); iter; iter = rest(iter)) {
+ obj_t *sym = first(iter);
+
+ if (!symbolp(sym)) {
+ yyerrorlf(1, spec_lineno, "non-symbol in cat directive");
+ continue;
+ } else {
+ obj_t *existing = assoc(bindings, sym);
+ obj_t *sep = nil;
+
+ if (rest(specline)) {
+ obj_t *sub = subst_vars(rest(specline), bindings);
+ sep = cat_str(sub, nil);
+ }
+
+ if (existing)
+ *cdr_l(existing) = cat_str(flatten(cdr(existing)), sep);
+ }
+ }
+
+ if ((spec = rest(spec)) == nil)
+ break;
+
+ goto repeat_spec_same_data;
+ } else if (sym == output) {
+ obj_t *specs = second(first_spec);
+ obj_t *dest_opt = third(first_spec);
+ obj_t *dest = dest_opt ? cat_str(subst_vars(dest_opt, bindings), nil)
+ : string(chk_strdup("-"));
+ fpip_t fp = (errno = 0, complex_open(dest, t));
+
+ yyerrorf(2, "opening data sink %s", c_str(dest));
+
+ if (complex_open_failed(fp)) {
+ if (errno != 0)
+ yyerrorf(2, "could not open %s: %s", c_str(dest), strerror(errno));
+ else
+ yyerrorf(2, "could not open %s", c_str(dest));
+ } else {
+ do_output(bindings, specs, fp.f);
+ complex_close(fp);
+ }
+
+ if ((spec = rest(spec)) == nil)
+ break;
+
+ goto repeat_spec_same_data;
+ }
+ }
+
+ if (dataline == nil)
+ return nil;
+
+ {
+ cons_bind (new_bindings, success,
+ match_line(bindings, specline, dataline, zero,
+ spec_linenum, num(data_lineno), first(files)));
+
+ if (nump(success) && c_num(success) < c_num(length_str(dataline))) {
+ yyerrorf(2, "spec only matches line to position %ld: %s",
+ c_num(success), c_str(dataline));
+ return nil;
+ }
+
+ if (!success)
+ return nil;
+
+ bindings = new_bindings;
+ }
+ }
+
+ return cons(bindings, if3(data, cons(data, num(data_lineno)), t));
+}
+
+int extract(obj_t *spec, obj_t *files, obj_t *predefined_bindings)
+{
+ cons_bind (bindings, success, match_files(spec, files, predefined_bindings,
+ nil, nil));
+
+ if (!output_produced) {
+ if (!opt_nobindings) {
+ if (bindings) {
+ bindings = nreverse(bindings);
+ dump_bindings(bindings);
+ }
+ }
+
+ if (!success)
+ puts("false");
+ }
+
+ return success ? 0 : EXIT_FAILURE;
+}
diff --git a/gc.c b/gc.c
new file mode 100644
index 00000000..2b98887b
--- /dev/null
+++ b/gc.c
@@ -0,0 +1,368 @@
+/* Copyright 2009
+ * Kaz Kylheku <kkylheku@gmail.com>
+ * Vancouver, Canada
+ * All rights reserved.
+ *
+ * BSD License:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <setjmp.h>
+#include <dirent.h>
+#include "lib.h"
+#include "gc.h"
+
+#define PROT_STACK_SIZE 1024
+#define HEAP_SIZE 16384
+#define REACHABLE 0x100
+#define FREE 0x200
+
+typedef struct heap {
+ struct heap *next;
+ obj_t block[HEAP_SIZE];
+} heap_t;
+
+int opt_gc_debug;
+obj_t **gc_stack_top;
+
+static obj_t **prot_stack[PROT_STACK_SIZE];
+static obj_t ***prot_stack_limit = prot_stack + PROT_STACK_SIZE;
+static obj_t ***top = prot_stack;
+
+static obj_t *free_list, **free_tail = &free_list;
+static heap_t *heap_list;
+
+int gc_enabled = 1;
+
+obj_t *prot1(obj_t **loc)
+{
+ assert (top < prot_stack_limit);
+ *top++ = loc;
+ return nil; /* for use in macros */
+}
+
+void rel1(obj_t **loc)
+{
+ /* protect and release calls must nest. */
+ if (*--top != loc)
+ abort();
+}
+
+void protect(obj_t **first, ...)
+{
+ obj_t **next = first;
+ va_list vl;
+ va_start (vl, first);
+
+ while (next) {
+ prot1(next);
+ next = va_arg(vl, obj_t **);
+ }
+
+ va_end (vl);
+}
+
+void release(obj_t **last, ...)
+{
+ obj_t **next = last;
+ va_list vl;
+ va_start (vl, last);
+
+ while (next) {
+ rel1(next);
+ next = va_arg(vl, obj_t **);
+ }
+
+ va_end (vl);
+}
+
+static void more()
+{
+ heap_t *heap = (heap_t *) chk_malloc(sizeof *heap);
+ obj_t *block = heap->block, *end = heap->block + HEAP_SIZE;
+
+ while (block < end) {
+ block->t.next = free_list;
+ block->t.type = FREE;
+ free_list = block++;
+ }
+
+ free_tail = &block[-1].t.next;
+
+ heap->next = heap_list;
+ heap_list = heap;
+}
+
+obj_t *make_obj(void)
+{
+ int try;
+
+ if (opt_gc_debug)
+ gc();
+
+ for (try = 0; try < 3; try++) {
+ if (free_list) {
+ obj_t *ret = free_list;
+ free_list = free_list->t.next;
+ return ret;
+ }
+
+ free_tail = &free_list;
+
+ switch (try) {
+ case 0: gc(); break;
+ case 1: more(); break;
+ }
+ }
+
+ return 0;
+}
+
+static void finalize(obj_t *obj)
+{
+ switch (obj->t.type) {
+ case CONS:
+ break;
+ case STR:
+ if (!opt_gc_debug) {
+ free(obj->st.str);
+ obj->st.str = 0;
+ }
+ break;
+ case CHR:
+ case NUM:
+ case SYM:
+ case FUN:
+ break;
+ case VEC:
+ if (!opt_gc_debug) {
+ free(obj->v.vec-2);
+ obj->v.vec = 0;
+ }
+ break;
+ case STREAM:
+ stream_close(obj);
+ break;
+ case LCONS:
+ break;
+ case COBJ:
+ obj->co.ops->destroy(obj);
+ break;
+ default:
+ assert (0 && "corrupt type field");
+ }
+}
+
+static void mark_obj(obj_t *obj)
+{
+ type_t t;
+
+ if (obj == nil)
+ return;
+
+ t = obj->t.type;
+
+ if ((t & REACHABLE) != 0)
+ return;
+
+ if ((t & FREE) != 0)
+ abort();
+
+ obj->t.type |= REACHABLE;
+
+ switch (t) {
+ case CONS:
+ mark_obj(obj->c.car);
+ mark_obj(obj->c.cdr);
+ break;
+ case STR:
+ mark_obj(obj->st.len);
+ break;
+ case CHR:
+ case NUM:
+ break;
+ case SYM:
+ mark_obj(obj->s.name);
+ mark_obj(obj->s.val);
+ break;
+ case FUN:
+ mark_obj(obj->f.env);
+ if (obj->f.functype == FINTERP)
+ mark_obj(obj->f.f.interp_fun);
+ break;
+ case VEC:
+ {
+ obj_t *alloc_size = obj->v.vec[-2];
+ obj_t *fill_ptr = obj->v.vec[-1];
+ long i, fp = c_num(fill_ptr);
+
+ mark_obj(alloc_size);
+ mark_obj(fill_ptr);
+
+ for (i = 0; i < fp; i++)
+ mark_obj(obj->v.vec[i]);
+ }
+ break;
+ case STREAM:
+ mark_obj(obj->sm.label_pushback);
+ break;
+ case LCONS:
+ mark_obj(obj->lc.car);
+ mark_obj(obj->lc.cdr);
+ mark_obj(obj->lc.func);
+ break;
+ case COBJ:
+ mark_obj(obj->co.cls);
+ break;
+ default:
+ assert (0 && "corrupt type field");
+ }
+}
+
+static int in_heap(obj_t *ptr)
+{
+ heap_t *heap;
+
+ for (heap = heap_list; heap != 0; heap = heap->next) {
+ if (ptr >= heap->block && ptr < heap->block + HEAP_SIZE)
+ if (((char *) ptr - (char *) heap->block) % sizeof (obj_t) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+static void mark_mem_region(obj_t **bottom, obj_t **top)
+{
+ if (bottom > top) {
+ obj_t **tmp = top;
+ top = bottom;
+ bottom = tmp;
+ }
+
+ while (bottom < top) {
+ obj_t *maybe_obj = *bottom;
+ if (in_heap(maybe_obj)) {
+ type_t t = maybe_obj->t.type;
+ if ((t & FREE) == 0)
+ mark_obj(maybe_obj);
+ }
+ bottom++;
+ }
+}
+
+static void mark(void)
+{
+ obj_t *gc_stack_bottom;
+ obj_t ***rootloc;
+
+ /*
+ * First, scan the officially registered locations.
+ */
+
+ for (rootloc = prot_stack; rootloc != top; rootloc++) {
+ if (*rootloc) /* stack may have nulls */
+ mark_obj(**rootloc);
+ }
+
+ mark_mem_region(&gc_stack_bottom, gc_stack_top);
+}
+
+static void sweep(void)
+{
+ heap_t *heap;
+ int dbg = opt_gc_debug;
+ long freed = 0;
+
+ for (heap = heap_list; heap != 0; heap = heap->next) {
+ obj_t *block, *end;
+ for (block = heap->block, end = heap->block + HEAP_SIZE;
+ block < end;
+ block++)
+ {
+ if (block->t.type & REACHABLE) {
+ block->t.type &= ~REACHABLE;
+ continue;
+ }
+
+ if (block->t.type & FREE)
+ continue;
+
+ if (0 && dbg) {
+ fprintf(stderr, "%s: finalizing: ", progname);
+ obj_print(block, stderr);
+ putc('\n', stderr);
+ }
+ finalize(block);
+ block->t.type |= FREE;
+ if (dbg) {
+ *free_tail = block;
+ block->t.next = nil;
+ free_tail = &block->t.next;
+ } else {
+ block->t.next = free_list;
+ free_list = block;
+ }
+ freed++;
+ }
+ }
+
+ if (dbg)
+ fprintf(stderr, "%s: gc freed %ld blocks\n", progname, freed);
+}
+
+void gc(void)
+{
+ if (gc_enabled) {
+ jmp_buf jmp;
+ setjmp(jmp);
+ mark();
+ sweep();
+ }
+}
+
+int gc_state(int enabled)
+{
+ int old = gc_enabled;
+ gc_enabled = enabled;
+ return old;
+}
+
+/*
+ * Useful functions for gdb'ing.
+ */
+void unmark(void)
+{
+ heap_t *heap;
+
+ for (heap = heap_list; heap != 0; heap = heap->next) {
+ obj_t *block, *end;
+ for (block = heap->block, end = heap->block + HEAP_SIZE;
+ block < end;
+ block++)
+ {
+ block->t.type &= ~(FREE | REACHABLE);
+ }
+ }
+}
diff --git a/gc.h b/gc.h
new file mode 100644
index 00000000..aaafbec2
--- /dev/null
+++ b/gc.h
@@ -0,0 +1,36 @@
+/* Copyright 2009
+ * Kaz Kylheku <kkylheku@gmail.com>
+ * Vancouver, Canada
+ * All rights reserved.
+ *
+ * BSD License:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+extern int opt_gc_debug;
+extern obj_t **gc_stack_top;
+
+obj_t *prot1(obj_t **loc);
+void rel1(obj_t **loc);
+void protect(obj_t **, ...);
+void release(obj_t **, ...);
+obj_t *make_obj(void);
+void gc(void);
+int gc_state(int);
diff --git a/lib.c b/lib.c
new file mode 100644
index 00000000..348b54f3
--- /dev/null
+++ b/lib.c
@@ -0,0 +1,1609 @@
+/* Copyright 2009
+ * Kaz Kylheku <kkylheku@gmail.com>
+ * Vancouver, Canada
+ * All rights reserved.
+ *
+ * BSD License:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <dirent.h>
+#include "lib.h"
+#include "gc.h"
+
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+obj_t *interned_syms;
+
+obj_t *null, *t, *cons_t, *str_t, *chr_t, *num_t, *sym_t, *fun_t, *vec_t;
+obj_t *stream_t, *lcons_t, *var, *regex, *set, *cset, *wild, *oneplus;
+obj_t *zeroplus, *optional, *compound, *or;
+obj_t *skip, *block, *next, *fail, *accept;
+obj_t *all, *some, *none, *maybe, *collect, *until, *coll;
+obj_t *output, *single, *frst, *lst, *empty, *repeat, *rep;
+obj_t *flattn, *forget, *mrge, *bind, *cat, *dir;
+
+obj_t *zero, *one, *two, *negone, *maxint, *minint;
+obj_t *null_string;
+obj_t *nil_string;
+obj_t *null_list;
+
+obj_t *identity_f;
+obj_t *equal_f;
+
+const char *progname;
+void *(*oom_realloc)(void *, size_t);
+
+
+obj_t *identity(obj_t *obj)
+{
+ return obj;
+}
+
+static obj_t *identity_tramp(obj_t *env, obj_t *obj)
+{
+ (void) env;
+ return identity(obj);
+}
+
+static obj_t *equal_tramp(obj_t *env, obj_t *, obj_t *);
+
+obj_t *typeof(obj_t *obj)
+{
+ if (obj == nil)
+ return null;
+ switch (obj->t.type) {
+ case CONS: return cons_t;
+ case STR: return str_t;
+ case CHR: return chr_t;
+ case NUM: return num_t;
+ case SYM: return sym_t;
+ case FUN: return fun_t;
+ case VEC: return vec_t;
+ case STREAM: return stream_t;
+ case LCONS: return lcons_t;
+ case COBJ: return obj->co.cls;
+ }
+ assert (0 && "corrupt type field");
+}
+
+obj_t *car(obj_t *cons)
+{
+ if (cons == nil)
+ return nil;
+ else switch (cons->t.type) {
+ case CONS:
+ return cons->c.car;
+ case LCONS:
+ if (cons->lc.func == nil) {
+ return cons->lc.car;
+ } else {
+ if (!funcall1(cons->lc.func, cons))
+ return nil;
+ return cons->lc.car;
+ }
+ default:
+ assert (0 && "corrupt type field");
+ }
+}
+
+obj_t *cdr(obj_t *cons)
+{
+ if (cons == nil)
+ return nil;
+ else switch (cons->t.type) {
+ case CONS:
+ return cons->c.cdr;
+ case LCONS:
+ if (cons->lc.func == nil) {
+ return cons->lc.cdr;
+ } else {
+ if (!funcall1(cons->lc.func, cons))
+ return nil;
+ return cons->lc.cdr;
+ }
+ default:
+ assert (0 && "corrupt type field");
+ }
+}
+
+obj_t **car_l(obj_t *cons)
+{
+ switch (cons->t.type) {
+ case CONS:
+ return &cons->c.car;
+ case LCONS:
+ funcall1(cons->lc.func, cons);
+ return &cons->lc.car;
+ default:
+ assert (0 && "corrupt type field");
+ }
+}
+
+obj_t **cdr_l(obj_t *cons)
+{
+ switch (cons->t.type) {
+ case CONS:
+ return &cons->c.cdr;
+ case LCONS:
+ funcall1(cons->lc.func, cons);
+ return &cons->lc.cdr;
+ default:
+ assert (0 && "corrupt type field");
+ }
+}
+
+obj_t *first(obj_t *cons)
+{
+ return car(cons);
+}
+
+obj_t *rest(obj_t *cons)
+{
+ return cdr(cons);
+}
+
+obj_t *second(obj_t *cons)
+{
+ return car(cdr(cons));
+}
+
+obj_t *third(obj_t *cons)
+{
+ return car(cdr(cdr(cons)));
+}
+
+obj_t *fourth(obj_t *cons)
+{
+ return car(cdr(cdr(cdr(cons))));
+}
+
+obj_t *fifth(obj_t *cons)
+{
+ return car(cdr(cdr(cdr(cdr(cons)))));
+}
+
+obj_t *sixth(obj_t *cons)
+{
+ return car(cdr(cdr(cdr(cdr(cdr(cons))))));
+}
+
+obj_t **tail(obj_t *cons)
+{
+ while (cdr(cons))
+ cons = cdr(cons);
+ return cdr_l(cons);
+}
+
+obj_t *copy_list(obj_t *list)
+{
+ list_collect_decl (out, tail);
+
+ while (consp(list)) {
+ list_collect(tail, car(list));
+ list = cdr(list);
+ }
+
+ list_collect_terminate(tail, list);
+
+ return out;
+}
+
+obj_t *nreverse(obj_t *in)
+{
+ obj_t *rev = nil;
+
+ while (in) {
+ obj_t *temp = cdr(in);
+ *cdr_l(in) = rev;
+ rev = in;
+ in = temp;
+ }
+
+ return rev;
+}
+
+obj_t *reverse(obj_t *in)
+{
+ obj_t *rev = nil;
+
+ while (in) {
+ rev = cons(car(in), rev);
+ in = cdr(in);
+ }
+
+ return rev;
+}
+
+obj_t *append2(obj_t *list1, obj_t *list2)
+{
+ list_collect_decl (out, tail);
+
+ while (list1) {
+ list_collect(tail, car(list1));
+ list1 = cdr(list1);
+ }
+
+ list_collect_terminate(tail, list2);
+ return out;
+}
+
+obj_t *nappend2(obj_t *list1, obj_t *list2)
+{
+ obj_t *temp, *iter;
+
+ if (list1 == nil)
+ return list2;
+
+ for (iter = list1; (temp = cdr(iter)) != nil; iter = temp)
+ ; /* empty */
+
+ *cdr_l(iter) = list2;
+ return list1;
+}
+
+obj_t *flatten_helper(obj_t *env, obj_t *item)
+{
+ return flatten(item);
+}
+
+obj_t *memq(obj_t *obj, obj_t *list)
+{
+ while (list && car(list) != obj)
+ list = cdr(list);
+ return list;
+}
+
+obj_t *tree_find(obj_t *obj, obj_t *tree)
+{
+ if (equal(obj, tree))
+ return t;
+ else if (consp(tree))
+ return some_satisfy(tree, bind2(func_n2(tree_find), obj), nil);
+ return nil;
+}
+
+obj_t *some_satisfy(obj_t *list, obj_t *pred, obj_t *key)
+{
+ if (!key)
+ key = identity_f;
+
+ for (; list; list = cdr(list)) {
+ if (funcall1(pred, funcall1(key, car(list))))
+ return t;
+ }
+
+ return nil;
+}
+
+obj_t *flatten(obj_t *list)
+{
+ if (atom(list))
+ return cons(list, nil);
+
+ return mappend(func_f1(nil, flatten_helper), list);
+}
+
+long c_num(obj_t *num);
+
+obj_t *equal(obj_t *left, obj_t *right)
+{
+ if (left == nil && right == nil)
+ return t;
+
+ if (left == nil || right == nil)
+ return nil;
+
+ switch (left->t.type) {
+ case CONS:
+ case LCONS:
+ if ((right->t.type == CONS || left->t.type == LCONS) &&
+ equal(car(left), car(right)) &&
+ equal(cdr(left), cdr(right)))
+ {
+ return t;
+ }
+ return nil;
+ case STR:
+ if (right->t.type == STR &&
+ strcmp(left->st.str, right->st.str) == 0)
+ return t;
+ return nil;
+ case CHR:
+ if (right->t.type == CHR &&
+ left->ch.ch == right->ch.ch)
+ return t;
+ return nil;
+ case NUM:
+ if (right->t.type == NUM &&
+ left->n.val == right->n.val)
+ return t;
+ return nil;
+ case SYM:
+ return right == left ? t : nil;
+ case FUN:
+ if (right->t.type == FUN &&
+ left->f.functype == right->f.functype)
+ {
+ switch (left->f.functype) {
+ case FINTERP: return (equal(left->f.f.interp_fun, right->f.f.interp_fun));
+ case F0: return (left->f.f.f0 == right->f.f.f0) ? t : nil;
+ case F1: return (left->f.f.f1 == right->f.f.f1) ? t : nil;
+ case F2: return (left->f.f.f2 == right->f.f.f2) ? t : nil;
+ case F3: return (left->f.f.f3 == right->f.f.f3) ? t : nil;
+ case F4: return (left->f.f.f4 == right->f.f.f4) ? t : nil;
+ case N0: return (left->f.f.n0 == right->f.f.n0) ? t : nil;
+ case N1: return (left->f.f.n1 == right->f.f.n1) ? t : nil;
+ case N2: return (left->f.f.n2 == right->f.f.n2) ? t : nil;
+ case N3: return (left->f.f.n3 == right->f.f.n3) ? t : nil;
+ case N4: return (left->f.f.n4 == right->f.f.n4) ? t : nil;
+ }
+ return nil;
+ }
+ case VEC:
+ if (right->t.type == VEC) {
+ long i, fill;
+ if (!equal(left->v.vec[vec_fill], right->v.vec[vec_fill]))
+ return nil;
+ fill = c_num(left->v.vec[vec_fill]);
+ for (i = 0; i < fill; i++) {
+ if (!equal(left->v.vec[i], right->v.vec[i]))
+ return nil;
+ }
+ return t;
+ }
+ break;
+ case STREAM:
+ return nil; /* Different stream objects never equal. */
+ case COBJ:
+ if (right->t.type == COBJ)
+ return left->co.ops->equal(left, right);
+ }
+
+ assert (0 && "notreached");
+ return nil;
+}
+
+static obj_t *equal_tramp(obj_t *env, obj_t *left, obj_t *right)
+{
+ (void) env;
+ return equal(left, right);
+}
+
+void *chk_malloc(size_t size)
+{
+ void *ptr = malloc(size);
+ if (size && ptr == 0)
+ ptr = oom_realloc(0, size);
+ return ptr;
+}
+
+void *chk_realloc(void *old, size_t size)
+{
+ void *newptr = realloc(old, size);
+ if (size != 0 && newptr == 0)
+ newptr = oom_realloc(old, size);
+ return newptr;
+}
+
+void *chk_strdup(const char *str)
+{
+ size_t size = strlen(str) + 1;
+ char *copy = chk_malloc(size);
+ memcpy(copy, str, size);
+ return copy;
+}
+
+
+obj_t *cons(obj_t *car, obj_t *cdr)
+{
+ obj_t *obj = make_obj();
+ obj->c.type = CONS;
+ obj->c.car = car;
+ obj->c.cdr = cdr;
+ return obj;
+}
+
+obj_t *list(obj_t *first, ...)
+{
+ va_list vl;
+ obj_t *list = nil;
+ obj_t *array[32], **ptr = array;
+
+ if (first != nao) {
+ obj_t *next = first;
+
+ va_start (vl, first);
+
+ do {
+ *ptr++ = next;
+ if (ptr == array + 32)
+ abort();
+ next = va_arg(vl, obj_t *);
+ } while (next != nao);
+
+ while (ptr > array)
+ list = cons(*--ptr, list);
+ }
+
+ return list;
+}
+
+obj_t *consp(obj_t *obj)
+{
+ if (!obj)
+ return nil;
+ return (obj->t.type == CONS || obj->t.type == LCONS) ? t : nil;
+}
+
+obj_t *nullp(obj_t *obj)
+{
+ return obj == 0 ? t : nil;
+}
+
+obj_t *atom(obj_t *obj)
+{
+ return (obj == nil || (obj->t.type != CONS && obj->t.type != LCONS))
+ ? t : nil;
+}
+
+obj_t *listp(obj_t *obj)
+{
+ return (obj == nil || obj->t.type == CONS || obj->t.type == LCONS)
+ ? t : nil;
+}
+
+obj_t *length(obj_t *list)
+{
+ long len = 0;
+ while (consp(list)) {
+ len++;
+ list = cdr(list);
+ }
+ return num(len);
+}
+
+obj_t *num(long val)
+{
+ obj_t *obj = make_obj();
+ obj->n.type = NUM;
+ obj->n.val = val;
+ return obj;
+}
+
+long c_num(obj_t *num)
+{
+ assert (num && num->t.type == NUM);
+ return num->n.val;
+}
+
+obj_t *nump(obj_t *num)
+{
+ return (num && num->n.type == NUM) ? t : nil;
+}
+
+obj_t *plus(obj_t *anum, obj_t *bnum)
+{
+ long a = c_num(anum);
+ long b = c_num(bnum);
+
+ assert (a <= 0 || b <= 0 || LONG_MAX - b >= a);
+ assert (a >= 0 || b >= 0 || LONG_MIN - b >= a);
+
+ return num(a + b);
+}
+
+obj_t *minus(obj_t *anum, obj_t *bnum)
+{
+ long a = c_num(anum);
+ long b = c_num(bnum);
+
+ assert (b != LONG_MIN || LONG_MIN == -LONG_MAX);
+ assert (a <= 0 || -b <= 0 || LONG_MAX + b >= a);
+ assert (a >= 0 || -b >= 0 || LONG_MIN + b >= a);
+
+ return num(a - b);
+}
+
+obj_t *neg(obj_t *anum)
+{
+ long n = c_num(anum);
+ return num(-n);
+}
+
+obj_t *zerop(obj_t *num)
+{
+ return c_num(num) == 0 ? t : nil;
+}
+
+obj_t *gt(obj_t *anum, obj_t *bnum)
+{
+ return c_num(anum) > c_num(bnum) ? t : nil;
+}
+
+obj_t *lt(obj_t *anum, obj_t *bnum)
+{
+ return c_num(anum) < c_num(bnum) ? t : nil;
+}
+
+obj_t *ge(obj_t *anum, obj_t *bnum)
+{
+ return c_num(anum) >= c_num(bnum) ? t : nil;
+}
+
+obj_t *le(obj_t *anum, obj_t *bnum)
+{
+ return c_num(anum) <= c_num(bnum) ? t : nil;
+}
+
+obj_t *numeq(obj_t *anum, obj_t *bnum)
+{
+ return c_num(anum) == c_num(bnum) ? t : nil;
+}
+
+obj_t *max2(obj_t *anum, obj_t *bnum)
+{
+ return c_num(anum) > c_num(bnum) ? anum : bnum;
+}
+
+obj_t *min2(obj_t *anum, obj_t *bnum)
+{
+ return c_num(anum) < c_num(bnum) ? anum : bnum;
+}
+
+obj_t *string(char *str)
+{
+ obj_t *obj = make_obj();
+ obj->st.type = STR;
+ obj->st.str = str;
+ obj->st.len = nil;
+ return obj;
+}
+
+obj_t *mkstring(obj_t *len, obj_t *ch)
+{
+ char *str = chk_malloc(c_num(len) + 1);
+ memset(str, c_chr(ch), c_num(len));
+ str[c_num(len)] = 0;
+ return string(str);
+}
+
+obj_t *copy_str(obj_t *str)
+{
+ return string(strdup(c_str(str)));
+}
+
+obj_t *stringp(obj_t *str)
+{
+ return (str && str->st.type == STR) ? t : nil;
+}
+
+obj_t *length_str(obj_t *str)
+{
+ assert (str && str->t.type == STR);
+ if (!str->st.len)
+ str->st.len = num(strlen(str->st.str));
+ return str->st.len;
+}
+
+const char *c_str(obj_t *str)
+{
+ assert (str && str->t.type == STR);
+ return str->st.str;
+}
+
+obj_t *search_str(obj_t *haystack, obj_t *needle, obj_t *start_num,
+ obj_t *from_end)
+{
+ const char *h = c_str(haystack);
+ long len = c_num(length_str(haystack));
+ long start = c_num(start_num);
+
+ if (start > len) {
+ return nil;
+ } else {
+ const char *n = c_str(needle), *good = 0, *pos, *from = h + start;
+
+ do {
+ pos = strstr(from, n);
+ } while (pos && (good = pos) && from_end && *(from = pos + 1));
+ return (good == 0) ? nil : num(good - h);
+ }
+}
+
+obj_t *search_str_tree(obj_t *haystack, obj_t *tree, obj_t *start_num,
+ obj_t *from_end)
+{
+ if (stringp(tree)) {
+ obj_t *result = search_str(haystack, tree, start_num, from_end);
+ if (result)
+ return cons(result, length_str(tree));
+ } else if (consp(tree)) {
+ while (tree) {
+ obj_t *result = search_str_tree(haystack, car(tree), start_num, from_end);
+ if (result)
+ return result;
+ tree = cdr(tree);
+ }
+ }
+
+ return nil;
+}
+
+obj_t *sub_str(obj_t *str_in, obj_t *from_num, obj_t *to_num)
+{
+ const char *str = c_str(str_in);
+ size_t len = c_num(length_str(str_in));
+ long from = c_num(from_num);
+ long to = to_num ? c_num(to_num) : len;
+
+ if (to < 0)
+ to = 0;
+ if (from < 0)
+ from = 0;
+ if (from > len)
+ from = len;
+ if (to > len)
+ to = len;
+
+ if (from >= to) {
+ return null_string;
+ } else {
+ size_t size = to - from + 1;
+ char *sub = chk_malloc(size);
+ strncpy(sub, str + from, size);
+ sub[size-1] = 0;
+ return string(sub);
+ }
+}
+
+obj_t *cat_str(obj_t *list, obj_t *sep)
+{
+ long total = 0;
+ obj_t *iter;
+ char *str, *ptr;
+ long len_sep = sep ? c_num(length_str(sep)) : 0;
+
+ for (iter = list; iter != nil; iter = cdr(iter)) {
+ obj_t *item = car(iter);
+ if (!item)
+ continue;
+ if (!stringp(item))
+ return nil;
+ total += c_num(length_str(item));
+ if (len_sep && cdr(iter))
+ total += len_sep;
+ }
+
+ str = chk_malloc(total + 1);
+
+ for (ptr = str, iter = list; iter != nil; iter = cdr(iter)) {
+ obj_t *item = car(iter);
+ long len;
+ if (!item)
+ continue;
+ len = c_num(length_str(item));
+ memcpy(ptr, c_str(item), len);
+ ptr += len;
+ if (len_sep && cdr(iter)) {
+ memcpy(ptr, c_str(sep), len_sep);
+ ptr += len_sep;
+ }
+ }
+ *ptr = 0;
+
+ return string(str);
+}
+
+obj_t *trim_str(obj_t *str)
+{
+ const char *start = c_str(str);
+ const char *end = start + c_num(length_str(str));
+
+ while (start[0] && isspace(start[0]))
+ start++;
+
+ while (end > start && isspace(end[-1]))
+ end--;
+
+ if (end == start) {
+ return null_string;
+ } else {
+ size_t len = end - start;
+ char *new = chk_malloc(len + 1);
+ memcpy(new, start, len);
+ new[len] = 0;
+ return string(new);
+ }
+}
+
+obj_t *chr(int ch)
+{
+ obj_t *obj = make_obj();
+ obj->ch.type = CHR;
+ obj->ch.ch = ch;
+ return obj;
+}
+
+int c_chr(obj_t *chr)
+{
+ assert (chr && chr->t.type == CHR);
+ return chr->ch.ch;
+}
+
+obj_t *sym_name(obj_t *sym)
+{
+ assert (sym && sym->t.type == SYM);
+ return sym->s.name;
+}
+
+obj_t *make_sym(obj_t *name)
+{
+ obj_t *obj = make_obj();
+ obj->s.type = SYM;
+ obj->s.name = name;
+ obj->s.val = nil;
+ return obj;
+}
+
+obj_t *intern(obj_t *str)
+{
+ obj_t *iter;
+
+ for (iter = interned_syms; iter != nil; iter = cdr(iter)) {
+ obj_t *sym = car(iter);
+ if (equal(sym_name(sym), str))
+ return sym;
+ }
+
+ interned_syms = cons(make_sym(str), interned_syms);
+ return car(interned_syms);
+}
+
+obj_t *symbolp(obj_t *sym)
+{
+ return (sym == nil || sym->s.type == SYM) ? t : nil;
+}
+
+obj_t *symbol_name(obj_t *sym)
+{
+ assert (sym == nil || sym->t.type == SYM);
+ return sym ? sym->s.name : nil_string;
+}
+
+obj_t *func_f0(obj_t *env, obj_t *(*fun)(obj_t *))
+{
+ obj_t *obj = make_obj();
+ obj->f.type = FUN;
+ obj->f.functype = F0;
+ obj->f.env = env;
+ obj->f.f.f0 = fun;
+ return obj;
+}
+
+obj_t *func_f1(obj_t *env, obj_t *(*fun)(obj_t *, obj_t *))
+{
+ obj_t *obj = make_obj();
+ obj->f.type = FUN;
+ obj->f.functype = F1;
+ obj->f.env = env;
+ obj->f.f.f1 = fun;
+ return obj;
+}
+
+obj_t *func_f2(obj_t *env, obj_t *(*fun)(obj_t *, obj_t *, obj_t *))
+{
+ obj_t *obj = make_obj();
+ obj->f.type = FUN;
+ obj->f.functype = F2;
+ obj->f.env = env;
+ obj->f.f.f2 = fun;
+ return obj;
+}
+
+obj_t *func_f3(obj_t *env, obj_t *(*fun)(obj_t *, obj_t *, obj_t *, obj_t *))
+{
+ obj_t *obj = make_obj();
+ obj->f.type = FUN;
+ obj->f.functype = F3;
+ obj->f.env = env;
+ obj->f.f.f3 = fun;
+ return obj;
+}
+
+obj_t *func_f4(obj_t *env, obj_t *(*fun)(obj_t *, obj_t *, obj_t *, obj_t *,
+ obj_t *))
+{
+ obj_t *obj = make_obj();
+ obj->f.type = FUN;
+ obj->f.functype = F4;
+ obj->f.env = env;
+ obj->f.f.f4 = fun;
+ return obj;
+}
+
+obj_t *func_n0(obj_t *(*fun)(void))
+{
+ obj_t *obj = make_obj();
+ obj->f.type = FUN;
+ obj->f.functype = N0;
+ obj->f.env = nil;
+ obj->f.f.n0 = fun;
+ return obj;
+}
+
+obj_t *func_n1(obj_t *(*fun)(obj_t *))
+{
+ obj_t *obj = make_obj();
+ obj->f.type = FUN;
+ obj->f.functype = N1;
+ obj->f.env = nil;
+ obj->f.f.n1 = fun;
+ return obj;
+}
+
+obj_t *func_n2(obj_t *(*fun)(obj_t *, obj_t *))
+{
+ obj_t *obj = make_obj();
+ obj->f.type = FUN;
+ obj->f.functype = N2;
+ obj->f.env = nil;
+ obj->f.f.n2 = fun;
+ return obj;
+}
+
+obj_t *func_n3(obj_t *(*fun)(obj_t *, obj_t *, obj_t *))
+{
+ obj_t *obj = make_obj();
+ obj->f.type = FUN;
+ obj->f.functype = N3;
+ obj->f.f.n3 = fun;
+ return obj;
+}
+
+obj_t *func_n4(obj_t *(*fun)(obj_t *, obj_t *, obj_t *, obj_t *))
+{
+ obj_t *obj = make_obj();
+ obj->f.type = FUN;
+ obj->f.functype = N4;
+ obj->f.f.n4 = fun;
+ return obj;
+}
+
+
+obj_t *apply(obj_t *fun, obj_t *arglist)
+{
+ obj_t *arg[4], **p = arg;
+
+ assert (fun && fun->f.type == FUN);
+ assert (arglist == nil || consp(arglist));
+
+ *p++ = car(arglist); arglist = cdr(arglist);
+ *p++ = car(arglist); arglist = cdr(arglist);
+ *p++ = car(arglist); arglist = cdr(arglist);
+ *p++ = car(arglist); arglist = cdr(arglist);
+
+ switch (fun->f.functype) {
+ case F0:
+ return fun->f.f.f0(fun);
+ case F1:
+ return fun->f.f.f1(fun, arg[0]);
+ case F2:
+ return fun->f.f.f2(fun, arg[0], arg[1]);
+ case F3:
+ return fun->f.f.f3(fun, arg[0], arg[1], arg[2]);
+ case F4:
+ return fun->f.f.f4(fun, arg[0], arg[1], arg[2], arg[3]);
+ case N0:
+ return fun->f.f.n0();
+ case N1:
+ return fun->f.f.n1(arg[0]);
+ case N2:
+ return fun->f.f.n2(arg[0], arg[1]);
+ case N3:
+ return fun->f.f.n3(arg[0], arg[1], arg[2]);
+ case N4:
+ return fun->f.f.n4(arg[0], arg[1], arg[2], arg[3]);
+ case FINTERP:
+ abort();
+ }
+
+ assert (0 && "bad functype");
+}
+
+obj_t *funcall(obj_t *fun)
+{
+ assert (fun && fun->f.type == FUN);
+
+ switch (fun->f.functype) {
+ case F0:
+ return fun->f.f.f0(fun->f.env);
+ case N0:
+ return fun->f.f.n0();
+ default:
+ abort();
+ }
+}
+
+obj_t *funcall1(obj_t *fun, obj_t *arg)
+{
+ assert (fun && fun->f.type == FUN);
+
+ switch (fun->f.functype) {
+ case F1:
+ return fun->f.f.f1(fun->f.env, arg);
+ case N1:
+ return fun->f.f.n1(arg);
+ default:
+ abort();
+ }
+}
+
+obj_t *funcall2(obj_t *fun, obj_t *arg1, obj_t *arg2)
+{
+ assert (fun && fun->f.type == FUN);
+
+ switch (fun->f.functype) {
+ case F2:
+ return fun->f.f.f2(fun->f.env, arg1, arg2);
+ case N2:
+ return fun->f.f.n2(arg1, arg2);
+ default:
+ abort();
+ }
+}
+
+obj_t *reduce_left(obj_t *fun, obj_t *list, obj_t *init, obj_t *key)
+{
+ if (!key)
+ key = identity_f;
+
+ for (; list; list = cdr(list))
+ init = funcall2(fun, init, funcall1(key, car(list)));
+
+ return init;
+}
+
+obj_t *do_bind2(obj_t *fcons, obj_t *arg2)
+{
+ return funcall2(car(fcons), cdr(fcons), arg2);
+}
+
+obj_t *bind2(obj_t *fun2, obj_t *arg)
+{
+ return func_f1(cons(fun2, arg), do_bind2);
+}
+
+static obj_t *do_chain(obj_t *fun1_list, obj_t *arg)
+{
+ for (; fun1_list; fun1_list = cdr(fun1_list))
+ arg = funcall1(car(fun1_list), arg);
+
+ return arg;
+}
+
+obj_t *chain(obj_t *fun1_list)
+{
+ return func_f1(fun1_list, do_chain);
+}
+
+obj_t *vector(obj_t *alloc)
+{
+ long alloc_plus = c_num(alloc) + 2;
+ obj_t *vec = make_obj();
+ obj_t **v = chk_malloc(alloc_plus * sizeof *v);
+ vec->v.type = VEC;
+ vec->v.vec = v + 2;
+ v[0] = alloc;
+ v[1] = zero;
+ return vec;
+}
+
+obj_t *vec_get_fill(obj_t *vec)
+{
+ assert (vec && vec->v.type == VEC);
+ return vec->v.vec[vec_fill];
+}
+
+obj_t *vec_set_fill(obj_t *vec, obj_t *fill)
+{
+ assert (vec && vec->v.type == VEC);
+
+ {
+ long new_fill = c_num(fill);
+ long old_fill = c_num(vec->v.vec[vec_fill]);
+ long old_alloc = c_num(vec->v.vec[vec_alloc]);
+ long fill_delta = new_fill - old_fill;
+ long alloc_delta = new_fill - old_alloc;
+
+ if (alloc_delta > 0) {
+ long new_alloc = max(new_fill, 2*old_alloc);
+ obj_t **newvec = chk_realloc(vec->v.vec - 2,
+ (new_alloc + 2)*sizeof *newvec);
+ vec->v.vec = newvec + 2;
+ vec->v.vec[vec_alloc] = num(new_alloc);
+ }
+
+ if (fill_delta > 0) {
+ long i;
+ for (i = old_fill; i < new_fill; i++)
+ vec->v.vec[i] = nil;
+ }
+
+ vec->v.vec[vec_fill] = fill;
+ }
+
+ return vec;
+}
+
+
+obj_t **vecref_l(obj_t *vec, obj_t *ind)
+{
+ assert (vec && vec->v.type == VEC);
+ assert (c_num(ind) < c_num(vec->v.vec[vec_fill]));
+ return vec->v.vec + c_num(ind);
+}
+
+obj_t *vec_push(obj_t *vec, obj_t *item)
+{
+ obj_t *fill = vec_get_fill(vec);
+ vec_set_fill(vec, plus(fill, one));
+ *vecref_l(vec, fill) = item;
+ return fill;
+}
+
+
+static obj_t *stdio_line_read(struct stream *sm)
+{
+ if (sm->handle == 0) {
+ return nil;
+ } else {
+ char *line = snarf_line((FILE *) sm->handle);
+
+ if (!line)
+ return nil;
+
+ return string(line);
+ }
+}
+
+static obj_t *stdio_line_write(struct stream *sm, obj_t *obj)
+{
+ assert (obj->t.type == STR);
+ if (sm->handle == 0)
+ return nil;
+ if (fputs(c_str(obj), (FILE *) sm->handle) == EOF)
+ return nil;
+ if (putc('\n', (FILE *) sm->handle) == EOF)
+ return nil;
+ return t;
+}
+
+static obj_t *stdio_close(struct stream *sm)
+{
+ FILE *f = (FILE *) sm->handle;
+
+ if (f != 0 && f != stdin && f != stdout) {
+ fclose((FILE *) sm->handle);
+ sm->handle = 0;
+ return t;
+ }
+ return nil;
+}
+
+static struct stream_ops stdio_line_stream_ops = {
+ stdio_line_read, stdio_line_write, stdio_close
+};
+
+obj_t *stdio_line_stream(FILE *f, obj_t *label)
+{
+ obj_t *sm = make_obj();
+ sm->sm.type = STREAM;
+ sm->sm.handle = f;
+ sm->sm.ops = &stdio_line_stream_ops;
+ sm->sm.label_pushback = label;
+ assert (atom(label));
+ return sm;
+}
+
+static obj_t *pipe_close(struct stream *sm)
+{
+ if (sm->handle != 0) {
+ pclose((FILE *) sm->handle);
+ sm->handle = 0;
+ return t;
+ }
+ return nil;
+}
+
+static struct stream_ops pipe_line_stream_ops = {
+ stdio_line_read, stdio_line_write, pipe_close
+};
+
+obj_t *pipe_line_stream(FILE *f, obj_t *label)
+{
+ obj_t *sm = make_obj();
+ sm->sm.type = STREAM;
+ sm->sm.handle = f;
+ sm->sm.ops = &pipe_line_stream_ops;
+ sm->sm.label_pushback = label;
+ assert (atom(label));
+ return sm;
+}
+
+obj_t *dirent_read(struct stream *sm)
+{
+ if (sm->handle == 0) {
+ return nil;
+ } else {
+ for (;;) {
+ struct dirent *e = readdir(sm->handle);
+ if (!e)
+ return nil;
+ if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, ".."))
+ continue;
+ return string(chk_strdup(e->d_name));
+ }
+ }
+}
+
+obj_t *dirent_close(struct stream *sm)
+{
+ if (sm->handle != 0) {
+ closedir((DIR *) sm->handle);
+ sm->handle = 0;
+ return t;
+ }
+
+ return nil;
+}
+
+static struct stream_ops dirent_stream_ops = {
+ dirent_read, 0, dirent_close
+};
+
+obj_t *dirent_stream(DIR *d, obj_t *label)
+{
+ obj_t *sm = make_obj();
+ sm->sm.type = STREAM;
+ sm->sm.handle = d;
+ sm->sm.ops = &dirent_stream_ops;
+ sm->sm.label_pushback = label;
+ assert (atom(label));
+ return sm;
+}
+
+obj_t *stream_get(obj_t *sm)
+{
+ assert (sm->sm.type == STREAM);
+
+ if (consp(sm->sm.label_pushback)) {
+ obj_t *ret = car(sm->sm.label_pushback);
+ sm->sm.label_pushback = cdr(sm->sm.label_pushback);
+ return ret;
+ }
+
+ return sm->sm.ops->read(&sm->sm);
+}
+
+obj_t *stream_pushback(obj_t *sm, obj_t *obj)
+{
+ assert (sm->sm.type == STREAM);
+ sm->sm.label_pushback = cons(obj, sm->sm.label_pushback);
+ return obj;
+}
+
+obj_t *stream_put(obj_t *sm, obj_t *obj)
+{
+ assert (sm->sm.type == STREAM);
+ return sm->sm.ops->write(&sm->sm, obj);
+}
+
+obj_t *stream_close(obj_t *sm)
+{
+ assert (sm->sm.type == STREAM);
+ return sm->sm.ops->close(&sm->sm);
+}
+
+
+static obj_t *make_lazycons(obj_t *func)
+{
+ obj_t *obj = make_obj();
+ obj->lc.type = LCONS;
+ obj->lc.car = obj->lc.cdr = nil;
+ obj->lc.func = func;
+ return obj;
+}
+
+static obj_t *lazy_stream_func(obj_t *stream, obj_t *lcons)
+{
+ obj_t *next = stream_get(stream);
+ obj_t *ahead = stream_get(stream);
+
+ lcons->lc.car = next;
+ lcons->lc.cdr = if2(ahead, make_lazycons(lcons->lc.func));
+ lcons->lc.func = nil;
+
+ if (!next || !ahead)
+ stream_close(stream);
+
+ if (ahead)
+ stream_pushback(stream, ahead);
+
+ return next;
+}
+
+obj_t *lazy_stream_cons(obj_t *stream)
+{
+ obj_t *first = stream_get(stream);
+
+ if (!first) {
+ stream_close(stream);
+ return nil;
+ }
+
+ stream_pushback(stream, first);
+
+ return make_lazycons(func_f1(stream, lazy_stream_func));
+}
+
+obj_t *cobj(void *handle, obj_t *cls_sym, struct cobj_ops *ops)
+{
+ obj_t *obj = make_obj();
+ obj->co.type = COBJ;
+ obj->co.handle = handle;
+ obj->co.ops = ops;
+ obj->co.cls = cls_sym;
+ return obj;
+}
+
+void cobj_print_op(obj_t *obj, FILE *out)
+{
+ fprintf(out, "#<");
+ obj_print(obj->co.cls, out);
+ fprintf(out, ": %p>", obj->co.handle);
+}
+
+obj_t *assoc(obj_t *list, obj_t *key)
+{
+ while (list) {
+ obj_t *elem = car(list);
+ if (equal(car(elem), key))
+ return elem;
+ list = cdr(list);
+ }
+
+ return nil;
+}
+
+obj_t *acons_new(obj_t *list, obj_t *key, obj_t *value)
+{
+ obj_t *existing = assoc(list, key);
+
+ if (existing) {
+ *cdr_l(existing) = value;
+ return list;
+ } else {
+ return cons(cons(key, value), list);
+ }
+}
+
+obj_t *alist_remove(obj_t *list, obj_t *keys)
+{
+ obj_t **plist = &list;
+
+ while (*plist) {
+ if (memq(car(car(*plist)), keys))
+ *plist = cdr(*plist);
+ else
+ plist = cdr_l(*plist);
+ }
+
+ return list;
+}
+
+obj_t *mapcar(obj_t *fun, obj_t *list)
+{
+ list_collect_decl (out, iter);
+
+ for (; list; list = cdr(list))
+ list_collect (iter, funcall1(fun, car(list)));
+
+ return out;
+}
+
+obj_t *mappend(obj_t *fun, obj_t *list)
+{
+ list_collect_decl (out, iter);
+
+ for (; list; list = cdr(list))
+ list_collect_append (iter, funcall1(fun, car(list)));
+
+ return out;
+}
+
+static void obj_init(void)
+{
+ int gc_save = gc_state(0);
+
+ /*
+ * No need to GC-protect the convenience variables which hold the interned
+ * symbols, because the interned_syms list holds a reference to all the
+ * symbols.
+ */
+
+ protect(&interned_syms, &zero, &one,
+ &two, &negone, &maxint, &minint,
+ &null_string, &nil_string,
+ &null_list, &equal_f,
+ &identity_f, 0);
+
+ null = intern(string(strdup("null")));
+ t = intern(string(strdup("t")));
+ cons_t = intern(string(strdup("cons")));
+ str_t = intern(string(strdup("str")));
+ chr_t = intern(string(strdup("chr")));
+ num_t = intern(string(strdup("num")));
+ sym_t = intern(string(strdup("sym")));
+ fun_t = intern(string(strdup("fun")));
+ vec_t = intern(string(strdup("vec")));
+ stream_t = intern(string(strdup("stream")));
+ lcons_t = intern(string(strdup("lcons")));
+ var = intern(string(strdup("var")));
+ regex = intern(string(strdup("regex")));
+ set = intern(string(strdup("set")));
+ cset = intern(string(strdup("cset")));
+ wild = intern(string(strdup("wild")));
+ oneplus = intern(string(strdup("1+")));
+ zeroplus = intern(string(strdup("0+")));
+ optional = intern(string(strdup("?")));
+ compound = intern(string(strdup("compound")));
+ or = intern(string(strdup("or")));
+ skip = intern(string(strdup("skip")));
+ block = intern(string(strdup("block")));
+ next = intern(string(strdup("next")));
+ fail = intern(string(strdup("fail")));
+ accept = intern(string(strdup("accept")));
+ all = intern(string(strdup("all")));
+ some = intern(string(strdup("some")));
+ none = intern(string(strdup("none")));
+ maybe = intern(string(strdup("maybe")));
+ collect = intern(string(strdup("collect")));
+ until = intern(string(strdup("until")));
+ coll = intern(string(strdup("coll")));
+ output = intern(string(strdup("output")));
+ single = intern(string(strdup("single")));
+ frst = intern(string(strdup("first")));
+ lst = intern(string(strdup("last")));
+ empty = intern(string(strdup("empty")));
+ repeat = intern(string(strdup("repeat")));
+ rep = intern(string(strdup("rep")));
+ flattn = intern(string(strdup("flatten")));
+ forget = intern(string(strdup("forget")));
+ mrge = intern(string(strdup("merge")));
+ bind = intern(string(strdup("bind")));
+ cat = intern(string(strdup("cat")));
+ dir = intern(string(strdup("dir")));
+
+ zero = num(0);
+ one = num(1);
+ two = num(2);
+ negone = num(-1);
+ maxint = num(LONG_MAX);
+ minint = num(LONG_MIN);
+
+ null_string = string(strdup(""));
+ nil_string = string(strdup("NIL"));
+
+ null_list = cons(nil, nil);
+
+ equal_f = func_f2(nil, equal_tramp);
+ identity_f = func_f1(nil, identity_tramp);
+
+ gc_state(gc_save);
+}
+
+void obj_print(obj_t *obj, FILE *out)
+{
+ if (obj == nil) {
+ fputs("nil", out);
+ return;
+ }
+
+ switch (obj->t.type) {
+ case CONS:
+ case LCONS:
+ {
+ obj_t *iter;
+ putc('(', out);
+ for (iter = obj; consp(iter); iter = cdr(iter)) {
+ obj_print(car(iter), out);
+ if (nullp(cdr(iter))) {
+ putc(')', out);
+ } else if (consp(cdr(iter))) {
+ putc(' ', out);
+ } else {
+ fputs(" . ", out);
+ obj_print(cdr(iter), out);
+ putc(')', out);
+ }
+ }
+ }
+ break;
+ case STR:
+ {
+ const char *ptr;
+ putc('"', out);
+ for (ptr = obj->st.str; *ptr; ptr++) {
+ switch (*ptr) {
+ case '\a': fputs("\\a", out); break;
+ case '\b': fputs("\\b", out); break;
+ case '\t': fputs("\\t", out); break;
+ case '\n': fputs("\\n", out); break;
+ case '\v': fputs("\\v", out); break;
+ case '\f': fputs("\\f", out); break;
+ case '\r': fputs("\\r", out); break;
+ case '"': fputs("\\\"", out); break;
+ case '\\': fputs("\\\\", out); break;
+ case 27: fputs("\\e", out); break;
+ default:
+ if (iscntrl(*ptr))
+ fprintf(out, "\\%03o", (int) *ptr);
+ else
+ putc(*ptr, out);
+ }
+ }
+ putc('"', out);
+ }
+ break;
+ case CHR:
+ {
+ int ch = obj->ch.ch;
+
+ putc('\'', out);
+ switch (ch) {
+ case '\a': fputs("\\a", out); break;
+ case '\b': fputs("\\b", out); break;
+ case '\t': fputs("\\t", out); break;
+ case '\n': fputs("\\n", out); break;
+ case '\v': fputs("\\v", out); break;
+ case '\f': fputs("\\f", out); break;
+ case '\r': fputs("\\r", out); break;
+ case '"': fputs("\\\"", out); break;
+ case '\\': fputs("\\\\", out); break;
+ case 27: fputs("\\e", out); break;
+ default:
+ if (iscntrl(ch))
+ fprintf(out, "\\%03o", ch);
+ else
+ putc(ch, out);
+ }
+ putc('\'', out);
+ }
+ break;
+ case NUM:
+ fprintf(out, "%ld", c_num(obj));
+ break;
+ case SYM:
+ fputs(c_str(symbol_name(obj)), out);
+ break;
+ case FUN:
+ fprintf(out, "#<function: f%d>", (int) obj->f.functype);
+ break;
+ case VEC:
+ {
+ long i, fill = c_num(obj->v.vec[vec_fill]);
+ fputs("#(", out);
+ for (i = 0; i < fill; i++) {
+ obj_print(obj->v.vec[i], out);
+ if (i < fill - 1)
+ putc(' ', out);
+ }
+ putc(')', out);
+ }
+ break;
+ case STREAM:
+ fprintf(out, "#<stream: ");
+ {
+ obj_t *iter;
+ /* skip stream pushback items to find label */
+ for (iter = obj->sm.label_pushback; consp(iter); iter = cdr(iter))
+ ;
+ obj_print(iter, out);
+ }
+ fprintf(out, ", %p>", (void *) obj->sm.handle);
+ break;
+ case COBJ:
+ obj->co.ops->print(obj, out);
+ break;
+ }
+}
+
+void init(const char *pn, void *(*oom)(void *, size_t))
+{
+ progname = pn;
+ obj_init();
+}
+
+void dump(obj_t *obj, FILE *out)
+{
+ obj_print(obj, out);
+ putc('\n', out);
+}
+
+/*
+ * Handy function for debugging in gdb,
+ * so we don't have to keep typing:
+ * (gdb) p dump(something, stdout)
+ */
+void d(obj_t *obj)
+{
+ dump(obj, stdout);
+}
+
+char *snarf_line(FILE *in)
+{
+ const size_t min_size = 512;
+ size_t size = 0;
+ size_t fill = 0;
+ char *buf = 0;
+
+ for (;;) {
+ int ch = getc(in);
+
+ if (ch == EOF && buf == 0)
+ break;
+
+ if (fill >= size) {
+ size_t newsize = size ? size * 2 : min_size;
+ buf = chk_realloc(buf, newsize);
+ size = newsize;
+ }
+
+ if (ch == '\n') {
+ buf[fill++] = 0;
+ break;
+ }
+ buf[fill++] = ch;
+ }
+
+ if (buf)
+ buf = chk_realloc(buf, fill);
+
+ return buf;
+}
+
+obj_t *snarf(FILE *in)
+{
+ list_collect_decl (list, iter);
+ char *str;
+
+ while ((str = snarf_line(in)) != 0)
+ list_collect (iter, string(str));
+
+ return list;
+}
diff --git a/lib.h b/lib.h
new file mode 100644
index 00000000..026efb97
--- /dev/null
+++ b/lib.h
@@ -0,0 +1,331 @@
+/* Copyright 2009
+ * Kaz Kylheku <kkylheku@gmail.com>
+ * Vancouver, Canada
+ * All rights reserved.
+ *
+ * BSD License:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+typedef enum type {
+ CONS = 1, STR, CHR, NUM, SYM, FUN, VEC, STREAM, LCONS, COBJ
+} type_t;
+
+typedef enum functype
+{
+ FINTERP, /* Interpreted function. */
+ F0, F1, F2, F3, F4, /* Intrinsic functions with env. */
+ N0, N1, N2, N3, N4 /* No-env intrinsics. */
+} functype_t;
+
+typedef union obj obj_t;
+
+struct any {
+ type_t type;
+ void *dummy[2];
+ obj_t *next; /* GC free list */
+};
+
+struct cons {
+ type_t type;
+ obj_t *car, *cdr;
+};
+
+struct string {
+ type_t type;
+ char *str;
+ obj_t *len;
+};
+
+struct chr {
+ type_t type;
+ int ch;
+};
+
+struct num {
+ type_t type;
+ long val;
+};
+
+struct sym {
+ type_t type;
+ obj_t *name;
+ obj_t *val;
+};
+
+struct func {
+ type_t type;
+ functype_t functype;
+ obj_t *env;
+ union {
+ obj_t *interp_fun;
+ obj_t *(*f0)(obj_t *);
+ obj_t *(*f1)(obj_t *, obj_t *);
+ obj_t *(*f2)(obj_t *, obj_t *, obj_t *);
+ obj_t *(*f3)(obj_t *, obj_t *, obj_t *, obj_t *);
+ obj_t *(*f4)(obj_t *, obj_t *, obj_t *, obj_t *, obj_t *);
+ obj_t *(*n0)(void);
+ obj_t *(*n1)(obj_t *);
+ obj_t *(*n2)(obj_t *, obj_t *);
+ obj_t *(*n3)(obj_t *, obj_t *, obj_t *);
+ obj_t *(*n4)(obj_t *, obj_t *, obj_t *, obj_t *);
+ } f;
+};
+
+enum vecindex { vec_alloc = -2, vec_fill = -1 };
+
+struct vec {
+ type_t type;
+ /* vec points two elements down */
+ /* vec[-2] is allocated size */
+ /* vec[-1] is fill pointer */
+ obj_t **vec;
+};
+
+struct stream {
+ type_t type;
+ void *handle;
+ struct stream_ops *ops;
+ obj_t *label_pushback; /* label-terminated pushback stack */
+};
+
+struct stream_ops {
+ obj_t *(*read)(struct stream *);
+ obj_t *(*write)(struct stream *, obj_t *);
+ obj_t *(*close)(struct stream *);
+};
+
+/*
+ * Lazy cons. When initially constructed, acts as a promise. The car and cdr
+ * cache pointers are nil, and func points to a function. The job of the
+ * function is to force the promise: fill car and cdr, and then flip func to
+ * nil. After that, the lazy cons resembles an ordinary cons. Of course, either
+ * car or cdr can point to more lazy conses.
+ */
+
+struct lazy_cons {
+ type_t type;
+ obj_t *car, *cdr;
+ obj_t *func; /* when nil, car and cdr are valid */
+};
+
+struct cobj {
+ type_t type;
+ void *handle;
+ struct cobj_ops *ops;
+ obj_t *cls;
+};
+
+struct cobj_ops {
+ obj_t *(*equal)(obj_t *self, obj_t *other);
+ void (*print)(obj_t *self, FILE *);
+ void (*destroy)(obj_t *self);
+};
+
+union obj {
+ struct any t;
+ struct cons c;
+ struct string st;
+ struct chr ch;
+ struct num n;
+ struct sym s;
+ struct func f;
+ struct vec v;
+ struct stream sm;
+ struct lazy_cons lc;
+ struct cobj co;
+};
+
+extern obj_t *interned_syms;
+
+extern obj_t *t, *cons_t, *str_t, *chr_t, *num_t, *sym_t, *fun_t, *vec_t;
+extern obj_t *stream_t, *lcons_t, *var, *regex, *set, *cset, *wild, *oneplus;
+extern obj_t *zeroplus, *optional, *compound, *or;
+extern obj_t *skip, *block, *next, *fail, *accept;
+extern obj_t *all, *some, *none, *maybe, *collect, *until, *coll;
+extern obj_t *output, *single, *frst, *lst, *empty, *repeat, *rep;
+extern obj_t *flattn, *forget, *mrge, *bind, *cat, *dir;
+
+extern obj_t *zero, *one, *two, *negone, *maxint, *minint;
+extern obj_t *null_string;
+extern obj_t *null_list; /* (NIL) */
+
+extern obj_t *identity_f;
+extern obj_t *equal_f;
+
+extern const char *progname;
+extern void *(*oom_realloc)(void *, size_t);
+
+obj_t *identity(obj_t *obj);
+obj_t *typeof(obj_t *obj);
+obj_t *car(obj_t *cons);
+obj_t *cdr(obj_t *cons);
+obj_t **car_l(obj_t *cons);
+obj_t **cdr_l(obj_t *cons);
+obj_t *first(obj_t *cons);
+obj_t *rest(obj_t *cons);
+obj_t *second(obj_t *cons);
+obj_t *third(obj_t *cons);
+obj_t *fourth(obj_t *cons);
+obj_t *fifth(obj_t *cons);
+obj_t *sixth(obj_t *cons);
+obj_t **tail(obj_t *cons);
+obj_t *copy_list(obj_t *list);
+obj_t *nreverse(obj_t *in);
+obj_t *reverse(obj_t *in);
+obj_t *append2(obj_t *list1, obj_t *list2);
+obj_t *nappend2(obj_t *list1, obj_t *list2);
+obj_t *flatten(obj_t *list);
+obj_t *memq(obj_t *obj, obj_t *list);
+obj_t *tree_find(obj_t *obj, obj_t *tree);
+obj_t *some_satisfy(obj_t *list, obj_t *pred, obj_t *key);
+long c_num(obj_t *num);
+obj_t *nump(obj_t *num);
+obj_t *equal(obj_t *left, obj_t *right);
+void *chk_malloc(size_t size);
+void *chk_realloc(void*, size_t size);
+void *chk_strdup(const char *str);
+obj_t *cons(obj_t *car, obj_t *cdr);
+obj_t *list(obj_t *first, ...); /* terminated by nao */
+obj_t *consp(obj_t *obj);
+obj_t *nullp(obj_t *obj);
+obj_t *atom(obj_t *obj);
+obj_t *listp(obj_t *obj);
+obj_t *length(obj_t *list);
+obj_t *num(long val);
+long c_num(obj_t *num);
+obj_t *plus(obj_t *anum, obj_t *bnum);
+obj_t *minus(obj_t *anum, obj_t *bnum);
+obj_t *neg(obj_t *num);
+obj_t *zerop(obj_t *num);
+obj_t *gt(obj_t *anum, obj_t *bnum);
+obj_t *lt(obj_t *anum, obj_t *bnum);
+obj_t *ge(obj_t *anum, obj_t *bnum);
+obj_t *le(obj_t *anum, obj_t *bnum);
+obj_t *numeq(obj_t *anum, obj_t *bnum);
+obj_t *max2(obj_t *anum, obj_t *bnum);
+obj_t *min2(obj_t *anum, obj_t *bnum);
+obj_t *string(char *str);
+obj_t *mkstring(obj_t *len, obj_t *ch);
+obj_t *copy_str(obj_t *str);
+obj_t *stringp(obj_t *str);
+obj_t *length_str(obj_t *str);
+const char *c_str(obj_t *str);
+obj_t *search_str(obj_t *haystack, obj_t *needle, obj_t *start_num,
+ obj_t *from_end);
+obj_t *search_str_tree(obj_t *haystack, obj_t *tree, obj_t *start_num,
+ obj_t *from_end);
+obj_t *sub_str(obj_t *str_in, obj_t *from_num, obj_t *to_num);
+obj_t *cat_str(obj_t *list, obj_t *sep);
+obj_t *trim_str(obj_t *str);
+obj_t *chr(int ch);
+int c_chr(obj_t *chr);
+obj_t *sym_name(obj_t *sym);
+obj_t *make_sym(obj_t *name);
+obj_t *intern(obj_t *str);
+obj_t *symbolp(obj_t *sym);
+obj_t *symbol_name(obj_t *sym);
+obj_t *func_f0(obj_t *, obj_t *(*fun)(obj_t *));
+obj_t *func_f1(obj_t *, obj_t *(*fun)(obj_t *, obj_t *));
+obj_t *func_f2(obj_t *, obj_t *(*fun)(obj_t *, obj_t *, obj_t *));
+obj_t *func_f3(obj_t *, obj_t *(*fun)(obj_t *, obj_t *, obj_t *, obj_t *));
+obj_t *func_f4(obj_t *, obj_t *(*fun)(obj_t *, obj_t *, obj_t *, obj_t *,
+ obj_t *));
+obj_t *func_n0(obj_t *(*fun)(void));
+obj_t *func_n1(obj_t *(*fun)(obj_t *));
+obj_t *func_n2(obj_t *(*fun)(obj_t *, obj_t *));
+obj_t *func_n3(obj_t *(*fun)(obj_t *, obj_t *, obj_t *));
+obj_t *func_n4(obj_t *(*fun)(obj_t *, obj_t *, obj_t *, obj_t *));
+obj_t *apply(obj_t *fun, obj_t *arglist);
+obj_t *funcall(obj_t *fun);
+obj_t *funcall1(obj_t *fun, obj_t *arg);
+obj_t *funcall2(obj_t *fun, obj_t *arg1, obj_t *arg2);
+obj_t *reduce_left(obj_t *fun, obj_t *list, obj_t *init, obj_t *key);
+obj_t *bind2(obj_t *fun2, obj_t *arg);
+obj_t *chain(obj_t *fun1_list);
+obj_t *vector(obj_t *alloc);
+obj_t *vec_get_fill(obj_t *vec);
+obj_t *vec_set_fill(obj_t *vec, obj_t *fill);
+obj_t **vecref_l(obj_t *vec, obj_t *ind);
+obj_t *vec_push(obj_t *vec, obj_t *item);
+obj_t *stdio_line_stream(FILE *f, obj_t *label);
+obj_t *pipe_line_stream(FILE *f, obj_t *label);
+obj_t *dirent_stream(DIR *d, obj_t *label);
+obj_t *stream_get(obj_t *sm);
+obj_t *stream_pushback(obj_t *sm, obj_t *obj);
+obj_t *stream_put(obj_t *sm, obj_t *obj);
+obj_t *stream_close(obj_t *sm);
+obj_t *lazy_stream_cons(obj_t *stream);
+obj_t *cobj(void *handle, obj_t *cls_sym, struct cobj_ops *ops);
+void cobj_print_op(obj_t *, FILE *); /* Print function for struct cobj_ops */
+obj_t *assoc(obj_t *list, obj_t *key);
+obj_t *acons_new(obj_t *list, obj_t *key, obj_t *value);
+obj_t *alist_remove(obj_t *list, obj_t *keys);
+obj_t *mapcar(obj_t *fun, obj_t *list);
+obj_t *mappend(obj_t *fun, obj_t *list);
+void obj_print(obj_t *obj, FILE *);
+void init(const char *progname, void *(*oom_realloc)(void *, size_t));
+void dump(obj_t *obj, FILE *);
+char *snarf_line(FILE *in);
+obj_t *snarf(FILE *in);
+obj_t *match(obj_t *spec, obj_t *data);
+
+#define nil ((obj_t *) 0)
+
+#define nao ((obj_t *) -1) /* "not an object", useful as a sentinel. */
+
+#define eq(a, b) ((a) == (b) ? t : nil)
+
+#define if2(a, b) ((a) ? (b) : nil)
+
+#define if3(a, b, c) ((a) ? (b) : (c))
+
+#define list_collect_decl(OUT, PTAIL) \
+ obj_t *OUT = nil, **PTAIL = &OUT
+
+#define list_collect(PTAIL, OBJ) \
+ do { \
+ *PTAIL = cons(OBJ, nil); \
+ PTAIL = cdr_l(*PTAIL); \
+ } while(0)
+
+#define list_collect_nconc(PTAIL, OBJ) \
+ do { \
+ obj_t *o_b_j = (OBJ); \
+ *PTAIL = o_b_j; \
+ if (o_b_j) \
+ PTAIL = tail(o_b_j); \
+ } while (0)
+
+#define list_collect_append(PTAIL, OBJ) \
+ do { \
+ obj_t *o_b_j = copy_list(OBJ); \
+ *PTAIL = o_b_j; \
+ if (o_b_j) \
+ PTAIL = tail(o_b_j); \
+ } while (0)
+
+#define list_collect_terminate(PTAIL, OBJ) \
+ do *PTAIL = (OBJ); while(0)
+
+#define cons_bind(CAR, CDR, CONS) \
+ obj_t *c_o_n_s ## CAR ## CDR = CONS; \
+ obj_t *CAR = car(c_o_n_s ## CAR ## CDR); \
+ obj_t *CDR = cdr(c_o_n_s ## CAR ## CDR)
diff --git a/regex.c b/regex.c
new file mode 100644
index 00000000..a48b3ff5
--- /dev/null
+++ b/regex.c
@@ -0,0 +1,631 @@
+/* Copyright 2009
+ * Kaz Kylheku <kkylheku@gmail.com>
+ * Vancouver, Canada
+ * All rights reserved.
+ *
+ * BSD License:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <dirent.h>
+#include "lib.h"
+#include "regex.h"
+
+#define NFA_SET_SIZE 512
+
+#define CHAR_SET_INDEX(CH) ((CH) / (sizeof (bitcell_t) * CHAR_BIT))
+#define CHAR_SET_BIT(CH) ((CH) % (sizeof (bitcell_t) * CHAR_BIT))
+
+void char_set_clear(char_set_t *set)
+{
+ static const char_set_t blank = { { 0 } };
+ *set = blank;
+}
+
+void char_set_compl(char_set_t *set)
+{
+ int i;
+ for (i = 0; i < CHAR_SET_SIZE; i++)
+ set->bitcell[i] ^= BITCELL_ALL1;
+}
+
+void char_set_add(char_set_t *set, int ch)
+{
+ set->bitcell[CHAR_SET_INDEX(ch)] |= (1 << CHAR_SET_BIT(ch));
+}
+
+void char_set_add_range(char_set_t *set, int ch0, int ch1)
+{
+ if (ch0 <= ch1) {
+ int i;
+ int bt0 = CHAR_SET_BIT(ch0);
+ int bc0 = CHAR_SET_INDEX(ch0);
+ bitcell_t mask0 = ~((BITCELL_LIT(1) << bt0) - 1);
+ int bt1 = CHAR_SET_BIT(ch1);
+ int bc1 = CHAR_SET_INDEX(ch1);
+ bitcell_t mask1 = ((BITCELL_LIT(1) << (bt1 + 1) % 32) - 1);
+
+ switch (bc1 - bc0) {
+ case 0:
+ set->bitcell[bc0] |= (mask0 & mask1);
+ break;
+ default:
+ set->bitcell[bc0] |= mask0;
+ set->bitcell[bc1] |= mask1;
+ case 1:
+ for (i = bc0 + 1; i < bc1; i++)
+ set->bitcell[i] = BITCELL_ALL1;
+ break;
+ }
+ }
+}
+
+int char_set_contains(char_set_t *set, int ch)
+{
+ return (set->bitcell[CHAR_SET_INDEX(ch)] & (1 << CHAR_SET_BIT(ch))) != 0;
+}
+
+nfa_state_t *nfa_state_accept(void)
+{
+ nfa_state_t *st = (nfa_state_t *) chk_malloc(sizeof *st);
+ st->a.kind = nfa_accept;
+ st->a.visited = 0;
+ return st;
+}
+
+nfa_state_t *nfa_state_empty(nfa_state_t *t0, nfa_state_t *t1)
+{
+ nfa_state_t *st = (nfa_state_t *) chk_malloc(sizeof *st);
+ st->e.kind = nfa_empty;
+ st->e.visited = 0;
+ st->e.trans0 = t0;
+ st->e.trans1 = t1;
+ return st;
+}
+
+nfa_state_t *nfa_state_single(nfa_state_t *t, int ch)
+{
+ nfa_state_t *st = (nfa_state_t *) chk_malloc(sizeof *st);
+ st->o.kind = nfa_single;
+ st->o.visited = 0;
+ st->o.trans = t;
+ st->o.ch = ch;
+ return st;
+}
+
+nfa_state_t *nfa_state_wild(nfa_state_t *t)
+{
+ nfa_state_t *st = (nfa_state_t *) chk_malloc(sizeof *st);
+ st->o.kind = nfa_wild;
+ st->o.visited = 0;
+ st->o.trans = t;
+ st->o.ch = 0;
+ return st;
+}
+
+void nfa_state_free(nfa_state_t *st)
+{
+ if (st->a.kind == nfa_set)
+ free(st->s.set);
+ free(st);
+}
+
+void nfa_state_shallow_free(nfa_state_t *st)
+{
+ free(st);
+}
+
+nfa_state_t *nfa_state_set(nfa_state_t *t)
+{
+ nfa_state_t *st = (nfa_state_t *) chk_malloc(sizeof *st);
+ char_set_t *cs = (char_set_t *) chk_malloc(sizeof *cs);
+ char_set_clear(cs);
+ st->s.kind = nfa_set;
+ st->s.visited = 0;
+ st->s.trans = t;
+ st->s.set = cs;
+ return st;
+}
+
+/*
+ * An acceptance state is converted to an empty transition
+ * state with specified transitions. It thereby loses
+ * its acceptance state status. This is used during
+ * compilation to hook new output paths into an inner NFA,
+ * either back to itself, or to a new state in the
+ * surrounding new NFA.
+ */
+void nfa_state_empty_convert(nfa_state_t *acc, nfa_state_t *t0, nfa_state_t *t1)
+{
+ assert (acc->a.kind == nfa_accept);
+ acc->e.kind = nfa_empty;
+ acc->e.trans0 = t0;
+ acc->e.trans1 = t1;
+}
+
+/*
+ * Acceptance state takes on the kind of st, and all associated
+ * data. I.e. we merge the identity of accept,
+ * with the contents of st, such that the new state has
+ * all of the outgoing arrows of st, and
+ * all of the incoming arrows of acc.
+ * This is easily done with an assignment, provided
+ * that st doesn't have any incoming arrows.
+ * We ensure that start states don't have any incoming
+ * arrows in the compiler, by ensuring that repetition
+ * operators terminate their backwards arrows on an
+ * existing start state, and allocate a new start
+ * state in front of it.
+ */
+void nfa_state_merge(nfa_state_t *acc, nfa_state_t *st)
+{
+ assert (acc->a.kind == nfa_accept);
+ *acc = *st;
+}
+
+nfa_t nfa_make(nfa_state_t *s, nfa_state_t *acc)
+{
+ nfa_t ret;
+ ret.start = s;
+ ret.accept = acc;
+ return ret;
+}
+
+/*
+ * Combine two NFA's representing regexps that are catenated.
+ * The acceptance state of the predecessor is merged with the start state of
+ * the successor.
+ */
+nfa_t nfa_combine(nfa_t pred, nfa_t succ)
+{
+ nfa_t ret;
+ ret.start = pred.start;
+ ret.accept = succ.accept;
+ nfa_state_merge(pred.accept, succ.start);
+ nfa_state_shallow_free(succ.start); /* No longer needed. */
+ return ret;
+}
+
+nfa_t nfa_compile_set(obj_t *args, int compl)
+{
+ nfa_state_t *acc = nfa_state_accept();
+ nfa_state_t *s = nfa_state_set(acc);
+ char_set_t *set = s->s.set;
+ nfa_t ret = nfa_make(s, acc);
+
+ for (; args; args = rest(args)) {
+ obj_t *item = first(args);
+
+ if (consp(item)) {
+ obj_t *from = car(item);
+ obj_t *to = cdr(item);
+
+ assert (typeof(from) == chr_t && typeof(to) == chr_t);
+ char_set_add_range(set, c_chr(from), c_chr(to));
+ } else if (typeof(item) == chr_t) {
+ char_set_add(set, c_chr(item));
+ } else {
+ assert(0 && "bad regex set");
+ }
+ }
+
+ if (compl)
+ char_set_compl(set);
+
+ return ret;
+}
+
+/*
+ * Input is the items from a regex form,
+ * not including the regex symbol.
+ * I.e. (rest '(regex ...)) not '(regex ...).
+ */
+nfa_t nfa_compile_regex(obj_t *items)
+{
+ if (nullp(items)) {
+ nfa_state_t *acc = nfa_state_accept();
+ nfa_state_t *s = nfa_state_empty(acc, 0);
+ nfa_t nfa = nfa_make(s, acc);
+ return nfa;
+ } else {
+ obj_t *item = first(items), *others = rest(items);
+ nfa_t nfa;
+
+ if (typeof(item) == chr_t) {
+ nfa_state_t *acc = nfa_state_accept();
+ nfa_state_t *s = nfa_state_single(acc, c_chr(item));
+ nfa = nfa_make(s, acc);
+ } else if (item == wild) {
+ nfa_state_t *acc = nfa_state_accept();
+ nfa_state_t *s = nfa_state_wild(acc);
+ nfa = nfa_make(s, acc);
+ } else if (consp(item)) {
+ obj_t *sym = first(item);
+ obj_t *args = rest(item);
+
+ if (sym == set) {
+ nfa = nfa_compile_set(args, 0);
+ } else if (sym == cset) {
+ nfa = nfa_compile_set(args, 1);
+ } else if (sym == compound) {
+ nfa = nfa_compile_regex(args);
+ } else if (sym == zeroplus) {
+ nfa_t nfa_args = nfa_compile_regex(args);
+ nfa_state_t *acc = nfa_state_accept();
+ /* New start state has empty transitions going through
+ the inner NFA, or skipping it right to the new acceptance state. */
+ nfa_state_t *s = nfa_state_empty(nfa_args.start, acc);
+ /* Convert acceptance state of inner NFA to one which has
+ an empty transition back to the start state, and
+ an empty transition to the new acceptance state. */
+ nfa_state_empty_convert(nfa_args.accept, nfa_args.start, acc);
+ nfa = nfa_make(s, acc);
+ } else if (sym == oneplus) {
+ /* One-plus case differs from zero-plus in that the new start state
+ does not have an empty transition to the acceptance state.
+ So the inner NFA must be traversed once. */
+ nfa_t nfa_args = nfa_compile_regex(args);
+ nfa_state_t *acc = nfa_state_accept();
+ nfa_state_t *s = nfa_state_empty(nfa_args.start, 0); /* <-- diff */
+ nfa_state_empty_convert(nfa_args.accept, nfa_args.start, acc);
+ nfa = nfa_make(s, acc);
+ } else if (sym == optional) {
+ /* In this case, we can keep the acceptance state of the inner
+ NFA as the acceptance state of the new NFA. We simply add
+ a new start state which can short-circuit to it via an empty
+ transition. */
+ nfa_t nfa_args = nfa_compile_regex(args);
+ nfa_state_t *s = nfa_state_empty(nfa_args.start, nfa_args.accept);
+ nfa = nfa_make(s, nfa_args.accept);
+ } else if (sym == or) {
+ /* Simple: make a new start and acceptance state, which form
+ the ends of a spindle that goes through two branches. */
+ nfa_t nfa_first = nfa_compile_regex(first(args));
+ nfa_t nfa_second = nfa_compile_regex(second(args));
+ nfa_state_t *acc = nfa_state_accept();
+ /* New state s has empty transitions into each inner NFA. */
+ nfa_state_t *s = nfa_state_empty(nfa_first.start, nfa_second.start);
+ /* Acceptance state of each inner NFA converted to empty
+ transition to new combined acceptance state. */
+ nfa_state_empty_convert(nfa_first.accept, acc, 0);
+ nfa_state_empty_convert(nfa_second.accept, acc, 0);
+ nfa = nfa_make(s, acc);
+ } else {
+ assert (0 && "internal error: bad operator in regex");
+ }
+ } else {
+ assert (0 && "internal error: bad regex item");
+ }
+
+ /* We made an NFA for the first item, but others follow.
+ Compile the others to an NFA recursively, then
+ stick it with this NFA. */
+ if (others) {
+ nfa_t nfa_others = nfa_compile_regex(others);
+ nfa = nfa_combine(nfa, nfa_others);
+ }
+
+ return nfa;
+ }
+}
+
+int nfa_all_states(nfa_state_t **inout, int num, int visited)
+{
+ int i;
+
+ for (i = 0; i < num; i++)
+ inout[i]->a.visited = visited;
+
+ for (i = 0; i < num; i++) {
+ nfa_state_t *s = inout[i];
+
+ if (num >= NFA_SET_SIZE)
+ abort();
+
+ switch (s->a.kind) {
+ case nfa_accept:
+ break;
+ case nfa_empty:
+ {
+ nfa_state_t *e0 = s->e.trans0;
+ nfa_state_t *e1 = s->e.trans1;
+
+ if (e0 && e0->a.visited != visited) {
+ e0->a.visited = visited;
+ inout[num++] = e0;
+ }
+ if (e1 && e1->a.visited != visited) {
+ e1->a.visited = visited;
+ inout[num++] = e1;
+ }
+ }
+ break;
+ case nfa_wild:
+ case nfa_single:
+ case nfa_set:
+ if (s->o.trans->a.visited != visited) {
+ s->o.trans->a.visited = visited;
+ inout[num++] = s->o.trans;
+ }
+ break;
+ }
+ }
+
+ if (num > NFA_SET_SIZE)
+ abort();
+
+ return num;
+}
+
+void nfa_free(nfa_t nfa)
+{
+ nfa_state_t **all = chk_malloc(NFA_SET_SIZE * sizeof *all);
+ int nstates, i;
+
+ all[0] = nfa.start;
+ all[1] = nfa.accept;
+
+ nstates = nfa_all_states(all, 2, nfa.start->a.visited);
+
+ for (i = 0; i < nstates; i++)
+ nfa_state_free(all[i]);
+
+ free(all);
+}
+
+/*
+ * Compute the epsilon-closure of the NFA states stored in the set in, whose
+ * size is given by nin. The results are stored in the set out, the size of
+ * which is returned. The stack parameter provides storage used by the
+ * algorithm, so it doesn't have to be allocated and freed repeatedly.
+ * The visited parameter is a stamp used for marking states which are added
+ * to the epsilon-closure set, so that sets are not added twice.
+ * If any of the states added to the closure are acceptance states,
+ * the accept parameter is used to store the flag 1.
+ *
+ * An epsilon-closure is the set of all input states, plus all additional
+ * states which are reachable from that set with empty (epsilon) transitions.
+ * (Transitions that don't do not consume and match an input character).
+ */
+int nfa_closure(nfa_state_t **stack, nfa_state_t **in, int nin,
+ nfa_state_t **out, int visited, int *accept)
+{
+ int i, nout = 0;
+ int stackp = 0;
+
+ /* First, add all states in the input state to the closure,
+ push them on the stack, and mark them as visited. */
+ for (i = 0; i < nin; i++) {
+ if (stackp >= NFA_SET_SIZE)
+ abort();
+ in[i]->a.visited = visited;
+ stack[stackp++] = in[i];
+ out[nout++] = in[i];
+ if (in[i]->a.kind == nfa_accept)
+ *accept = 1;
+ }
+
+ while (stackp) {
+ nfa_state_t *top = stack[--stackp];
+
+ if (nout >= NFA_SET_SIZE)
+ abort();
+
+ /* Only states of type nfa_empty are interesting.
+ Each such state at most two epsilon transitions. */
+
+ if (top->a.kind == nfa_empty) {
+ nfa_state_t *e0 = top->e.trans0;
+ nfa_state_t *e1 = top->e.trans1;
+
+ if (e0 && e0->a.visited != visited) {
+ e0->a.visited = visited;
+ stack[stackp++] = e0;
+ out[nout++] = e0;
+ if (e0->a.kind == nfa_accept)
+ *accept = 1;
+ }
+
+ if (e1 && e1->a.visited != visited) {
+ e1->a.visited = visited;
+ stack[stackp++] = e1;
+ out[nout++] = e1;
+ if (e1->a.kind == nfa_accept)
+ *accept = 1;
+ }
+ }
+ }
+
+ if (nout > NFA_SET_SIZE)
+ abort();
+
+ return nout;
+}
+
+/*
+ * Compute the move set from a given set of NFA states. The move
+ * set is the set of states which are reachable from the set of
+ * input states on the consumpion of the input character given by ch.
+ */
+int nfa_move(nfa_state_t **in, int nin, nfa_state_t **out, int ch)
+{
+ int i, nmove;
+
+ for (nmove = 0, i = 0; i < nin; i++) {
+ nfa_state_t *s = in[i];
+
+ switch (s->a.kind) {
+ case nfa_wild:
+ /* Unconditional match; don't have to look at ch. */
+ break;
+ case nfa_single:
+ if (s->o.ch == ch) /* Character match. */
+ break;
+ continue; /* no match */
+ case nfa_set:
+ if (char_set_contains(s->s.set, ch)) /* Set match. */
+ break;
+ continue; /* no match */
+ default:
+ /* Epsilon-transition and acceptance states have no character moves. */
+ continue;
+ }
+
+ /* The state matches the character, so add it to the move set.
+ C trick: all character-transitioning state types have the
+ pointer to the next state in the same position,
+ among a common set of leading struct members in the union. */
+
+ if (nmove >= NFA_SET_SIZE)
+ abort();
+ out[nmove++] = s->o.trans;
+ }
+
+ return nmove;
+}
+
+/*
+ * Match regex against the string in. The match is
+ * anchored to the front of the string; to search
+ * within the string, a .* must be added to the front
+ * of the regex.
+ *
+ * Returns the length of the prefix of the string
+ * which matches the regex. Or, if you will,
+ * the position of the first mismatching
+ * character.
+ *
+ * If the regex does not match at all, zero is
+ * returned.
+ *
+ * Matching stops when a state is reached from which
+ * there are no transitions on the next input character,
+ * or when the string runs out of characters.
+ * The most recently visited acceptance state then
+ * determines the match length (defaulting to zero
+ * if no acceptance states were encountered).
+ */
+long nfa_run(nfa_t nfa, const char *str)
+{
+ const char *last_accept_pos = 0, *ptr = str;
+ unsigned visited = nfa.start->a.visited + 1;
+ nfa_state_t **move = chk_malloc(NFA_SET_SIZE * sizeof *move);
+ nfa_state_t **clos = chk_malloc(NFA_SET_SIZE * sizeof *clos);
+ nfa_state_t **stack = chk_malloc(NFA_SET_SIZE * sizeof *stack);
+ int nmove = 1, nclos;
+ int accept = 0;
+
+ move[0] = nfa.start;
+
+ nclos = nfa_closure(stack, move, nmove, clos, visited++, &accept);
+
+ if (accept)
+ last_accept_pos = ptr;
+
+ for (; *ptr != 0; ptr++) {
+ int ch = *ptr;
+
+ accept = 0;
+
+ nmove = nfa_move(clos, nclos, move, ch);
+ nclos = nfa_closure(stack, move, nmove, clos, visited++, &accept);
+
+ if (accept)
+ last_accept_pos = ptr + 1;
+
+ if (nclos == 0) /* dead end; no match */
+ break;
+ }
+
+ nfa.start->a.visited = visited;
+
+ free(stack);
+ free(clos);
+ free(move);
+
+ return last_accept_pos ? last_accept_pos - str : -1;
+}
+
+static obj_t *regex_equal(obj_t *self, obj_t *other)
+{
+ return self == other ? t : nil; /* eq equality only */
+}
+
+static void regex_destroy(obj_t *regex)
+{
+ nfa_t *pnfa = (nfa_t *) regex->co.handle;
+ nfa_free(*pnfa);
+ free(pnfa);
+ regex->co.handle = 0;
+}
+
+static struct cobj_ops regex_obj_ops = {
+ regex_equal, cobj_print_op, regex_destroy
+};
+
+obj_t *regex_compile(obj_t *regex_sexp)
+{
+ nfa_t *pnfa = chk_malloc(sizeof *pnfa);
+ *pnfa = nfa_compile_regex(regex_sexp);
+ return cobj(pnfa, regex, &regex_obj_ops);
+}
+
+nfa_t *regex_nfa(obj_t *reg)
+{
+ assert (reg->co.type == COBJ && reg->co.cls == regex);
+ return (nfa_t *) reg->co.handle;
+}
+
+obj_t *search_regex(obj_t *haystack, obj_t *needle_regex, obj_t *start_num,
+ obj_t *from_end)
+{
+ const char *h = c_str(haystack);
+ long len = c_num(length_str(haystack));
+ long start = c_num(start_num);
+ nfa_t *pnfa = regex_nfa(needle_regex);
+
+ if (start > len) {
+ return nil;
+ } else {
+ long begin = from_end ? len : start;
+ long end = from_end ? start - 1 : len + 1;
+ int incr = from_end ? -1 : 1;
+ long i;
+
+ for (i = begin; i != end; i += incr) {
+ long span = nfa_run(*pnfa, h + i);
+ if (span >= 0)
+ return cons(num(i), num(span));
+ }
+
+ return nil;
+ }
+}
+
+obj_t *match_regex(obj_t *str, obj_t *reg, obj_t *pos)
+{
+ nfa_t *pnfa = regex_nfa(reg);
+ long cpos = c_num(pos);
+ long span = nfa_run(*pnfa, c_str(str) + cpos);
+ return span >= 0 ? num(span + cpos) : nil;
+}
diff --git a/regex.h b/regex.h
new file mode 100644
index 00000000..10fcf4b4
--- /dev/null
+++ b/regex.h
@@ -0,0 +1,107 @@
+/* Copyright 2009
+ * Kaz Kylheku <kkylheku@gmail.com>
+ * Vancouver, Canada
+ * All rights reserved.
+ *
+ * BSD License:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <limits.h>
+
+typedef unsigned int bitcell_t;
+#define BITCELL_ALL1 UINT_MAX
+#define BITCELL_LIT(NUMTOKEN) NUMTOKEN ## U
+
+#define CHAR_SET_SIZE ((UCHAR_MAX + 1) / (sizeof (bitcell_t) * CHAR_BIT))
+
+typedef struct char_set {
+ bitcell_t bitcell[CHAR_SET_SIZE];
+} char_set_t;
+
+void char_set_clear(char_set_t *);
+void char_set_compl(char_set_t *);
+void char_set_add(char_set_t *, int);
+void char_set_add_range(char_set_t *, int, int); /* inclusive */
+int char_set_contains(char_set_t *, int);
+
+typedef enum {
+ nfa_accept, nfa_empty, nfa_wild, nfa_single, nfa_set
+} nfa_kind_t;
+
+typedef union nfa_state nfa_state_t;
+
+struct nfa_state_accept {
+ nfa_kind_t kind;
+ unsigned visited;
+};
+
+struct nfa_state_empty {
+ nfa_kind_t kind;
+ unsigned visited;
+ nfa_state_t *trans0;
+ nfa_state_t *trans1;
+};
+
+struct nfa_state_single {
+ nfa_kind_t kind;
+ unsigned visited;
+ nfa_state_t *trans;
+ int ch;
+};
+
+struct nfa_state_set {
+ nfa_kind_t kind;
+ unsigned visited;
+ nfa_state_t *trans;
+ char_set_t *set;
+};
+
+union nfa_state {
+ struct nfa_state_accept a;
+ struct nfa_state_empty e;
+ struct nfa_state_single o;
+ struct nfa_state_set s;
+};
+
+nfa_state_t *nfa_state_accept(void);
+nfa_state_t *nfa_state_empty(nfa_state_t *, nfa_state_t *);
+nfa_state_t *nfa_state_single(nfa_state_t *, int ch);
+nfa_state_t *nfa_state_wild(nfa_state_t *);
+nfa_state_t *nfa_state_set(nfa_state_t *);
+void nfa_state_free(nfa_state_t *st);
+void nfa_state_shallow_free(nfa_state_t *st);
+void nfa_state_merge(nfa_state_t *accept, nfa_state_t *);
+
+typedef struct nfa nfa_t;
+
+struct nfa {
+ nfa_state_t *start;
+ nfa_state_t *accept;
+};
+
+nfa_t nfa_compile_regex(obj_t *regex);
+void nfa_free(nfa_t);
+long nfa_run(nfa_t nfa, const char *str);
+obj_t *regex_compile(obj_t *regex_sexp);
+nfa_t *regex_nfa(obj_t *);
+obj_t *search_regex(obj_t *haystack, obj_t *needle_regex, obj_t *start_num,
+ obj_t *from_end);
+obj_t *match_regex(obj_t *str, obj_t *regex, obj_t *pos);
diff --git a/tests/001/data b/tests/001/data
new file mode 100644
index 00000000..fb74617d
--- /dev/null
+++ b/tests/001/data
@@ -0,0 +1,87 @@
+UID PID PPID C STIME TTY TIME CMD
+root 1 0 0 Aug21 ? 00:01:11 init [5]
+root 2 1 0 Aug21 ? 00:00:00 [ksoftirqd/0]
+root 3 1 0 Aug21 ? 00:01:23 [events/0]
+root 4 3 0 Aug21 ? 00:00:00 [khelper]
+root 5 3 0 Aug21 ? 00:00:00 [kacpid]
+root 16 3 0 Aug21 ? 00:00:00 [kblockd/0]
+root 29 3 0 Aug21 ? 00:00:00 [aio/0]
+root 17 1 0 Aug21 ? 00:00:00 [khubd]
+root 28 1 0 Aug21 ? 00:00:06 [kswapd0]
+root 103 1 0 Aug21 ? 00:00:00 [kseriod]
+root 175 1 0 Aug21 ? 00:00:00 [scsi_eh_0]
+root 186 1 0 Aug21 ? 00:04:49 [kjournald]
+root 870 1 0 Aug21 ? 00:00:00 udevd
+root 1068 1 0 Aug21 ? 00:00:00 /sbin/dhclient -1 -q -lf /var/lib/dhcp/dhclient-eth0.leases -pf /var/run/dhclient-eth0.pid eth0
+root 1235 1 0 Aug21 ? 00:00:00 [kjournald]
+root 1236 1 0 Aug21 ? 00:00:09 [kjournald]
+root 1620 1 0 Aug21 ? 00:00:16 syslogd -m 0
+root 1624 1 0 Aug21 ? 00:00:00 klogd -x
+rpc 1645 1 0 Aug21 ? 00:00:08 portmap
+rpcuser 1665 1 0 Aug21 ? 00:00:00 rpc.statd
+root 1698 1 0 Aug21 ? 00:00:07 rpc.idmapd
+root 1766 1 0 Aug21 ? 00:05:17 /usr/sbin/vmware-guestd --background /var/run/vmware-guestd.pid
+root 1790 1 0 Aug21 ? 00:00:23 [rpciod]
+root 1791 1 0 Aug21 ? 00:00:00 [lockd]
+root 1821 1 0 Aug21 ? 00:00:00 ypbind
+root 1839 1 0 Aug21 ? 00:00:00 /usr/sbin/acpid
+root 1851 1 0 Aug21 ? 00:01:33 cupsd
+root 1887 1 0 Aug21 ? 00:00:00 /usr/sbin/sshd
+root 1902 1 0 Aug21 ? 00:00:00 xinetd -stayalive -pidfile /var/run/xinetd.pid
+root 1921 1 0 Aug21 ? 00:00:00 rpc.rquotad
+root 1925 1 0 Aug21 ? 00:00:00 [nfsd]
+root 1926 1 0 Aug21 ? 00:00:00 [nfsd]
+root 1927 1 0 Aug21 ? 00:00:00 [nfsd]
+root 1928 1 0 Aug21 ? 00:00:00 [nfsd]
+root 1929 1 0 Aug21 ? 00:00:00 [nfsd]
+root 1930 1 0 Aug21 ? 00:00:00 [nfsd]
+root 1931 1 0 Aug21 ? 00:00:00 [nfsd]
+root 1932 1 0 Aug21 ? 00:00:00 [nfsd]
+root 1936 1 0 Aug21 ? 00:00:00 rpc.mountd
+root 1963 1 0 Aug21 ? 00:00:29 crond
+xfs 1989 1 0 Aug21 ? 00:00:01 xfs -droppriv -daemon
+daemon 2008 1 0 Aug21 ? 00:00:03 /usr/sbin/atd
+dbus 2027 1 0 Aug21 ? 00:00:00 dbus-daemon-1 --system
+root 2041 1 0 Aug21 ? 00:00:00 cups-config-daemon
+root 2052 1 0 Aug21 ? 00:05:00 hald
+root 2062 1 0 Aug21 tty1 00:00:00 /sbin/mingetty tty1
+root 2124 1 0 Aug21 ? 00:00:00 /usr/bin/gdm-binary -nodaemon
+root 2184 2124 0 Aug21 ? 00:00:00 /usr/bin/gdm-binary -nodaemon
+root 2354 2184 1 Aug21 ? 12:18:15 /usr/X11R6/bin/X :0 -audit 0 -auth /var/gdm/:0.Xauth -nolisten tcp vt7
+kaz 2551 2184 0 Aug21 ? 00:00:01 /usr/bin/gnome-session
+kaz 2579 1 0 Aug21 ? 00:00:00 /usr/bin/ssh-agent -s
+kaz 2625 1 0 Aug21 ? 00:00:00 /usr/bin/dbus-launch --exit-with-session /etc/X11/xinit/Xclients
+kaz 2626 1 0 Aug21 ? 00:00:00 dbus-daemon-1 --fork --print-pid 8 --print-address 6 --session
+kaz 2631 1 0 Aug21 ? 00:00:47 /usr/libexec/gconfd-2 11
+kaz 2634 1 0 Aug21 ? 00:00:00 /usr/bin/gnome-keyring-daemon
+kaz 2636 1 0 Aug21 ? 00:00:00 /usr/libexec/bonobo-activation-server --ac-activate --ior-output-fd=18
+kaz 2638 1 0 Aug21 ? 00:00:00 /usr/libexec/gnome-settings-daemon --oaf-activate-iid=OAFIID:GNOME_SettingsDaemon --oaf-ior-fd=22
+kaz 2644 1 0 Aug21 ? 00:13:10 /usr/libexec/gam_server
+kaz 2661 1 0 Aug21 ? 00:01:18 xscreensaver -nosplash
+kaz 2685 1 0 Aug21 ? 00:00:00 /usr/bin/metacity --sm-client-id=default1
+kaz 2689 1 0 Aug21 ? 00:00:02 gnome-panel --sm-client-id default2
+kaz 2691 1 0 Aug21 ? 00:27:25 nautilus --no-default-window --sm-client-id default3
+kaz 2693 1 0 Aug21 ? 00:00:00 gnome-volume-manager --sm-client-id default6
+kaz 2695 1 0 Aug21 ? 00:00:37 eggcups --sm-client-id default5
+kaz 2698 1 0 Aug21 ? 00:00:00 /usr/libexec/gnome-vfs-daemon --oaf-activate-iid=OAFIID:GNOME_VFS_Daemon_Factory --oaf-ior-fd=28
+kaz 2701 1 0 Aug21 ? 00:01:31 pam-panel-icon --sm-client-id default0
+kaz 2707 1 1 Aug21 ? 11:09:59 /usr/bin/python /usr/bin/rhn-applet-gui --sm-client-id default4
+root 2717 2701 0 Aug21 ? 00:02:30 /sbin/pam_timestamp_check -d root
+kaz 2718 1 0 Aug21 ? 00:00:05 /usr/libexec/mapping-daemon
+kaz 2720 1 0 Aug21 ? 00:00:00 /usr/libexec/wnck-applet --oaf-activate-iid=OAFIID:GNOME_Wncklet_Factory --oaf-ior-fd=30
+kaz 2722 1 0 Aug21 ? 00:14:36 /usr/libexec/mixer_applet2 --oaf-activate-iid=OAFIID:GNOME_MixerApplet_Factory --oaf-ior-fd=32
+kaz 2726 1 0 Aug21 ? 00:43:09 /usr/libexec/clock-applet --oaf-activate-iid=OAFIID:GNOME_ClockApplet_Factory --oaf-ior-fd=34
+kaz 2728 1 0 Aug21 ? 00:00:00 /usr/libexec/notification-area-applet --oaf-activate-iid=OAFIID:GNOME_NotificationAreaApplet_Factory --oaf-ior-fd=36
+root 30737 1 0 Aug26 ? 00:00:00 /sbin/dhclient -1 -q -lf /var/lib/dhcp/dhclient-eth0.leases -pf /var/run/dhclient-eth0.pid eth0
+root 31905 1887 0 Aug27 ? 00:00:06 sshd: kaz [priv]
+kaz 31907 31905 0 Aug27 ? 00:00:55 sshd: kaz@pts/2
+kaz 31908 31907 0 Aug27 pts/2 00:00:01 -bash
+root 32672 1887 0 Aug27 ? 00:00:06 sshd: kaz [priv]
+kaz 32674 32672 0 Aug27 ? 00:02:11 sshd: kaz@pts/4
+kaz 32675 32674 0 Aug27 pts/4 00:00:06 -bash
+root 27121 3 0 Sep12 ? 00:00:00 [pdflush]
+root 27243 3 0 Sep12 ? 00:00:31 [pdflush]
+root 27682 1887 0 Sep12 ? 00:00:03 sshd: kaz [priv]
+kaz 27684 27682 0 Sep12 ? 00:09:06 sshd: kaz@pts/1
+kaz 27685 27684 0 Sep12 pts/1 00:00:26 -bash
+kaz 6481 27685 0 17:47 pts/1 00:00:00 ps -ef
diff --git a/tests/001/query-1.expected b/tests/001/query-1.expected
new file mode 100644
index 00000000..e55250c9
--- /dev/null
+++ b/tests/001/query-1.expected
@@ -0,0 +1,688 @@
+UID[0]="root"
+UID[1]="root"
+UID[2]="root"
+UID[3]="root"
+UID[4]="root"
+UID[5]="root"
+UID[6]="root"
+UID[7]="root"
+UID[8]="root"
+UID[9]="root"
+UID[10]="root"
+UID[11]="root"
+UID[12]="root"
+UID[13]="root"
+UID[14]="root"
+UID[15]="root"
+UID[16]="root"
+UID[17]="root"
+UID[18]="rpc"
+UID[19]="rpcuser"
+UID[20]="root"
+UID[21]="root"
+UID[22]="root"
+UID[23]="root"
+UID[24]="root"
+UID[25]="root"
+UID[26]="root"
+UID[27]="root"
+UID[28]="root"
+UID[29]="root"
+UID[30]="root"
+UID[31]="root"
+UID[32]="root"
+UID[33]="root"
+UID[34]="root"
+UID[35]="root"
+UID[36]="root"
+UID[37]="root"
+UID[38]="root"
+UID[39]="root"
+UID[40]="xfs"
+UID[41]="daemon"
+UID[42]="dbus"
+UID[43]="root"
+UID[44]="root"
+UID[45]="root"
+UID[46]="root"
+UID[47]="root"
+UID[48]="root"
+UID[49]="kaz"
+UID[50]="kaz"
+UID[51]="kaz"
+UID[52]="kaz"
+UID[53]="kaz"
+UID[54]="kaz"
+UID[55]="kaz"
+UID[56]="kaz"
+UID[57]="kaz"
+UID[58]="kaz"
+UID[59]="kaz"
+UID[60]="kaz"
+UID[61]="kaz"
+UID[62]="kaz"
+UID[63]="kaz"
+UID[64]="kaz"
+UID[65]="kaz"
+UID[66]="kaz"
+UID[67]="root"
+UID[68]="kaz"
+UID[69]="kaz"
+UID[70]="kaz"
+UID[71]="kaz"
+UID[72]="kaz"
+UID[73]="root"
+UID[74]="root"
+UID[75]="kaz"
+UID[76]="kaz"
+UID[77]="root"
+UID[78]="kaz"
+UID[79]="kaz"
+UID[80]="root"
+UID[81]="root"
+UID[82]="root"
+UID[83]="kaz"
+UID[84]="kaz"
+UID[85]="kaz"
+PID[0]="1"
+PID[1]="2"
+PID[2]="3"
+PID[3]="4"
+PID[4]="5"
+PID[5]="16"
+PID[6]="29"
+PID[7]="17"
+PID[8]="28"
+PID[9]="103"
+PID[10]="175"
+PID[11]="186"
+PID[12]="870"
+PID[13]="1068"
+PID[14]="1235"
+PID[15]="1236"
+PID[16]="1620"
+PID[17]="1624"
+PID[18]="1645"
+PID[19]="1665"
+PID[20]="1698"
+PID[21]="1766"
+PID[22]="1790"
+PID[23]="1791"
+PID[24]="1821"
+PID[25]="1839"
+PID[26]="1851"
+PID[27]="1887"
+PID[28]="1902"
+PID[29]="1921"
+PID[30]="1925"
+PID[31]="1926"
+PID[32]="1927"
+PID[33]="1928"
+PID[34]="1929"
+PID[35]="1930"
+PID[36]="1931"
+PID[37]="1932"
+PID[38]="1936"
+PID[39]="1963"
+PID[40]="1989"
+PID[41]="2008"
+PID[42]="2027"
+PID[43]="2041"
+PID[44]="2052"
+PID[45]="2062"
+PID[46]="2124"
+PID[47]="2184"
+PID[48]="2354"
+PID[49]="2551"
+PID[50]="2579"
+PID[51]="2625"
+PID[52]="2626"
+PID[53]="2631"
+PID[54]="2634"
+PID[55]="2636"
+PID[56]="2638"
+PID[57]="2644"
+PID[58]="2661"
+PID[59]="2685"
+PID[60]="2689"
+PID[61]="2691"
+PID[62]="2693"
+PID[63]="2695"
+PID[64]="2698"
+PID[65]="2701"
+PID[66]="2707"
+PID[67]="2717"
+PID[68]="2718"
+PID[69]="2720"
+PID[70]="2722"
+PID[71]="2726"
+PID[72]="2728"
+PID[73]="30737"
+PID[74]="31905"
+PID[75]="31907"
+PID[76]="31908"
+PID[77]="32672"
+PID[78]="32674"
+PID[79]="32675"
+PID[80]="27121"
+PID[81]="27243"
+PID[82]="27682"
+PID[83]="27684"
+PID[84]="27685"
+PID[85]="6481"
+PPID[0]="0"
+PPID[1]="1"
+PPID[2]="1"
+PPID[3]="3"
+PPID[4]="3"
+PPID[5]="3"
+PPID[6]="3"
+PPID[7]="1"
+PPID[8]="1"
+PPID[9]="1"
+PPID[10]="1"
+PPID[11]="1"
+PPID[12]="1"
+PPID[13]="1"
+PPID[14]="1"
+PPID[15]="1"
+PPID[16]="1"
+PPID[17]="1"
+PPID[18]="1"
+PPID[19]="1"
+PPID[20]="1"
+PPID[21]="1"
+PPID[22]="1"
+PPID[23]="1"
+PPID[24]="1"
+PPID[25]="1"
+PPID[26]="1"
+PPID[27]="1"
+PPID[28]="1"
+PPID[29]="1"
+PPID[30]="1"
+PPID[31]="1"
+PPID[32]="1"
+PPID[33]="1"
+PPID[34]="1"
+PPID[35]="1"
+PPID[36]="1"
+PPID[37]="1"
+PPID[38]="1"
+PPID[39]="1"
+PPID[40]="1"
+PPID[41]="1"
+PPID[42]="1"
+PPID[43]="1"
+PPID[44]="1"
+PPID[45]="1"
+PPID[46]="1"
+PPID[47]="2124"
+PPID[48]="2184"
+PPID[49]="2184"
+PPID[50]="1"
+PPID[51]="1"
+PPID[52]="1"
+PPID[53]="1"
+PPID[54]="1"
+PPID[55]="1"
+PPID[56]="1"
+PPID[57]="1"
+PPID[58]="1"
+PPID[59]="1"
+PPID[60]="1"
+PPID[61]="1"
+PPID[62]="1"
+PPID[63]="1"
+PPID[64]="1"
+PPID[65]="1"
+PPID[66]="1"
+PPID[67]="2701"
+PPID[68]="1"
+PPID[69]="1"
+PPID[70]="1"
+PPID[71]="1"
+PPID[72]="1"
+PPID[73]="1"
+PPID[74]="1887"
+PPID[75]="31905"
+PPID[76]="31907"
+PPID[77]="1887"
+PPID[78]="32672"
+PPID[79]="32674"
+PPID[80]="3"
+PPID[81]="3"
+PPID[82]="1887"
+PPID[83]="27682"
+PPID[84]="27684"
+PPID[85]="27685"
+C[0]="0"
+C[1]="0"
+C[2]="0"
+C[3]="0"
+C[4]="0"
+C[5]="0"
+C[6]="0"
+C[7]="0"
+C[8]="0"
+C[9]="0"
+C[10]="0"
+C[11]="0"
+C[12]="0"
+C[13]="0"
+C[14]="0"
+C[15]="0"
+C[16]="0"
+C[17]="0"
+C[18]="0"
+C[19]="0"
+C[20]="0"
+C[21]="0"
+C[22]="0"
+C[23]="0"
+C[24]="0"
+C[25]="0"
+C[26]="0"
+C[27]="0"
+C[28]="0"
+C[29]="0"
+C[30]="0"
+C[31]="0"
+C[32]="0"
+C[33]="0"
+C[34]="0"
+C[35]="0"
+C[36]="0"
+C[37]="0"
+C[38]="0"
+C[39]="0"
+C[40]="0"
+C[41]="0"
+C[42]="0"
+C[43]="0"
+C[44]="0"
+C[45]="0"
+C[46]="0"
+C[47]="0"
+C[48]="1"
+C[49]="0"
+C[50]="0"
+C[51]="0"
+C[52]="0"
+C[53]="0"
+C[54]="0"
+C[55]="0"
+C[56]="0"
+C[57]="0"
+C[58]="0"
+C[59]="0"
+C[60]="0"
+C[61]="0"
+C[62]="0"
+C[63]="0"
+C[64]="0"
+C[65]="0"
+C[66]="1"
+C[67]="0"
+C[68]="0"
+C[69]="0"
+C[70]="0"
+C[71]="0"
+C[72]="0"
+C[73]="0"
+C[74]="0"
+C[75]="0"
+C[76]="0"
+C[77]="0"
+C[78]="0"
+C[79]="0"
+C[80]="0"
+C[81]="0"
+C[82]="0"
+C[83]="0"
+C[84]="0"
+C[85]="0"
+STIME[0]="Aug21"
+STIME[1]="Aug21"
+STIME[2]="Aug21"
+STIME[3]="Aug21"
+STIME[4]="Aug21"
+STIME[5]="Aug21"
+STIME[6]="Aug21"
+STIME[7]="Aug21"
+STIME[8]="Aug21"
+STIME[9]="Aug21"
+STIME[10]="Aug21"
+STIME[11]="Aug21"
+STIME[12]="Aug21"
+STIME[13]="Aug21"
+STIME[14]="Aug21"
+STIME[15]="Aug21"
+STIME[16]="Aug21"
+STIME[17]="Aug21"
+STIME[18]="Aug21"
+STIME[19]="Aug21"
+STIME[20]="Aug21"
+STIME[21]="Aug21"
+STIME[22]="Aug21"
+STIME[23]="Aug21"
+STIME[24]="Aug21"
+STIME[25]="Aug21"
+STIME[26]="Aug21"
+STIME[27]="Aug21"
+STIME[28]="Aug21"
+STIME[29]="Aug21"
+STIME[30]="Aug21"
+STIME[31]="Aug21"
+STIME[32]="Aug21"
+STIME[33]="Aug21"
+STIME[34]="Aug21"
+STIME[35]="Aug21"
+STIME[36]="Aug21"
+STIME[37]="Aug21"
+STIME[38]="Aug21"
+STIME[39]="Aug21"
+STIME[40]="Aug21"
+STIME[41]="Aug21"
+STIME[42]="Aug21"
+STIME[43]="Aug21"
+STIME[44]="Aug21"
+STIME[45]="Aug21"
+STIME[46]="Aug21"
+STIME[47]="Aug21"
+STIME[48]="Aug21"
+STIME[49]="Aug21"
+STIME[50]="Aug21"
+STIME[51]="Aug21"
+STIME[52]="Aug21"
+STIME[53]="Aug21"
+STIME[54]="Aug21"
+STIME[55]="Aug21"
+STIME[56]="Aug21"
+STIME[57]="Aug21"
+STIME[58]="Aug21"
+STIME[59]="Aug21"
+STIME[60]="Aug21"
+STIME[61]="Aug21"
+STIME[62]="Aug21"
+STIME[63]="Aug21"
+STIME[64]="Aug21"
+STIME[65]="Aug21"
+STIME[66]="Aug21"
+STIME[67]="Aug21"
+STIME[68]="Aug21"
+STIME[69]="Aug21"
+STIME[70]="Aug21"
+STIME[71]="Aug21"
+STIME[72]="Aug21"
+STIME[73]="Aug26"
+STIME[74]="Aug27"
+STIME[75]="Aug27"
+STIME[76]="Aug27"
+STIME[77]="Aug27"
+STIME[78]="Aug27"
+STIME[79]="Aug27"
+STIME[80]="Sep12"
+STIME[81]="Sep12"
+STIME[82]="Sep12"
+STIME[83]="Sep12"
+STIME[84]="Sep12"
+STIME[85]="17:47"
+TTY[0]="?"
+TTY[1]="?"
+TTY[2]="?"
+TTY[3]="?"
+TTY[4]="?"
+TTY[5]="?"
+TTY[6]="?"
+TTY[7]="?"
+TTY[8]="?"
+TTY[9]="?"
+TTY[10]="?"
+TTY[11]="?"
+TTY[12]="?"
+TTY[13]="?"
+TTY[14]="?"
+TTY[15]="?"
+TTY[16]="?"
+TTY[17]="?"
+TTY[18]="?"
+TTY[19]="?"
+TTY[20]="?"
+TTY[21]="?"
+TTY[22]="?"
+TTY[23]="?"
+TTY[24]="?"
+TTY[25]="?"
+TTY[26]="?"
+TTY[27]="?"
+TTY[28]="?"
+TTY[29]="?"
+TTY[30]="?"
+TTY[31]="?"
+TTY[32]="?"
+TTY[33]="?"
+TTY[34]="?"
+TTY[35]="?"
+TTY[36]="?"
+TTY[37]="?"
+TTY[38]="?"
+TTY[39]="?"
+TTY[40]="?"
+TTY[41]="?"
+TTY[42]="?"
+TTY[43]="?"
+TTY[44]="?"
+TTY[45]="tty1"
+TTY[46]="?"
+TTY[47]="?"
+TTY[48]="?"
+TTY[49]="?"
+TTY[50]="?"
+TTY[51]="?"
+TTY[52]="?"
+TTY[53]="?"
+TTY[54]="?"
+TTY[55]="?"
+TTY[56]="?"
+TTY[57]="?"
+TTY[58]="?"
+TTY[59]="?"
+TTY[60]="?"
+TTY[61]="?"
+TTY[62]="?"
+TTY[63]="?"
+TTY[64]="?"
+TTY[65]="?"
+TTY[66]="?"
+TTY[67]="?"
+TTY[68]="?"
+TTY[69]="?"
+TTY[70]="?"
+TTY[71]="?"
+TTY[72]="?"
+TTY[73]="?"
+TTY[74]="?"
+TTY[75]="?"
+TTY[76]="pts/2"
+TTY[77]="?"
+TTY[78]="?"
+TTY[79]="pts/4"
+TTY[80]="?"
+TTY[81]="?"
+TTY[82]="?"
+TTY[83]="?"
+TTY[84]="pts/1"
+TTY[85]="pts/1"
+TIME[0]="00:01:11"
+TIME[1]="00:00:00"
+TIME[2]="00:01:23"
+TIME[3]="00:00:00"
+TIME[4]="00:00:00"
+TIME[5]="00:00:00"
+TIME[6]="00:00:00"
+TIME[7]="00:00:00"
+TIME[8]="00:00:06"
+TIME[9]="00:00:00"
+TIME[10]="00:00:00"
+TIME[11]="00:04:49"
+TIME[12]="00:00:00"
+TIME[13]="00:00:00"
+TIME[14]="00:00:00"
+TIME[15]="00:00:09"
+TIME[16]="00:00:16"
+TIME[17]="00:00:00"
+TIME[18]="00:00:08"
+TIME[19]="00:00:00"
+TIME[20]="00:00:07"
+TIME[21]="00:05:17"
+TIME[22]="00:00:23"
+TIME[23]="00:00:00"
+TIME[24]="00:00:00"
+TIME[25]="00:00:00"
+TIME[26]="00:01:33"
+TIME[27]="00:00:00"
+TIME[28]="00:00:00"
+TIME[29]="00:00:00"
+TIME[30]="00:00:00"
+TIME[31]="00:00:00"
+TIME[32]="00:00:00"
+TIME[33]="00:00:00"
+TIME[34]="00:00:00"
+TIME[35]="00:00:00"
+TIME[36]="00:00:00"
+TIME[37]="00:00:00"
+TIME[38]="00:00:00"
+TIME[39]="00:00:29"
+TIME[40]="00:00:01"
+TIME[41]="00:00:03"
+TIME[42]="00:00:00"
+TIME[43]="00:00:00"
+TIME[44]="00:05:00"
+TIME[45]="00:00:00"
+TIME[46]="00:00:00"
+TIME[47]="00:00:00"
+TIME[48]="12:18:15"
+TIME[49]="00:00:01"
+TIME[50]="00:00:00"
+TIME[51]="00:00:00"
+TIME[52]="00:00:00"
+TIME[53]="00:00:47"
+TIME[54]="00:00:00"
+TIME[55]="00:00:00"
+TIME[56]="00:00:00"
+TIME[57]="00:13:10"
+TIME[58]="00:01:18"
+TIME[59]="00:00:00"
+TIME[60]="00:00:02"
+TIME[61]="00:27:25"
+TIME[62]="00:00:00"
+TIME[63]="00:00:37"
+TIME[64]="00:00:00"
+TIME[65]="00:01:31"
+TIME[66]="11:09:59"
+TIME[67]="00:02:30"
+TIME[68]="00:00:05"
+TIME[69]="00:00:00"
+TIME[70]="00:14:36"
+TIME[71]="00:43:09"
+TIME[72]="00:00:00"
+TIME[73]="00:00:00"
+TIME[74]="00:00:06"
+TIME[75]="00:00:55"
+TIME[76]="00:00:01"
+TIME[77]="00:00:06"
+TIME[78]="00:02:11"
+TIME[79]="00:00:06"
+TIME[80]="00:00:00"
+TIME[81]="00:00:31"
+TIME[82]="00:00:03"
+TIME[83]="00:09:06"
+TIME[84]="00:00:26"
+TIME[85]="00:00:00"
+CMD[0]="init [5]"
+CMD[1]="[ksoftirqd/0]"
+CMD[2]="[events/0]"
+CMD[3]="[khelper]"
+CMD[4]="[kacpid]"
+CMD[5]="[kblockd/0]"
+CMD[6]="[aio/0]"
+CMD[7]="[khubd]"
+CMD[8]="[kswapd0]"
+CMD[9]="[kseriod]"
+CMD[10]="[scsi_eh_0]"
+CMD[11]="[kjournald]"
+CMD[12]="udevd"
+CMD[13]="/sbin/dhclient -1"
+CMD[14]="[kjournald]"
+CMD[15]="[kjournald]"
+CMD[16]="syslogd -m"
+CMD[17]="klogd -x"
+CMD[18]="portmap"
+CMD[19]="rpc.statd"
+CMD[20]="rpc.idmapd"
+CMD[21]="/usr/sbin/vmware-guestd --background"
+CMD[22]="[rpciod]"
+CMD[23]="[lockd]"
+CMD[24]="ypbind"
+CMD[25]="/usr/sbin/acpid"
+CMD[26]="cupsd"
+CMD[27]="/usr/sbin/sshd"
+CMD[28]="xinetd -stayalive"
+CMD[29]="rpc.rquotad"
+CMD[30]="[nfsd]"
+CMD[31]="[nfsd]"
+CMD[32]="[nfsd]"
+CMD[33]="[nfsd]"
+CMD[34]="[nfsd]"
+CMD[35]="[nfsd]"
+CMD[36]="[nfsd]"
+CMD[37]="[nfsd]"
+CMD[38]="rpc.mountd"
+CMD[39]="crond"
+CMD[40]="xfs -droppriv"
+CMD[41]="/usr/sbin/atd"
+CMD[42]="dbus-daemon-1 --system"
+CMD[43]="cups-config-daemon"
+CMD[44]="hald"
+CMD[45]="/sbin/mingetty tty1"
+CMD[46]="/usr/bin/gdm-binary -nodaemon"
+CMD[47]="/usr/bin/gdm-binary -nodaemon"
+CMD[48]="/usr/X11R6/bin/X :0"
+CMD[49]="/usr/bin/gnome-session"
+CMD[50]="/usr/bin/ssh-agent -s"
+CMD[51]="/usr/bin/dbus-launch --exit-with-session"
+CMD[52]="dbus-daemon-1 --fork"
+CMD[53]="/usr/libexec/gconfd-2 11"
+CMD[54]="/usr/bin/gnome-keyring-daemon"
+CMD[55]="/usr/libexec/bonobo-activation-server --ac-activate"
+CMD[56]="/usr/libexec/gnome-settings-daemon --oaf-activate-iid=OAFIID:GNOME_SettingsDaemon"
+CMD[57]="/usr/libexec/gam_server"
+CMD[58]="xscreensaver -nosplash"
+CMD[59]="/usr/bin/metacity --sm-client-id=default1"
+CMD[60]="gnome-panel --sm-client-id"
+CMD[61]="nautilus --no-default-window"
+CMD[62]="gnome-volume-manager --sm-client-id"
+CMD[63]="eggcups --sm-client-id"
+CMD[64]="/usr/libexec/gnome-vfs-daemon --oaf-activate-iid=OAFIID:GNOME_VFS_Daemon_Factory"
+CMD[65]="pam-panel-icon --sm-client-id"
+CMD[66]="/usr/bin/python /usr/bin/rhn-applet-gui"
+CMD[67]="/sbin/pam_timestamp_check -d"
+CMD[68]="/usr/libexec/mapping-daemon"
+CMD[69]="/usr/libexec/wnck-applet --oaf-activate-iid=OAFIID:GNOME_Wncklet_Factory"
+CMD[70]="/usr/libexec/mixer_applet2 --oaf-activate-iid=OAFIID:GNOME_MixerApplet_Factory"
+CMD[71]="/usr/libexec/clock-applet --oaf-activate-iid=OAFIID:GNOME_ClockApplet_Factory"
+CMD[72]="/usr/libexec/notification-area-applet --oaf-activate-iid=OAFIID:GNOME_NotificationAreaApplet_Factory"
+CMD[73]="/sbin/dhclient -1"
+CMD[74]="sshd: kaz"
+CMD[75]="sshd: kaz@pts/2"
+CMD[76]="-bash"
+CMD[77]="sshd: kaz"
+CMD[78]="sshd: kaz@pts/4"
+CMD[79]="-bash"
+CMD[80]="[pdflush]"
+CMD[81]="[pdflush]"
+CMD[82]="sshd: kaz"
+CMD[83]="sshd: kaz@pts/1"
+CMD[84]="-bash"
+CMD[85]="ps -ef"
diff --git a/tests/001/query-1.txr b/tests/001/query-1.txr
new file mode 100644
index 00000000..8b37b62d
--- /dev/null
+++ b/tests/001/query-1.txr
@@ -0,0 +1,7 @@
+@#
+@# This file is in the public domain.
+@# It was authored by Kaz Kylheku <kkylheku@gmail.com> in 2009
+@#
+@(collect)
+@UID@/ +/@{PID /[0-9]+/}@/ +/@PPID@/ +/@C@/ +/@STIME@/ +/@TTY@/ +/@TIME @{CMD /[^ ]+( +[^ ]+)?/}@/.*/
+@(end)
diff --git a/tests/001/query-2.expected b/tests/001/query-2.expected
new file mode 100644
index 00000000..90f363ce
--- /dev/null
+++ b/tests/001/query-2.expected
@@ -0,0 +1,696 @@
+UID[0]="UID"
+UID[1]="root"
+UID[2]="root"
+UID[3]="root"
+UID[4]="root"
+UID[5]="root"
+UID[6]="root"
+UID[7]="root"
+UID[8]="root"
+UID[9]="root"
+UID[10]="root"
+UID[11]="root"
+UID[12]="root"
+UID[13]="root"
+UID[14]="root"
+UID[15]="root"
+UID[16]="root"
+UID[17]="root"
+UID[18]="root"
+UID[19]="rpc"
+UID[20]="rpcuser"
+UID[21]="root"
+UID[22]="root"
+UID[23]="root"
+UID[24]="root"
+UID[25]="root"
+UID[26]="root"
+UID[27]="root"
+UID[28]="root"
+UID[29]="root"
+UID[30]="root"
+UID[31]="root"
+UID[32]="root"
+UID[33]="root"
+UID[34]="root"
+UID[35]="root"
+UID[36]="root"
+UID[37]="root"
+UID[38]="root"
+UID[39]="root"
+UID[40]="root"
+UID[41]="xfs"
+UID[42]="daemon"
+UID[43]="dbus"
+UID[44]="root"
+UID[45]="root"
+UID[46]="root"
+UID[47]="root"
+UID[48]="root"
+UID[49]="root"
+UID[50]="kaz"
+UID[51]="kaz"
+UID[52]="kaz"
+UID[53]="kaz"
+UID[54]="kaz"
+UID[55]="kaz"
+UID[56]="kaz"
+UID[57]="kaz"
+UID[58]="kaz"
+UID[59]="kaz"
+UID[60]="kaz"
+UID[61]="kaz"
+UID[62]="kaz"
+UID[63]="kaz"
+UID[64]="kaz"
+UID[65]="kaz"
+UID[66]="kaz"
+UID[67]="kaz"
+UID[68]="root"
+UID[69]="kaz"
+UID[70]="kaz"
+UID[71]="kaz"
+UID[72]="kaz"
+UID[73]="kaz"
+UID[74]="root"
+UID[75]="root"
+UID[76]="kaz"
+UID[77]="kaz"
+UID[78]="root"
+UID[79]="kaz"
+UID[80]="kaz"
+UID[81]="root"
+UID[82]="root"
+UID[83]="root"
+UID[84]="kaz"
+UID[85]="kaz"
+UID[86]="kaz"
+PID[0]="PID"
+PID[1]="1"
+PID[2]="2"
+PID[3]="3"
+PID[4]="4"
+PID[5]="5"
+PID[6]="16"
+PID[7]="29"
+PID[8]="17"
+PID[9]="28"
+PID[10]="103"
+PID[11]="175"
+PID[12]="186"
+PID[13]="870"
+PID[14]="1068"
+PID[15]="1235"
+PID[16]="1236"
+PID[17]="1620"
+PID[18]="1624"
+PID[19]="1645"
+PID[20]="1665"
+PID[21]="1698"
+PID[22]="1766"
+PID[23]="1790"
+PID[24]="1791"
+PID[25]="1821"
+PID[26]="1839"
+PID[27]="1851"
+PID[28]="1887"
+PID[29]="1902"
+PID[30]="1921"
+PID[31]="1925"
+PID[32]="1926"
+PID[33]="1927"
+PID[34]="1928"
+PID[35]="1929"
+PID[36]="1930"
+PID[37]="1931"
+PID[38]="1932"
+PID[39]="1936"
+PID[40]="1963"
+PID[41]="1989"
+PID[42]="2008"
+PID[43]="2027"
+PID[44]="2041"
+PID[45]="2052"
+PID[46]="2062"
+PID[47]="2124"
+PID[48]="2184"
+PID[49]="2354"
+PID[50]="2551"
+PID[51]="2579"
+PID[52]="2625"
+PID[53]="2626"
+PID[54]="2631"
+PID[55]="2634"
+PID[56]="2636"
+PID[57]="2638"
+PID[58]="2644"
+PID[59]="2661"
+PID[60]="2685"
+PID[61]="2689"
+PID[62]="2691"
+PID[63]="2693"
+PID[64]="2695"
+PID[65]="2698"
+PID[66]="2701"
+PID[67]="2707"
+PID[68]="2717"
+PID[69]="2718"
+PID[70]="2720"
+PID[71]="2722"
+PID[72]="2726"
+PID[73]="2728"
+PID[74]="30737"
+PID[75]="31905"
+PID[76]="31907"
+PID[77]="31908"
+PID[78]="32672"
+PID[79]="32674"
+PID[80]="32675"
+PID[81]="27121"
+PID[82]="27243"
+PID[83]="27682"
+PID[84]="27684"
+PID[85]="27685"
+PID[86]="6481"
+PPID[0]="PPID"
+PPID[1]="0"
+PPID[2]="1"
+PPID[3]="1"
+PPID[4]="3"
+PPID[5]="3"
+PPID[6]="3"
+PPID[7]="3"
+PPID[8]="1"
+PPID[9]="1"
+PPID[10]="1"
+PPID[11]="1"
+PPID[12]="1"
+PPID[13]="1"
+PPID[14]="1"
+PPID[15]="1"
+PPID[16]="1"
+PPID[17]="1"
+PPID[18]="1"
+PPID[19]="1"
+PPID[20]="1"
+PPID[21]="1"
+PPID[22]="1"
+PPID[23]="1"
+PPID[24]="1"
+PPID[25]="1"
+PPID[26]="1"
+PPID[27]="1"
+PPID[28]="1"
+PPID[29]="1"
+PPID[30]="1"
+PPID[31]="1"
+PPID[32]="1"
+PPID[33]="1"
+PPID[34]="1"
+PPID[35]="1"
+PPID[36]="1"
+PPID[37]="1"
+PPID[38]="1"
+PPID[39]="1"
+PPID[40]="1"
+PPID[41]="1"
+PPID[42]="1"
+PPID[43]="1"
+PPID[44]="1"
+PPID[45]="1"
+PPID[46]="1"
+PPID[47]="1"
+PPID[48]="2124"
+PPID[49]="2184"
+PPID[50]="2184"
+PPID[51]="1"
+PPID[52]="1"
+PPID[53]="1"
+PPID[54]="1"
+PPID[55]="1"
+PPID[56]="1"
+PPID[57]="1"
+PPID[58]="1"
+PPID[59]="1"
+PPID[60]="1"
+PPID[61]="1"
+PPID[62]="1"
+PPID[63]="1"
+PPID[64]="1"
+PPID[65]="1"
+PPID[66]="1"
+PPID[67]="1"
+PPID[68]="2701"
+PPID[69]="1"
+PPID[70]="1"
+PPID[71]="1"
+PPID[72]="1"
+PPID[73]="1"
+PPID[74]="1"
+PPID[75]="1887"
+PPID[76]="31905"
+PPID[77]="31907"
+PPID[78]="1887"
+PPID[79]="32672"
+PPID[80]="32674"
+PPID[81]="3"
+PPID[82]="3"
+PPID[83]="1887"
+PPID[84]="27682"
+PPID[85]="27684"
+PPID[86]="27685"
+C[0]="C"
+C[1]="0"
+C[2]="0"
+C[3]="0"
+C[4]="0"
+C[5]="0"
+C[6]="0"
+C[7]="0"
+C[8]="0"
+C[9]="0"
+C[10]="0"
+C[11]="0"
+C[12]="0"
+C[13]="0"
+C[14]="0"
+C[15]="0"
+C[16]="0"
+C[17]="0"
+C[18]="0"
+C[19]="0"
+C[20]="0"
+C[21]="0"
+C[22]="0"
+C[23]="0"
+C[24]="0"
+C[25]="0"
+C[26]="0"
+C[27]="0"
+C[28]="0"
+C[29]="0"
+C[30]="0"
+C[31]="0"
+C[32]="0"
+C[33]="0"
+C[34]="0"
+C[35]="0"
+C[36]="0"
+C[37]="0"
+C[38]="0"
+C[39]="0"
+C[40]="0"
+C[41]="0"
+C[42]="0"
+C[43]="0"
+C[44]="0"
+C[45]="0"
+C[46]="0"
+C[47]="0"
+C[48]="0"
+C[49]="1"
+C[50]="0"
+C[51]="0"
+C[52]="0"
+C[53]="0"
+C[54]="0"
+C[55]="0"
+C[56]="0"
+C[57]="0"
+C[58]="0"
+C[59]="0"
+C[60]="0"
+C[61]="0"
+C[62]="0"
+C[63]="0"
+C[64]="0"
+C[65]="0"
+C[66]="0"
+C[67]="1"
+C[68]="0"
+C[69]="0"
+C[70]="0"
+C[71]="0"
+C[72]="0"
+C[73]="0"
+C[74]="0"
+C[75]="0"
+C[76]="0"
+C[77]="0"
+C[78]="0"
+C[79]="0"
+C[80]="0"
+C[81]="0"
+C[82]="0"
+C[83]="0"
+C[84]="0"
+C[85]="0"
+C[86]="0"
+STIME[0]="STIME"
+STIME[1]="Aug21"
+STIME[2]="Aug21"
+STIME[3]="Aug21"
+STIME[4]="Aug21"
+STIME[5]="Aug21"
+STIME[6]="Aug21"
+STIME[7]="Aug21"
+STIME[8]="Aug21"
+STIME[9]="Aug21"
+STIME[10]="Aug21"
+STIME[11]="Aug21"
+STIME[12]="Aug21"
+STIME[13]="Aug21"
+STIME[14]="Aug21"
+STIME[15]="Aug21"
+STIME[16]="Aug21"
+STIME[17]="Aug21"
+STIME[18]="Aug21"
+STIME[19]="Aug21"
+STIME[20]="Aug21"
+STIME[21]="Aug21"
+STIME[22]="Aug21"
+STIME[23]="Aug21"
+STIME[24]="Aug21"
+STIME[25]="Aug21"
+STIME[26]="Aug21"
+STIME[27]="Aug21"
+STIME[28]="Aug21"
+STIME[29]="Aug21"
+STIME[30]="Aug21"
+STIME[31]="Aug21"
+STIME[32]="Aug21"
+STIME[33]="Aug21"
+STIME[34]="Aug21"
+STIME[35]="Aug21"
+STIME[36]="Aug21"
+STIME[37]="Aug21"
+STIME[38]="Aug21"
+STIME[39]="Aug21"
+STIME[40]="Aug21"
+STIME[41]="Aug21"
+STIME[42]="Aug21"
+STIME[43]="Aug21"
+STIME[44]="Aug21"
+STIME[45]="Aug21"
+STIME[46]="Aug21"
+STIME[47]="Aug21"
+STIME[48]="Aug21"
+STIME[49]="Aug21"
+STIME[50]="Aug21"
+STIME[51]="Aug21"
+STIME[52]="Aug21"
+STIME[53]="Aug21"
+STIME[54]="Aug21"
+STIME[55]="Aug21"
+STIME[56]="Aug21"
+STIME[57]="Aug21"
+STIME[58]="Aug21"
+STIME[59]="Aug21"
+STIME[60]="Aug21"
+STIME[61]="Aug21"
+STIME[62]="Aug21"
+STIME[63]="Aug21"
+STIME[64]="Aug21"
+STIME[65]="Aug21"
+STIME[66]="Aug21"
+STIME[67]="Aug21"
+STIME[68]="Aug21"
+STIME[69]="Aug21"
+STIME[70]="Aug21"
+STIME[71]="Aug21"
+STIME[72]="Aug21"
+STIME[73]="Aug21"
+STIME[74]="Aug26"
+STIME[75]="Aug27"
+STIME[76]="Aug27"
+STIME[77]="Aug27"
+STIME[78]="Aug27"
+STIME[79]="Aug27"
+STIME[80]="Aug27"
+STIME[81]="Sep12"
+STIME[82]="Sep12"
+STIME[83]="Sep12"
+STIME[84]="Sep12"
+STIME[85]="Sep12"
+STIME[86]="17:47"
+TTY[0]="TTY"
+TTY[1]="?"
+TTY[2]="?"
+TTY[3]="?"
+TTY[4]="?"
+TTY[5]="?"
+TTY[6]="?"
+TTY[7]="?"
+TTY[8]="?"
+TTY[9]="?"
+TTY[10]="?"
+TTY[11]="?"
+TTY[12]="?"
+TTY[13]="?"
+TTY[14]="?"
+TTY[15]="?"
+TTY[16]="?"
+TTY[17]="?"
+TTY[18]="?"
+TTY[19]="?"
+TTY[20]="?"
+TTY[21]="?"
+TTY[22]="?"
+TTY[23]="?"
+TTY[24]="?"
+TTY[25]="?"
+TTY[26]="?"
+TTY[27]="?"
+TTY[28]="?"
+TTY[29]="?"
+TTY[30]="?"
+TTY[31]="?"
+TTY[32]="?"
+TTY[33]="?"
+TTY[34]="?"
+TTY[35]="?"
+TTY[36]="?"
+TTY[37]="?"
+TTY[38]="?"
+TTY[39]="?"
+TTY[40]="?"
+TTY[41]="?"
+TTY[42]="?"
+TTY[43]="?"
+TTY[44]="?"
+TTY[45]="?"
+TTY[46]="tty1"
+TTY[47]="?"
+TTY[48]="?"
+TTY[49]="?"
+TTY[50]="?"
+TTY[51]="?"
+TTY[52]="?"
+TTY[53]="?"
+TTY[54]="?"
+TTY[55]="?"
+TTY[56]="?"
+TTY[57]="?"
+TTY[58]="?"
+TTY[59]="?"
+TTY[60]="?"
+TTY[61]="?"
+TTY[62]="?"
+TTY[63]="?"
+TTY[64]="?"
+TTY[65]="?"
+TTY[66]="?"
+TTY[67]="?"
+TTY[68]="?"
+TTY[69]="?"
+TTY[70]="?"
+TTY[71]="?"
+TTY[72]="?"
+TTY[73]="?"
+TTY[74]="?"
+TTY[75]="?"
+TTY[76]="?"
+TTY[77]="pts/2"
+TTY[78]="?"
+TTY[79]="?"
+TTY[80]="pts/4"
+TTY[81]="?"
+TTY[82]="?"
+TTY[83]="?"
+TTY[84]="?"
+TTY[85]="pts/1"
+TTY[86]="pts/1"
+TIME[0]="TIME"
+TIME[1]="00:01:11"
+TIME[2]="00:00:00"
+TIME[3]="00:01:23"
+TIME[4]="00:00:00"
+TIME[5]="00:00:00"
+TIME[6]="00:00:00"
+TIME[7]="00:00:00"
+TIME[8]="00:00:00"
+TIME[9]="00:00:06"
+TIME[10]="00:00:00"
+TIME[11]="00:00:00"
+TIME[12]="00:04:49"
+TIME[13]="00:00:00"
+TIME[14]="00:00:00"
+TIME[15]="00:00:00"
+TIME[16]="00:00:09"
+TIME[17]="00:00:16"
+TIME[18]="00:00:00"
+TIME[19]="00:00:08"
+TIME[20]="00:00:00"
+TIME[21]="00:00:07"
+TIME[22]="00:05:17"
+TIME[23]="00:00:23"
+TIME[24]="00:00:00"
+TIME[25]="00:00:00"
+TIME[26]="00:00:00"
+TIME[27]="00:01:33"
+TIME[28]="00:00:00"
+TIME[29]="00:00:00"
+TIME[30]="00:00:00"
+TIME[31]="00:00:00"
+TIME[32]="00:00:00"
+TIME[33]="00:00:00"
+TIME[34]="00:00:00"
+TIME[35]="00:00:00"
+TIME[36]="00:00:00"
+TIME[37]="00:00:00"
+TIME[38]="00:00:00"
+TIME[39]="00:00:00"
+TIME[40]="00:00:29"
+TIME[41]="00:00:01"
+TIME[42]="00:00:03"
+TIME[43]="00:00:00"
+TIME[44]="00:00:00"
+TIME[45]="00:05:00"
+TIME[46]="00:00:00"
+TIME[47]="00:00:00"
+TIME[48]="00:00:00"
+TIME[49]="12:18:15"
+TIME[50]="00:00:01"
+TIME[51]="00:00:00"
+TIME[52]="00:00:00"
+TIME[53]="00:00:00"
+TIME[54]="00:00:47"
+TIME[55]="00:00:00"
+TIME[56]="00:00:00"
+TIME[57]="00:00:00"
+TIME[58]="00:13:10"
+TIME[59]="00:01:18"
+TIME[60]="00:00:00"
+TIME[61]="00:00:02"
+TIME[62]="00:27:25"
+TIME[63]="00:00:00"
+TIME[64]="00:00:37"
+TIME[65]="00:00:00"
+TIME[66]="00:01:31"
+TIME[67]="11:09:59"
+TIME[68]="00:02:30"
+TIME[69]="00:00:05"
+TIME[70]="00:00:00"
+TIME[71]="00:14:36"
+TIME[72]="00:43:09"
+TIME[73]="00:00:00"
+TIME[74]="00:00:00"
+TIME[75]="00:00:06"
+TIME[76]="00:00:55"
+TIME[77]="00:00:01"
+TIME[78]="00:00:06"
+TIME[79]="00:02:11"
+TIME[80]="00:00:06"
+TIME[81]="00:00:00"
+TIME[82]="00:00:31"
+TIME[83]="00:00:03"
+TIME[84]="00:09:06"
+TIME[85]="00:00:26"
+TIME[86]="00:00:00"
+CMD[0]="CMD"
+CMD[1]="init [5] "
+CMD[2]="[ksoftirqd/0]"
+CMD[3]="[events/0]"
+CMD[4]="[khelper]"
+CMD[5]="[kacpid]"
+CMD[6]="[kblockd/0]"
+CMD[7]="[aio/0]"
+CMD[8]="[khubd]"
+CMD[9]="[kswapd0]"
+CMD[10]="[kseriod]"
+CMD[11]="[scsi_eh_0]"
+CMD[12]="[kjournald]"
+CMD[13]="udevd"
+CMD[14]="/sbin/dhclient -1 -q -lf /var/lib/dhcp/dhclient-eth0.leases -pf /var/run/dhclient-eth0.pid eth0"
+CMD[15]="[kjournald]"
+CMD[16]="[kjournald]"
+CMD[17]="syslogd -m 0"
+CMD[18]="klogd -x"
+CMD[19]="portmap"
+CMD[20]="rpc.statd"
+CMD[21]="rpc.idmapd"
+CMD[22]="/usr/sbin/vmware-guestd --background /var/run/vmware-guestd.pid"
+CMD[23]="[rpciod]"
+CMD[24]="[lockd]"
+CMD[25]="ypbind"
+CMD[26]="/usr/sbin/acpid"
+CMD[27]="cupsd"
+CMD[28]="/usr/sbin/sshd"
+CMD[29]="xinetd -stayalive -pidfile /var/run/xinetd.pid"
+CMD[30]="rpc.rquotad"
+CMD[31]="[nfsd]"
+CMD[32]="[nfsd]"
+CMD[33]="[nfsd]"
+CMD[34]="[nfsd]"
+CMD[35]="[nfsd]"
+CMD[36]="[nfsd]"
+CMD[37]="[nfsd]"
+CMD[38]="[nfsd]"
+CMD[39]="rpc.mountd"
+CMD[40]="crond"
+CMD[41]="xfs -droppriv -daemon"
+CMD[42]="/usr/sbin/atd"
+CMD[43]="dbus-daemon-1 --system"
+CMD[44]="cups-config-daemon"
+CMD[45]="hald"
+CMD[46]="/sbin/mingetty tty1"
+CMD[47]="/usr/bin/gdm-binary -nodaemon"
+CMD[48]="/usr/bin/gdm-binary -nodaemon"
+CMD[49]="/usr/X11R6/bin/X :0 -audit 0 -auth /var/gdm/:0.Xauth -nolisten tcp vt7"
+CMD[50]="/usr/bin/gnome-session"
+CMD[51]="/usr/bin/ssh-agent -s"
+CMD[52]="/usr/bin/dbus-launch --exit-with-session /etc/X11/xinit/Xclients"
+CMD[53]="dbus-daemon-1 --fork --print-pid 8 --print-address 6 --session"
+CMD[54]="/usr/libexec/gconfd-2 11"
+CMD[55]="/usr/bin/gnome-keyring-daemon"
+CMD[56]="/usr/libexec/bonobo-activation-server --ac-activate --ior-output-fd=18"
+CMD[57]="/usr/libexec/gnome-settings-daemon --oaf-activate-iid=OAFIID:GNOME_SettingsDaemon --oaf-ior-fd=22"
+CMD[58]="/usr/libexec/gam_server"
+CMD[59]="xscreensaver -nosplash"
+CMD[60]="/usr/bin/metacity --sm-client-id=default1"
+CMD[61]="gnome-panel --sm-client-id default2"
+CMD[62]="nautilus --no-default-window --sm-client-id default3"
+CMD[63]="gnome-volume-manager --sm-client-id default6"
+CMD[64]="eggcups --sm-client-id default5"
+CMD[65]="/usr/libexec/gnome-vfs-daemon --oaf-activate-iid=OAFIID:GNOME_VFS_Daemon_Factory --oaf-ior-fd=28"
+CMD[66]="pam-panel-icon --sm-client-id default0"
+CMD[67]="/usr/bin/python /usr/bin/rhn-applet-gui --sm-client-id default4"
+CMD[68]="/sbin/pam_timestamp_check -d root"
+CMD[69]="/usr/libexec/mapping-daemon"
+CMD[70]="/usr/libexec/wnck-applet --oaf-activate-iid=OAFIID:GNOME_Wncklet_Factory --oaf-ior-fd=30"
+CMD[71]="/usr/libexec/mixer_applet2 --oaf-activate-iid=OAFIID:GNOME_MixerApplet_Factory --oaf-ior-fd=32"
+CMD[72]="/usr/libexec/clock-applet --oaf-activate-iid=OAFIID:GNOME_ClockApplet_Factory --oaf-ior-fd=34"
+CMD[73]="/usr/libexec/notification-area-applet --oaf-activate-iid=OAFIID:GNOME_NotificationAreaApplet_Factory --oaf-ior-fd=36"
+CMD[74]="/sbin/dhclient -1 -q -lf /var/lib/dhcp/dhclient-eth0.leases -pf /var/run/dhclient-eth0.pid eth0"
+CMD[75]="sshd: kaz [priv] "
+CMD[76]="sshd: kaz@pts/2 "
+CMD[77]="-bash"
+CMD[78]="sshd: kaz [priv] "
+CMD[79]="sshd: kaz@pts/4 "
+CMD[80]="-bash"
+CMD[81]="[pdflush]"
+CMD[82]="[pdflush]"
+CMD[83]="sshd: kaz [priv] "
+CMD[84]="sshd: kaz@pts/1 "
+CMD[85]="-bash"
+CMD[86]="ps -ef"
diff --git a/tests/001/query-2.txr b/tests/001/query-2.txr
new file mode 100644
index 00000000..c040ca9f
--- /dev/null
+++ b/tests/001/query-2.txr
@@ -0,0 +1,7 @@
+@#
+@# This file is in the public domain.
+@# It was authored by Kaz Kylheku <kkylheku@gmail.com> in 2009
+@#
+@(collect)
+@{UID 8} @{PID 5} @{PPID 5} @{C 1} @{STIME 5} @{TTY 8} @{TIME 8} @CMD
+@(end)
diff --git a/tests/001/query-3.expected b/tests/001/query-3.expected
new file mode 100644
index 00000000..4acab394
--- /dev/null
+++ b/tests/001/query-3.expected
@@ -0,0 +1,87 @@
+UID PID
+root 1
+root 2
+root 3
+root 4
+root 5
+root 16
+root 29
+root 17
+root 28
+root 103
+root 175
+root 186
+root 870
+root 1068
+root 1235
+root 1236
+root 1620
+root 1624
+rpc 1645
+rpcuser 1665
+root 1698
+root 1766
+root 1790
+root 1791
+root 1821
+root 1839
+root 1851
+root 1887
+root 1902
+root 1921
+root 1925
+root 1926
+root 1927
+root 1928
+root 1929
+root 1930
+root 1931
+root 1932
+root 1936
+root 1963
+xfs 1989
+daemon 2008
+dbus 2027
+root 2041
+root 2052
+root 2062
+root 2124
+root 2184
+root 2354
+kaz 2551
+kaz 2579
+kaz 2625
+kaz 2626
+kaz 2631
+kaz 2634
+kaz 2636
+kaz 2638
+kaz 2644
+kaz 2661
+kaz 2685
+kaz 2689
+kaz 2691
+kaz 2693
+kaz 2695
+kaz 2698
+kaz 2701
+kaz 2707
+root 2717
+kaz 2718
+kaz 2720
+kaz 2722
+kaz 2726
+kaz 2728
+root 30737
+root 31905
+kaz 31907
+kaz 31908
+root 32672
+kaz 32674
+kaz 32675
+root 27121
+root 27243
+root 27682
+kaz 27684
+kaz 27685
+kaz 6481
diff --git a/tests/001/query-3.txr b/tests/001/query-3.txr
new file mode 100644
index 00000000..ec12c9f2
--- /dev/null
+++ b/tests/001/query-3.txr
@@ -0,0 +1,21 @@
+@#
+@# This file is in the public domain.
+@# It was authored by Kaz Kylheku <kkylheku@gmail.com> in 2009
+@#
+@/.*/@# skip header
+@(collect)
+@ (coll)@{TOKEN /[^ ]+/}@(end)
+@ (bind (UID PID PPID C STIME TTY TIME . CMD) TOKEN)
+@ (cat CMD) @#
+@ (forget TOKEN)
+@(end)
+@(output)
+@(repeat)
+@{UID 8} @{PID -5}
+@(first)
+UID PID
+@{UID 8} @{PID -5}
+@(empty)
+No processes!
+@(end)
+@(end)
diff --git a/tests/002/etc/passwd b/tests/002/etc/passwd
new file mode 100644
index 00000000..d7bf21b0
--- /dev/null
+++ b/tests/002/etc/passwd
@@ -0,0 +1,32 @@
+root:x:0:0:root:/root:/bin/bash
+bin:x:1:1:bin:/bin:/sbin/nologin
+daemon:x:2:2:daemon:/sbin:/sbin/nologin
+adm:x:3:4:adm:/var/adm:/sbin/nologin
+lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
+sync:x:5:0:sync:/sbin:/bin/sync
+shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
+halt:x:7:0:halt:/sbin:/sbin/halt
+mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
+news:x:9:13:news:/etc/news:
+uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
+operator:x:11:0:operator:/root:/sbin/nologin
+games:x:12:100:games:/usr/games:/sbin/nologin
+gopher:x:13:30:gopher:/var/gopher:/sbin/nologin
+ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
+nobody:x:99:99:Nobody:/:/sbin/nologin
+dbus:x:81:81:System message bus:/:/sbin/nologin
+vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin
+rpm:x:37:37::/var/lib/rpm:/sbin/nologin
+haldaemon:x:68:68:HAL daemon:/:/sbin/nologin
+netdump:x:34:34:Network Crash Dump user:/var/crash:/bin/bash
+nscd:x:28:28:NSCD Daemon:/:/sbin/nologin
+sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
+rpc:x:32:32:Portmapper RPC user:/:/sbin/nologin
+rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
+nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
+mailnull:x:47:47::/var/spool/mqueue:/sbin/nologin
+smmsp:x:51:51::/var/spool/mqueue:/sbin/nologin
+pcap:x:77:77::/var/arpwatch:/sbin/nologin
+xfs:x:43:43:X Font Server:/etc/X11/fs:/sbin/nologin
+ntp:x:38:38::/etc/ntp:/sbin/nologin
+gdm:x:42:42::/var/gdm:/sbin/nologin
diff --git a/tests/002/proc/1/status b/tests/002/proc/1/status
new file mode 100644
index 00000000..722c84b6
--- /dev/null
+++ b/tests/002/proc/1/status
@@ -0,0 +1,31 @@
+Name: init
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 1
+Pid: 1
+PPid: 0
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 1920 kB
+VmLck: 0 kB
+VmRSS: 552 kB
+VmData: 180 kB
+VmStk: 324 kB
+VmExe: 27 kB
+VmLib: 1353 kB
+StaBrk: 08050000 kB
+Brk: 09714000 kB
+StaStk: bffb2830 kB
+ExecLim: 0804f000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: fffffffe57f0d8fc
+SigCgt: 00000000280b2603
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1/task/1/stat b/tests/002/proc/1/task/1/stat
new file mode 100644
index 00000000..cd0c6dd8
--- /dev/null
+++ b/tests/002/proc/1/task/1/stat
@@ -0,0 +1 @@
+1 (init) S 0 0 0 0 -1 4194560 787 8132519 20 1725 10 7247 29847 236686 16 0 1 0 56 1966080 138 4294967295 134512640 134541009 3220908080 3220898444 2340770 0 0 1475401980 671819267 3222765173 0 0 0 0 0 0
diff --git a/tests/002/proc/103/status b/tests/002/proc/103/status
new file mode 100644
index 00000000..ce0c54f9
--- /dev/null
+++ b/tests/002/proc/103/status
@@ -0,0 +1,20 @@
+Name: kseriod
+State: S (sleeping)
+SleepAVG: 97%
+Tgid: 103
+Pid: 103
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffbfff
+SigIgn: 0000000000000000
+SigCgt: 0000000000004100
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/103/task/103/stat b/tests/002/proc/103/task/103/stat
new file mode 100644
index 00000000..87dd4271
--- /dev/null
+++ b/tests/002/proc/103/task/103/stat
@@ -0,0 +1 @@
+103 (kseriod) S 1 1 1 0 -1 64 0 0 0 0 0 2 0 0 15 0 1 0 68 0 0 4294967295 0 0 0 0 0 0 2147467263 0 16640 3223538745 0 0 17 0 0 0
diff --git a/tests/002/proc/1068/status b/tests/002/proc/1068/status
new file mode 100644
index 00000000..1ee30bf7
--- /dev/null
+++ b/tests/002/proc/1068/status
@@ -0,0 +1,31 @@
+Name: dhclient
+State: S (sleeping)
+SleepAVG: 90%
+Tgid: 1068
+Pid: 1068
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 2872 kB
+VmLck: 0 kB
+VmRSS: 1068 kB
+VmData: 328 kB
+VmStk: 736 kB
+VmExe: 349 kB
+VmLib: 1391 kB
+StaBrk: 080ae000 kB
+Brk: 099d7000 kB
+StaStk: bff4ae00 kB
+ExecLim: 0809f000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1068/task/1068/stat b/tests/002/proc/1068/task/1068/stat
new file mode 100644
index 00000000..31a36593
--- /dev/null
+++ b/tests/002/proc/1068/task/1068/stat
@@ -0,0 +1 @@
+1068 (dhclient) S 1 1068 1068 0 -1 4227136 231 33968 0 0 3 67 3 167 6 -10 1 0 4355 2940928 267 4294967295 134508544 134865956 3220483584 3220482684 2340770 0 0 0 0 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/1235/status b/tests/002/proc/1235/status
new file mode 100644
index 00000000..b7e28314
--- /dev/null
+++ b/tests/002/proc/1235/status
@@ -0,0 +1,20 @@
+Name: kjournald
+State: S (sleeping)
+SleepAVG: 9%
+Tgid: 1235
+Pid: 1235
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffffff
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1235/task/1235/stat b/tests/002/proc/1235/task/1235/stat
new file mode 100644
index 00000000..18b620ce
--- /dev/null
+++ b/tests/002/proc/1235/task/1235/stat
@@ -0,0 +1 @@
+1235 (kjournald) S 1 1 1 0 -1 4194368 0 0 0 0 0 0 0 0 24 0 1 0 6213 0 0 4294967295 0 0 0 0 0 0 2147483647 0 0 4170054760 0 0 17 0 0 0
diff --git a/tests/002/proc/1236/status b/tests/002/proc/1236/status
new file mode 100644
index 00000000..459cdaf6
--- /dev/null
+++ b/tests/002/proc/1236/status
@@ -0,0 +1,20 @@
+Name: kjournald
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 1236
+Pid: 1236
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffffff
+SigIgn: 0000000000000000
+SigCgt: 000000000000feff
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1236/task/1236/stat b/tests/002/proc/1236/task/1236/stat
new file mode 100644
index 00000000..e0d98f40
--- /dev/null
+++ b/tests/002/proc/1236/task/1236/stat
@@ -0,0 +1 @@
+1236 (kjournald) S 1 1 1 0 -1 4194368 0 0 0 0 0 954 0 0 15 0 1 0 6217 0 0 4294967295 0 0 0 0 0 0 2147483647 0 65279 4170054760 0 0 17 0 0 0
diff --git a/tests/002/proc/15812/status b/tests/002/proc/15812/status
new file mode 100644
index 00000000..597790df
--- /dev/null
+++ b/tests/002/proc/15812/status
@@ -0,0 +1,31 @@
+Name: ssh
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 15812
+Pid: 15812
+PPid: 32675
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 4184 kB
+VmLck: 0 kB
+VmRSS: 2348 kB
+VmData: 644 kB
+VmStk: 48 kB
+VmExe: 230 kB
+VmLib: 3066 kB
+StaBrk: 0014f000 kB
+Brk: 0806b000 kB
+StaStk: bffff9a0 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000008004006
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/15812/task/15812/stat b/tests/002/proc/15812/task/15812/stat
new file mode 100644
index 00000000..36790164
--- /dev/null
+++ b/tests/002/proc/15812/task/15812/stat
@@ -0,0 +1 @@
+15812 (ssh) S 32675 15812 32675 34820 15812 4194304 696 0 0 0 151 4315 0 0 16 0 1 0 184209784 4284416 587 4294967295 1118208 1354060 3221223840 3221214156 3086915490 0 0 4096 134234118 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/16/status b/tests/002/proc/16/status
new file mode 100644
index 00000000..d14fa89b
--- /dev/null
+++ b/tests/002/proc/16/status
@@ -0,0 +1,20 @@
+Name: kblockd/0
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 16
+Pid: 16
+PPid: 3
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffffff
+SigIgn: 0000000000010000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/16/task/16/stat b/tests/002/proc/16/task/16/stat
new file mode 100644
index 00000000..01052f69
--- /dev/null
+++ b/tests/002/proc/16/task/16/stat
@@ -0,0 +1 @@
+16 (kblockd/0) S 3 0 0 0 -1 32832 0 0 0 0 0 47 0 0 5 -10 1 0 62 0 0 4294967295 0 0 0 0 0 0 2147483647 65536 0 3222484651 0 0 17 0 0 0
diff --git a/tests/002/proc/1620/status b/tests/002/proc/1620/status
new file mode 100644
index 00000000..dccb9e72
--- /dev/null
+++ b/tests/002/proc/1620/status
@@ -0,0 +1,31 @@
+Name: syslogd
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 1620
+Pid: 1620
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 1516 kB
+VmLck: 0 kB
+VmRSS: 628 kB
+VmData: 148 kB
+VmStk: 16 kB
+VmExe: 28 kB
+VmLib: 1288 kB
+StaBrk: 0011a000 kB
+Brk: 08021000 kB
+StaStk: bffffe20 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000000206
+SigCgt: 0000000000016001
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1620/task/1620/stat b/tests/002/proc/1620/task/1620/stat
new file mode 100644
index 00000000..c3378aec
--- /dev/null
+++ b/tests/002/proc/1620/task/1620/stat
@@ -0,0 +1 @@
+1620 (syslogd) S 1 1620 1620 0 -1 4194368 91 0 0 0 8 1685 0 0 16 0 1 3000 8195 1552384 157 4294967295 1118208 1147540 3221224992 3221223468 3086915490 0 0 518 90113 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/1624/status b/tests/002/proc/1624/status
new file mode 100644
index 00000000..6882a4a7
--- /dev/null
+++ b/tests/002/proc/1624/status
@@ -0,0 +1,31 @@
+Name: klogd
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 1624
+Pid: 1624
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 1456 kB
+VmLck: 0 kB
+VmRSS: 464 kB
+VmData: 152 kB
+VmStk: 8 kB
+VmExe: 19 kB
+VmLib: 1249 kB
+StaBrk: 00118000 kB
+Brk: 08021000 kB
+StaStk: bffffe20 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: fffffffe7ff1b4fc
+SigCgt: 00000000000a4a03
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1624/task/1624/stat b/tests/002/proc/1624/task/1624/stat
new file mode 100644
index 00000000..456fa43d
--- /dev/null
+++ b/tests/002/proc/1624/task/1624/stat
@@ -0,0 +1 @@
+1624 (klogd) S 1 1624 1624 0 -1 4194624 41 0 0 0 0 6 0 0 16 0 1 0 8206 1490944 116 4294967295 1118208 1138592 3221224992 3221224740 3086915490 0 0 2146546940 674307 3222406663 0 0 17 0 0 0
diff --git a/tests/002/proc/16248/status b/tests/002/proc/16248/status
new file mode 100644
index 00000000..0756ddf5
--- /dev/null
+++ b/tests/002/proc/16248/status
@@ -0,0 +1,31 @@
+Name: su
+State: S (sleeping)
+SleepAVG: 83%
+Tgid: 16248
+Pid: 16248
+PPid: 31908
+TracerPid: 0
+Uid: 524 0 0 0
+Gid: 503 503 503 0
+FDSize: 256
+Groups: 501 503 512
+VmSize: 4300 kB
+VmLck: 0 kB
+VmRSS: 1216 kB
+VmData: 392 kB
+VmStk: 24 kB
+VmExe: 18 kB
+VmLib: 1690 kB
+StaBrk: 00117000 kB
+Brk: 08021000 kB
+StaStk: bffff9a0 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: fffffffe7ffb9eff
+SigIgn: 0000000000000000
+SigCgt: 0000000000004000
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/16248/task/16248/stat b/tests/002/proc/16248/task/16248/stat
new file mode 100644
index 00000000..709c1906
--- /dev/null
+++ b/tests/002/proc/16248/task/16248/stat
@@ -0,0 +1 @@
+16248 (su) S 31908 16248 31908 34818 29839 4194560 398 0 0 0 0 1 0 0 17 0 1 0 361193770 4403200 304 4294967295 1118208 1137492 3221223840 3221223220 3086915490 0 2147196671 0 16384 3222425215 0 0 17 0 0 0
diff --git a/tests/002/proc/16249/status b/tests/002/proc/16249/status
new file mode 100644
index 00000000..fb71b0b0
--- /dev/null
+++ b/tests/002/proc/16249/status
@@ -0,0 +1,31 @@
+Name: bash
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 16249
+Pid: 16249
+PPid: 16248
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 256
+Groups: 0 1 2 3 4 6 10 606
+VmSize: 4340 kB
+VmLck: 0 kB
+VmRSS: 1464 kB
+VmData: 296 kB
+VmStk: 20 kB
+VmExe: 577 kB
+VmLib: 1307 kB
+StaBrk: 080e3000 kB
+Brk: 08122000 kB
+StaStk: bffffeb0 kB
+ExecLim: 080d8000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000010000
+SigBlk: 0000000000010000
+SigIgn: 0000000000384004
+SigCgt: 000000004b813efb
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/16249/task/16249/stat b/tests/002/proc/16249/task/16249/stat
new file mode 100644
index 00000000..186edd84
--- /dev/null
+++ b/tests/002/proc/16249/task/16249/stat
@@ -0,0 +1 @@
+16249 (bash) S 16248 16249 31908 34818 29839 4194560 4193 733480 0 0 3 90 15 3364 16 0 1 0 361193918 4444160 366 4294967295 134508544 135099520 3221225136 3221223204 2340770 0 65536 3686404 1266761467 3222425215 0 0 17 0 0 0
diff --git a/tests/002/proc/1645/status b/tests/002/proc/1645/status
new file mode 100644
index 00000000..4084d915
--- /dev/null
+++ b/tests/002/proc/1645/status
@@ -0,0 +1,31 @@
+Name: portmap
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 1645
+Pid: 1645
+PPid: 1
+TracerPid: 0
+Uid: 32 32 32 32
+Gid: 32 32 32 32
+FDSize: 32
+Groups:
+VmSize: 1604 kB
+VmLck: 0 kB
+VmRSS: 632 kB
+VmData: 156 kB
+VmStk: 20 kB
+VmExe: 27 kB
+VmLib: 1357 kB
+StaBrk: 00119000 kB
+Brk: 08021000 kB
+StaStk: bffffe30 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000011000
+SigCgt: 0000000000000002
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/1645/task/1645/stat b/tests/002/proc/1645/task/1645/stat
new file mode 100644
index 00000000..dd085ba8
--- /dev/null
+++ b/tests/002/proc/1645/task/1645/stat
@@ -0,0 +1 @@
+1645 (portmap) S 1 1645 1645 0 -1 4194624 43040 0 1 0 29 870 0 0 16 0 1 0 8233 1642496 158 4294967295 1118208 1146168 3221225008 3221224700 3086915490 0 0 69632 2 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1665/status b/tests/002/proc/1665/status
new file mode 100644
index 00000000..87c0b24f
--- /dev/null
+++ b/tests/002/proc/1665/status
@@ -0,0 +1,31 @@
+Name: rpc.statd
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 1665
+Pid: 1665
+PPid: 1
+TracerPid: 0
+Uid: 29 29 29 29
+Gid: 29 29 29 29
+FDSize: 32
+Groups:
+VmSize: 1676 kB
+VmLck: 0 kB
+VmRSS: 796 kB
+VmData: 156 kB
+VmStk: 12 kB
+VmExe: 38 kB
+VmLib: 1414 kB
+StaBrk: 0011c000 kB
+Brk: 08021000 kB
+StaStk: bffffe30 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000011000
+SigCgt: 0000000000004203
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/1665/task/1665/stat b/tests/002/proc/1665/task/1665/stat
new file mode 100644
index 00000000..82a47421
--- /dev/null
+++ b/tests/002/proc/1665/task/1665/stat
@@ -0,0 +1 @@
+1665 (rpc.statd) S 1 1665 1665 0 -1 4194624 156 0 0 0 4 54 0 0 16 0 1 0 8250 1716224 199 4294967295 1118208 1157716 3221225008 3221224092 3086915490 0 0 69632 16899 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/1698/status b/tests/002/proc/1698/status
new file mode 100644
index 00000000..d7bc5d80
--- /dev/null
+++ b/tests/002/proc/1698/status
@@ -0,0 +1,31 @@
+Name: rpc.idmapd
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 1698
+Pid: 1698
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 3964 kB
+VmLck: 0 kB
+VmRSS: 1044 kB
+VmData: 344 kB
+VmStk: 8 kB
+VmExe: 39 kB
+VmLib: 3393 kB
+StaBrk: 08055000 kB
+Brk: 08076000 kB
+StaStk: bffffe20 kB
+ExecLim: 08051000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000000000
+SigCgt: 0000000000000a01
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1698/task/1698/stat b/tests/002/proc/1698/task/1698/stat
new file mode 100644
index 00000000..ece1a38d
--- /dev/null
+++ b/tests/002/proc/1698/task/1698/stat
@@ -0,0 +1 @@
+1698 (rpc.idmapd) S 1 1698 1698 0 -1 4194368 44 0 0 0 3 814 0 0 16 0 1 0 8312 4059136 261 4294967295 134508544 134549496 3221224992 3221224160 2340770 0 0 0 2561 0 0 0 17 0 0 0
diff --git a/tests/002/proc/17/status b/tests/002/proc/17/status
new file mode 100644
index 00000000..b5ce79c8
--- /dev/null
+++ b/tests/002/proc/17/status
@@ -0,0 +1,20 @@
+Name: khubd
+State: S (sleeping)
+SleepAVG: 0%
+Tgid: 17
+Pid: 17
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: fffffffffffffeff
+SigIgn: 0000000000000000
+SigCgt: 0000000000004100
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/17/task/17/stat b/tests/002/proc/17/task/17/stat
new file mode 100644
index 00000000..6efd905b
--- /dev/null
+++ b/tests/002/proc/17/task/17/stat
@@ -0,0 +1 @@
+17 (khubd) S 1 1 1 0 -1 64 0 0 0 0 0 0 0 0 25 0 1 0 63 0 0 4294967295 0 0 0 0 0 0 2147483391 0 16640 3223828654 0 0 17 0 0 0
diff --git a/tests/002/proc/175/status b/tests/002/proc/175/status
new file mode 100644
index 00000000..639924fa
--- /dev/null
+++ b/tests/002/proc/175/status
@@ -0,0 +1,20 @@
+Name: scsi_eh_0
+State: S (sleeping)
+SleepAVG: 29%
+Tgid: 175
+Pid: 175
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffffff
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/175/task/175/stat b/tests/002/proc/175/task/175/stat
new file mode 100644
index 00000000..33f34646
--- /dev/null
+++ b/tests/002/proc/175/task/175/stat
@@ -0,0 +1 @@
+175 (scsi_eh_0) S 1 1 1 0 -1 32832 0 0 0 0 0 0 0 0 22 0 1 0 511 0 0 4294967295 0 0 0 0 0 0 2147483647 0 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1766/status b/tests/002/proc/1766/status
new file mode 100644
index 00000000..8096c2ba
--- /dev/null
+++ b/tests/002/proc/1766/status
@@ -0,0 +1,31 @@
+Name: vmware-guestd
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 1766
+Pid: 1766
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 1588 kB
+VmLck: 0 kB
+VmRSS: 496 kB
+VmData: 164 kB
+VmStk: 16 kB
+VmExe: 131 kB
+VmLib: 1301 kB
+StaBrk: 0806e000 kB
+Brk: 0808f000 kB
+StaStk: bffffdc0 kB
+ExecLim: ffffffff
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000000000
+SigCgt: 0000000000004a07
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1766/task/1766/stat b/tests/002/proc/1766/task/1766/stat
new file mode 100644
index 00000000..116d84e4
--- /dev/null
+++ b/tests/002/proc/1766/task/1766/stat
@@ -0,0 +1 @@
+1766 (vmware-guestd) S 1 1766 1766 0 -1 320 99 134485 0 0 7949 24362 10 1014 15 0 1 0 8505 1626112 124 4294967295 134508544 134642788 3221224896 3221223516 2340770 0 0 0 18951 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/1790/status b/tests/002/proc/1790/status
new file mode 100644
index 00000000..44486c30
--- /dev/null
+++ b/tests/002/proc/1790/status
@@ -0,0 +1,20 @@
+Name: rpciod
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 1790
+Pid: 1790
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: fffffffffffffeff
+SigIgn: 0000000000000000
+SigCgt: 0000000000000100
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1790/task/1790/stat b/tests/002/proc/1790/task/1790/stat
new file mode 100644
index 00000000..dc92506d
--- /dev/null
+++ b/tests/002/proc/1790/task/1790/stat
@@ -0,0 +1 @@
+1790 (rpciod) S 1 1 1 0 -1 4194368 0 0 0 0 0 2366 0 0 15 0 1 0 8870 0 0 4294967295 0 0 0 0 0 0 2147483391 0 256 4171735819 0 0 17 0 0 0
diff --git a/tests/002/proc/1791/status b/tests/002/proc/1791/status
new file mode 100644
index 00000000..42548907
--- /dev/null
+++ b/tests/002/proc/1791/status
@@ -0,0 +1,20 @@
+Name: lockd
+State: S (sleeping)
+SleepAVG: 68%
+Tgid: 1791
+Pid: 1791
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: fffffffffffffeff
+SigIgn: 0000000000000000
+SigCgt: 0000000000000100
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1791/task/1791/stat b/tests/002/proc/1791/task/1791/stat
new file mode 100644
index 00000000..4f961c85
--- /dev/null
+++ b/tests/002/proc/1791/task/1791/stat
@@ -0,0 +1 @@
+1791 (lockd) S 1 1 1 0 -1 4194368 0 0 0 0 0 0 0 0 18 0 1 0 8871 0 0 4294967295 0 0 0 0 0 0 2147483391 0 256 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1821/status b/tests/002/proc/1821/status
new file mode 100644
index 00000000..6d138cd8
--- /dev/null
+++ b/tests/002/proc/1821/status
@@ -0,0 +1,31 @@
+Name: ypbind
+State: S (sleeping)
+SleepAVG: 87%
+Tgid: 1821
+Pid: 1821
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 24164 kB
+VmLck: 0 kB
+VmRSS: 808 kB
+VmData: 20652 kB
+VmStk: 16 kB
+VmExe: 27 kB
+VmLib: 1377 kB
+StaBrk: 08050000 kB
+Brk: 08071000 kB
+StaStk: bffffe30 kB
+ExecLim: 0804f000
+Threads: 3
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000014407
+SigIgn: 0000000000000000
+SigCgt: 0000000180000000
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1821/task/1821/stat b/tests/002/proc/1821/task/1821/stat
new file mode 100644
index 00000000..d501d22c
--- /dev/null
+++ b/tests/002/proc/1821/task/1821/stat
@@ -0,0 +1 @@
+1821 (ypbind) S 1 1820 1820 0 -1 4194624 42 0 0 0 0 1 0 0 17 0 3 0 8956 24743936 202 4294967295 134512640 134540552 3221225008 3221224476 2340770 0 82951 0 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1821/task/1822/stat b/tests/002/proc/1821/task/1822/stat
new file mode 100644
index 00000000..63cb2b0c
--- /dev/null
+++ b/tests/002/proc/1821/task/1822/stat
@@ -0,0 +1 @@
+1822 (ypbind) S 1 1820 1820 0 -1 4194368 5 0 0 0 0 0 0 0 23 0 3 0 8956 24743936 202 4294967295 134512640 134540552 3221225008 3084800692 2340770 0 0 0 0 0 0 0 -1 0 0 0
diff --git a/tests/002/proc/1821/task/1826/stat b/tests/002/proc/1821/task/1826/stat
new file mode 100644
index 00000000..5a396aff
--- /dev/null
+++ b/tests/002/proc/1821/task/1826/stat
@@ -0,0 +1 @@
+1826 (ypbind) S 1 1820 1820 0 -1 4194624 7 0 0 0 18 5402 0 0 16 0 3 0 8959 24743936 202 4294967295 134512640 134540552 3221225008 3074310752 2340770 0 82951 0 0 0 0 0 -1 0 0 0
diff --git a/tests/002/proc/1839/status b/tests/002/proc/1839/status
new file mode 100644
index 00000000..9a9e8380
--- /dev/null
+++ b/tests/002/proc/1839/status
@@ -0,0 +1,31 @@
+Name: acpid
+State: S (sleeping)
+SleepAVG: 12%
+Tgid: 1839
+Pid: 1839
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 1460 kB
+VmLck: 0 kB
+VmRSS: 548 kB
+VmData: 148 kB
+VmStk: 16 kB
+VmExe: 17 kB
+VmLib: 1251 kB
+StaBrk: 0804e000 kB
+Brk: 0806f000 kB
+StaStk: bffffe20 kB
+ExecLim: 0804c000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000000004007
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1839/task/1839/stat b/tests/002/proc/1839/task/1839/stat
new file mode 100644
index 00000000..2573613f
--- /dev/null
+++ b/tests/002/proc/1839/task/1839/stat
@@ -0,0 +1 @@
+1839 (acpid) S 1 1839 1839 0 -1 4194368 91 0 0 0 0 0 0 0 24 0 1 0 8976 1495040 137 4294967295 134508544 134526724 3221224992 3221224092 2340770 0 0 4096 16391 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/1851/status b/tests/002/proc/1851/status
new file mode 100644
index 00000000..84d5c10e
--- /dev/null
+++ b/tests/002/proc/1851/status
@@ -0,0 +1,31 @@
+Name: cupsd
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 1851
+Pid: 1851
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 7780 kB
+VmLck: 0 kB
+VmRSS: 2252 kB
+VmData: 1544 kB
+VmStk: 104 kB
+VmExe: 243 kB
+VmLib: 3649 kB
+StaBrk: 00150000 kB
+Brk: 08038000 kB
+StaStk: bffffe30 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000000016201
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1851/task/1851/stat b/tests/002/proc/1851/task/1851/stat
new file mode 100644
index 00000000..6847d8b2
--- /dev/null
+++ b/tests/002/proc/1851/task/1851/stat
@@ -0,0 +1 @@
+1851 (cupsd) S 1 1851 1851 0 -1 4194624 12577 6075 1 5 154 9399 0 31 16 0 1 0 9095 7966720 563 4294967295 1118208 1367304 3221225008 3221207052 3086915490 0 0 4096 90625 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/186/status b/tests/002/proc/186/status
new file mode 100644
index 00000000..6eac3e48
--- /dev/null
+++ b/tests/002/proc/186/status
@@ -0,0 +1,20 @@
+Name: kjournald
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 186
+Pid: 186
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffffff
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/186/task/186/stat b/tests/002/proc/186/task/186/stat
new file mode 100644
index 00000000..941871d0
--- /dev/null
+++ b/tests/002/proc/186/task/186/stat
@@ -0,0 +1 @@
+186 (kjournald) S 1 1 1 0 -1 64 0 0 0 0 0 29336 0 0 15 0 1 0 845 0 0 4294967295 0 0 0 0 0 0 2147483647 0 0 4170054760 0 0 17 0 0 0
diff --git a/tests/002/proc/1887/status b/tests/002/proc/1887/status
new file mode 100644
index 00000000..79391da3
--- /dev/null
+++ b/tests/002/proc/1887/status
@@ -0,0 +1,31 @@
+Name: sshd
+State: S (sleeping)
+SleepAVG: 87%
+Tgid: 1887
+Pid: 1887
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 3924 kB
+VmLck: 0 kB
+VmRSS: 1652 kB
+VmData: 356 kB
+VmStk: 16 kB
+VmExe: 295 kB
+VmLib: 3069 kB
+StaBrk: 00161000 kB
+Brk: 08021000 kB
+StaStk: bffffe20 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000000014005
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1887/task/1887/stat b/tests/002/proc/1887/task/1887/stat
new file mode 100644
index 00000000..848f1662
--- /dev/null
+++ b/tests/002/proc/1887/task/1887/stat
@@ -0,0 +1 @@
+1887 (sshd) S 1 1887 1887 0 -1 4194624 318 27221747 0 854 27 50 861229 380242 16 0 1 360000 9201 4018176 413 4294967295 1118208 1420332 3221224992 3221220652 3086915490 0 0 4096 81925 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/1902/status b/tests/002/proc/1902/status
new file mode 100644
index 00000000..9311271f
--- /dev/null
+++ b/tests/002/proc/1902/status
@@ -0,0 +1,31 @@
+Name: xinetd
+State: S (sleeping)
+SleepAVG: 76%
+Tgid: 1902
+Pid: 1902
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 2068 kB
+VmLck: 0 kB
+VmRSS: 860 kB
+VmData: 316 kB
+VmStk: 12 kB
+VmExe: 143 kB
+VmLib: 1533 kB
+StaBrk: 00137000 kB
+Brk: 08021000 kB
+StaStk: bffffd90 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000381000
+SigCgt: 000000007fc3eeff
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1902/task/1902/stat b/tests/002/proc/1902/task/1902/stat
new file mode 100644
index 00000000..9ff7cc6c
--- /dev/null
+++ b/tests/002/proc/1902/task/1902/stat
@@ -0,0 +1 @@
+1902 (xinetd) S 1 1902 1902 0 -1 4194624 149 0 0 0 0 3 0 0 18 0 1 0 9223 2117632 215 4294967295 1118208 1265004 3221224848 3221224332 3086915490 0 0 3674112 2143547135 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/1921/status b/tests/002/proc/1921/status
new file mode 100644
index 00000000..0dc21447
--- /dev/null
+++ b/tests/002/proc/1921/status
@@ -0,0 +1,31 @@
+Name: rpc.rquotad
+State: S (sleeping)
+SleepAVG: 78%
+Tgid: 1921
+Pid: 1921
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 3740 kB
+VmLck: 0 kB
+VmRSS: 812 kB
+VmData: 156 kB
+VmStk: 8 kB
+VmExe: 58 kB
+VmLib: 1414 kB
+StaBrk: 00121000 kB
+Brk: 08021000 kB
+StaStk: bffffe20 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000010000
+SigCgt: 0000000000004003
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1921/task/1921/stat b/tests/002/proc/1921/task/1921/stat
new file mode 100644
index 00000000..24e001ea
--- /dev/null
+++ b/tests/002/proc/1921/task/1921/stat
@@ -0,0 +1 @@
+1921 (rpc.rquotad) S 1 1921 1921 0 -1 4194368 10 0 0 0 0 0 0 0 16 0 1 0 9314 3829760 203 4294967295 1118208 1178312 3221224992 3221224428 3086915490 0 0 65536 16387 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1925/status b/tests/002/proc/1925/status
new file mode 100644
index 00000000..4fa3ce56
--- /dev/null
+++ b/tests/002/proc/1925/status
@@ -0,0 +1,20 @@
+Name: nfsd
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 1925
+Pid: 1925
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: fffffffffffffef8
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1925/task/1925/stat b/tests/002/proc/1925/task/1925/stat
new file mode 100644
index 00000000..13126047
--- /dev/null
+++ b/tests/002/proc/1925/task/1925/stat
@@ -0,0 +1 @@
+1925 (nfsd) S 1 1 1 0 -1 5242944 0 0 0 0 0 0 0 0 15 0 1 0 9319 0 0 4294967295 0 0 0 0 0 0 2147483384 0 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1926/status b/tests/002/proc/1926/status
new file mode 100644
index 00000000..1ddfdcba
--- /dev/null
+++ b/tests/002/proc/1926/status
@@ -0,0 +1,20 @@
+Name: nfsd
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 1926
+Pid: 1926
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: fffffffffffffef8
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1926/task/1926/stat b/tests/002/proc/1926/task/1926/stat
new file mode 100644
index 00000000..a77e08ee
--- /dev/null
+++ b/tests/002/proc/1926/task/1926/stat
@@ -0,0 +1 @@
+1926 (nfsd) S 1 1 1 0 -1 5242944 0 0 0 0 0 0 0 0 15 0 1 0 9319 0 0 4294967295 0 0 0 0 0 0 2147483384 0 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1927/status b/tests/002/proc/1927/status
new file mode 100644
index 00000000..a1e35914
--- /dev/null
+++ b/tests/002/proc/1927/status
@@ -0,0 +1,20 @@
+Name: nfsd
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 1927
+Pid: 1927
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: fffffffffffffef8
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1927/task/1927/stat b/tests/002/proc/1927/task/1927/stat
new file mode 100644
index 00000000..39ae7655
--- /dev/null
+++ b/tests/002/proc/1927/task/1927/stat
@@ -0,0 +1 @@
+1927 (nfsd) S 1 1 1 0 -1 5242944 0 0 0 0 0 0 0 0 15 0 1 0 9319 0 0 4294967295 0 0 0 0 0 0 2147483384 0 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1928/status b/tests/002/proc/1928/status
new file mode 100644
index 00000000..3d321e6a
--- /dev/null
+++ b/tests/002/proc/1928/status
@@ -0,0 +1,20 @@
+Name: nfsd
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 1928
+Pid: 1928
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: fffffffffffffef8
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1928/task/1928/stat b/tests/002/proc/1928/task/1928/stat
new file mode 100644
index 00000000..e5d9a35c
--- /dev/null
+++ b/tests/002/proc/1928/task/1928/stat
@@ -0,0 +1 @@
+1928 (nfsd) S 1 1 1 0 -1 5242944 0 0 0 0 0 0 0 0 15 0 1 0 9319 0 0 4294967295 0 0 0 0 0 0 2147483384 0 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1929/status b/tests/002/proc/1929/status
new file mode 100644
index 00000000..eff4d826
--- /dev/null
+++ b/tests/002/proc/1929/status
@@ -0,0 +1,20 @@
+Name: nfsd
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 1929
+Pid: 1929
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: fffffffffffffef8
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1929/task/1929/stat b/tests/002/proc/1929/task/1929/stat
new file mode 100644
index 00000000..87ddc5d2
--- /dev/null
+++ b/tests/002/proc/1929/task/1929/stat
@@ -0,0 +1 @@
+1929 (nfsd) S 1 1 1 0 -1 5242944 0 0 0 0 0 0 0 0 15 0 1 0 9319 0 0 4294967295 0 0 0 0 0 0 2147483384 0 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1930/status b/tests/002/proc/1930/status
new file mode 100644
index 00000000..47cc48e0
--- /dev/null
+++ b/tests/002/proc/1930/status
@@ -0,0 +1,20 @@
+Name: nfsd
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 1930
+Pid: 1930
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: fffffffffffffef8
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1930/task/1930/stat b/tests/002/proc/1930/task/1930/stat
new file mode 100644
index 00000000..80e19691
--- /dev/null
+++ b/tests/002/proc/1930/task/1930/stat
@@ -0,0 +1 @@
+1930 (nfsd) S 1 1 1 0 -1 5242944 0 0 0 0 0 0 0 0 15 0 1 0 9319 0 0 4294967295 0 0 0 0 0 0 2147483384 0 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1931/status b/tests/002/proc/1931/status
new file mode 100644
index 00000000..083527ae
--- /dev/null
+++ b/tests/002/proc/1931/status
@@ -0,0 +1,20 @@
+Name: nfsd
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 1931
+Pid: 1931
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: fffffffffffffef8
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1931/task/1931/stat b/tests/002/proc/1931/task/1931/stat
new file mode 100644
index 00000000..7622b243
--- /dev/null
+++ b/tests/002/proc/1931/task/1931/stat
@@ -0,0 +1 @@
+1931 (nfsd) S 1 1 1 0 -1 5242944 0 0 0 0 0 0 0 0 15 0 1 0 9320 0 0 4294967295 0 0 0 0 0 0 2147483384 0 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1932/status b/tests/002/proc/1932/status
new file mode 100644
index 00000000..da0270e7
--- /dev/null
+++ b/tests/002/proc/1932/status
@@ -0,0 +1,20 @@
+Name: nfsd
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 1932
+Pid: 1932
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: fffffffffffffef8
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1932/task/1932/stat b/tests/002/proc/1932/task/1932/stat
new file mode 100644
index 00000000..f23746ae
--- /dev/null
+++ b/tests/002/proc/1932/task/1932/stat
@@ -0,0 +1 @@
+1932 (nfsd) S 1 1 1 0 -1 5242944 0 0 0 0 0 0 0 0 15 0 1 0 9320 0 0 4294967295 0 0 0 0 0 0 2147483384 0 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1936/status b/tests/002/proc/1936/status
new file mode 100644
index 00000000..24164d9e
--- /dev/null
+++ b/tests/002/proc/1936/status
@@ -0,0 +1,31 @@
+Name: rpc.mountd
+State: S (sleeping)
+SleepAVG: 78%
+Tgid: 1936
+Pid: 1936
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 1720 kB
+VmLck: 0 kB
+VmRSS: 788 kB
+VmData: 176 kB
+VmStk: 16 kB
+VmExe: 59 kB
+VmLib: 1413 kB
+StaBrk: 00126000 kB
+Brk: 08021000 kB
+StaStk: bffffe20 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000011000
+SigCgt: 0000000000004003
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1936/task/1936/stat b/tests/002/proc/1936/task/1936/stat
new file mode 100644
index 00000000..a73031b4
--- /dev/null
+++ b/tests/002/proc/1936/task/1936/stat
@@ -0,0 +1 @@
+1936 (rpc.mountd) S 1 1936 1936 0 -1 4194368 11 0 0 0 0 0 0 0 16 0 1 0 9342 1761280 197 4294967295 1118208 1178768 3221224992 3221224380 3086915490 0 0 69632 16387 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/1963/status b/tests/002/proc/1963/status
new file mode 100644
index 00000000..07e9f495
--- /dev/null
+++ b/tests/002/proc/1963/status
@@ -0,0 +1,31 @@
+Name: crond
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 1963
+Pid: 1963
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 3680 kB
+VmLck: 0 kB
+VmRSS: 844 kB
+VmData: 148 kB
+VmStk: 12 kB
+VmExe: 34 kB
+VmLib: 1382 kB
+StaBrk: 0011b000 kB
+Brk: 08021000 kB
+StaStk: bffffe30 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000000000
+SigCgt: 0000000000014003
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/1963/task/1963/stat b/tests/002/proc/1963/task/1963/stat
new file mode 100644
index 00000000..8de2fa7c
--- /dev/null
+++ b/tests/002/proc/1963/task/1963/stat
@@ -0,0 +1 @@
+1963 (crond) S 1 1963 1963 0 -1 4194368 147407 117767066 0 10 4 2984 51742 613826 16 0 1 0 9365 3768320 211 4294967295 1118208 1154032 3221225008 3221224132 3086915490 0 0 0 81923 0 0 0 17 0 0 0
diff --git a/tests/002/proc/1989/status b/tests/002/proc/1989/status
new file mode 100644
index 00000000..286f5635
--- /dev/null
+++ b/tests/002/proc/1989/status
@@ -0,0 +1,31 @@
+Name: xfs
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 1989
+Pid: 1989
+PPid: 1
+TracerPid: 0
+Uid: 43 43 43 43
+Gid: 43 43 43 43
+FDSize: 32
+Groups: 43
+VmSize: 3340 kB
+VmLck: 0 kB
+VmRSS: 1732 kB
+VmData: 808 kB
+VmStk: 24 kB
+VmExe: 68 kB
+VmLib: 2316 kB
+StaBrk: 0805e000 kB
+Brk: 08115000 kB
+StaStk: bffffde0 kB
+ExecLim: 08059000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000000014a03
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/1989/task/1989/stat b/tests/002/proc/1989/task/1989/stat
new file mode 100644
index 00000000..33f45f52
--- /dev/null
+++ b/tests/002/proc/1989/task/1989/stat
@@ -0,0 +1 @@
+1989 (xfs) S 1 1989 1989 0 -1 4194624 362 0 3 0 0 111 0 0 16 0 1 0 9444 3420160 433 4294967295 134508544 134578492 3221224928 3221224268 2340770 0 0 4096 84483 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/2/status b/tests/002/proc/2/status
new file mode 100644
index 00000000..62dfa2fd
--- /dev/null
+++ b/tests/002/proc/2/status
@@ -0,0 +1,20 @@
+Name: ksoftirqd/0
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 2
+Pid: 2
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffffff
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/2/task/2/stat b/tests/002/proc/2/task/2/stat
new file mode 100644
index 00000000..f6cbdbe0
--- /dev/null
+++ b/tests/002/proc/2/task/2/stat
@@ -0,0 +1 @@
+2 (ksoftirqd/0) S 1 0 0 0 -1 32832 0 0 0 0 0 21 0 0 34 19 1 0 56 0 0 4294967295 0 0 0 0 0 0 2147483647 0 0 3222430418 0 0 17 0 0 0
diff --git a/tests/002/proc/2008/status b/tests/002/proc/2008/status
new file mode 100644
index 00000000..023cd6e2
--- /dev/null
+++ b/tests/002/proc/2008/status
@@ -0,0 +1,31 @@
+Name: atd
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2008
+Pid: 2008
+PPid: 1
+TracerPid: 0
+Uid: 0 2 2 2
+Gid: 0 2 2 2
+FDSize: 32
+Groups:
+VmSize: 1552 kB
+VmLck: 0 kB
+VmRSS: 648 kB
+VmData: 152 kB
+VmStk: 8 kB
+VmExe: 15 kB
+VmLib: 1337 kB
+StaBrk: 00116000 kB
+Brk: 08021000 kB
+StaStk: bffffe30 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000000000
+SigCgt: 0000000000014003
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2008/task/2008/stat b/tests/002/proc/2008/task/2008/stat
new file mode 100644
index 00000000..90dd8342
--- /dev/null
+++ b/tests/002/proc/2008/task/2008/stat
@@ -0,0 +1 @@
+2008 (atd) S 1 2008 2008 0 -1 4194368 27 0 0 0 1 324 0 0 16 0 1 0 9484 1589248 162 4294967295 1118208 1133868 3221225008 3221224180 3086915490 0 0 0 81923 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2027/status b/tests/002/proc/2027/status
new file mode 100644
index 00000000..4fe16ecc
--- /dev/null
+++ b/tests/002/proc/2027/status
@@ -0,0 +1,31 @@
+Name: dbus-daemon-1
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2027
+Pid: 2027
+PPid: 1
+TracerPid: 0
+Uid: 81 81 81 81
+Gid: 81 81 81 81
+FDSize: 32
+Groups:
+VmSize: 12588 kB
+VmLck: 0 kB
+VmRSS: 1396 kB
+VmData: 10408 kB
+VmStk: 12 kB
+VmExe: 478 kB
+VmLib: 1614 kB
+StaBrk: 080c2000 kB
+Brk: 080e3000 kB
+StaStk: bffffe00 kB
+ExecLim: 080c0000
+Threads: 2
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000180004001
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2027/task/2027/stat b/tests/002/proc/2027/task/2027/stat
new file mode 100644
index 00000000..08e2c322
--- /dev/null
+++ b/tests/002/proc/2027/task/2027/stat
@@ -0,0 +1 @@
+2027 (dbus-daemon-1) S 1 2027 2027 0 -1 4194624 103 0 0 0 3 26 0 0 16 0 2 0 9624 12890112 349 4294967295 134512640 135003120 3221224960 3221223576 2340770 0 0 4096 16385 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2027/task/2032/stat b/tests/002/proc/2027/task/2032/stat
new file mode 100644
index 00000000..f2728b16
--- /dev/null
+++ b/tests/002/proc/2027/task/2032/stat
@@ -0,0 +1 @@
+2032 (dbus-daemon-1) S 1 2027 2027 0 -1 4194368 1 0 0 0 0 0 0 0 17 0 2 0 9654 12890112 349 4294967295 134512640 135003120 3221224960 3086897108 2340770 0 0 4096 16385 0 0 0 -1 0 0 0
diff --git a/tests/002/proc/2041/status b/tests/002/proc/2041/status
new file mode 100644
index 00000000..7647f5a4
--- /dev/null
+++ b/tests/002/proc/2041/status
@@ -0,0 +1,31 @@
+Name: cups-config-dae
+State: S (sleeping)
+SleepAVG: 90%
+Tgid: 2041
+Pid: 2041
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 2680 kB
+VmLck: 0 kB
+VmRSS: 1032 kB
+VmData: 160 kB
+VmStk: 4 kB
+VmExe: 12 kB
+VmLib: 2452 kB
+StaBrk: 0804d000 kB
+Brk: 0806e000 kB
+StaStk: bffffe10 kB
+ExecLim: 0804c000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/2041/task/2041/stat b/tests/002/proc/2041/task/2041/stat
new file mode 100644
index 00000000..31cd6415
--- /dev/null
+++ b/tests/002/proc/2041/task/2041/stat
@@ -0,0 +1 @@
+2041 (cups-config-dae) S 1 2041 2041 0 -1 4194368 190 0 0 0 0 3 0 0 16 0 1 0 9710 2744320 258 4294967295 134512640 134525464 3221224976 3221224584 2340770 0 0 4096 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2052/status b/tests/002/proc/2052/status
new file mode 100644
index 00000000..67cabe42
--- /dev/null
+++ b/tests/002/proc/2052/status
@@ -0,0 +1,31 @@
+Name: hald
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2052
+Pid: 2052
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 5508 kB
+VmLck: 0 kB
+VmRSS: 3968 kB
+VmData: 2436 kB
+VmStk: 12 kB
+VmExe: 198 kB
+VmLib: 2762 kB
+StaBrk: 08095000 kB
+Brk: 08259000 kB
+StaStk: bffffe30 kB
+ExecLim: 08079000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000010014000
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/2052/task/2052/stat b/tests/002/proc/2052/task/2052/stat
new file mode 100644
index 00000000..86a3b686
--- /dev/null
+++ b/tests/002/proc/2052/task/2052/stat
@@ -0,0 +1 @@
+2052 (hald) S 1 2052 2052 0 -1 4194624 4658 21123 2 1 505 29985 4 152 16 0 1 0 9776 5640192 992 4294967295 134508544 134711616 3221225008 3221224584 2340770 0 0 4096 268517376 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2062/status b/tests/002/proc/2062/status
new file mode 100644
index 00000000..1d5064a0
--- /dev/null
+++ b/tests/002/proc/2062/status
@@ -0,0 +1,31 @@
+Name: mingetty
+State: S (sleeping)
+SleepAVG: 74%
+Tgid: 2062
+Pid: 2062
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 1440 kB
+VmLck: 0 kB
+VmRSS: 400 kB
+VmData: 148 kB
+VmStk: 4 kB
+VmExe: 8 kB
+VmLib: 1252 kB
+StaBrk: 0804c000 kB
+Brk: 0806d000 kB
+StaStk: bffffe70 kB
+ExecLim: 0804b000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/2062/task/2062/stat b/tests/002/proc/2062/task/2062/stat
new file mode 100644
index 00000000..6ce0fdbb
--- /dev/null
+++ b/tests/002/proc/2062/task/2062/stat
@@ -0,0 +1 @@
+2062 (mingetty) S 1 2062 2062 1025 2062 4194560 118 0 0 0 0 1 0 0 18 0 1 0 9818 1474560 100 4294967295 134512640 134521080 3221225072 3221224116 2340770 0 0 0 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2124/status b/tests/002/proc/2124/status
new file mode 100644
index 00000000..fae54999
--- /dev/null
+++ b/tests/002/proc/2124/status
@@ -0,0 +1,31 @@
+Name: gdm-binary
+State: S (sleeping)
+SleepAVG: 97%
+Tgid: 2124
+Pid: 2124
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 256
+Groups:
+VmSize: 10584 kB
+VmLck: 0 kB
+VmRSS: 2368 kB
+VmData: 204 kB
+VmStk: 12 kB
+VmExe: 252 kB
+VmLib: 7780 kB
+StaBrk: 00153000 kB
+Brk: 08021000 kB
+StaStk: bffffb20 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001800
+SigCgt: 0000000001814223
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/2124/task/2124/stat b/tests/002/proc/2124/task/2124/stat
new file mode 100644
index 00000000..d5e956ad
--- /dev/null
+++ b/tests/002/proc/2124/task/2124/stat
@@ -0,0 +1 @@
+2124 (gdm-binary) S 1 2124 2124 0 -1 4194560 1760 3352 3 0 0 15 0 15 15 0 1 0 9924 10838016 592 4294967295 1118208 1376668 3221224224 3221222616 3086915490 0 0 536877056 25248291 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2184/status b/tests/002/proc/2184/status
new file mode 100644
index 00000000..72f9493f
--- /dev/null
+++ b/tests/002/proc/2184/status
@@ -0,0 +1,31 @@
+Name: gdm-binary
+State: S (sleeping)
+SleepAVG: 97%
+Tgid: 2184
+Pid: 2184
+PPid: 2124
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 503 42 503 42
+FDSize: 32
+Groups: 501 503 512
+VmSize: 11176 kB
+VmLck: 0 kB
+VmRSS: 3368 kB
+VmData: 440 kB
+VmStk: 24 kB
+VmExe: 252 kB
+VmLib: 8024 kB
+StaBrk: 00153000 kB
+Brk: 08021000 kB
+StaStk: bffffb20 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001200
+SigCgt: 0000000001014802
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/2184/task/2184/stat b/tests/002/proc/2184/task/2184/stat
new file mode 100644
index 00000000..cebb2379
--- /dev/null
+++ b/tests/002/proc/2184/task/2184/stat
@@ -0,0 +1 @@
+2184 (gdm-binary) S 2124 2184 2124 0 -1 4194624 517 6651 0 40 0 20 25 99 15 0 1 0 10022 11444224 842 4294967295 1118208 1376668 3221224224 3221221052 3086915490 0 0 536875520 16861186 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/2354/status b/tests/002/proc/2354/status
new file mode 100644
index 00000000..d18b6ea9
--- /dev/null
+++ b/tests/002/proc/2354/status
@@ -0,0 +1,31 @@
+Name: X
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 2354
+Pid: 2354
+PPid: 2184
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups: 0
+VmSize: 23376 kB
+VmLck: 0 kB
+VmRSS: 20472 kB
+VmData: 16472 kB
+VmStk: 28 kB
+VmExe: 1523 kB
+VmLib: 4345 kB
+StaBrk: 08209000 kB
+Brk: 087b2000 kB
+StaStk: bffffab0 kB
+ExecLim: b7fbe000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020301000
+SigCgt: 00000000518066cb
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/2354/task/2354/stat b/tests/002/proc/2354/task/2354/stat
new file mode 100644
index 00000000..f38d6d78
--- /dev/null
+++ b/tests/002/proc/2354/task/2354/stat
@@ -0,0 +1 @@
+2354 (X) S 2184 2354 2124 0 -1 4194560 11589 1368 0 0 2769826 1725037 26 9 15 0 1 0 10269 42811392 5118 4294967295 134508544 136068148 3221224112 3221222188 2340770 0 0 540020736 1367369419 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/2551/status b/tests/002/proc/2551/status
new file mode 100644
index 00000000..b6cd63db
--- /dev/null
+++ b/tests/002/proc/2551/status
@@ -0,0 +1,31 @@
+Name: gnome-session
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2551
+Pid: 2551
+PPid: 2184
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 20324 kB
+VmLck: 0 kB
+VmRSS: 9800 kB
+VmData: 2672 kB
+VmStk: 64 kB
+VmExe: 120 kB
+VmLib: 14224 kB
+StaBrk: 0806b000 kB
+Brk: 08218000 kB
+StaStk: bffffb50 kB
+ExecLim: 08067000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020000000
+SigCgt: 00000001800114f0
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2551/task/2551/stat b/tests/002/proc/2551/task/2551/stat
new file mode 100644
index 00000000..34f5656a
--- /dev/null
+++ b/tests/002/proc/2551/task/2551/stat
@@ -0,0 +1 @@
+2551 (gnome-session) S 2184 2551 2551 0 -1 4194560 5363 10700 11 11 4 100 0 63 16 0 1 0 43719 20811776 2450 4294967295 134512640 134636224 3221224272 3221223592 2340770 0 0 536870912 70896 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2579/status b/tests/002/proc/2579/status
new file mode 100644
index 00000000..27062e20
--- /dev/null
+++ b/tests/002/proc/2579/status
@@ -0,0 +1,31 @@
+Name: ssh-agent
+State: S (sleeping)
+SleepAVG: 58%
+Tgid: 2579
+Pid: 2579
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 99 503
+FDSize: 32
+Groups: 501 503 512
+VmSize: 3556 kB
+VmLck: 0 kB
+VmRSS: 1124 kB
+VmData: 344 kB
+VmStk: 8 kB
+VmExe: 53 kB
+VmLib: 2983 kB
+StaBrk: 00121000 kB
+Brk: 08021000 kB
+StaStk: bffffde0 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001002
+SigCgt: 0000000000004001
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2579/task/2579/stat b/tests/002/proc/2579/task/2579/stat
new file mode 100644
index 00000000..86030075
--- /dev/null
+++ b/tests/002/proc/2579/task/2579/stat
@@ -0,0 +1 @@
+2579 (ssh-agent) S 1 2579 2579 0 -1 4194368 19 0 0 0 0 0 0 0 18 0 1 0 43777 3641344 281 4294967295 1118208 1173240 3221224928 3221224476 3086915490 0 0 536875010 16385 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/2625/status b/tests/002/proc/2625/status
new file mode 100644
index 00000000..47e7e165
--- /dev/null
+++ b/tests/002/proc/2625/status
@@ -0,0 +1,31 @@
+Name: dbus-launch
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2625
+Pid: 2625
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 32
+Groups: 501 503 512
+VmSize: 2472 kB
+VmLck: 0 kB
+VmRSS: 652 kB
+VmData: 164 kB
+VmStk: 8 kB
+VmExe: 10 kB
+VmLib: 2222 kB
+StaBrk: 0804c000 kB
+Brk: 0806d000 kB
+StaStk: bffffb40 kB
+ExecLim: 0804b000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020000000
+SigCgt: 0000000000000001
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2625/task/2625/stat b/tests/002/proc/2625/task/2625/stat
new file mode 100644
index 00000000..456cdaaf
--- /dev/null
+++ b/tests/002/proc/2625/task/2625/stat
@@ -0,0 +1 @@
+2625 (dbus-launch) S 1 2551 2551 0 -1 4194368 95 0 0 0 0 0 0 0 16 0 1 0 43814 2531328 163 4294967295 134512640 134523568 3221224256 3221222556 2340770 0 0 536870912 1 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/2626/status b/tests/002/proc/2626/status
new file mode 100644
index 00000000..03251438
--- /dev/null
+++ b/tests/002/proc/2626/status
@@ -0,0 +1,31 @@
+Name: dbus-daemon-1
+State: S (sleeping)
+SleepAVG: 97%
+Tgid: 2626
+Pid: 2626
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 32
+Groups: 501 503 512
+VmSize: 12588 kB
+VmLck: 0 kB
+VmRSS: 1364 kB
+VmData: 10408 kB
+VmStk: 12 kB
+VmExe: 478 kB
+VmLib: 1614 kB
+StaBrk: 080c2000 kB
+Brk: 080e3000 kB
+StaStk: bffffb30 kB
+ExecLim: 080c0000
+Threads: 2
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001000
+SigCgt: 0000000180004001
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2626/task/2626/stat b/tests/002/proc/2626/task/2626/stat
new file mode 100644
index 00000000..3bad16f9
--- /dev/null
+++ b/tests/002/proc/2626/task/2626/stat
@@ -0,0 +1 @@
+2626 (dbus-daemon-1) S 1 2626 2626 0 -1 4194368 160 0 0 0 0 2 0 0 16 0 2 0 43817 12890112 341 4294967295 134512640 135003120 3221224240 3221222856 2340770 0 0 536875008 16385 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2626/task/2627/stat b/tests/002/proc/2626/task/2627/stat
new file mode 100644
index 00000000..40ab4554
--- /dev/null
+++ b/tests/002/proc/2626/task/2627/stat
@@ -0,0 +1 @@
+2627 (dbus-daemon-1) S 1 2626 2626 0 -1 4194368 1 0 0 0 0 0 0 0 20 0 2 0 43818 12890112 341 4294967295 134512640 135003120 3221224240 3086897108 2340770 0 0 536875008 16385 0 0 0 -1 0 0 0
diff --git a/tests/002/proc/2631/status b/tests/002/proc/2631/status
new file mode 100644
index 00000000..f64f4d61
--- /dev/null
+++ b/tests/002/proc/2631/status
@@ -0,0 +1,31 @@
+Name: gconfd-2
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2631
+Pid: 2631
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 9928 kB
+VmLck: 0 kB
+VmRSS: 6572 kB
+VmData: 4792 kB
+VmStk: 8 kB
+VmExe: 47 kB
+VmLib: 2873 kB
+StaBrk: 08056000 kB
+Brk: 084f8000 kB
+StaStk: bffffb10 kB
+ExecLim: 08054000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001002
+SigCgt: 00000001800046e9
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2631/task/2631/stat b/tests/002/proc/2631/task/2631/stat
new file mode 100644
index 00000000..16b52632
--- /dev/null
+++ b/tests/002/proc/2631/task/2631/stat
@@ -0,0 +1 @@
+2631 (gconfd-2) S 1 2551 2551 0 -1 4194304 2657 0 1 0 86 4751 0 0 16 0 1 0 43842 10166272 1643 4294967295 134512640 134561456 3221224208 3221223384 2340770 0 0 536875010 18153 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2634/status b/tests/002/proc/2634/status
new file mode 100644
index 00000000..f6b11b67
--- /dev/null
+++ b/tests/002/proc/2634/status
@@ -0,0 +1,31 @@
+Name: gnome-keyring-d
+State: S (sleeping)
+SleepAVG: 61%
+Tgid: 2634
+Pid: 2634
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 32
+Groups: 501 503 512
+VmSize: 2192 kB
+VmLck: 0 kB
+VmRSS: 1004 kB
+VmData: 156 kB
+VmStk: 8 kB
+VmExe: 66 kB
+VmLib: 1878 kB
+StaBrk: 0805b000 kB
+Brk: 0807c000 kB
+StaStk: bffffad0 kB
+ExecLim: 08059000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001000
+SigCgt: 0000000000004003
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2634/task/2634/stat b/tests/002/proc/2634/task/2634/stat
new file mode 100644
index 00000000..a19fdf35
--- /dev/null
+++ b/tests/002/proc/2634/task/2634/stat
@@ -0,0 +1 @@
+2634 (gnome-keyring-d) S 1 2551 2551 0 -1 4194368 59 0 0 0 0 0 0 0 19 0 1 0 44050 2244608 251 4294967295 134512640 134581220 3221224144 3221223768 2340770 0 0 536875008 16387 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2636/status b/tests/002/proc/2636/status
new file mode 100644
index 00000000..78681966
--- /dev/null
+++ b/tests/002/proc/2636/status
@@ -0,0 +1,31 @@
+Name: bonobo-activati
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 2636
+Pid: 2636
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 6984 kB
+VmLck: 0 kB
+VmRSS: 2756 kB
+VmData: 608 kB
+VmStk: 16 kB
+VmExe: 69 kB
+VmLib: 3999 kB
+StaBrk: 0805d000 kB
+Brk: 080e1000 kB
+StaStk: bffffa30 kB
+ExecLim: 0805a000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001000
+SigCgt: 0000000180000000
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2636/task/2636/stat b/tests/002/proc/2636/task/2636/stat
new file mode 100644
index 00000000..34bbe4eb
--- /dev/null
+++ b/tests/002/proc/2636/task/2636/stat
@@ -0,0 +1 @@
+2636 (bonobo-activati) S 1 2636 2636 0 -1 4194304 1170 84 4 0 0 22 0 4 16 0 1 0 44059 7151616 689 4294967295 134512640 134583432 3221223984 3221223368 2340770 0 0 536875008 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2638/status b/tests/002/proc/2638/status
new file mode 100644
index 00000000..7967b64d
--- /dev/null
+++ b/tests/002/proc/2638/status
@@ -0,0 +1,31 @@
+Name: gnome-settings-
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2638
+Pid: 2638
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 18224 kB
+VmLck: 0 kB
+VmRSS: 6852 kB
+VmData: 744 kB
+VmStk: 68 kB
+VmExe: 141 kB
+VmLib: 14459 kB
+StaBrk: 08072000 kB
+Brk: 080f8000 kB
+StaStk: bffff7a0 kB
+ExecLim: 0806c000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001000
+SigCgt: 0000000180010cf0
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2638/task/2638/stat b/tests/002/proc/2638/task/2638/stat
new file mode 100644
index 00000000..b0011b44
--- /dev/null
+++ b/tests/002/proc/2638/task/2638/stat
@@ -0,0 +1 @@
+2638 (gnome-settings-) S 1 2636 2636 0 -1 4194304 2615 11311 5 0 1 29 1 47 16 0 1 0 44112 18661376 1713 4294967295 134512640 134657092 3221223328 3221222856 2340770 0 0 536875008 68848 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2644/status b/tests/002/proc/2644/status
new file mode 100644
index 00000000..fbb4906e
--- /dev/null
+++ b/tests/002/proc/2644/status
@@ -0,0 +1,31 @@
+Name: gam_server
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2644
+Pid: 2644
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 2304 kB
+VmLck: 0 kB
+VmRSS: 1264 kB
+VmData: 288 kB
+VmStk: 16 kB
+VmExe: 40 kB
+VmLib: 1880 kB
+StaBrk: 08054000 kB
+Brk: 08096000 kB
+StaStk: bffff7e0 kB
+ExecLim: 08053000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001000
+SigCgt: 0000000210000800
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2644/task/2644/stat b/tests/002/proc/2644/task/2644/stat
new file mode 100644
index 00000000..7e6a6389
--- /dev/null
+++ b/tests/002/proc/2644/task/2644/stat
@@ -0,0 +1 @@
+2644 (gam_server) S 1 2643 2643 0 -1 4194304 365 0 0 0 291 80080 0 0 16 0 1 0 44183 2359296 316 4294967295 134512640 134553980 3221223392 3221223016 2340770 0 0 536875008 268437504 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2661/status b/tests/002/proc/2661/status
new file mode 100644
index 00000000..1e07a8a4
--- /dev/null
+++ b/tests/002/proc/2661/status
@@ -0,0 +1,31 @@
+Name: xscreensaver
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2661
+Pid: 2661
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 4584 kB
+VmLck: 0 kB
+VmRSS: 2336 kB
+VmData: 552 kB
+VmStk: 52 kB
+VmExe: 204 kB
+VmLib: 3588 kB
+StaBrk: 0807d000 kB
+Brk: 080c9000 kB
+StaStk: bffff7f0 kB
+ExecLim: 08079000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020000000
+SigCgt: 00000000418144ff
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2661/task/2661/stat b/tests/002/proc/2661/task/2661/stat
new file mode 100644
index 00000000..9da2461f
--- /dev/null
+++ b/tests/002/proc/2661/task/2661/stat
@@ -0,0 +1 @@
+2661 (xscreensaver) S 1 2636 2636 0 -1 4194304 848 442 3 13 18 7929 0 1 16 0 1 0 44257 4694016 584 4294967295 134500352 134709912 3221223408 3221221372 2340770 0 0 536870912 1098990847 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/2685/status b/tests/002/proc/2685/status
new file mode 100644
index 00000000..b9a4012c
--- /dev/null
+++ b/tests/002/proc/2685/status
@@ -0,0 +1,31 @@
+Name: metacity
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 2685
+Pid: 2685
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 12332 kB
+VmLck: 0 kB
+VmRSS: 5260 kB
+VmData: 768 kB
+VmStk: 64 kB
+VmExe: 430 kB
+VmLib: 8582 kB
+StaBrk: 080bd000 kB
+Brk: 08167000 kB
+StaStk: bffffa80 kB
+ExecLim: 080b4000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000021001000
+SigCgt: 0000000180000000
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2685/task/2685/stat b/tests/002/proc/2685/task/2685/stat
new file mode 100644
index 00000000..41ba36c8
--- /dev/null
+++ b/tests/002/proc/2685/task/2685/stat
@@ -0,0 +1 @@
+2685 (metacity) S 1 2685 2685 0 -1 4194304 1755 697 5 1 5 30 0 1 15 0 1 0 44486 12627968 1315 4294967295 134512640 134953484 3221224064 3221223096 2340770 0 0 553652224 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2689/status b/tests/002/proc/2689/status
new file mode 100644
index 00000000..73b53a1f
--- /dev/null
+++ b/tests/002/proc/2689/status
@@ -0,0 +1,31 @@
+Name: gnome-panel
+State: S (sleeping)
+SleepAVG: 87%
+Tgid: 2689
+Pid: 2689
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 32
+Groups: 501 503 512
+VmSize: 22136 kB
+VmLck: 0 kB
+VmRSS: 12408 kB
+VmData: 3808 kB
+VmStk: 64 kB
+VmExe: 436 kB
+VmLib: 14536 kB
+StaBrk: 080c1000 kB
+Brk: 08446000 kB
+StaStk: bffffa70 kB
+ExecLim: 080b6000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001000
+SigCgt: 0000000180010cf0
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2689/task/2689/stat b/tests/002/proc/2689/task/2689/stat
new file mode 100644
index 00000000..fa62d4f6
--- /dev/null
+++ b/tests/002/proc/2689/task/2689/stat
@@ -0,0 +1 @@
+2689 (gnome-panel) S 1 2689 2689 0 -1 4194304 3290 0 10 0 27 220 0 0 17 0 1 0 44541 22667264 3102 4294967295 134512640 134959452 3221224048 3221223560 2340770 0 0 536875008 68848 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2691/status b/tests/002/proc/2691/status
new file mode 100644
index 00000000..02e1e14e
--- /dev/null
+++ b/tests/002/proc/2691/status
@@ -0,0 +1,31 @@
+Name: nautilus
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 2691
+Pid: 2691
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 42136 kB
+VmLck: 0 kB
+VmRSS: 18296 kB
+VmData: 21388 kB
+VmStk: 72 kB
+VmExe: 590 kB
+VmLib: 16310 kB
+StaBrk: 080ed000 kB
+Brk: 0834f000 kB
+StaStk: bffffa60 kB
+ExecLim: 080dc000
+Threads: 10
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001000
+SigCgt: 0000000180010cf0
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2691/task/2691/stat b/tests/002/proc/2691/task/2691/stat
new file mode 100644
index 00000000..4c6319cf
--- /dev/null
+++ b/tests/002/proc/2691/task/2691/stat
@@ -0,0 +1 @@
+2691 (nautilus) S 1 2691 2691 0 -1 4194304 6760 25 25 0 768 163818 0 4 15 0 10 0 44549 43147264 4574 4294967295 134512640 135116832 3221224032 3221223208 2340770 0 0 536875008 68848 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2691/task/2696/stat b/tests/002/proc/2691/task/2696/stat
new file mode 100644
index 00000000..60c53157
--- /dev/null
+++ b/tests/002/proc/2691/task/2696/stat
@@ -0,0 +1 @@
+2696 (nautilus) S 1 2691 2691 0 -1 4194368 11 25 0 0 0 2 0 4 15 0 10 0 44786 43147264 4574 4294967295 134512640 135116832 3221224032 3084718936 2340770 0 0 536875008 68848 0 0 0 -1 0 0 0
diff --git a/tests/002/proc/2691/task/2708/stat b/tests/002/proc/2691/task/2708/stat
new file mode 100644
index 00000000..c27effae
--- /dev/null
+++ b/tests/002/proc/2691/task/2708/stat
@@ -0,0 +1 @@
+2708 (nautilus) S 1 2691 2691 0 -1 4194368 4 25 0 0 0 0 0 4 15 0 10 0 44890 43147264 4574 4294967295 134512640 135116832 3221224032 3074229164 2340770 0 0 536875008 68848 3225405204 0 0 -1 0 0 0
diff --git a/tests/002/proc/2691/task/2709/stat b/tests/002/proc/2691/task/2709/stat
new file mode 100644
index 00000000..e21a2ead
--- /dev/null
+++ b/tests/002/proc/2691/task/2709/stat
@@ -0,0 +1 @@
+2709 (nautilus) S 1 2691 2691 0 -1 4194368 12 25 0 0 0 2 0 4 15 0 10 0 44890 43147264 4574 4294967295 134512640 135116832 3221224032 3073962924 2340770 0 0 536875008 68848 3225409776 0 0 -1 0 0 0
diff --git a/tests/002/proc/2691/task/2710/stat b/tests/002/proc/2691/task/2710/stat
new file mode 100644
index 00000000..170584e9
--- /dev/null
+++ b/tests/002/proc/2691/task/2710/stat
@@ -0,0 +1 @@
+2710 (nautilus) S 1 2691 2691 0 -1 4194368 4 25 0 0 0 0 0 4 15 0 10 0 44890 43147264 4574 4294967295 134512640 135116832 3221224032 3073696684 2340770 0 0 536875008 68848 3225404448 0 0 -1 0 0 0
diff --git a/tests/002/proc/2691/task/2711/stat b/tests/002/proc/2691/task/2711/stat
new file mode 100644
index 00000000..b198b937
--- /dev/null
+++ b/tests/002/proc/2691/task/2711/stat
@@ -0,0 +1 @@
+2711 (nautilus) S 1 2691 2691 0 -1 4194368 3 25 0 0 0 0 0 4 15 0 10 0 44891 43147264 4574 4294967295 134512640 135116832 3221224032 3073430444 2340770 0 0 536875008 68848 3225410892 0 0 -1 0 0 0
diff --git a/tests/002/proc/2691/task/2712/stat b/tests/002/proc/2691/task/2712/stat
new file mode 100644
index 00000000..26d3fad8
--- /dev/null
+++ b/tests/002/proc/2691/task/2712/stat
@@ -0,0 +1 @@
+2712 (nautilus) S 1 2691 2691 0 -1 4194368 3 25 0 0 0 0 0 4 15 0 10 0 44891 43147264 4574 4294967295 134512640 135116832 3221224032 3073164204 2340770 0 0 536875008 68848 3225406032 0 0 -1 0 0 0
diff --git a/tests/002/proc/2691/task/2713/stat b/tests/002/proc/2691/task/2713/stat
new file mode 100644
index 00000000..fab4e652
--- /dev/null
+++ b/tests/002/proc/2691/task/2713/stat
@@ -0,0 +1 @@
+2713 (nautilus) S 1 2691 2691 0 -1 4194368 6 25 0 0 0 0 0 4 15 0 10 0 44891 43147264 4574 4294967295 134512640 135116832 3221224032 3072897964 2340770 0 0 536875008 68848 3225405132 0 0 -1 0 0 0
diff --git a/tests/002/proc/2691/task/2714/stat b/tests/002/proc/2691/task/2714/stat
new file mode 100644
index 00000000..46aacf24
--- /dev/null
+++ b/tests/002/proc/2691/task/2714/stat
@@ -0,0 +1 @@
+2714 (nautilus) S 1 2691 2691 0 -1 4194368 63 25 0 0 0 5 0 4 16 0 10 0 44891 43147264 4574 4294967295 134512640 135116832 3221224032 3072631724 2340770 0 0 536875008 68848 3225408660 0 0 -1 0 0 0
diff --git a/tests/002/proc/2691/task/2715/stat b/tests/002/proc/2691/task/2715/stat
new file mode 100644
index 00000000..382b125b
--- /dev/null
+++ b/tests/002/proc/2691/task/2715/stat
@@ -0,0 +1 @@
+2715 (nautilus) S 1 2691 2691 0 -1 4194368 7 25 0 0 0 1 0 4 15 0 10 0 44891 43147264 4574 4294967295 134512640 135116832 3221224032 3072365484 2340770 0 0 536875008 68848 3225407508 0 0 -1 0 0 0
diff --git a/tests/002/proc/2693/status b/tests/002/proc/2693/status
new file mode 100644
index 00000000..723c6b52
--- /dev/null
+++ b/tests/002/proc/2693/status
@@ -0,0 +1,31 @@
+Name: gnome-volume-ma
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2693
+Pid: 2693
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 17928 kB
+VmLck: 0 kB
+VmRSS: 6212 kB
+VmData: 612 kB
+VmStk: 64 kB
+VmExe: 17 kB
+VmLib: 14459 kB
+StaBrk: 08050000 kB
+Brk: 080b6000 kB
+StaStk: bffffa60 kB
+ExecLim: 0804d000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001000
+SigCgt: 00000001800144f0
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2693/task/2693/stat b/tests/002/proc/2693/task/2693/stat
new file mode 100644
index 00000000..8bda4bb5
--- /dev/null
+++ b/tests/002/proc/2693/task/2693/stat
@@ -0,0 +1 @@
+2693 (gnome-volume-ma) S 1 2693 2693 0 -1 4194304 1919 0 0 0 2 37 0 0 16 0 1 0 44557 18358272 1553 4294967295 134512640 134530528 3221224032 3221223496 2340770 0 0 536875008 83184 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2695/status b/tests/002/proc/2695/status
new file mode 100644
index 00000000..a7785592
--- /dev/null
+++ b/tests/002/proc/2695/status
@@ -0,0 +1,31 @@
+Name: eggcups
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2695
+Pid: 2695
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 32
+Groups: 501 503 512
+VmSize: 39476 kB
+VmLck: 0 kB
+VmRSS: 7744 kB
+VmData: 21228 kB
+VmStk: 64 kB
+VmExe: 106 kB
+VmLib: 14878 kB
+StaBrk: 08067000 kB
+Brk: 080ec000 kB
+StaStk: bffffa70 kB
+ExecLim: 08063000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001001
+SigCgt: 00000001800144f2
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2695/task/2695/stat b/tests/002/proc/2695/task/2695/stat
new file mode 100644
index 00000000..bcca5a53
--- /dev/null
+++ b/tests/002/proc/2695/task/2695/stat
@@ -0,0 +1 @@
+2695 (eggcups) S 1 2695 2695 0 -1 4194304 1939 0 5 0 37 3761 0 0 16 0 1 0 44706 40423424 1936 4294967295 134512640 134622204 3221224048 3221223160 2340770 0 0 536875009 83186 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2698/status b/tests/002/proc/2698/status
new file mode 100644
index 00000000..f2083aa3
--- /dev/null
+++ b/tests/002/proc/2698/status
@@ -0,0 +1,31 @@
+Name: gnome-vfs-daemo
+State: S (sleeping)
+SleepAVG: 87%
+Tgid: 2698
+Pid: 2698
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 20048 kB
+VmLck: 0 kB
+VmRSS: 3552 kB
+VmData: 10664 kB
+VmStk: 20 kB
+VmExe: 74 kB
+VmLib: 6790 kB
+StaBrk: 08066000 kB
+Brk: 080a8000 kB
+StaStk: bffff7a0 kB
+ExecLim: 0805b000
+Threads: 2
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001000
+SigCgt: 0000000180010800
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2698/task/2698/stat b/tests/002/proc/2698/task/2698/stat
new file mode 100644
index 00000000..5ba02563
--- /dev/null
+++ b/tests/002/proc/2698/task/2698/stat
@@ -0,0 +1 @@
+2698 (gnome-vfs-daemo) S 1 2636 2636 0 -1 4194304 1015 15 1 0 1 13 0 1 16 0 2 0 44787 20529152 888 4294967295 134512640 134589128 3221223328 3221222936 2340770 0 0 536875008 67584 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2698/task/2699/stat b/tests/002/proc/2698/task/2699/stat
new file mode 100644
index 00000000..6ed657ad
--- /dev/null
+++ b/tests/002/proc/2698/task/2699/stat
@@ -0,0 +1 @@
+2699 (gnome-vfs-daemo) S 1 2636 2636 0 -1 4194368 10 15 0 0 0 0 0 1 15 0 2 0 44798 20529152 888 4294967295 134512640 134589128 3221223328 3084776280 2340770 0 0 536875008 67584 0 0 0 -1 0 0 0
diff --git a/tests/002/proc/2701/status b/tests/002/proc/2701/status
new file mode 100644
index 00000000..2ac68d03
--- /dev/null
+++ b/tests/002/proc/2701/status
@@ -0,0 +1,31 @@
+Name: pam-panel-icon
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2701
+Pid: 2701
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 32
+Groups: 501 503 512
+VmSize: 12152 kB
+VmLck: 0 kB
+VmRSS: 4196 kB
+VmData: 512 kB
+VmStk: 64 kB
+VmExe: 35 kB
+VmLib: 9101 kB
+StaBrk: 08053000 kB
+Brk: 080b6000 kB
+StaStk: bffffa60 kB
+ExecLim: 08051000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020000000
+SigCgt: 0000000180000000
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2701/task/2701/stat b/tests/002/proc/2701/task/2701/stat
new file mode 100644
index 00000000..5e5e4d0d
--- /dev/null
+++ b/tests/002/proc/2701/task/2701/stat
@@ -0,0 +1 @@
+2701 (pam-panel-icon) S 1 2701 2701 0 -1 4194304 1202 0 0 0 37 9236 0 0 16 0 1 0 44814 12443648 1049 4294967295 134512640 134548696 3221224032 3221223576 2340770 0 0 536870912 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2707/status b/tests/002/proc/2707/status
new file mode 100644
index 00000000..2d915b5a
--- /dev/null
+++ b/tests/002/proc/2707/status
@@ -0,0 +1,31 @@
+Name: rhn-applet-gui
+State: R (running)
+SleepAVG: 98%
+Tgid: 2707
+Pid: 2707
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 32
+Groups: 501 503 512
+VmSize: 28748 kB
+VmLck: 0 kB
+VmRSS: 16676 kB
+VmData: 5364 kB
+VmStk: 100 kB
+VmExe: 1 kB
+VmLib: 19507 kB
+StaBrk: 0804a000 kB
+Brk: 082aa000 kB
+StaStk: bffffa50 kB
+ExecLim: 08049000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000021001000
+SigCgt: 00000001800104f0
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2707/task/2707/stat b/tests/002/proc/2707/task/2707/stat
new file mode 100644
index 00000000..5b1c3e62
--- /dev/null
+++ b/tests/002/proc/2707/task/2707/stat
@@ -0,0 +1 @@
+2707 (rhn-applet-gui) S 1 2707 2707 0 -1 4194304 4561 0 19 0 64467 4019712 0 0 25 10 1 0 44857 29437952 4169 4294967295 134512640 134514336 3221224016 3221220104 2340770 0 0 553652224 66800 0 0 0 17 0 0 0
diff --git a/tests/002/proc/27121/status b/tests/002/proc/27121/status
new file mode 100644
index 00000000..1326a5f1
--- /dev/null
+++ b/tests/002/proc/27121/status
@@ -0,0 +1,20 @@
+Name: pdflush
+State: S (sleeping)
+SleepAVG: 97%
+Tgid: 27121
+Pid: 27121
+PPid: 3
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffffff
+SigIgn: 0000000000010000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/27121/task/27121/stat b/tests/002/proc/27121/task/27121/stat
new file mode 100644
index 00000000..bea101fe
--- /dev/null
+++ b/tests/002/proc/27121/task/27121/stat
@@ -0,0 +1 @@
+27121 (pdflush) S 3 0 0 0 -1 41024 0 0 0 0 0 1 0 0 15 0 1 0 192847829 0 0 4294967295 0 0 0 0 0 0 2147483647 65536 0 3222571503 0 0 17 0 0 0
diff --git a/tests/002/proc/2717/status b/tests/002/proc/2717/status
new file mode 100644
index 00000000..2db485b0
--- /dev/null
+++ b/tests/002/proc/2717/status
@@ -0,0 +1,31 @@
+Name: pam_timestamp_c
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2717
+Pid: 2717
+PPid: 2701
+TracerPid: 0
+Uid: 524 0 0 0
+Gid: 503 503 503 503
+FDSize: 32
+Groups: 501 503 512
+VmSize: 1620 kB
+VmLck: 0 kB
+VmRSS: 640 kB
+VmData: 156 kB
+VmStk: 16 kB
+VmExe: 4 kB
+VmLib: 1392 kB
+StaBrk: 0804b000 kB
+Brk: 0806c000 kB
+StaStk: bffffa70 kB
+ExecLim: 0804a000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/2717/task/2717/stat b/tests/002/proc/2717/task/2717/stat
new file mode 100644
index 00000000..e353ebe3
--- /dev/null
+++ b/tests/002/proc/2717/task/2717/stat
@@ -0,0 +1 @@
+2717 (pam_timestamp_c) S 2701 2701 2701 0 -1 4194560 201 0 0 0 71 15175 0 0 16 0 1 0 44953 1658880 160 4294967295 134512640 134517636 3221224048 3221214844 2340770 0 0 536870912 0 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/2718/status b/tests/002/proc/2718/status
new file mode 100644
index 00000000..70b16ad9
--- /dev/null
+++ b/tests/002/proc/2718/status
@@ -0,0 +1,31 @@
+Name: mapping-daemon
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2718
+Pid: 2718
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 32
+Groups: 501 503 512
+VmSize: 2108 kB
+VmLck: 0 kB
+VmRSS: 812 kB
+VmData: 156 kB
+VmStk: 8 kB
+VmExe: 9 kB
+VmLib: 1879 kB
+StaBrk: 0804c000 kB
+Brk: 0806d000 kB
+StaStk: bffffa80 kB
+ExecLim: 0804b000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2718/task/2718/stat b/tests/002/proc/2718/task/2718/stat
new file mode 100644
index 00000000..de695b4e
--- /dev/null
+++ b/tests/002/proc/2718/task/2718/stat
@@ -0,0 +1 @@
+2718 (mapping-daemon) S 1 2691 2691 0 -1 4194304 249 0 0 0 2 562 0 0 16 0 1 0 44956 2158592 203 4294967295 134512640 134522612 3221224064 3221223544 2340770 0 0 536870912 0 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2720/status b/tests/002/proc/2720/status
new file mode 100644
index 00000000..fc792728
--- /dev/null
+++ b/tests/002/proc/2720/status
@@ -0,0 +1,31 @@
+Name: wnck-applet
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2720
+Pid: 2720
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 19696 kB
+VmLck: 0 kB
+VmRSS: 8536 kB
+VmData: 1804 kB
+VmStk: 68 kB
+VmExe: 66 kB
+VmLib: 14574 kB
+StaBrk: 0805d000 kB
+Brk: 081ec000 kB
+StaStk: bffff7b0 kB
+ExecLim: 08059000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001000
+SigCgt: 00000001800104f0
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2720/task/2720/stat b/tests/002/proc/2720/task/2720/stat
new file mode 100644
index 00000000..70e66c8a
--- /dev/null
+++ b/tests/002/proc/2720/task/2720/stat
@@ -0,0 +1 @@
+2720 (wnck-applet) S 1 2636 2636 0 -1 4194304 2257 0 3 0 9 65 0 0 16 0 1 0 45806 20168704 2134 4294967295 134512640 134580268 3221223344 3221222696 2340770 0 0 536875008 66800 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2722/status b/tests/002/proc/2722/status
new file mode 100644
index 00000000..cd89611b
--- /dev/null
+++ b/tests/002/proc/2722/status
@@ -0,0 +1,31 @@
+Name: mixer_applet2
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 2722
+Pid: 2722
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 21128 kB
+VmLck: 0 kB
+VmRSS: 9276 kB
+VmData: 2188 kB
+VmStk: 68 kB
+VmExe: 29 kB
+VmLib: 15639 kB
+StaBrk: 08053000 kB
+Brk: 08243000 kB
+StaStk: bffff7a0 kB
+ExecLim: 08050000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001000
+SigCgt: 00000001800100f0
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2722/task/2722/stat b/tests/002/proc/2722/task/2722/stat
new file mode 100644
index 00000000..9dd73cdb
--- /dev/null
+++ b/tests/002/proc/2722/task/2722/stat
@@ -0,0 +1 @@
+2722 (mixer_applet2) S 1 2636 2636 0 -1 4194304 2479 0 11 0 462 88674 0 0 15 0 1 0 45960 21635072 2319 4294967295 134512640 134542828 3221223328 3221222680 2340770 0 0 536875008 65776 0 0 0 17 0 0 0
diff --git a/tests/002/proc/27243/status b/tests/002/proc/27243/status
new file mode 100644
index 00000000..71ce2ec0
--- /dev/null
+++ b/tests/002/proc/27243/status
@@ -0,0 +1,20 @@
+Name: pdflush
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 27243
+Pid: 27243
+PPid: 3
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffffff
+SigIgn: 0000000000010000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/27243/task/27243/stat b/tests/002/proc/27243/task/27243/stat
new file mode 100644
index 00000000..850d4843
--- /dev/null
+++ b/tests/002/proc/27243/task/27243/stat
@@ -0,0 +1 @@
+27243 (pdflush) S 3 0 0 0 -1 41024 0 0 0 0 0 3224 0 0 15 0 1 0 192851014 0 0 4294967295 0 0 0 0 0 0 2147483647 65536 0 3222571503 0 0 17 0 0 0
diff --git a/tests/002/proc/2726/status b/tests/002/proc/2726/status
new file mode 100644
index 00000000..6b6be86f
--- /dev/null
+++ b/tests/002/proc/2726/status
@@ -0,0 +1,31 @@
+Name: clock-applet
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 2726
+Pid: 2726
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 19576 kB
+VmLck: 0 kB
+VmRSS: 8052 kB
+VmData: 1012 kB
+VmStk: 68 kB
+VmExe: 92 kB
+VmLib: 15472 kB
+StaBrk: 08064000 kB
+Brk: 0812b000 kB
+StaStk: bffff7a0 kB
+ExecLim: 08060000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001000
+SigCgt: 00000001800104f0
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2726/task/2726/stat b/tests/002/proc/2726/task/2726/stat
new file mode 100644
index 00000000..208ce1a5
--- /dev/null
+++ b/tests/002/proc/2726/task/2726/stat
@@ -0,0 +1 @@
+2726 (clock-applet) S 1 2636 2636 0 -1 4194304 2152 0 6 0 5100 258062 0 0 16 0 1 0 46077 20045824 2013 4294967295 134512640 134607244 3221223328 3221222680 2340770 0 0 536875008 66800 0 0 0 17 0 0 0
diff --git a/tests/002/proc/2728/status b/tests/002/proc/2728/status
new file mode 100644
index 00000000..ccb9860a
--- /dev/null
+++ b/tests/002/proc/2728/status
@@ -0,0 +1,31 @@
+Name: notification-ar
+State: S (sleeping)
+SleepAVG: 97%
+Tgid: 2728
+Pid: 2728
+PPid: 1
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 17888 kB
+VmLck: 0 kB
+VmRSS: 6800 kB
+VmData: 756 kB
+VmStk: 68 kB
+VmExe: 23 kB
+VmLib: 14261 kB
+StaBrk: 08051000 kB
+Brk: 080db000 kB
+StaStk: bffff780 kB
+ExecLim: 0804e000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000020001000
+SigCgt: 00000001800104f0
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/2728/task/2728/stat b/tests/002/proc/2728/task/2728/stat
new file mode 100644
index 00000000..47fa70e5
--- /dev/null
+++ b/tests/002/proc/2728/task/2728/stat
@@ -0,0 +1 @@
+2728 (notification-ar) S 1 2636 2636 0 -1 4194304 1816 0 1 0 3 15 0 0 16 0 1 0 46126 18317312 1700 4294967295 134512640 134537192 3221223296 3221222648 2340770 0 0 536875008 66800 0 0 0 17 0 0 0
diff --git a/tests/002/proc/27682/status b/tests/002/proc/27682/status
new file mode 100644
index 00000000..6c644758
--- /dev/null
+++ b/tests/002/proc/27682/status
@@ -0,0 +1,31 @@
+Name: sshd
+State: S (sleeping)
+SleepAVG: 97%
+Tgid: 27682
+Pid: 27682
+PPid: 1887
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 6836 kB
+VmLck: 0 kB
+VmRSS: 2368 kB
+VmData: 436 kB
+VmStk: 24 kB
+VmExe: 295 kB
+VmLib: 3277 kB
+StaBrk: 00161000 kB
+Brk: 08021000 kB
+StaStk: bffffe10 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000000006001
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/27682/task/27682/stat b/tests/002/proc/27682/task/27682/stat
new file mode 100644
index 00000000..5e7a9644
--- /dev/null
+++ b/tests/002/proc/27682/task/27682/stat
@@ -0,0 +1 @@
+27682 (sshd) S 1887 27682 27682 0 -1 4194560 1209 79 0 0 119 230 2 0 16 0 1 0 193311484 7000064 592 4294967295 1118208 1420332 3221224976 3221220452 3086915490 0 0 4096 24577 0 0 0 17 0 0 0
diff --git a/tests/002/proc/27684/status b/tests/002/proc/27684/status
new file mode 100644
index 00000000..3a2542d6
--- /dev/null
+++ b/tests/002/proc/27684/status
@@ -0,0 +1,31 @@
+Name: sshd
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 27684
+Pid: 27684
+PPid: 27682
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 32
+Groups: 501 503 512
+VmSize: 7012 kB
+VmLck: 0 kB
+VmRSS: 2456 kB
+VmData: 596 kB
+VmStk: 40 kB
+VmExe: 295 kB
+VmLib: 3277 kB
+StaBrk: 00161000 kB
+Brk: 08049000 kB
+StaStk: bffffe10 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000000012000
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/27684/task/27684/stat b/tests/002/proc/27684/task/27684/stat
new file mode 100644
index 00000000..3f12ecc5
--- /dev/null
+++ b/tests/002/proc/27684/task/27684/stat
@@ -0,0 +1 @@
+27684 (sshd) S 27682 27682 27682 0 -1 4194624 126 0 0 0 2094 54068 0 0 15 0 1 0 193311752 7180288 614 4294967295 1118208 1420332 3221224976 3221220428 3086915490 0 0 4096 73728 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/27685/status b/tests/002/proc/27685/status
new file mode 100644
index 00000000..2d59eb08
--- /dev/null
+++ b/tests/002/proc/27685/status
@@ -0,0 +1,31 @@
+Name: bash
+State: S (sleeping)
+SleepAVG: 82%
+Tgid: 27685
+Pid: 27685
+PPid: 27684
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 5964 kB
+VmLck: 0 kB
+VmRSS: 2960 kB
+VmData: 1584 kB
+VmStk: 20 kB
+VmExe: 577 kB
+VmLib: 1415 kB
+StaBrk: 080e3000 kB
+Brk: 08262000 kB
+StaStk: bffffe40 kB
+ExecLim: 080d8000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000384004
+SigCgt: 000000004b813efb
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/27685/task/27685/stat b/tests/002/proc/27685/task/27685/stat
new file mode 100644
index 00000000..4b79d09a
--- /dev/null
+++ b/tests/002/proc/27685/task/27685/stat
@@ -0,0 +1 @@
+27685 (bash) S 27684 27685 27685 34817 27685 4194304 360749 21090866 0 85 72 3121 178898 151420 16 0 1 0 193311757 6107136 740 4294967295 134508544 135099520 3221225024 3221221396 2340770 0 0 3686404 1266761467 0 0 0 17 0 0 0
diff --git a/tests/002/proc/28/status b/tests/002/proc/28/status
new file mode 100644
index 00000000..1e39d66a
--- /dev/null
+++ b/tests/002/proc/28/status
@@ -0,0 +1,20 @@
+Name: kswapd0
+State: S (sleeping)
+SleepAVG: 52%
+Tgid: 28
+Pid: 28
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffffff
+SigIgn: 0000000000000000
+SigCgt: 0000000000004100
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/28/task/28/stat b/tests/002/proc/28/task/28/stat
new file mode 100644
index 00000000..c6e9af6b
--- /dev/null
+++ b/tests/002/proc/28/task/28/stat
@@ -0,0 +1 @@
+28 (kswapd0) S 1 1 1 0 -1 264256 0 0 0 0 0 652 0 0 19 0 1 0 63 0 0 4294967295 0 0 0 0 0 0 2147483647 0 16640 3222597324 0 0 17 0 0 0
diff --git a/tests/002/proc/29/status b/tests/002/proc/29/status
new file mode 100644
index 00000000..864a05c4
--- /dev/null
+++ b/tests/002/proc/29/status
@@ -0,0 +1,20 @@
+Name: aio/0
+State: S (sleeping)
+SleepAVG: 19%
+Tgid: 29
+Pid: 29
+PPid: 3
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffffff
+SigIgn: 0000000000010000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/29/task/29/stat b/tests/002/proc/29/task/29/stat
new file mode 100644
index 00000000..89e2592d
--- /dev/null
+++ b/tests/002/proc/29/task/29/stat
@@ -0,0 +1 @@
+29 (aio/0) S 3 0 0 0 -1 32832 0 0 0 0 0 0 0 0 13 -10 1 0 63 0 0 4294967295 0 0 0 0 0 0 2147483647 65536 0 3222484651 0 0 17 0 0 0
diff --git a/tests/002/proc/29840/status b/tests/002/proc/29840/status
new file mode 100644
index 00000000..424da036
--- /dev/null
+++ b/tests/002/proc/29840/status
@@ -0,0 +1,31 @@
+Name: bash
+State: R (running)
+SleepAVG: 0%
+Tgid: 29840
+Pid: 29840
+PPid: 16249
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 256
+Groups: 0 1 2 3 4 6 10 606
+VmSize: 4340 kB
+VmLck: 0 kB
+VmRSS: 1556 kB
+VmData: 296 kB
+VmStk: 20 kB
+VmExe: 577 kB
+VmLib: 1307 kB
+StaBrk: 080e3000 kB
+Brk: 08122000 kB
+StaStk: bffffeb0 kB
+ExecLim: 080d8000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000010002
+SigIgn: 0000000000000000
+SigCgt: 0000000008010002
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/29840/task/29840/stat b/tests/002/proc/29840/task/29840/stat
new file mode 100644
index 00000000..8fdda47a
--- /dev/null
+++ b/tests/002/proc/29840/task/29840/stat
@@ -0,0 +1 @@
+29840 (bash) R 16249 29839 31908 34818 29839 4194368 39208 238138 0 0 0 299 2 805 25 0 1 0 361410776 4444160 389 4294967295 134508544 135099520 3221225136 3221222148 2340770 0 65538 0 134283266 0 0 0 17 0 0 0
diff --git a/tests/002/proc/3/status b/tests/002/proc/3/status
new file mode 100644
index 00000000..0e355850
--- /dev/null
+++ b/tests/002/proc/3/status
@@ -0,0 +1,20 @@
+Name: events/0
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 3
+Pid: 3
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffffff
+SigIgn: 0000000000010000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/3/task/3/stat b/tests/002/proc/3/task/3/stat
new file mode 100644
index 00000000..6b23245e
--- /dev/null
+++ b/tests/002/proc/3/task/3/stat
@@ -0,0 +1 @@
+3 (events/0) S 1 0 0 0 -1 33088 0 0 0 0 0 8491 0 0 5 -10 1 0 59 0 0 4294967295 0 0 0 0 0 0 2147483647 65536 0 3222484651 0 0 17 0 0 0
diff --git a/tests/002/proc/30737/status b/tests/002/proc/30737/status
new file mode 100644
index 00000000..d59c8336
--- /dev/null
+++ b/tests/002/proc/30737/status
@@ -0,0 +1,31 @@
+Name: dhclient
+State: S (sleeping)
+SleepAVG: 89%
+Tgid: 30737
+Pid: 30737
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 2156 kB
+VmLck: 0 kB
+VmRSS: 1120 kB
+VmData: 336 kB
+VmStk: 12 kB
+VmExe: 349 kB
+VmLib: 1483 kB
+StaBrk: 080ae000 kB
+Brk: 080f2000 kB
+StaStk: bffffd70 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000000000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/30737/task/30737/stat b/tests/002/proc/30737/task/30737/stat
new file mode 100644
index 00000000..57db355b
--- /dev/null
+++ b/tests/002/proc/30737/task/30737/stat
@@ -0,0 +1 @@
+30737 (dhclient) S 1 30737 30737 0 -1 64 235 30892 0 0 3 42 3 133 16 0 1 0 43693773 2207744 280 4294967295 134508544 134865956 3221224816 3221223916 2340770 0 0 0 0 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/31905/status b/tests/002/proc/31905/status
new file mode 100644
index 00000000..4043ed83
--- /dev/null
+++ b/tests/002/proc/31905/status
@@ -0,0 +1,31 @@
+Name: sshd
+State: S (sleeping)
+SleepAVG: 97%
+Tgid: 31905
+Pid: 31905
+PPid: 1887
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 6836 kB
+VmLck: 0 kB
+VmRSS: 2368 kB
+VmData: 436 kB
+VmStk: 24 kB
+VmExe: 295 kB
+VmLib: 3277 kB
+StaBrk: 00161000 kB
+Brk: 08021000 kB
+StaStk: bffffe10 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000000006001
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/31905/task/31905/stat b/tests/002/proc/31905/task/31905/stat
new file mode 100644
index 00000000..3e1a83a4
--- /dev/null
+++ b/tests/002/proc/31905/task/31905/stat
@@ -0,0 +1 @@
+31905 (sshd) S 1887 31905 31905 0 -1 4194560 1590 79 0 0 237 442 2 2 16 0 1 0 48905120 7000064 592 4294967295 1118208 1420332 3221224976 3221220452 3086915490 0 0 4096 24577 0 0 0 17 0 0 0
diff --git a/tests/002/proc/31907/status b/tests/002/proc/31907/status
new file mode 100644
index 00000000..8909962b
--- /dev/null
+++ b/tests/002/proc/31907/status
@@ -0,0 +1,31 @@
+Name: sshd
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 31907
+Pid: 31907
+PPid: 31905
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 32
+Groups: 501 503 512
+VmSize: 7012 kB
+VmLck: 0 kB
+VmRSS: 2452 kB
+VmData: 596 kB
+VmStk: 40 kB
+VmExe: 295 kB
+VmLib: 3277 kB
+StaBrk: 00161000 kB
+Brk: 08049000 kB
+StaStk: bffffe10 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000000012000
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/31907/task/31907/stat b/tests/002/proc/31907/task/31907/stat
new file mode 100644
index 00000000..03e624fd
--- /dev/null
+++ b/tests/002/proc/31907/task/31907/stat
@@ -0,0 +1 @@
+31907 (sshd) S 31905 31905 31905 0 -1 4194624 124 0 0 0 1956 3984 0 0 15 0 1 0 48905454 7180288 613 4294967295 1118208 1420332 3221224976 3221220428 3086915490 0 0 4096 73728 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/31908/status b/tests/002/proc/31908/status
new file mode 100644
index 00000000..220fddd7
--- /dev/null
+++ b/tests/002/proc/31908/status
@@ -0,0 +1,31 @@
+Name: bash
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 31908
+Pid: 31908
+PPid: 31907
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 4568 kB
+VmLck: 0 kB
+VmRSS: 1700 kB
+VmData: 404 kB
+VmStk: 20 kB
+VmExe: 577 kB
+VmLib: 1411 kB
+StaBrk: 080e3000 kB
+Brk: 0813b000 kB
+StaStk: bffffe40 kB
+ExecLim: 080d8000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000010000
+SigIgn: 0000000000384004
+SigCgt: 000000004b813efb
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/31908/task/31908/stat b/tests/002/proc/31908/task/31908/stat
new file mode 100644
index 00000000..73b91cbb
--- /dev/null
+++ b/tests/002/proc/31908/task/31908/stat
@@ -0,0 +1 @@
+31908 (bash) S 31907 31908 31908 34818 29839 4194304 6479 200590 0 1 8 166 702 8118 15 0 1 0 48905458 4677632 425 4294967295 134508544 135099520 3221225024 3221223620 2340770 0 65536 3686404 1266761467 3222425215 0 0 17 0 0 0
diff --git a/tests/002/proc/32672/status b/tests/002/proc/32672/status
new file mode 100644
index 00000000..5b73732a
--- /dev/null
+++ b/tests/002/proc/32672/status
@@ -0,0 +1,31 @@
+Name: sshd
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 32672
+Pid: 32672
+PPid: 1887
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 6836 kB
+VmLck: 0 kB
+VmRSS: 2368 kB
+VmData: 436 kB
+VmStk: 24 kB
+VmExe: 295 kB
+VmLib: 3277 kB
+StaBrk: 00161000 kB
+Brk: 08021000 kB
+StaStk: bffffe10 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000000006001
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/32672/task/32672/stat b/tests/002/proc/32672/task/32672/stat
new file mode 100644
index 00000000..41f89907
--- /dev/null
+++ b/tests/002/proc/32672/task/32672/stat
@@ -0,0 +1 @@
+32672 (sshd) S 1887 32672 32672 0 -1 4194560 1585 79 0 0 228 406 2 1 16 0 1 0 50052803 7000064 592 4294967295 1118208 1420332 3221224976 3221220452 3086915490 0 0 4096 24577 0 0 0 17 0 0 0
diff --git a/tests/002/proc/32674/status b/tests/002/proc/32674/status
new file mode 100644
index 00000000..10e40a47
--- /dev/null
+++ b/tests/002/proc/32674/status
@@ -0,0 +1,31 @@
+Name: sshd
+State: S (sleeping)
+SleepAVG: 98%
+Tgid: 32674
+Pid: 32674
+PPid: 32672
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 32
+Groups: 501 503 512
+VmSize: 7012 kB
+VmLck: 0 kB
+VmRSS: 2456 kB
+VmData: 596 kB
+VmStk: 40 kB
+VmExe: 295 kB
+VmLib: 3277 kB
+StaBrk: 00161000 kB
+Brk: 08049000 kB
+StaStk: bffffe10 kB
+ExecLim: b8000000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000001000
+SigCgt: 0000000000012000
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/32674/task/32674/stat b/tests/002/proc/32674/task/32674/stat
new file mode 100644
index 00000000..a396a213
--- /dev/null
+++ b/tests/002/proc/32674/task/32674/stat
@@ -0,0 +1 @@
+32674 (sshd) S 32672 32672 32672 0 -1 4194624 126 0 0 0 2332 10862 0 0 15 0 1 0 50053044 7180288 614 4294967295 1118208 1420332 3221224976 3221220428 3086915490 0 0 4096 73728 3222765173 0 0 17 0 0 0
diff --git a/tests/002/proc/32675/status b/tests/002/proc/32675/status
new file mode 100644
index 00000000..293ba9bc
--- /dev/null
+++ b/tests/002/proc/32675/status
@@ -0,0 +1,31 @@
+Name: bash
+State: S (sleeping)
+SleepAVG: 88%
+Tgid: 32675
+Pid: 32675
+PPid: 32674
+TracerPid: 0
+Uid: 524 524 524 524
+Gid: 503 503 503 503
+FDSize: 256
+Groups: 501 503 512
+VmSize: 4612 kB
+VmLck: 0 kB
+VmRSS: 1824 kB
+VmData: 444 kB
+VmStk: 24 kB
+VmExe: 577 kB
+VmLib: 1411 kB
+StaBrk: 080e3000 kB
+Brk: 08145000 kB
+StaStk: bffffe40 kB
+ExecLim: 080d8000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000010000
+SigIgn: 0000000000384004
+SigCgt: 000000004b813efb
+CapInh: 0000000000000000
+CapPrm: 0000000000000000
+CapEff: 0000000000000000
diff --git a/tests/002/proc/32675/task/32675/stat b/tests/002/proc/32675/task/32675/stat
new file mode 100644
index 00000000..ccdf4823
--- /dev/null
+++ b/tests/002/proc/32675/task/32675/stat
@@ -0,0 +1 @@
+32675 (bash) S 32674 32675 32675 34820 15812 4194304 50530 31756571 0 135 41 594 48025 222888 15 0 1 0 50053047 4722688 456 4294967295 134508544 135099520 3221225024 3221223620 2340770 0 65536 3686404 1266761467 3222425215 0 0 17 0 0 0
diff --git a/tests/002/proc/4/status b/tests/002/proc/4/status
new file mode 100644
index 00000000..5691a7e8
--- /dev/null
+++ b/tests/002/proc/4/status
@@ -0,0 +1,20 @@
+Name: khelper
+State: S (sleeping)
+SleepAVG: 92%
+Tgid: 4
+Pid: 4
+PPid: 3
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffffff
+SigIgn: 0000000000010000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/4/task/4/stat b/tests/002/proc/4/task/4/stat
new file mode 100644
index 00000000..b0d352e6
--- /dev/null
+++ b/tests/002/proc/4/task/4/stat
@@ -0,0 +1 @@
+4 (khelper) S 3 0 0 0 -1 32832 0 0 0 0 0 2 0 0 5 -10 1 0 59 0 0 4294967295 0 0 0 0 0 0 2147483647 65536 0 3222484651 0 0 17 0 0 0
diff --git a/tests/002/proc/5/status b/tests/002/proc/5/status
new file mode 100644
index 00000000..43a6ddf1
--- /dev/null
+++ b/tests/002/proc/5/status
@@ -0,0 +1,20 @@
+Name: kacpid
+State: S (sleeping)
+SleepAVG: 0%
+Tgid: 5
+Pid: 5
+PPid: 3
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: ffffffffffffffff
+SigIgn: 0000000000010000
+SigCgt: 0000000000000000
+CapInh: 0000000000000000
+CapPrm: 00000000ffffffff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/5/task/5/stat b/tests/002/proc/5/task/5/stat
new file mode 100644
index 00000000..4c368098
--- /dev/null
+++ b/tests/002/proc/5/task/5/stat
@@ -0,0 +1 @@
+5 (kacpid) S 3 0 0 0 -1 32832 0 0 0 0 0 0 0 0 15 -10 1 0 59 0 0 4294967295 0 0 0 0 0 0 2147483647 65536 0 3222484651 0 0 17 0 0 0
diff --git a/tests/002/proc/870/status b/tests/002/proc/870/status
new file mode 100644
index 00000000..fe77ae24
--- /dev/null
+++ b/tests/002/proc/870/status
@@ -0,0 +1,31 @@
+Name: udevd
+State: S (sleeping)
+SleepAVG: 87%
+Tgid: 870
+Pid: 870
+PPid: 1
+TracerPid: 0
+Uid: 0 0 0 0
+Gid: 0 0 0 0
+FDSize: 32
+Groups:
+VmSize: 1608 kB
+VmLck: 0 kB
+VmRSS: 452 kB
+VmData: 148 kB
+VmStk: 116 kB
+VmExe: 7 kB
+VmLib: 1301 kB
+StaBrk: 0804c000 kB
+Brk: 095ee000 kB
+StaStk: bffe51b0 kB
+ExecLim: 0804a000
+Threads: 1
+SigPnd: 0000000000000000
+ShdPnd: 0000000000000000
+SigBlk: 0000000000000000
+SigIgn: 0000000000000000
+SigCgt: 0000000000016002
+CapInh: 0000000000000000
+CapPrm: 00000000fffffeff
+CapEff: 00000000fffffeff
diff --git a/tests/002/proc/870/task/870/stat b/tests/002/proc/870/task/870/stat
new file mode 100644
index 00000000..0e069338
--- /dev/null
+++ b/tests/002/proc/870/task/870/stat
@@ -0,0 +1 @@
+870 (udevd) S 1 870 870 0 -1 4227072 511 56426 0 13 0 8 4 477 6 -10 1 1000 3475 1646592 113 4294967295 134512640 134519928 3221115312 3221114028 2340770 0 0 0 90114 3222765173 0 0 17 0 0 0
diff --git a/tests/002/query-1.expected b/tests/002/query-1.expected
new file mode 100644
index 00000000..3fb3a37e
--- /dev/null
+++ b/tests/002/query-1.expected
@@ -0,0 +1,90 @@
+USER PID PPID S NAME THREADS
+root 1 0 S init -
+root 2 1 S ksoftirqd/0 -
+root 3 1 S events/0 -
+root 4 3 S khelper -
+root 5 3 S kacpid -
+root 16 3 S kblockd/0 -
+root 17 1 S khubd -
+root 28 1 S kswapd0 -
+root 29 3 S aio/0 -
+root 103 1 S kseriod -
+root 175 1 S scsi_eh_0 -
+root 186 1 S kjournald -
+root 870 1 S udevd -
+root 1068 1 S dhclient -
+root 1235 1 S kjournald -
+root 1236 1 S kjournald -
+root 1620 1 S syslogd -
+root 1624 1 S klogd -
+rpc 1645 1 S portmap -
+rpcuser 1665 1 S rpc.statd -
+root 1698 1 S rpc.idmapd -
+root 1766 1 S vmware-guestd -
+root 1790 1 S rpciod -
+root 1791 1 S lockd -
+root 1821 1 S ypbind 1826, 1821
+root 1839 1 S acpid -
+root 1851 1 S cupsd -
+root 1887 1 S sshd -
+root 1902 1 S xinetd -
+root 1921 1 S rpc.rquotad -
+root 1925 1 S nfsd -
+root 1926 1 S nfsd -
+root 1927 1 S nfsd -
+root 1928 1 S nfsd -
+root 1929 1 S nfsd -
+root 1930 1 S nfsd -
+root 1931 1 S nfsd -
+root 1932 1 S nfsd -
+root 1936 1 S rpc.mountd -
+root 1963 1 S crond -
+xfs 1989 1 S xfs -
+root 2008 1 S atd -
+dbus 2027 1 S dbus-daemon-1 2027
+root 2041 1 S cups-config-dae -
+root 2052 1 S hald -
+root 2062 1 S mingetty -
+root 2124 1 S gdm-binary -
+root 2184 2124 S gdm-binary -
+root 2354 2184 S X -
+524 2551 2184 S gnome-session -
+524 2579 1 S ssh-agent -
+524 2625 1 S dbus-launch -
+524 2626 1 S dbus-daemon-1 2627
+524 2631 1 S gconfd-2 -
+524 2634 1 S gnome-keyring-d -
+524 2636 1 S bonobo-activati -
+524 2638 1 S gnome-settings- -
+524 2644 1 S gam_server -
+524 2661 1 S xscreensaver -
+524 2685 1 S metacity -
+524 2689 1 S gnome-panel -
+524 2691 1 S nautilus 2715, 2691, 2708, 2711, 2714, 2713, 2709, 2710, 2696
+524 2693 1 S gnome-volume-ma -
+524 2695 1 S eggcups -
+524 2698 1 S gnome-vfs-daemo 2698
+524 2701 1 S pam-panel-icon -
+524 2707 1 R rhn-applet-gui -
+524 2717 2701 S pam_timestamp_c -
+524 2718 1 S mapping-daemon -
+524 2720 1 S wnck-applet -
+524 2722 1 S mixer_applet2 -
+524 2726 1 S clock-applet -
+524 2728 1 S notification-ar -
+524 15812 32675 S ssh -
+524 16248 31908 S su -
+root 16249 16248 S bash -
+root 27121 3 S pdflush -
+root 27243 3 S pdflush -
+root 27682 1887 S sshd -
+524 27684 27682 S sshd -
+524 27685 27684 S bash -
+root 29840 16249 R bash -
+root 30737 1 S dhclient -
+root 31905 1887 S sshd -
+524 31907 31905 S sshd -
+524 31908 31907 S bash -
+root 32672 1887 S sshd -
+524 32674 32672 S sshd -
+524 32675 32674 S bash -
diff --git a/tests/002/query-1.txr b/tests/002/query-1.txr
new file mode 100644
index 00000000..fd4db03b
--- /dev/null
+++ b/tests/002/query-1.txr
@@ -0,0 +1,38 @@
+@#
+@# This file is in the public domain.
+@# It was authored by Kaz Kylheku <kkylheku@gmail.com> in 2009
+@#
+@(next)!ls @TESTDIR/proc | sort -n
+@(collect)
+@{process /[0-9]+/}
+@ (next)@TESTDIR/proc/@process/status
+Name: @name
+State: @state (@state_desc)
+SleepAVG: @sleep_avg%
+Tgid: @tgid
+Pid: @proc_id
+PPid: @parent_id
+@(bind pid proc_id)
+@(bind ppid parent_id)
+@(skip)
+Uid: @uid @/.*/
+Gid: @gid @/.*/
+@ (next)$@TESTDIR/proc/@process/task
+@ (collect)
+@thr
+@ (end)
+@ (bind thread thr)
+@ (some)
+@ (next)@TESTDIR/etc/passwd
+@ (skip)
+@user:@pw:@uid:@/.*/
+@ (and)
+@ (bind user uid)
+@ (end)
+@(end)
+@(output)
+USER PID PPID S NAME THREADS
+@ (repeat)
+@{user 8} @{proc_id -5} @{parent_id -5} @state @{name 16} @(rep)@thr, @(first)@(last)@thr@(single)-@(end)
+@ (end)
+@(end)
diff --git a/txr.1 b/txr.1
new file mode 100644
index 00000000..79a5eeaa
--- /dev/null
+++ b/txr.1
@@ -0,0 +1,1741 @@
+.\"Copyright (C) 2009, Kaz Kylheku <kkylheku@gmail.com>.
+.\"All rights reserved.
+.\"
+.\"BSD License:
+.\"
+.\"Redistribution and use in source and binary forms, with or without
+.\"modification, are permitted provided that the following conditions
+.\"are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in
+.\" the documentation and/or other materials provided with the
+.\" distribution.
+.\" 3. The name of the author may not be used to endorse or promote
+.\" products derived from this software without specific prior
+.\" written permission.
+.\"
+.\"THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+.\"IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+.\"WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+
+.TH txr 1 2009-09-09 "txr v. 011" "Text Extraction Utility"
+.SH NAME
+txr \- text extractor
+.SH SYNOPSIS
+.B txr [ options ] query-file { data-file }*
+.sp
+.SH DESCRIPTION
+.B txr
+is a query tool for extracting pieces of text buried in one or more text
+file based on pattern matching. A
+.B txr
+query specifies a pattern which matches (a prefix of) entire file, or
+multiple files. The pattern is matched against the material in the files, and
+free variables occurring in the pattern are bound to the pieces of text
+occurring in the corresponding positions. If the overall match is
+successful, then
+.B txr
+can do one of two things: it can report the list of variables which were bound,
+in the form of a set of variable assignments which can be evaluated by the
+.B eval
+command of the POSIX shell language, or generate a custom report according
+to special directives in the query.
+
+In addition to embedded variables which implicitly match text, the
+.B txr
+query language supports a number of directives, for matching text using regular
+expressions, for continuing a match in another file, for searching through a
+file for the place where an entire sub-query matches, for collecting lists, and
+for combining sub-queries using logical conjunction, disjunction and negation.
+
+When
+.B txr
+finds a match for a variable and binds it, if that variable occurs again
+later in the query, the variable's text is substituted, forcing a match for
+that exact text. Thus txr supports a rudimentary form of backreferencing
+unification, if you will. For example, the query
+
+ @FOO=@FOO
+
+will match material from the start of the line until the first equal sign,
+and bind it to the variable
+.IR FOO.
+Then, the material which follows the equal sign to the end of the line must
+match the contents bound to FOO. Hence the line "abc=abc" will match, but
+"abc=xyz" will fail to match.
+
+Generally, the scope of a variable's binding
+extends from its first successful match where the binding is established, to
+the end of the query. Unsuccessful subqueries have no effect on the
+bindings. Even if a failed subquery is partially successful, all of its
+bindings are thrown away. Some directives treat the bindings emanating
+from their subqueries in special ways.
+
+.SH ARGUMENTS AND OPTIONS
+
+Options other than -D may be combined together into a single argument.
+The -v and -q options are mutually exclusive. The one which occurs
+in the rightmost position in the argument list dominates.
+
+.IP -Dvar=value
+Bind the variable
+.IR var
+to the value
+.IR value
+prior to processing the query. The name is in scope over the entire
+query, so that all occurrence of the variable are substituted and
+match the equivalent text. If the value contains commas, these
+are interpreted as separators, which give rise to a list value.
+For instance -Da,b,c creates a list of the strings "a", "b" and "c".
+(See Collect Directive bellow). List variables provide a multiple
+match. That is to say, if a list variable occurs in a query, a successful
+match occurs if any of its values matches the text. If more than one
+value matches the text, the first one is taken.
+
+.IP -Dvar
+Binds the variable
+.IR var
+to an empty string value prior to processing the query.
+
+.IP -q
+Quiet operation during matching. Certain error messages are not reported on the
+standard error device (but the if the situations occur, they still fail the
+query). This option does not suppress error generation during the parsing
+of the query, only during its execution.
+
+.IP -v
+Verbose operation. Detailed logging is enabled.
+
+.IP -b
+Suppresses the printing of variable bindings for a successful query, and the
+word .IR false for a failed query. The program still sets an appropriate
+termination status.
+
+.IP -a num
+Specifies the maximum number of array dimensions to use for variables
+arising out of collect. The default is 1. Additional dimensions are
+expressed using numeric suffixes in the generated variable names.
+For instance, consider the three-dimensional list arising out of a triply
+nested collect: ((("a" "b") ("c" "d")) (("e" "f") ("g" "h"))).
+Suppose this is bound to a variable V. With -a 1, this will be
+reported as:
+
+ V_0_0[0]="a"
+ V_0_1[0]="b"
+ V_1_0[0]="c"
+ V_1_1[0]="d"
+ V_0_0[1]="e"
+ V_0_1[1]="f"
+ V_1_0[1]="g"
+ V_1_1[1]="h"
+
+The leftmost bracketed index is the most major index. That is to say,
+the dimension order is: NAME_m_m+1_..._n[1][2]...[m-1].
+
+.IP --help
+Prints usage summary on standard output, and terminates successfully.
+
+.IP --version
+Prints program version standard output, and terminates successfully.
+
+.IP --
+Signifies the end of the option list. This option does not combine with others, so for instance -b- does not mean -b --, but is an error.
+
+.IP -
+This argument is not interpreted as an option, but treated as a filename
+argument. After the first such argument, no more options are recognized. Even
+if another argument looks like an option, it is treated as a name.
+This special argument - means "read from standard input" instead of a file.
+The query file, or any of the data files, may be specified using this option.
+If two or more files are specified as -, the behavior is system-dependent.
+It may be possible to indicate EOF from the interactive terminal, and
+then specify more input which is interpreted as the second file, and so forth.
+
+.PP
+After the options, the remaining arguments are files. The first file argument
+specifies the query, and is mandatory. A file argument consisting of a single
+- means to read the standard input instead of opening a file. A file argument
+which begins with an exclamation symbol means that the rest of the argument is
+a shell command which is to be run as a coprocess, and its output read like a
+file.
+
+.PP
+.B txr
+begins by reading the query. The entire query is scanned, internalized
+and then begins executing. No file is opened until the query calls for a match
+for material from that file, but once opened, a file is always read in its
+entirety and stored in memory. A query may complete (successfully or not)
+before opening some or all of the files.
+
+If no files arguments are specified on the command line, it is up to the
+query to open a file, pipe or standard input via the @(next) directive
+prior to attempting to make a match. If a query attempts to match text,
+but has run out of files to process, the match fails.
+
+.SH STATUS AND ERROR REPORTING
+.B txr
+sends errors and verbose logs to the standard error device. The following paragraphs apply when
+.B txr
+is run without enabling verbose mode. If verbose mode is enabled, then
+.B txr
+issues diagnostics on the standard error device even in situations which are
+not erroneous.
+
+If the command line arguments are incorrect, or the query has a malformed
+syntax, or fails to match,
+.B txr
+issues an error diagnostic and terminates with a failed status.
+
+If the query is accepted, but fails to execute, either due to a
+semantic error or due to a mismatch against the data,
+.B txr
+terminates with a failed status, it also prints the word
+.IR false
+on standard output. (See NOTES ON FALSE below). Printing of false
+is suppressed if the query executed one or more @(output) directive
+directed to standard output.
+
+If the query is well-formed, and matches, then
+.B txr
+issues no diagnostics on standard error (except in the case of verbose
+reporting enabled by -v). If no variables were bound in the query, then
+nothing is printed on standard output. If the query has matched one or more
+variables, then these variables are printed on standard output, in the form of
+a shell script which, when evaluated, will cause shell variables to be
+assigned. Printing of these variables is suppressed if the query executed one
+or more @(output) directive directed to standard output.
+
+.SH BASIC QUERY SYNTAX AND SEMANTICS
+
+.SS Comments
+
+A query may contain comments which are delimited by the sequence @# and
+extend to the end of the line. No whitespace can occur between the @ and #.
+A comment which begins on a line swallows that entire line, as well as the
+newline which terminates it. In essence, the entire comment disappears.
+If the comment follows some material in a line, then it does not consume
+the newline. Thus, the following two queries are equivalent:
+
+ 1. @a@# comment: match whole line against variable @a
+ @# this comment disappears entirely
+ @b
+
+ 2. @a
+ @b
+
+The comment after the @a does not consume the newline, but the
+comment which follows does. Without this intuitive behavior,
+line comment would give rise to empty lines that must match empty
+lines in the data, leading to spurious mismatches.
+
+.SS Text
+
+character for character. Text which occurs at the beginning of a line matches
+the beginning of a line. Text which starts in the middle of a line, other than
+following a variable, must match exactly at the current position, where the
+previous match left off. Moreover, if the text is the last element in the line,
+its match is anchored to the end of the line.
+
+The semantics of text matching next to a variable is discussed in the following
+section.
+
+A query may not leave unmatched material in a line which is covered by the
+query. However, a query may leave unmatched lines.
+
+In the following example, the query matches the text, even though
+the text has an extra line.
+
+ Query: Four score and seven
+ years ago our
+
+ Text: Four score and seven
+ years ago our
+ forefathers
+
+In the following example, the query
+.B fails
+to match the text, because the text has extra material on one
+line.
+
+ Query: I can carry nearly eighty gigs
+ in my head
+
+ Text: I can carry nearly eighty gigs of data
+ in my head
+
+Needless to say, if the text has insufficient material relative
+to the query, that is a failure also.
+
+To match arbitrary material from the current position to the end
+of a line, the "match any sequence of characters, including empty"
+regular expression @/.*/ can be used. Example:
+
+ Query: I can carry nearly eighty gigs@/.*/
+
+ Text: I can carry nearly eighty gigs of data
+
+In this example, the query matches, since the regular expression
+matches the string "of data". (See Regular Expressions section below).
+
+.SS Special Characters in Text
+
+Control characters may be embedded directly in a query (with the exception of
+newline characters). An alternative to embedding is to use escape syntax.
+The following escapes are supported:
+
+.IP @\\a
+Alert character (ASCII 7, BEL).
+.IP @\\b
+Backspace (ASCII 8, BS).
+.IP @\\t
+Horizontal tab (ASCII 9, HT).
+.IP @\\n
+Line feed (ASCII 10, LF). Serves as abstract newline on POSIX systems.
+.IP @\\v
+Vertical tab (ASCII 11, VT).
+.IP @\\f
+Form feed (ASCII 12, FF). This character clears the screen on many
+kinds of terminals, or ejects a page of text from a line printer.
+.IP @\\r
+Carriage return (ASCII 13, CR).
+.IP @\\e
+Escape (ASCII 27, ESC)
+.IP @\\x<hex>
+A @\\x followed by a sequence of hex digits is interpreted as a hexadecimal
+numeric character code. For instance @\\x41 is the ASCII character A.
+.IP @\\<octal>
+A @\\ followed by a sequence of octal digits (0 through 7) is interpreted
+as an octal character code. For instance @\\010 is character 8, same as @\\b.
+.PP
+
+Note that if a newline is embedded into a query line with @\\n, this
+does not split the line into two; it's embedded into the line and
+thus cannot match anything. However, @\\n may be useful in the @(cat)
+directive and in @(output).
+
+.SS Variables
+
+Much of the query syntax consists of arbitrary text, which matches file data
+character for character. Embedded within the query may be variables and
+directives which are introduced by a @ character. Two consecutive @@
+characters encode a literal @.
+
+A variable matching or substitution directive is written in one of several
+ways:
+
+ @NAME
+ @{NAME}
+ @*NAME
+ @*{NAME}
+ @{NAME /RE/}
+ @{NAME NUMBER}
+
+The forms with an * indicate a long match, see Longest Match below.
+The last two forms with the embedded regexp /RE/ or number have special
+semantics, see Positive Match below.
+
+The name itself may consist of any combination of one or more letters, numbers,
+and underscores, and must begin with a letter or underscore. Case is
+sensitive, so that @FOO is different from @foo, which is different from @Foo.
+The braces around a name can be used when material which follows would
+otherwise be interpreted as being part of the name. For instance @FOO_bar
+introduces the name "FOO_bar", whereas @{FOO}_bar means the variable named
+"FOO" followed by the text "_bar". There may be whitespace between the @ and
+the name, or opening brace. Whitespace is also allowed in the interior of the
+braces. It is not significant.
+
+If a variable has no prior binding, then it specifies a match. The
+match is determined from some current position in the data: the
+character which immediately follows all that has been matched previously.
+If a variable occurs at the start of a line, it matches some text
+at the start of the line. If it occurs at the end of a line, it matches
+everything from the current position to the end of the line.
+
+The extent of the matched text (the text bound to the variable) is determined
+by looking at what follows the variable. A variable may be followed by a piece
+of text, a regular expression directive, another variable, or nothing (i.e.
+occurs at the end of a line).
+
+If the variable is followed by nothing, the
+match extends from the current position in the data, to the end of the line.
+Example:
+
+ pattern: "a b c @FOO"
+ data: "a b c defghijk"
+ result: FOO="defghijk"
+
+If the variable is followed by text (all non-directive material extending to
+the end of the line, or to the start of another directive), then the extent of
+the match is determined by searching for the first occurrence of that text
+within the line, starting at the current position. The variable matches
+everything between the current position and the matching position (not
+including the matching position). Any whitespace which follows the
+variable (and is not enclosed inside braces that surround the variable
+name) is part of the text. For example:
+
+ pattern: "a b @FOO e f"
+ data: "a b c d e f"
+ result: FOO="c d"
+
+In the above example, the pattern text "a b " matches the
+data "a b ". So when the @FOO variable is processed, the data being
+matched is the remaining "c d e f". The text which follows @FOO
+is " e f". This is found within the data "c d e f" at position 3
+(counting from 0). So positions 0-2 ("c d") constitute the matching
+text which is bound to FOO.
+
+If the variable is followed by a regular expression directive,
+the extent is determined by finding the closest match for the
+regular expression. (See Regular Expressions section below).
+
+.SS Consecutive Variables
+
+If an unbound variable is followed by another unbound variable, the
+combination is a semantic error which will fail the query. A
+diagnostic message will be issued, unless operating in quiet mode via -q.
+The reason is that there is no way to bind two consecutive variables to
+an extent of text; this is an ambiguous situation, since there is no
+matching criterion for dividing the text between two variables.
+(In theory, a repetition of the same variable, like @FOO@FOO, could
+find a solution by dividing the match extent in half, which would work
+only in the case when it contains an even number of characters.
+This behavior seems to have dubious value).
+
+An unbound variable may be followed by one which is bound. The bound
+variable is replaced by the text which it denotes, and the logic proceeds
+accordingly. Variables are never bound to regular expressions, so
+the regular expression match does not arise in this case.
+The @* syntax for longest match is available. Example:
+
+ pattern: "@FOO:@BAR@FOO"
+ data: "xyz:defxyz"
+ result: FOO=xyz, BAR=def
+
+Here, FOO is matched with "xyz", based on the delimiting around the
+colon. The colon in the pattern then matches the colon in the data,
+so that BAR is considered for matching against "defxyz".
+BAR is followed by FOO, which is already bound to "xyz".
+Thus "xyz" is located in the "defxyz" data following "def",
+and so BAR is bound to "def".
+
+If an unbound variable is followed by a variable which is bound to a list, or
+nested list, then each character string in the list is tried in turn to produce
+a match. The first match is taken.
+
+.SS Longest Match
+
+The closest-match behavior for text and regular expressions can be
+overridden to longest match behavior. A special syntax is provided
+for this: an asterisk between the @ and the variable, e.g:
+
+ pattern: "a @*{FOO}cd"
+ data: "a b cdcdcdcd"
+ result: FOO="b cdcdcd"
+
+ pattern: "a @{FOO}cd"
+ data: "a b cdcdcd"
+ result: FOO="b "
+
+In the former example, the match extends to the rightmost occurrence of "cd",
+and so FOO receives "b cdcdcd". In the latter example, the *
+syntax isn't used, and so a leftmost match takes place. The extent
+covers only the "b ", stopping at the first "cd" occurrence.
+
+.SS Positive Match
+
+The syntax variants
+
+ @{NAME /RE/}
+ @{NAME NUMBER}
+
+specify a variable binding that is driven by a positive match derived
+from a regular expression or character count, rather than from trailing
+material (which may be regarded as a "negative" match, since the variable is
+bound to material which is
+.B skipped
+in order to match the trailing material). In the /RE/ form, the match
+extends over all characters from the current position which match
+the regular expression RE.
+
+In the NUMBER form, the match processes a field of text which
+consists of the specified number of characters, which must be nonnegative
+number. If the data line doesn't have that many characters starting at the
+current position, the match fails. A match for zero characters produces an
+empty string. The text which is actually matched by this construct
+is all text within the specified field, but excluding leading and
+trailing whitespace. If the field contains only spaces, then an empty
+string is extracted.
+
+A number is made up of digits, optionally preceded by a + or - sign.
+
+This syntax is processed without consideration of what other
+syntax follows. A positive match may be directly followed by an unbound
+variable.
+
+.SS Regular Expressions
+
+Like text, a regular expression (regexp) must match text in the data. A regexp
+which occurs at the beginning of a line matches the beginning of a line. A
+regexp which occurs elsewhere, other than following a variable, must match
+exactly starting at the current position, where the previous match left off. A
+regexp which occurs at the end of a line must match from the current position
+to the end of the line.
+
+The semantics of a regular expression which follow variables is
+discussed in the preceding section Variables.
+
+A regular expression, as a standalone directive, looks like this:
+
+ @/RE/
+
+where RE is regular expression syntax.
+.B txr
+contains an original implementation of regular expressions, which
+supports the following syntax:
+.IP .
+matches any character.
+.IP []
+Character class: matches a single character, from the set specified by
+the class. Supports basic regexp character class syntax; no POSIX
+notation like [:digit:]. The class [a-zA-Z] means match an uppercase
+or lowercase letter; the class [0-9a-f] means match a digit or
+a lowercase letter, the class [^0-9] means match a non-digit, et cetera.
+A ] or - can be used within a character class, but must be escaped
+with a backslash. Two backslashes code for one backslash. So
+for instance [\[\-] means match a [ or - character, [^^] means match
+any character other than ^, and [\^\\] means match either a ^ or a
+backslash.
+.IP (RE)
+If RE is a regular expression, then so is (RE).
+The contents of parentheses denote one regular expression unit, so that for
+instance in (RE)*, the * operator applies to the entire parenthesized group.
+.IP (RE)?
+optionally matches the preceding regular expression (RE).
+.IP (RE)+
+matches the preceding expression one or more times.
+.IP (RE)*
+matches the preceding expression zero or more times.
+.IP (RE1)(RE2)
+Two consecutive regular expressions denote catenation:
+the left expression must match, and then the right.
+
+.IP (RE1)|(RE2)
+matches either the expression RE1 or RE2.
+
+.PP
+Any of the special characters, including the delimiting /, can be escaped with
+a backslash to suppress its meaning and denote the character itself.
+
+Furthermore, all of the same escapes are as described in the section Special
+Characters in Text above---the difference is that in regular expressions, the @
+character is not required, so for example a tab is coded as \\t rather
+than @\\t.
+
+Any escaped character which does not fall into the above escaping conventions,
+or any unescaped character which is not a regular expression operator, denotes
+one-position match of that character itself.
+
+Character classes and parentheses have the highest precedence.
+
+The postfix operators ?, + and * have the second highest precedence, and
+associate left to right, so that in A+?*, the * applies to A+?, and the ?
+applies to A+.
+
+Catenation is on the next lower precedence rung, so that AB? means "match A,
+and then optionally B" not "match A and B, as one optional unit". The latter
+must be written (AB)? using parentheses to override precedence.
+
+The disjunction operator | has the lowest precedence, lower than catenation.
+Thus abc|def means "match abc, or match def". The meaning "match ab,
+then c or d, then ef" must be expressed as ab(c|d)ef, or using
+a character class: ab[cd]ef.
+
+In
+.b txr,
+regular expression matches do not span multiple lines. There is no way
+to match a newline character since it's simply not internally represented in
+the data.
+
+It's possible for a regular expression to match an empty string.
+For instance, if the next input character is z, facing a
+the regular expression /a?/, there is a zero-character match:
+the regular expression's state machine can reach an acceptance
+state without consuming any characters. Examples:
+
+ pattern: @A@/a?/@/.*/
+ data: zzzzz
+ result: A=""
+
+ pattern: @{A /a?/}@B
+ data: zzzzz
+ result: A="", B="zzzz"
+
+ pattern: @*A@/a?/
+ data: zzzzz
+ result: A="zzzzz"
+
+In the first example, variable @A is followed by a regular expression
+which can match an empty string. The expression faces the letter "z"
+at position 0 in the data line. A zero-character match occurs there,
+therefore the variable A takes on the empty string. The @/.*/ regular
+expression then consumes the line.
+
+Similarly, in the second example, the /a?/ regular expression faces
+a "z", and thus yields an empty string which is bound to A. Variable
+@B consumes the entire line.
+
+The third example request the longest match for the variable binding.
+Thus, a search takes place for the rightmost position where the
+regular expression matches. The regular expression matches anywhere,
+including the empty string after the last character, which is
+the rightmost place. Thus variable A fetches the entire line.
+
+.SS Directives
+
+The general syntax of a directive is:
+
+ @EXPR
+
+where expr is a parenthesized list of subexpressions. A subexpression
+is an symbol, number, regular expression, or a parenthesized expression.
+So, examples of valid directives are:
+
+ @(banana)
+
+ @(a b c (d e f))
+
+ @( a (b (c d) (e ) ))
+
+ @(a /[a-z]*/ b)
+
+A symbol is lexically the same thing as a variable and the same rules
+apply. Tokens that look like numbers are treated as numbers.
+
+Some directives are involved in structuring the overall syntax of the query.
+
+There are syntactic constraints that depend on the directive. For instance the
+@(next) directive can take argument material, which is everything that follows
+on the same line, until the end of the line. But @(skip) does not take
+argument material. Most directives must be the first item of a line.
+
+A summary of the available directives follows:
+
+.IP @(next)
+Continue matching in another file.
+
+.IP @(block)
+The remaining query is treated as an anonymous or named block.
+Blocks may be referenced by @(accept) and @(fail) directives.
+Blocks are discussed in the section Blocks below.
+
+.IP @(skip)
+Treat the remaining query as a subquery unit, and search the lines of
+the input file until that subquery matches somewhere.
+A skip is also an anonymous block.
+
+.IP @(some)
+Match some clauses in parallel. At least one has to match.
+
+.IP @(all)
+Match some clauses in parallel. Each one must match.
+
+.IP @(none)
+Match some clauses in parallel. None must match.
+
+.IP @(maybe)
+Match some clauses in parallel. None must match.
+
+.IP @(collect)
+Search the data for multiple matches of a clause. Collect the
+bindings in the clause into lists, which are output as array variables.
+The @(collect) directive is line oriented. It works with a multi-line
+pattern and scans line by line. A similar directive called @(coll)
+works within one line.
+
+A collect is an anonymous block.
+
+.IP @(and)
+Separator of clauses for @(some), @(all), and @(none).
+Equivalent to @(or). Choice is stylistic.
+
+.IP @(or)
+Separator of clauses for @(some), @(all), and @(none).
+Equivalent to @(and). Choice is stylistic.
+
+.IP @(end)
+Required terminator for @(some), @(all), @(none), @(maybe), @(collect),
+@(output), and @(repeat).
+
+.IP @(fail)
+Terminate the processing of a block, as if it were a failed match.
+Blocks are discussed in the section Blocks below.
+
+.IP @(accept)
+Terminate the processing of a block, as if it were a successful match.
+What bindings emerge may depend on the kind of block: collect
+has special semantics. Blocks are discussed in the section Blocks below.
+
+.IP @(flatten)
+Normalizes a set of specified variables to one-dimensional lists. Those
+variables which have scalar value are reduced to lists of that value.
+Those which are lists of lists (to an arbitrary level of nesting) are converted
+to flat lists of their leaf values.
+
+.IP @(merge)
+Binds a new variable which is the result of merging two or more
+other variables. Merging has somewhat complicated semantics.
+
+.IP @(cat)
+Decimates a list (any number of dimensions) to a string, by catenating its
+constituent strings, with an optional separator string between all of the
+values.
+
+.IP @(bind)
+Binds one or more variables against another variable using a structural
+pattern. A limited form of unification takes place which can cause a match to
+fail.
+
+.IP @(output)
+A directive which encloses an output clause in the query. An output section
+does not match text, but produces text. The directives above are not
+understood in an output clause.
+
+.IP @(repeat)
+A directive understood within an @(output) section, for repeating multi-line
+text, with successive substitutions pulled from lists. A version @(rept)
+produces repeated text within one line.
+
+.PP
+
+.SS The Next Directive
+
+The next directive comes in two forms. It can occur by itself as the
+only element in a query line:
+
+ @(next)
+
+Or it may be followed by material, which may contain variables.
+All of the variables must be bound. For example:
+
+ @(next)/path/to/@foo.txt
+
+Both forms indicate that the remainder of the query applies
+to a new file. The lone @(next) switches to the next file in the
+argument list which was passed to the
+.B txr
+utility. The second form diverts the remainder of the query to a file whose
+name is given by the trailing material, after variable substitutions are
+performed.
+
+Note that "remainder of the query" refers to the subquery in which
+the next directive appears, not necessarily the entire query.
+
+For example, the following query looks for the line starting with "xyz"
+at the top of the file "foo.txt", within a some directive.
+After the @(end) which terminates the @(some), the "abc" is matched in the
+current file.
+
+ @(some)
+ @(next)foo.txt
+ xyz@suffix
+ @(end)
+ abc
+
+However, if the @(some) subquery successfully matched "xyz@suffix" within the
+file foo.text, there is now a binding for the suffix variable, which
+is globally visible to the remainder of the entire query.
+
+The @(next) directive supports the file name conventions as the command
+line. The name - means standard input. Text which starts with a ! is
+interpreted as a shell command whose output is read like a file. These
+interpretations are applied after variable substitution. If the file is
+specified as @a, but the variable a expands to "!echo foo", then the output of
+the "echo foo" command will be processed.
+
+.SS The Skip Directive
+
+The skip directive considers the remainder of the query as a search
+pattern. The remainder is no longer required to strictly match at the
+current line in the current file. Rather, the current file is searched,
+starting with the current line, for the first line where the entire remainder
+of the query will successfully match. If no such line is found, the skip
+directive fails. If a matching position is found, the remainder of
+the query is understood to be processed there.
+
+Of course, the remainder of the query can itself contain skip directives.
+Each such directive performs a recursive subsearch.
+
+The skip directive has an optional numeric argument. The value of this
+argument limits the range of lines scanned for a match. Judicious use
+of this feature can improve the performance of queries.
+
+Example: scan until "size: @SIZE" matches, which must happen within
+the next 15 lines:
+
+ @(skip 15)
+ size: @SIZE
+
+Without the range limitation skip will keep searching until it consumes
+the entire input source. While sometimes this is what is intended,
+often it is not. Sometimes a skip is nested within a collect, or
+following another skip. For instance, consider:
+
+ @(collect)
+ begin @BEG_SYMBOL
+ @(skip)
+ end @BEG_SYMBOL
+ @(end)
+
+The collect iterates over the entire input. But, potentially, so does
+the skip. Suppose that "begin x" is matched, but the data has no
+matching "end x". The skip will search in vain all the way to the end of the
+data, and then the collect will try another iteration back at the
+beginning, just one line down from the original starting point. If it is a
+reasonable expectation that an "end x" occurs 15 lines of a "begin x", this can
+be written instead:
+
+ @(collect)
+ begin @BEG_SYMBOL
+ @(skip 15)
+ end @BEG_SYMBOL
+ @(end)
+
+.SS The Some, All, None and Maybe directives
+
+These directives combine multiple subqueries, which are applied at the same position in parallel. The syntax of all three follows this example:
+
+ @(some)
+ <subquery1>
+ .
+ .
+ .
+ @(and)
+ <subquery2>
+ .
+ .
+ .
+ @(and)
+ <subquery3>
+ .
+ .
+ .
+ @(end)
+
+The @(some), @(all) or @(none) directive must appear as the only element in a
+query line. It must be followed by at least one subquery clause, and terminated
+by @(end). If there are two or more subqueries, these additional clauses are
+indicated by @(and) or @(or), which are interchangeable. The @(and), @(or) and
+@(end) directives also must appear as the only element in a query line.
+
+The syntax supports arbitrary nesting. For example:
+
+ QUERY: SYNTAX TREE:
+
+ @(all) all -+
+ @ (skip) +- skip -+
+ @ (some) | +- some -+
+ it | | +- TEXT
+ @ (and) | | +- and
+ @ (none) | | +- none -+
+ was | | | +- TEXT
+ @ (end) | | | +- end
+ @ (end) | | +- end
+ a dark | +- TEXT
+ @(end) *- end
+
+nesting can be indicated using whitespace between @ and the
+directive expression. Thus, the above is an @(all) query containing a @(skip)
+clause which applies to a @(some) that is followed by the the text
+line "a dark". The @(some) clause combines the text line "it",
+and a @(none) clause which contains just one clause consisting of
+the line "was".
+
+The semantics of the some, all, none and maybe directives is:
+
+.IP @(all)
+Each of the clauses is matched at the current position. If any of the
+clauses fails to match, the directive fails (and thus does not produce
+any variable bindings).
+
+.IP @(some)
+Each of the clauses is matched at the current position. If any
+of the clauses succeed, the directive succeeds. The bindings from
+all successful clauses are retained.
+
+.IP @(none)
+Each of the clauses is matched at the current position. The
+directive succeeds only if all of the clauses fail. If
+any clause succeeds, the directive fails. Thus, this
+directive never produces variable bindings.
+
+.IP @(maybe)
+Each of the clauses is matched at the current position.
+The directive succeeds even if all of the clauses fail.
+Whatever bindings are found in any of the clauses are
+retained.
+
+When a @(some) or @(all) directive matches successfully, or a @(maybe)
+directive matches something, the query advances by the greatest number of lines
+matched in any of the subclauses. For instance if there are two subclauses, and
+one of them matches three lines, but the other one matches five lines, then the
+overall clause is considered to have made a five line match at its position. If
+more directives follow, they begin matching five lines down from that position.
+
+.SS The Collect Directive
+
+The syntax of the collect directive is:
+
+ @(collect)
+ ... lines of subquery
+ @(end)
+
+or with an until clause:
+
+ @(collect)
+ ... lines of subquery
+ @(until)
+ ... lines of subquery
+ @(end)
+
+
+The the subquery is matched repeatedly, starting at the current line.
+If it fails to match, it is tried starting at the subsequent line.
+If it matches successfully, it is tried at the line following the
+entire extent of matched data, if there is one. Thus, the collected regions do
+not overlap.
+
+The collect as a whole always succeeds, even if the subquery does not match at
+any position, and even if the until clause does not match. That is to say, a
+query will never fail for the reason that a collect didn't collect anything.
+
+If no until clause is specified, the collect is unbounded. It consumes the entire data file. If any query material follows such the collect clause, it will
+fail if it tries to match anything in the current file; but of course, it
+is possible to continue matching in another file by means of @(next).
+
+If an until clause is specified, the collection stops when that clause matches
+at the current position (and that last position is also collected, if it
+matches). If the collection is stopped by a match in the until clause,
+any variables bound in that clause also emerge out of the overall collect
+clause (but these bindings are single values, not lists).
+
+Example:
+
+ Query: @(collect)
+ @a
+ @(until)
+ 42
+ @(end)
+
+ Data: 1
+ 2
+ 3
+ 42
+ 5
+ 6
+
+ Output: a[0]="1"
+ a[1]="2"
+ a[2]="3"
+ a[3]="42"
+
+The binding variables within the clause of a collect are treated specially.
+The multiple matches for each variable are collected into lists,
+which then appear as array variables in the final output.
+
+Example:
+
+ Query: @(collect)
+ @a:@b:@c
+ @(end)
+
+ Data: John:Doe:101
+ Mary:Jane:202
+ Bob:Coder:313
+
+ Output:
+ a[0]="John"
+ a[1]="Mary"
+ a[2]="Bob"
+ b[0]="Doe"
+ b[1]="Jane"
+ b[2]="Coder"
+ c[0]="101"
+ c[1]="202"
+ c[2]="313"
+
+The query matches the data in three places, so each variable becomes
+a list of three elements, reported as an array.
+
+Variables with list bindings may be referenced in a query. They denote a
+multiple match. The -D command line option can establish a one-dimensional
+list binding.
+
+Collect clauses may be nested. Variable matches collated into lists in an
+inner collect, are again collated into nested lists in the outer collect.
+Thus an unbound variable wrapped in N nestings of @(collect) will
+be an N-dimensional list. A one dimensional list is a list of strings;
+a two dimensional list is a list of lists of strings, etc.
+
+It is important to note that the variables which are bound within the main
+clause of a collect---i.e. the variables which are subject to
+collection---appear as normal one-value bindings. The collation into lists
+happens outside of the collect. So for instance in the query:
+
+ @(collect)
+ @x=@x
+ @(end)
+
+The left @x establishes a binding for some material preceding an equal sign.
+The right @x refers to that binding. The value of @x is different in each
+iteration, and these values are collected. What finally comes out of the
+collect clause is list variable called x which holds each value that
+was ever instantiated under that name within the collect clause.
+
+If the collect stops before exhausting the data file---that is to say,
+it is terminated by a successful match in the until clause---then
+the material consumed by the until clause is considered consumed.
+The current position in the data set which now faces any further
+query material is located beyond the last line which matches
+the until clause. This is true even if the until clause and collect
+clause both match simultaneously, and the clause matches a different
+number of lines. If this last collect matches a greater number of lines
+than the terminating until, then some of the material covered by this last
+collect will be again matched by query lines which follow the collect
+directive.
+
+.SS The Coll Directive
+
+The coll directive is a kind of miniature version of the collect directive.
+Whereas the collect directive works with multi-line clauses on line-oriented
+material, coll works within a single line. With coll, it is possible to
+recognize repeating regularities within a line and collect lists.
+
+Regular-expression based Positive Match variables work well with coll.
+
+Example: collect a comma-separated list, terminated by a space.
+
+ pattern: @(coll)@{A /[^, ]+/}@(until) @(end)@B
+ data: foo,bar,xyzzy blorch
+ result: A[0]="foo"
+ A[1]="bar"
+ A[2]="xyzzy"
+ B=blorch
+
+Here, the variable A is bound to tokens which match the regular
+expression /[^, ]+/: non-empty sequence of characters other than commas or
+spaces.
+
+Like its big cousin, the coll directive searches for matches. If no match
+occurs at the current character position, it tries at the next character
+position. Whenever a match occurs, it continues at the character position which
+follows the last character of the match, if such a position exists.
+
+If not bounded by an until clause, it will exhaust the entire line. If the
+until clause matches, then the remainder of the data line following the extent
+consumed by the until clause is available for more matching.
+
+Coll clauses nest, and variables bound within a coll are available to within
+the rest of the coll clause, including the until clause, and appear as single
+values. The final list aggregation is only visible after the coll clause.
+
+The behavior of coll is troublesome, when delimited variables are used,
+because in text file formats, the material which separates items is not
+repeated after the last item. For instance, a comma-separated list usually
+not appear as "a,b,c," but rather "a,b,c". There might not be any explicit
+termination---the last item might be at the very end of the line.
+
+So for instance, the following result is not satisfactory:
+
+ pattern: @(coll)@a @(end)
+ data: 1 2 3 4 5
+ result: a[0]="1"
+ a[1]="2"
+ a[2]="3"
+ a[3]="4"
+
+What happened to the 5? After matching "4 ", coll continues to look for
+matches. It tries "5", which does not match, because it is not followed by a
+space. Then the line is consumed. So in this sequence, a valid item is either
+followed by a space, or by nothing. So it is tempting to try this:
+
+ pattern: @(coll)@a@/ ?/@(end)
+ data: 1 2 3 4 5
+ result: a[0]=""
+ a[1]=""
+ a[2]=""
+ a[3]=""
+ a[4]=""
+ a[5]=""
+ a[6]=""
+ a[7]=""
+ a[8]=""
+
+however, the problem is that the regular expression / ?/ (match either a space
+or nothing), matches at any position. So when it is used as a variable
+delimiter, it matches at the current position, which binds the empty string to
+the variable, the extent of the match being zero. In this situation, the coll
+directive proceeds character by character. The solution is to use
+positive matching: specify the regular expression which matches the item,
+rather than a trying to match whatever follows. The collect directive will
+recognize all items which match the regular expression.
+
+ pattern: @(coll)@{a /[^ ]+/}@(end)
+ data: 1 2 3 4 5
+ result: a[0]="1"
+ a[1]="2"
+ a[2]="3"
+ a[3]="4"
+ a[4]="5"
+
+The until clause can specify a pattern which, when recognized, terminates
+the collection. So for instance, suppose that the list of items may
+or may not be terminated by a semicolon. We must exclude
+the semicolon from being a valid character inside an item, and
+add an until clause which recognizes a semicolon:
+
+ pattern: @(coll)@{a /[^ ;]+/}@(until);@(end)
+
+ data: 1 2 3 4 5;
+ result: a[0]="1"
+ a[1]="2"
+ a[2]="3"
+ a[3]="4"
+ a[4]="5"
+
+ data: 1 2 3 4 5
+ result: a[0]="1"
+ a[1]="2"
+ a[2]="3"
+ a[3]="4"
+ a[4]="5"
+
+Semicolon or not, the items are collected properly.
+
+.SS The Flatten Directive.
+
+The flatten directive can be used to convert variables to one dimensional
+lists. Variables which have a scalar value are converted to lists containing
+that value. Variables which are multidimensional lists are flattened to
+one-dimensional lists.
+
+Example (without @(flatten))
+
+ pattern: @b
+ @(collect)
+ @(collect)
+ @a
+ @(end)
+ @(end)
+
+ data: 0
+ 1
+ 2
+ 3
+ 4
+ 5
+
+ result: b="0"
+ a_0[0]="1"
+ a_1[0]="2"
+ a_2[0]="3"
+ a_3[0]="4"
+ a_4[0]="5"
+
+Example (with flatten):
+
+ pattern: @b
+ @(collect)
+ @(collect)
+ @a
+ @(end)
+ @(end)
+ @(flatten a b)
+
+ data: 0
+ 1
+ 2
+ 3
+ 4
+ 5
+
+ result: b[0]="0"
+ a[0]="1"
+ a[1]="2"
+ a[2]="3"
+ a[3]="4"
+ a[4]="5"
+
+
+.SS The Cat Directive
+
+The @(cat) directive converts a list variable into a single
+piece of text. Optionally, a separating piece of text can be inserted
+in between the elements. This piece is written to the right of
+the @(cat) directive, and spans to the end of the line. It may
+contain variable substitutions.
+
+Example:
+
+ pattern: @(coll)@{a /[^ ]+/}@(end)
+ @(cat a):
+ data: 1 2 3 4 5
+ result: a="1:2:3:4:5"
+
+
+.SS The Bind Directive
+
+The @(bind) directive is a kind of pattern match, which matches one or more
+variables on the left hand side to the value of a variable on the right hand
+side. The right hand side variable must have a binding, or else the directive
+fails. Any variables on the left hand side which are unbound receive a matching
+piece of the right hand side value. Any variables on the left which are already
+bound must match their corresponding value, or the bind fails. Any variables
+which are already bound and which do match their corresponding value remain
+unchanged (the match can be inexact).
+
+The simplest bind is of one variable against itself, for instance bind A
+against A:
+
+ @(bind A A)
+
+This will fail if A is not bound, (and complain loudly). If A is bound, it
+succeeds, since A matches A.
+
+The next simplest bind binds one variable to another:
+
+ @(bind A B)
+
+Here, if A is unbound, it takes on the same value as B. If A is bound, it has
+to match B, or the bind fails. Matching means that either
+
+- A and B are the same text
+- A is text, B is a list, and A occurs within B.
+- vice versa: B is text, A is a list, and B occurs within A.
+- A and B are lists and are either identical, or one is
+ found as substructure within the other.
+
+The left hand side of a bind can be a nested list pattern containing variables.
+The last item of a list at any nesting level can be preceded by a dot, which
+means that the variable matches the rest of the list from that position.
+
+Example: suppose that the list A contains ("now" "now" "brown" "cow"). Then the
+directive @(bind (H N . C) A), assuming that H, N and C are unbound variables,
+will bind H to "how", N to "now", and C to the remainder of the list ("brown"
+"cow").
+
+Example: suppose that the list A is nested to two dimensions and contains
+(("how" "now") ("brown" "cow")). Then @(bind ((H N) (B C)) A)
+binds H to "how", N to "now", B to "brown" and C to "cow".
+
+The dot notation may be used at any nesting level. it must be preceded and
+followed by a symbol: the forms (.) (. X) and (X .) are invalid.
+
+.SH BLOCKS
+
+.SS Introduction
+
+Blocks are sections of a query which are denoted by a name. Blocks denoted by
+the name nil are understood as anonymous.
+
+The @(block <name>) directive introduces a named block, except when the name is
+the word nil. The @(block) directive introduces an unnamed block, equivalent
+to @(block nil).
+
+The @(skip) and @(collect) directives introduce implicit anonymous blocks.
+
+.SS Block Scope
+
+The names of blocks are in a distinct namespace from the variable binding
+space. So @(block foo) has no interaction with the variable @foo.
+
+A block extends from the @(block ...) directive which introduces it,
+to the end of the subquery in which that directive is contained. For instance:
+
+ @(some)
+ abc
+ @(block foo)
+ xyz
+ @(end)
+
+Here, the block foo occurs in a @(some) clause, and so it extends to the @(end)
+which terminates that clause. After that @(end), the name foo is not
+associated with a block (is not "in scope"). A block which is not contained in
+any subquery extends to the end of the overall query. Blocks are never
+terminated by @(end).
+
+The implicit anonymous blocks introduced by @(skip) has the same scope
+as the @(skip): it extends over all of the material which follows the skip, to the end of the containing subquery.
+
+The scope of the implicit anonymous block introduced by @(collect) spans only
+that collect coincides with the scope of that collect: from the @(collect)
+to its matching @(end).
+
+.SS Block Nesting
+
+Blocks may nest, and nested blocks may have the same names as blocks in
+which they are nested. For instance:
+
+@(block)
+@(block)
+...
+
+is a nesting of two anonymous blocks, and
+
+@(block foo)
+@(block foo)
+
+is a nesting of two named blocks which happen to have the same name.
+When a nested block has the same name as an outer block, it creates
+a block scope in which the outer block is "shadowed"; that is to say,
+directives which refer to that block name within the nested block refer to the
+inner block, and not to the outer one.
+
+A more complicated example of nesting is:
+
+@(skip)
+abc
+@(block)
+@(some)
+@(block foo)
+@(end)
+
+Here, the @(skip) introduces an anonymous block. The explicit anonymous
+@(block) is nested within skip's anonymous block and shadows it.
+The foo block is nested within both of these.
+
+.SS Block Semantics
+
+A block normally does nothing. The query material in the block is evaluated
+normally. However, a block serves as a termination point for @(fail) and
+@(accept) directives which are in scope of that block and refer to it.
+
+The precise meaning of these directives is:
+
+.IP @(fail <name>)
+
+Immediately terminate the enclosing query block called <name>, as if that block failed to match anything. If more than one block by that name encloses
+the directive, the inner-most block is terminated. No bindings
+emerge from a failed block.
+
+.IP @(fail)
+
+Immediately terminate the innermost enclosing anonymous block, as if
+that block failed to match.
+
+If the implicit block introduced by @(skip) is terminated in this manner,
+this has the effect of causing the skip itself to fail. I.e. the behavior
+is as if skip search did not find a match for the trailing material,
+except that it takes place prematurely (before the end of the available
+data source is reached).
+
+If the implicit block associated with a @(collect) is terminated this way,
+then the entire collect fails. This is a special behavior, because a
+collect normally does not fail, even if it matches and collects nothing!
+
+To prematurely terminate a collect by means of its anonymous block, without
+failing it, use @(accept).
+
+.IP @(accept <name>)
+
+Immediately terminate the enclosing query block called <name>, as if that block
+successfully matched. If more than one block by that name encloses the
+directive, the inner-most block is terminated. Any bindings established within
+that block until this point emerge from that block.
+
+.IP @(accept)
+
+Immediately terminate the innermost enclosing anonymous block, as if
+that block successfully mached. Any bindings established within
+that block until this point emerge from that block.
+
+If the implicit block introduced by @(skip) is terminated in this manner,
+this has the effect of causing the skip itself to succeed, as if
+all of the trailing material succesfully matched.
+
+If the implicit block associated with a @(collect) is terminated this way,
+then the collection stops. All bindings collected in the current iteration of
+the collect are discarded. Bindings collected in previous iterations are
+retained, and collated into lists in accordance with the semantics of collect.
+
+Example: alternative way to @(until) termination:
+
+ @(collect)
+ @ (maybe)
+ ---
+ @ (accept)
+ @ (end)
+ @LINE
+ @(end)
+
+This query will collect entire lines into a list called LINE. However,
+if the line --- is matched (by the embedded @(maybe)), the collection
+is terminated. Only the lines up to, and not including the --- line,
+are collected. The effect is similar to:
+
+ @(collect)
+ @LINE
+ @(until)
+ ---
+ @(end)
+
+However, the following example has a different meaning:
+
+ @(collect)
+ @LINE
+ @ (maybe)
+ ---
+ @ (accept)
+ @ (end)
+ @(end)
+
+Now, lines are collected until the end of the data source, or until a line is
+found which is followed by a --- line. If such a line is found,
+the collection stops, and that line is not included in the collection!
+The @(accept) terminates the process of the collect body, and so the
+action of collecting the last @LINE binding into the list is not performed.
+
+.SS Data Extent of Terminated Blocks
+
+A data block may have matched some material prior to being terminated by
+accept. In that case, it is deemed to have only matched that material,
+and not any material which follows. This may matter, depending on the context
+in which the block occurs.
+
+Example:
+
+ Query: @(some)
+ @(block foo)
+ @first
+ @(accept foo)
+ @ignored
+ @(end)
+ @second
+
+ Data: 1
+ 2
+ 3
+
+ Output: first="1"
+ second="2"
+
+At the point where the accept occurs, the foo block has matched the first line,
+bound the text "1" to the variable @first. The block is then terminated.
+Not only does the @first binding emerge from this terminated block, but
+what also emerges is that the block advanced the data past the first line to
+the second line. So next, the @(some) directive ends, and propagates the
+bindings and position. Thus the @second which follows then matches the second
+line and takes the text "2".
+
+In the following query, the foo block occurs inside a maybe clause.
+Inside the foo block there is a @(some) clause. Its first subclause
+matches variable @first and then terminates block foo. Since block foo is
+outside of the @(some) directive, this has the effect of terminating the
+@(some) clause:
+
+ Query: @(maybe)
+ @(block foo)
+ @ (some)
+ @first
+ @ (accept foo)
+ @ (or)
+ @one
+ @two
+ @three
+ @four
+ @ (end)
+ @(end)
+ @second
+
+ Data: 1
+ 2
+ 3
+ 4
+ 5
+
+ Output: first="1"
+ second="2"
+
+The second clause of the @(some) directive, namely:
+
+ @one
+ @two
+ @three
+ @four
+
+is never processed. The reason is that subclauses are processed in top
+to bottom order, but the processing was aborted within the
+first clause the @(accept foo). The @(some) construct never had the
+opportunity to match four lines.
+
+If the @(accept foo) line is removed from the above query, the output
+is different:
+
+ Query: @(maybe)
+ @(block foo)
+ @ (some)
+ @first
+ @# <-- @(accept foo) removed from here!!!
+ @ (or)
+ @one
+ @two
+ @three
+ @four
+ @ (end)
+ @(end)
+ @second
+
+ Data: 1
+ 2
+ 3
+ 4
+ 5
+
+ Output: first="1"
+ one="1"
+ two="2"
+ three="3"
+ four="4"
+ second="5"
+
+Now, all clauses of the @(some) directive have the opportunity to match.
+The second clause grabs four lines, which is the longest match.
+And so, the next line of input available for matching is 5, which goes
+to the @second variable.
+
+.SH OUTPUT
+
+A
+.B txr
+query may perform custom output. Output is performed by @(output) clauses,
+which may be embedded anywhere in the query, or placed at the end. Output
+occurs as a side effect of producing a part of a query which contains an
+@(output) directive, and is executed even if that part of the query ultimately
+fails to find a match. Thus output can be useful for debugging.
+An output clause specifies that its output goes to a file, pipe, or (by
+default) standard output. If any output clause is executed whose destination is
+standard output,
+.B txr
+makes a note of this, and later, just prior to termination, suppresses the
+usual printing of the variable bindings or the word false.
+
+.SS The Output Directive
+
+The syntax of the @(output) directive is:
+
+ @(output)...optional destination...
+ .
+ . one or more output directives or lines
+ .
+ @(end)
+
+The optional destination is a filename, the special name, - which
+redirects to standard output, or a shell command preceded by the ! symbol.
+Variables are substituted in the directive.
+
+.SS Output Text
+
+Text in an output clause is not matched against anything, but is output
+verbatim to the destination file, device or command pipe.
+
+.SS Output Variables
+
+Variables occurring in an output clause do not match anything, but instead their
+contents are output. A variable being output must be a simple string, not a
+list. Lists may be output within @(repeat) or @(rep) clauses. A list variable
+must be wrapped in as many nestings of these clauses as it has dimensions. For
+instance, a two-dimensional list may be mentioned in output if it is inside a
+@(rep) or @(repeat) clause which is itself wrapped inside another @(rep) or
+@(repeat) clause.
+
+In an output clause, the @{NAME NUMBER} variable syntax generates fixed-width
+field, which contains the variable's text. The absolute value of the
+number specifies the field width. For instance -20 and 20 both specify a field
+width of twenty. If the text is longer than the field, then it overflows the
+field. If the text is shorter than the field, then it is left-adjusted within
+that field, if the width is specified as a positive number, and right-adjusted
+if the width is specified as negative.
+
+.SS The Repeat Directive
+
+The repeat directive is generates repeated text from a ``boilerplate'',
+by taking successive elements from lists. The syntax of repeat is
+like this:
+
+ @(repeat)
+ .
+ .
+ main clause material, required
+ .
+ .
+ special clauses, optional
+ .
+ .
+ @(end)
+
+Repeat has four types of special clauses, any of which may be
+specified with empty contents, or omitted entirely. They are explained
+below.
+
+All of the material in the main clause and optional clauses
+is examined for the presence of variables. If none of the variables
+hold lists which contain at least one item, then no output is performed,
+(unless the repeat specifies an @(empty) clause, see below).
+Otherwise, among those variables which contain non-empty lists, repeat finds
+the length of the longest list. This length of this list determines the number
+of repetitions, R.
+
+If the repeat contains only a main clause, then the lines of this clause is
+output R times. Over the first repetition, all of the variables which, outside
+of the repeat, contain lists are locally rebound to just their first item. Over
+the second repetition, all of the list variables are bound to their second
+item, and so forth. Any variables which hold shorter lists than the longest
+list eventually end up with empty values over some repetitions.
+
+Example: if the list A holds "1", "2" and "3"; the list B holds "A", "B";
+and the variable C holds "X", then
+
+ @(repeat)
+ >> @C
+ >> @A @B
+ @(end)
+
+will produce three repetitions (since there are two lists, the longest
+of which has three items). The output is:
+
+ >> X
+ >> 1 A
+ >> X
+ >> 2 B
+ >> X
+ >> 3
+
+The last line has a trailing space, since it is produced by "@A @B",
+where @B has an empty value. Since C is not a list variable, it
+produces the same value in each repetition.
+
+The special clauses are:
+
+.IP @(single)
+If the repeat produces exactly one repetition, then the contents of this clause
+are processed for that one and only repetition, instead of the main clause
+or any other clause which would otherwise be processed.
+
+.IP @(first)
+The body of this clause specifies an alternative body to be used for the first
+repetition, instead of the material from the main clause.
+
+.IP @(last)
+The body of this clause is used instead of the main clause for the last
+repetition.
+
+.IP @(empty)
+If the repeat produces no repetitions, then the body of this clause is output.
+If this clause is absent or empty, the repeat produces no output.
+
+.PP
+The precedence among the clauses which take an iteration is:
+single > first > last > main. That is if two or more of these clauses
+can apply to a repetition, then the leftmost one in this precedence list
+applies. For instance, if there is just a single repetition, then any of these
+special clause types can apply to that repetition, since it is the only
+repetition, as well as the first and last one. In this situation, if
+there is a single clause present, then the repetition is processed
+using that clause. Otherwise, if there is a first clause present, that
+clause is used. Failing that, a last clause applies. Only if none of these
+clauses are present will the repetition be processed using the main clause.
+
+.SS Nested Repeats
+
+If a repeat clause encloses variables which holds multidimensional lists,
+those lists require additional nesting levels of repeat (or rep).
+It is an error to attempt to output a list variable which has not been
+decimated into primary elements via a repeat construct.
+
+Suppose that a variable X is two-dimensional (contains a list of lists). X
+must be twice nested in a repeat. The outer repeat will walk over the lists
+contained in X. The inner repeat will walk over the elements of each of these
+lists.
+
+A nested repeat may be embedded in any of the clauses of a repeat,
+not only the main clause.
+
+.SS The Rep Directive
+
+The @(rep) directive is similar to @(repeat), but whereas @(repeat) is line
+oriented, @(rep) generates material within a line. It has all the same clauses,
+but everything is specified within one line:
+
+ @(rep)... main material ... .... special clauses ...@(end)
+
+More than one @(rep) can occur within a line, mixed with other material.
+A @(rep) can be nested within a @(repeat) or within another @(rep).
+
+.SS Repeat and Rep Examples
+
+Example 1: show the list L in parentheses, with spaces between
+the elements, or the symbol NIL if the list is empty:
+
+ @(output)
+ @(rep)@L @(single)(@L)@(first)(@L @(last)@L)@(empty)NIL@(end)
+ @(end)
+
+Here, the @(empty) clause specifies NIL. So if there are no repetitions,
+the text NIL is produced. If there is a single item in the list L,
+then @(single)(@L) produces that item between parentheses. Otherwise
+if there are two or more items, the first item is produced with
+a leading parenthesis followed by a space by @(first)(@L , and
+the last item is produced with a closing parenthesis: @(last)@L).
+All items in between are emitted with a trailing space by
+the main clause: @(rep)@L .
+
+Example 2: show the list L like Example 1 above, but the empty list is ().
+
+ @(output)
+ (@(rep)@L @(last)@L@(end))
+ @(end)
+
+This is simpler. The parentheses are part of the text which
+surrounds the @(rep) construct, produced unconditionally.
+If the list L is empty, then @(rep) produces no output, resulting in ().
+If the list L has one or more items, then they are produced with
+spaces each one, except the last which has no space.
+If the list has exactly one item, then the @(last) applies to it
+instead of the main clause: it is produced with no trailing space.
+
+.SH NOTES ON FALSE
+
+The reason for printing the word
+.IR false
+on standard output when
+a query doesn't match, in addition to returning a failed termination
+status, is that the output of
+.B txr
+may be collected by a shell script, by the application of eval to command
+substitution syntax. Printing
+.IR false
+will cause eval to evaluate the
+.IR false
+command, and thus failed status will propagate from the eval
+itself. The eval command conceals the termination status of a
+program run via command substitution. That is to say, if a program
+fails, without producing output, its output is substituted into the eval
+command which then succeeds, masking the failure of the program. For example:
+
+ eval "$(false)"
+
+appears successful: the false utility indicates a failed status, but
+produces no output. Eval evaluates an empty script and reports success;
+the failed status of the false program is forgotten.
+Note the difference between the above and this:
+
+ eval "$(echo false)"
+
+This command has a failed status. The echo prints the word false and succeeds;
+this false word is then evaluated as a script, and thus interpreted as the
+false command which fails. This failure
+.B is
+propagated as the result of the eval
+command.
diff --git a/unwind.c b/unwind.c
new file mode 100644
index 00000000..c573c16d
--- /dev/null
+++ b/unwind.c
@@ -0,0 +1,88 @@
+/* Copyright 2009
+ * Kaz Kylheku <kkylheku@gmail.com>
+ * Vancouver, Canada
+ * All rights reserved.
+ *
+ * BSD License:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <setjmp.h>
+#include <dirent.h>
+#include "lib.h"
+#include "unwind.h"
+
+static uw_frame_t *uw_stack;
+static uw_frame_t *uw_exit_point;
+
+static void uw_unwind_to_exit_point()
+{
+ while (uw_stack && uw_stack != uw_exit_point)
+ uw_stack = uw_stack->uw.up;
+
+ if (!uw_stack)
+ abort();
+
+ uw_exit_point = 0;
+
+ switch (uw_stack->uw.type) {
+ case UW_BLOCK:
+ longjmp(uw_stack->bl.jb, 1);
+ break;
+ }
+
+ abort();
+}
+
+void uw_push_block(uw_frame_t *fr, obj_t *tag)
+{
+ fr->bl.type = UW_BLOCK;
+ fr->bl.tag = tag;
+ fr->bl.result = nil;
+ fr->bl.up = uw_stack;
+ uw_stack = fr;
+}
+
+void uw_pop_frame(uw_frame_t *fr)
+{
+ assert (fr == uw_stack);
+ uw_stack = uw_stack->uw.up;
+}
+
+obj_t *uw_block_return(obj_t *tag, obj_t *result)
+{
+ uw_frame_t *ex;
+
+ for (ex = uw_stack; ex != 0; ex = ex->uw.up) {
+ if (ex->uw.type == UW_BLOCK && ex->bl.tag == tag)
+ break;
+ }
+
+ if (ex == 0)
+ return nil;
+
+ ex->bl.result = result;
+ uw_exit_point = ex;
+ uw_unwind_to_exit_point();
+ abort();
+}
diff --git a/unwind.h b/unwind.h
new file mode 100644
index 00000000..8863fdff
--- /dev/null
+++ b/unwind.h
@@ -0,0 +1,66 @@
+/* Copyright 2009
+ * Kaz Kylheku <kkylheku@gmail.com>
+ * Vancouver, Canada
+ * All rights reserved.
+ *
+ * BSD License:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+typedef union uw_frame uw_frame_t;
+typedef enum uw_frtype uw_frtype_t;
+
+enum uw_frtype { UW_BLOCK };
+
+struct uw_common {
+ uw_frame_t *up;
+ uw_frtype_t type;
+};
+
+struct uw_block {
+ uw_frame_t *up;
+ uw_frtype_t type;
+ obj_t *tag;
+ obj_t *result;
+ jmp_buf jb;
+};
+
+union uw_frame {
+ struct uw_common uw;
+ struct uw_block bl;
+};
+
+void uw_push_block(uw_frame_t *, obj_t *tag);
+obj_t *uw_block_return(obj_t *tag, obj_t *result);
+void uw_pop_frame(uw_frame_t *);
+
+#define uw_block_begin(TAG, RESULTVAR) \
+ obj_t *RESULTVAR = nil; \
+ { \
+ uw_frame_t uw_fr; \
+ uw_push_block(&uw_fr, TAG); \
+ if (setjmp(uw_fr.bl.jb)) { \
+ RESULTVAR = uw_fr.bl.result; \
+ } else {
+
+#define uw_block_end \
+ } \
+ uw_pop_frame(&uw_fr); \
+ }