From 6e0f574d6a3d98531d1a7eb10a7e00bb335df4e4 Mon Sep 17 00:00:00 2001 From: Kaz Kylheku Date: Thu, 5 Aug 2021 06:52:25 -0700 Subject: txr: @(eof) takes argument for binding termination status. We extend the matching context structures to keep track of the underlying stream from which lines are being taken via the lazy list. Then the implementation of the @(eof) directive, when it hits the eof condition, can use this stream to gain access to the termination status. * match.c (match_line_ctx, match_files_ctx): New member, stream. (ml_all): Take stream argument and initialize new member. (h_call, do_match_line): Pass stream argument to h_call. (mf_all, mf_file_data): Take stream argument and initialize new member. (mf_from_ml): Propagate stream from line context to file context. (freeform_prepare, v_next_impl, match_filter, match_fun, extract): Pass stream argument where now needed. (v_eof): Implement termination status binding via the stream stored in the context. (open_data_source): Store stream in match files context. * tests/010/eof-status.txr: New file. * tests/010/eof-status.expected: New file. * Makefile (tst/tests/010/eof-status.ok): -B option for new test. * txr.1: Documented eof directive, argument and all. * stdlib/doc-syms.tl: Updated. --- Makefile | 1 + match.c | 69 ++++++++++++++++++++++++++++++------------- stdlib/doc-syms.tl | 1 + tests/010/eof-status.expected | 2 ++ tests/010/eof-status.txr | 3 ++ txr.1 | 60 ++++++++++++++++++++++++++++++++++++- 6 files changed, 114 insertions(+), 22 deletions(-) create mode 100644 tests/010/eof-status.expected create mode 100644 tests/010/eof-status.txr diff --git a/Makefile b/Makefile index cac9b3c0..b3084a23 100644 --- a/Makefile +++ b/Makefile @@ -418,6 +418,7 @@ tst/tests/009/json.ok: TXR_ARGS := $(addprefix tests/009/,webapp.json pass1.json tst/tests/010/align-columns.ok: TXR_ARGS := tests/010/align-columns.dat tst/tests/010/block.ok: TXR_OPTS := -B tst/tests/010/reghash.ok: TXR_OPTS := -B +tst/tests/010/eof-status.ok: TXR_OPTS := -B tst/tests/013/maze.ok: TXR_ARGS := 20 20 tst/tests/018/chmod.ok: TXR_ARGS := tst/tests/018/tempfile diff --git a/match.c b/match.c index f80aa548..f0ed32f4 100644 --- a/match.c +++ b/match.c @@ -454,11 +454,11 @@ static val vars_to_bindings(val spec, val vars, val bindings) } typedef struct { - val bindings, specline, dataline, base, pos, data, data_lineno, file; + val bindings, specline, dataline, base, pos, data, data_lineno, file, stream; } match_line_ctx; static match_line_ctx ml_all(val bindings, val specline, val dataline, val pos, - val data, val data_lineno, val file) + val data, val data_lineno, val file, val stream) { match_line_ctx c; c.bindings = bindings; @@ -469,6 +469,7 @@ static match_line_ctx ml_all(val bindings, val specline, val dataline, val pos, c.data = data; c.data_lineno = data_lineno; c.file = file; + c.stream = stream; return c; } @@ -1485,11 +1486,11 @@ static val h_chr(match_line_ctx *c) } typedef struct { - val spec, files, curfile, bindings, data, data_lineno; + val spec, files, curfile, stream, bindings, data, data_lineno; } match_files_ctx; static match_files_ctx mf_all(val spec, val files, val bindings, val data, - val curfile); + val curfile, val stream); static val v_fun(match_files_ctx *c); @@ -1513,7 +1514,7 @@ static val h_call(match_line_ctx *c) if (ret == decline_k) { val spec = cons(new_specline, nil); - match_files_ctx vc = mf_all(spec, nil, c->bindings, nil, c->file); + match_files_ctx vc = mf_all(spec, nil, c->bindings, nil, c->file, c->stream); val vresult = v_fun(&vc); if (vresult == next_spec_k) { @@ -1585,7 +1586,7 @@ static val do_match_line(match_line_ctx *c) } else if (result == decline_k) { val spec = rlcp(cons(cons(elem, nil), nil), elem); match_files_ctx vc = mf_all(spec, nil, c->bindings, - nil, c->file); + nil, c->file, c->stream); val vresult = v_fun(&vc); if (vresult == next_spec_k) { @@ -2233,12 +2234,13 @@ void do_output(val bindings, val specs, val filter, val out) } static match_files_ctx mf_all(val spec, val files, val bindings, - val data, val curfile) + val data, val curfile, val stream) { match_files_ctx c; c.spec = spec; c.files = files; c.curfile = curfile; + c.stream = stream; c.bindings = bindings; c.data = data; c.data_lineno = if3(data, one, zero); @@ -2279,11 +2281,12 @@ static match_files_ctx mf_spec_bindings(match_files_ctx c, val spec, } static match_files_ctx mf_file_data(match_files_ctx c, val file, - val data, val data_lineno) + val stream, val data, val data_lineno) { match_files_ctx nc = c; nc.files = cons(file, c.files); nc.curfile = file; + nc.stream = stream; nc.data = data; nc.data_lineno = data_lineno; return nc; @@ -2296,6 +2299,7 @@ static match_files_ctx mf_from_ml(match_line_ctx ml) mf.spec = cons(ml.specline, nil); mf.files = nil; mf.curfile = ml.file; + mf.stream = ml.stream; mf.bindings = ml.bindings; mf.data = nil; mf.data_lineno = ml.data_lineno; @@ -2556,7 +2560,7 @@ val freeform_prepare(val vals, match_files_ctx *c, match_line_ctx *mlc) if2(stringp(second(vals)), second(vals))); val dataline = lazy_str(c->data, term, limit); *mlc = ml_all(c->bindings, first_spec, dataline, zero, - c->data, c->data_lineno, c->curfile); + c->data, c->data_lineno, c->curfile, c->stream); return limit; } @@ -2684,7 +2688,7 @@ static val v_next_impl(match_files_ctx *c) sem_error(specline, lit("(next :env) takes no additional arguments"), nao); } else { cons_bind (new_bindings, success, - match_files(mf_file_data(*c, lit("env"), env(), one))); + match_files(mf_file_data(*c, lit("env"), nil, env(), one))); if (success) return cons(new_bindings, @@ -2697,7 +2701,8 @@ static val v_next_impl(match_files_ctx *c) meta = t; } else if (!source) { cons_bind (new_bindings, success, - match_files(mf_all(c->spec, nil, c->bindings, nil, lit("empty")))); + match_files(mf_all(c->spec, nil, c->bindings, nil, + lit("empty"), nil))); if (success) return cons(new_bindings, @@ -2757,7 +2762,7 @@ static val v_next_impl(match_files_ctx *c) { cons_bind (new_bindings, success, - match_files(mf_file_data(*c, lit("var"), + match_files(mf_file_data(*c, lit("var"), nil, lazy_flatten(cdr(existing)), one))); if (success) @@ -2775,7 +2780,7 @@ static val v_next_impl(match_files_ctx *c) { cons_bind (new_bindings, success, - match_files(mf_file_data(*c, lit("var"), + match_files(mf_file_data(*c, lit("var"), nil, lazy_flatten(list_val), one))); if (success) @@ -2786,7 +2791,7 @@ static val v_next_impl(match_files_ctx *c) } else if (tlist_p) { val list_val = txeval(specline, tlist_expr, c->bindings); cons_bind (new_bindings, success, - match_files(mf_file_data(*c, lit("var"), + match_files(mf_file_data(*c, lit("var"), nil, lazy_flatten(list_val), one))); if (success) @@ -2804,7 +2809,7 @@ static val v_next_impl(match_files_ctx *c) { cons_bind (new_bindings, success, - match_files(mf_file_data(*c, lit("var"), + match_files(mf_file_data(*c, lit("var"), nil, split_str(str_val, lit("\n")), one))); if (success) @@ -2837,7 +2842,7 @@ static val v_next_impl(match_files_ctx *c) if (stream) { cons_bind (new_bindings, success, - match_files(mf_file_data(*c, str, + match_files(mf_file_data(*c, str, stream, lazy_stream_cons(stream), one))); if (success) @@ -4159,7 +4164,26 @@ static val v_eof(match_files_ctx *c) if (c->data && car(c->data)) { debuglf(c->spec, lit("eof failed to match at ~d"), c->data_lineno, nao); return nil; + } else { + spec_bind (specline, first_spec, c->spec); + val args = rest(first_spec); + + if (rest(args)) + sem_error(specline, lit("eof directive takes takes at most one argument"), nao); + + if (args) { + val pat = car(args); + val close_status = if3(streamp(c->stream), close_stream(c->stream, t), t); + + c->bindings = dest_bind(specline, c->bindings, pat, close_status, eql_f); + + if (c->bindings == t) { + debuglf(specline, lit("line mismatch (line ~d vs. ~s)"), c->data_lineno, pat, nao); + return nil; + } + } } + return next_spec_k; } @@ -4602,6 +4626,7 @@ static void open_data_source(match_files_ctx *c) c->files = cons(name, cdr(c->files)); /* Get rid of cons and nothrow */ c->curfile = source_spec; + c->stream = stream; if ((c->data = lazy_stream_cons(stream)) != nil) c->data_lineno = one; @@ -4685,7 +4710,8 @@ repeat_spec_same_data: cons_bind (new_bindings, success, match_line_completely(ml_all(c.bindings, specline, dataline, zero, - c.data, c.data_lineno, c.curfile))); + c.data, c.data_lineno, + c.curfile, c.stream))); if (!success) return nil; @@ -4712,7 +4738,7 @@ val match_filter(val name, val arg, val other_args) val spec = cons(list(cons(name, cons(in_arg_sym, cons(out_arg_sym, other_args))), nao), nil); - match_files_ctx c = mf_all(spec, nil, bindings, nil, nil); + match_files_ctx c = mf_all(spec, nil, bindings, nil, nil, nil); val ret = v_fun(&c); (void) first_spec; @@ -4750,7 +4776,8 @@ val match_fun(val name, val args, val input_in, val files_in) lazy_stream_cons(input), input); /* TODO: pass through source location context */ - match_files_ctx c = mf_all(spec, files, in_bindings, data, curfile); + match_files_ctx c = mf_all(spec, files, in_bindings, data, + curfile, if2(streamp(input), input)); val ret; ret = v_fun(&c); @@ -4767,14 +4794,14 @@ val match_fun(val name, val args, val input_in, val files_in) val include(val specline) { val spec = cons(specline, nil); - match_files_ctx c = mf_all(spec, nil, nil, nil, nil); + match_files_ctx c = mf_all(spec, nil, nil, nil, nil, nil); return v_load(&c); } val extract(val spec, val files, val predefined_bindings) { val result = match_files(mf_all(spec, files, predefined_bindings, - t, nil)); + t, nil, nil)); cons_bind (bindings, success, result); if (opt_print_bindings) { diff --git a/stdlib/doc-syms.tl b/stdlib/doc-syms.tl index b01264f2..c586cc62 100644 --- a/stdlib/doc-syms.tl +++ b/stdlib/doc-syms.tl @@ -618,6 +618,7 @@ ("env-vbind" "N-03389BE3") ("env-vbindings" "N-0018DCDC") ("enxio" "N-036B1BDB") + ("eof" "N-0336501B") ("eopnotsupp" "N-036B1BDB") ("eoverflow" "N-036B1BDB") ("eownerdead" "N-036B1BDB") diff --git a/tests/010/eof-status.expected b/tests/010/eof-status.expected new file mode 100644 index 00000000..2b636133 --- /dev/null +++ b/tests/010/eof-status.expected @@ -0,0 +1,2 @@ +a="a" +status="5" diff --git a/tests/010/eof-status.txr b/tests/010/eof-status.txr new file mode 100644 index 00000000..0da9c633 --- /dev/null +++ b/tests/010/eof-status.txr @@ -0,0 +1,3 @@ +@(next (open-command "echo a; exit 5")) +@a +@(eof status) diff --git a/txr.1 b/txr.1 index b4fe9a7f..13e127ee 100644 --- a/txr.1 +++ b/txr.1 @@ -3582,7 +3582,7 @@ A summary of the available directives follows: .coIP @(eof) Explicitly match the end of file. Fails if unmatched data remains in -the input stream. +the input stream. Can capture or match the termination status of a pipe. .coIP @(eol) Explicitly match the end of line. Fails if the current position is not the @@ -5008,6 +5008,64 @@ lines: @(do (tprint remainder)) .brev +.dir eof + +The +.code eof +directive, if not given any argument, matches successfully when no more input +is available from the current input source. + +In the following example, the +.meta line +variable captures the text +.str "One-line file" +and then since that is the last line of input, the +.code eof +directive matches: + +.IP code: +.mono +\ @line + @(eof) +.onom + +.IP data: +.mono +\ One-line file +.onom +.PP + +If the data consisted of two or more lines, +.code eof +would fail. + +The +.code eof +directive may be given a single argument, which is a pattern that matches the +termination status of the input source. This is useful when the input source +is a process pipe. For the purposes of +.codn eof , +sources which are not process pipes have the symbol +.code t +as their termination status. + +In the following example, which assumes the availability of a POSIX shell +command interpreter in the host system, the variable +.meta a +captures the string +.str a +and the +.meta status +variable captures the integer value +.codn 5 , +which is the termination status of the command: + +.verb + @(next (open-command "echo a; exit 5")) + @a + @(eof status) +.brev + .dirs some all none maybe cases choose These directives, called the parallel directives, combine multiple subqueries, -- cgit v1.2.3