diff options
-rw-r--r-- | ChangeLog | 28 | ||||
-rw-r--r-- | filter.c | 19 | ||||
-rw-r--r-- | filter.h | 1 | ||||
-rw-r--r-- | lib.c | 49 | ||||
-rw-r--r-- | lib.h | 9 | ||||
-rw-r--r-- | match.c | 42 | ||||
-rw-r--r-- | txr.1 | 77 |
7 files changed, 210 insertions, 15 deletions
@@ -1,5 +1,33 @@ 2011-09-26 Kaz Kylheku <kaz@kylheku.com> + New feature: @(deffilter) + + Bugfix in @(throw) when non-symbol is thrown: exception message + referred to the symbol throw rather than the erroneous object. + + * filter.c (build_filter_from_list, register_filter): New functions. + + * filter.h (register_filter): New function declared. + + * lib.c (deffilter_s): New variable defined. + (chain): Function changed from single list argument to variable + argument list to reduce the complexity of use. + (do_and, and): New functions. + (obj_init): deffilter_s initializatio added. + + * lib.h (deffilter_s, and): New declarations. + (chain): Declaration updated to new function signature. + (eq): Changed from macro to inline function. + + * match.c (do_output_line): Simplified expression involving chain. + (do_output): Likewise. + (match_files): Bugfix in error handling of throw. + Implementation of deffilter. + + * txr.1: Documented deffilter. + +2011-09-26 Kaz Kylheku <kaz@kylheku.com> + Trie compression. Hash table iteration. Bugfix in typeof. @@ -136,6 +136,20 @@ static val build_filter(struct filter_pair *pair) return trie; } +static val build_filter_from_list(val list) +{ + val trie = make_trie(); + val iter; + + for (iter = list; iter; iter = cdr(iter)) { + val pair = car(iter); + trie_add(trie, first(pair), second(pair)); + } + + trie_compress(&trie); + return trie; +} + static val trie_filter_string(val filter, val str) { val len = length_str(str); @@ -190,6 +204,11 @@ val filter_string(val filter, val str) uw_throwf(error_s, lit("filter_string: invalid filter ~a"), filter, nao); } +val register_filter(val sym, val table) +{ + return sethash(filters, sym, build_filter_from_list(table)); +} + static struct filter_pair to_html_table[] = { { L"<", L"<" }, { L">", L">" }, @@ -32,5 +32,6 @@ val trie_value_at(val node); val trie_lookup_feed_char(val node, val ch); val get_filter_trie(val sym); val filter_string(val trie, val str); +val register_filter(val sym, val table); void filter_init(void); @@ -60,7 +60,7 @@ val all_s, some_s, none_s, maybe_s, cases_s, collect_s, until_s, coll_s; val define_s, output_s, single_s, first_s, last_s, empty_s; val repeat_s, rep_s, flatten_s, forget_s; val local_s, merge_s, bind_s, cat_s; -val try_s, catch_s, finally_s, throw_s, defex_s; +val try_s, catch_s, finally_s, throw_s, defex_s, deffilter_s; val error_s, type_error_s, internal_error_s; val numeric_error_s, range_error_s; val query_error_s, file_error_s, process_error_s; @@ -1483,9 +1483,51 @@ static val do_chain(val fun1_list, val arg) return arg; } -val chain(val fun1_list) +val chain(val first_fun, ...) { - return func_f1(fun1_list, do_chain); + va_list vl; + list_collect_decl (out, iter); + + if (first_fun != nao) { + val next_fun; + va_start (vl, first_fun); + list_collect (iter, first_fun); + + while ((next_fun = va_arg(vl, val)) != nao) + list_collect (iter, next_fun); + + va_end (vl); + } + + return func_f1(out, do_chain); +} + +static val do_and(val fun1_list, val arg) +{ + for (; fun1_list; fun1_list = cdr(fun1_list)) + if (nullp(funcall1(car(fun1_list), arg))) + return nil; + + return t; +} + +val and(val first_fun, ...) +{ + va_list vl; + list_collect_decl (out, iter); + + if (first_fun != nao) { + val next_fun; + va_start (vl, first_fun); + list_collect (iter, first_fun); + + while ((next_fun = va_arg(vl, val)) != nao) + list_collect (iter, next_fun); + + va_end (vl); + } + + return func_f1(out, do_and); } val vector(val alloc) @@ -2085,6 +2127,7 @@ static void obj_init(void) finally_s = intern(lit("finally"), user_package); throw_s = intern(lit("throw"), user_package); defex_s = intern(lit("defex"), user_package); + deffilter_s = intern(lit("deffilter"), user_package); error_s = intern(lit("error"), user_package); type_error_s = intern(lit("type_error"), user_package); internal_error_s = intern(lit("internal_error"), user_package); @@ -222,7 +222,7 @@ extern val all_s, some_s, none_s, maybe_s, cases_s, collect_s, until_s, coll_s; extern val define_s, output_s, single_s, first_s, last_s, empty_s; extern val repeat_s, rep_s, flatten_s, forget_s; extern val local_s, merge_s, bind_s, cat_s; -extern val try_s, catch_s, finally_s, throw_s, defex_s; +extern val try_s, catch_s, finally_s, throw_s, defex_s, deffilter_s; extern val error_s, type_error_s, internal_error_s; extern val numeric_error_s, range_error_s; extern val query_error_s, file_error_s, process_error_s; @@ -350,7 +350,8 @@ val funcall2(val fun, val arg1, val arg2); val reduce_left(val fun, val list, val init, val key); val bind2(val fun2, val arg); val bind2other(val fun2, val arg2); -val chain(val fun1_list); +val chain(val first_fun, ...); +val and(val first_fun, ...); val vector(val alloc); val vec_get_fill(val vec); val vec_set_fill(val vec, val fill); @@ -392,9 +393,9 @@ val match(val spec, val data); #define nil ((obj_t *) 0) -#define nao ((obj_t *) (1 << TAG_SHIFT)) /* "not an object" sentinel value. */ +INLINE val eq(val a, val b) { return ((a) == (b) ? t : nil); } -#define eq(a, b) ((a) == (b) ? t : nil) +#define nao ((obj_t *) (1 << TAG_SHIFT)) /* "not an object" sentinel value. */ #define if2(a, b) ((a) ? (b) : nil) @@ -805,9 +805,9 @@ static void do_output_line(val bindings, val specline, val bind_cp = extract_bindings(bindings, elem); val max_depth = reduce_left(func_n2(max2), bind_cp, zero, - chain(list(func_n1(cdr), - func_n1(robust_length), - nao))); + chain(func_n1(cdr), + func_n1(robust_length), + nao)); if (equal(max_depth, zero) && empty_clauses) { do_output_line(bindings, empty_clauses, spec_lineno, filter, out); @@ -871,9 +871,9 @@ static void do_output(val bindings, val specs, val filter, val out) val bind_cp = extract_bindings(bindings, first_elem); val max_depth = reduce_left(func_n2(max2), bind_cp, zero, - chain(list(func_n1(cdr), - func_n1(robust_length), - nao))); + chain(func_n1(cdr), + func_n1(robust_length), + nao)); if (equal(max_depth, zero) && empty_clauses) { do_output(bind_cp, empty_clauses, filter, out); @@ -1672,12 +1672,40 @@ repeat_spec_same_data: val args = rest(rest(first_spec)); if (!symbolp(type)) sem_error(spec_linenum, lit("throw: ~a is not a type symbol"), - first(first_spec), nao); + type, nao); { val values = mapcar(bind2other(func_n2(eval_form), bindings), args); uw_throw(type, values); } + } else if (sym == deffilter_s) { + val sym = second(first_spec); + val table = rest(rest(first_spec)); + + if (!symbolp(sym)) + sem_error(spec_linenum, lit("deffilter: ~a is not a symbol"), + first(first_spec), nao); + + if (!all_satisfy(table, and(func_n1(listp), + chain(func_n1(length), + bind2(func_n2(eq), two), + nao), + chain(func_n1(first), + func_n1(stringp), + nao), + chain(func_n1(second), + func_n1(stringp), + nao), + nao), + nil)) + sem_error(spec_linenum, + lit("deffilter arguments must be string pairs"), + nao); + register_filter(sym, table); + /* TODO: warn about replaced filter. */ + if ((spec = rest(spec)) == nil) + break; + goto repeat_spec_same_data; } else { val func = uw_get_func(sym); @@ -973,6 +973,13 @@ 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. +.IP @(deffilter) +This directive is used for defining named filters, which are useful +for filtering variable substitutions in output blocks. Filters are useful +when data must be translated between different representations that +have different special characters or other syntax, requiring escaping +or similar treatment. + .PP .SS The Next Directive @@ -2484,7 +2491,8 @@ This is what filtering is for. Filtering is applied to the contents of output variables, not to any template text. .B txr implements named filters. Currently, the only built-in filters available are -:to_html and :from_html. User-defined filters are not possible. +:to_html and :from_html. User-defined filters are possible, however. +See notes on the deffilter directive below. To escape HTML characters in all variable substitutions occuring in an output clause, specify :filter :to_html in the directive: @@ -2499,6 +2507,73 @@ To filter an individual variable, add the syntax to the variable spec: @{x :filter :to_html} @(end) +.SS The Deffilter Directive + +The deffilter directive allows a query to define a custom filter, which +can then be used in @(output) clauses to transform substituted data. + +This directive's syntax is illustrated in this example: + + Query: @(deffilter rot13 + ("a" "n") + ("b" "o") + ("c" "p") + ("d" "q") + ("e" "r") + ("f" "s") + ("g" "t") + ("h" "u") + ("i" "v") + ("j" "w") + ("k" "x") + ("l" "y") + ("m" "z") + ("n" "a") + ("o" "b") + ("p" "c") + ("q" "d") + ("r" "e") + ("s" "f") + ("t" "g") + ("u" "h") + ("v" "i") + ("w" "j") + ("x" "k") + ("y" "l") + ("z" "m")) + @(collect) + @line + @(end) + @(output :filter rot13) + @(repeat) + @line + @(end) + @(end) + + Input: hey there! + + Output: url gurer! + + +The deffilter symbol must be followed by the name of the filter to be defined, +followed by pairs of strings. Each pair specifies a piece of text to be +filtered from the left hand side to the right hand side. + +Filtering works using a longest match algorithm. The input is scanned from left +to right, and the longest piece of text is identified at every character +position which matches a string on the left hand side, and that text is +replaced with the right hand string. + +If none of the strings matches at a given character position, then that +character is passed through untranslated, and the scan continues at the next +character in the input. + +If a filter definition accidentally +contains two or more repetitions of the same left hand string with different +right hand translations, the later ones take precedence. No warning is issued. + + + .SH EXCEPTIONS |