diff options
author | Kaz Kylheku <kaz@kylheku.com> | 2012-01-25 09:59:40 -0800 |
---|---|---|
committer | Kaz Kylheku <kaz@kylheku.com> | 2012-01-25 09:59:40 -0800 |
commit | 11173ced6d65339869fe74fbc9c4452a75e3fe26 (patch) | |
tree | 6c7e21fa0f7ebab6d5b4af9dc960fd96660682be | |
parent | 8b4578f295cc022e8bf0bb62d1a8cf8673636f27 (diff) | |
download | txr-11173ced6d65339869fe74fbc9c4452a75e3fe26.tar.gz txr-11173ced6d65339869fe74fbc9c4452a75e3fe26.tar.bz2 txr-11173ced6d65339869fe74fbc9c4452a75e3fe26.zip |
* eval.c (dwim_s): New symbol variable.
(dwim_loc, op_dwim): New static functions.
(op_modplace): Support assignment to dwim forms
with the help of dwim_loc.
(expand_place): Handle dwim places.
(eval_init): Initialize dwim_s. Register dwim operator
in op_table.
* eval.h (dwim_s): Declared.
* lib.c (chr_str, chr_str_set): Allow negative indices to index
backwards from end of string.
(vecref, vecref_l): Allow negative indices to index from
rear of array.
(obj_print, obj_pprint): Render (dwim ...) forms as [...].
* parser.l: Peoduce new METABKT token type for @[,
and '[', ']' tokens.
* parser.y (METABKT): New token. %type declaration for '['.
(list): Support square-bracket style of list, translated
into dwim form.
(meta_expr): Support @[...] variant.
(yybadtoken): Handle METABKT in switch.
* txr.1: Documented [...] syntax and dwim operator.
* txr.vim: Updated.
-rw-r--r-- | ChangeLog | 31 | ||||
-rw-r--r-- | eval.c | 149 | ||||
-rw-r--r-- | eval.h | 2 | ||||
-rw-r--r-- | lib.c | 78 | ||||
-rw-r--r-- | parser.l | 10 | ||||
-rw-r--r-- | parser.y | 14 | ||||
-rw-r--r-- | txr.1 | 108 | ||||
-rw-r--r-- | txr.vim | 10 |
8 files changed, 360 insertions, 42 deletions
@@ -1,3 +1,34 @@ +2012-01-25 Kaz Kylheku <kaz@kylheku.com> + + * eval.c (dwim_s): New symbol variable. + (dwim_loc, op_dwim): New static functions. + (op_modplace): Support assignment to dwim forms + with the help of dwim_loc. + (expand_place): Handle dwim places. + (eval_init): Initialize dwim_s. Register dwim operator + in op_table. + + * eval.h (dwim_s): Declared. + + * lib.c (chr_str, chr_str_set): Allow negative indices to index + backwards from end of string. + (vecref, vecref_l): Allow negative indices to index from + rear of array. + (obj_print, obj_pprint): Render (dwim ...) forms as [...]. + + * parser.l: Peoduce new METABKT token type for @[, + and '[', ']' tokens. + + * parser.y (METABKT): New token. %type declaration for '['. + (list): Support square-bracket style of list, translated + into dwim form. + (meta_expr): Support @[...] variant. + (yybadtoken): Handle METABKT in switch. + + * txr.1: Documented [...] syntax and dwim operator. + + * txr.vim: Updated. + 2012-01-21 Kaz Kylheku <kaz@kylheku.com> Version 54 @@ -56,7 +56,7 @@ val top_vb, top_fb; val op_table; val eval_error_s; -val progn_s, prog1_s, let_s, let_star_s, lambda_s, call_s; +val dwim_s, progn_s, prog1_s, let_s, let_star_s, lambda_s, call_s; val cond_s, if_s, defvar_s, defun_s; val inc_s, dec_s, push_s, pop_s, flip_s, gethash_s, car_s, cdr_s, vecref_s; val for_s, for_star_s, each_s, each_star_s, collect_each_s, collect_each_star_s; @@ -637,6 +637,67 @@ static val op_defun(val form, val env) return name; } +static val *dwim_loc(val form, val env) +{ + val obj = eval(second(form), env, form); + val args = eval_args(rest(rest(form)), env, form); + + if (!obj) + eval_error(form, lit("[~s ]: cannot assign nil"), obj, nao); + + switch (type(obj)) { + case LIT: + case STR: + case LSTR: + eval_error(form, lit("[~s ...]: string element assignment not implemented!"), + obj, nao); + case SYM: + case FUN: + eval_error(form, lit("[~s ...]: assigning through function not implemented!"), + obj, nao); + case VEC: + if (rest(args)) + eval_error(form, lit("[~s ...]: vector indexing needs one arg"), + obj, nao); + return vecref_l(obj, first(args)); + case CONS: + case LCONS: + if (rest(args)) + eval_error(form, lit("[~s ...]: list indexing needs one arg"), + obj, nao); + { + val index = first(args); + val cell = obj; + if (!bignump(index) && !fixnump(index)) + eval_error(form, lit("[~s ~s]: index must be integer"), + cell, index, nao); + for (; gt(index, zero); index = minus(index, one)) + cell = cdr(cell); + if (lt(index, zero) || !cell) + eval_error(form, lit("[~s ~s]: cannot assign nonexistent location"), + cell, first(args), nao); + return car_l(cell); + } + case COBJ: + { + if (hashp(obj)) { + val new_p, *loc; + if (length(args) != two) + eval_error(form, lit("[~s ...]: hash indexing needs two args"), + obj, nao); + loc = gethash_l(obj, first(args), &new_p); + if (new_p) + *loc = second(args); + return loc; + } + } + default: + eval_error(form, lit("object ~s not supported by [] notation"), obj, nao); + } + + return 0; +} + static val op_modplace(val form, val env) { uses_or2; @@ -665,7 +726,9 @@ static val op_modplace(val form, val env) } else if (consp(place)) { /* TODO: dispatch these with hash table. */ val sym = car(place); - if (sym == gethash_s) { + if (sym == dwim_s) { + loc = dwim_loc(place, env); + } else if (sym == gethash_s) { val hash = eval(second(place), env, form); val key = eval(third(place), env, form); val new_p; @@ -675,7 +738,7 @@ static val op_modplace(val form, val env) } else if (sym == car_s) { val cons = eval(second(place), env, form); loc = car_l(cons); - } else if (sym == car_s) { + } else if (sym == cdr_s) { val cons = eval(second(place), env, form); loc = cdr_l(cons); } else if (sym == vecref_s) { @@ -813,6 +876,72 @@ static val op_return_from(val form, val env) abort(); } +static val op_dwim(val form, val env) +{ + val obj = eval(second(form), env, form); + val args = eval_args(rest(rest(form)), env, form); + + if (!obj) + return nil; + + switch (type(obj)) { + case LIT: + case STR: + case LSTR: + if (rest(args)) + eval_error(form, lit("[~s ...]: string indexing needs one arg"), + obj, nao); + return chr_str(obj, first(args)); + case SYM: + { + val fbinding = lookup_fun(env, obj); + + if (!fbinding) + eval_error(form, lit("[~s ...]: no function exists named ~s"), + obj, obj, nao); + + return apply(cdr(fbinding), args, form); + } + case FUN: + return apply(obj, args, form); + case VEC: + if (rest(args)) + eval_error(form, lit("[~s ...]: vector indexing needs one arg"), + obj, nao); + return vecref(obj, first(args)); + case CONS: + case LCONS: + if (rest(args)) + eval_error(form, lit("[~s ...]: list indexing needs one arg"), + obj, nao); + { + val index = first(args); + if (!bignump(index) && !fixnump(index)) + eval_error(form, lit("[~s ~s]: index must be integer"), + obj, index, nao); + if (lt(index, zero)) + return nil; + for (; gt(index, zero); index = minus(index, one)) + obj = cdr(obj); + return car(obj); + } + case COBJ: + { + if (hashp(obj)) { + if (length(args) != two) + eval_error(form, lit("[~s ...]: hash indexing needs two args"), + obj, nao); + return gethash_n(obj, first(args), second(args)); + } + } + /* fallthrough */ + default: + eval_error(form, lit("object ~s not supported by [] notation"), obj, nao); + } + + return nil; +} + static val subst_vars(val forms, val env) { list_collect_decl(out, iter); @@ -914,7 +1043,15 @@ static val expand_place(val place) return place; } else { val sym = first(place); - if (sym == gethash_s) { + if (sym == dwim_s) { + val args = rest(place); + val args_ex = expand_forms(args); + + if (args == args_ex) + return place; + + return rlcp(cons(sym, args_ex), place); + } if (sym == gethash_s) { val hash = second(place); val key = third(place); val dfl_val = fourth(place); @@ -1195,7 +1332,7 @@ val expand(val form) } else { /* funtion call also handles: progn, prog1, call, if, and, or, - unwind-protect, return */ + unwind-protect, return, dwim */ val args = rest(form); val args_ex = expand_forms(args); @@ -1477,6 +1614,7 @@ void eval_init(void) top_vb = make_hash(t, nil, nil); op_table = make_hash(nil, nil, nil); + dwim_s = intern(lit("dwim"), user_package); progn_s = intern(lit("progn"), user_package); prog1_s = intern(lit("prog1"), user_package); let_s = intern(lit("let"), user_package); @@ -1548,6 +1686,7 @@ void eval_init(void) sethash(op_table, block_s, cptr((mem_t *) op_block)); sethash(op_table, return_s, cptr((mem_t *) op_return)); sethash(op_table, return_from_s, cptr((mem_t *) op_return_from)); + sethash(op_table, dwim_s, cptr((mem_t *) op_dwim)); sethash(op_table, quasi_s, cptr((mem_t *) op_quasi_lit)); reg_fun(cons_s, func_n2(cons)); @@ -24,6 +24,8 @@ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ +extern val dwim_s; + val make_env(val fbindings, val vbindings, val up_env); val env_fbind(val env, val sym, val fun); val env_vbind(val env, val sym, val obj); @@ -1630,27 +1630,41 @@ val chr_num(val num) return chr(n); } -val chr_str(val str, val index) +val chr_str(val str, val ind) { - bug_unless (length_str_gt(str, index)); + cnum index = c_num(ind); + + if (index < 0) { + ind = plus(length_str(str), ind); + index = c_num(ind); + } + + bug_unless (index >= 0 && length_str_gt(str, ind)); if (lazy_stringp(str)) { - lazy_str_force_upto(str, index); - return chr(c_str(str->ls.prefix)[c_num(index)]); + lazy_str_force_upto(str, ind); + return chr(c_str(str->ls.prefix)[index]); } else { - return chr(c_str(str)[c_num(index)]); + return chr(c_str(str)[index]); } } -val chr_str_set(val str, val index, val chr) +val chr_str_set(val str, val ind, val chr) { - bug_unless (length_str_gt(str, index)); + cnum index = c_num(ind); + + if (index < 0) { + ind = plus(length_str(str), ind); + index = c_num(ind); + } + + bug_unless (index >= 0 && length_str_gt(str, ind)); if (lazy_stringp(str)) { - lazy_str_force_upto(str, index); - str->ls.prefix->st.str[c_num(index)] = c_chr(chr); + lazy_str_force_upto(str, ind); + str->ls.prefix->st.str[index] = c_chr(chr); } else { - str->st.str[c_num(index)] = c_chr(chr); + str->st.str[index] = c_chr(chr); } return chr; @@ -2493,16 +2507,20 @@ val vec_set_length(val vec, val length) val vecref(val vec, val ind) { - type_check(vec, VEC); - range_bug_unless (c_num(ind) < c_num(vec->v.vec[vec_length])); - return vec->v.vec[c_num(ind)]; + cnum index = c_num(ind); + cnum len = c_num(length_vec(vec)); + if (index < 0) + index = len + index; + range_bug_unless (index >= 0 && index < len); + return vec->v.vec[index]; } val *vecref_l(val vec, val ind) { - type_check(vec, VEC); - range_bug_unless (c_num(ind) < c_num(vec->v.vec[vec_length])); - return vec->v.vec + c_num(ind); + cnum index = c_num(ind); + cnum len = c_num(length_vec(vec)); + range_bug_unless (index >= 0 && index < len); + return vec->v.vec + index; } val vec_push(val vec, val item) @@ -3385,17 +3403,25 @@ val obj_print(val obj, val out) obj_print(second(obj), out); } else { val iter; - put_char(out, chr('(')); + val closepar = chr(')'); + if (sym == dwim_s && consp(cdr(obj))) { + put_char(out, chr('[')); + obj = cdr(obj); + closepar = chr(']'); + } else { + put_char(out, chr('(')); + } + for (iter = obj; consp(iter); iter = cdr(iter)) { obj_print(car(iter), out); if (nullp(cdr(iter))) { - put_char(out, chr(')')); + put_char(out, closepar); } else if (consp(cdr(iter))) { put_char(out, chr(' ')); } else { put_string(out, lit(" . ")); obj_print(cdr(iter), out); - put_char(out, chr(')')); + put_char(out, closepar); } } } @@ -3524,17 +3550,25 @@ val obj_pprint(val obj, val out) obj_pprint(second(obj), out); } else { val iter; - put_char(out, chr('(')); + val closepar = chr(')'); + if (sym == dwim_s && consp(cdr(obj))) { + put_char(out, chr('[')); + obj = cdr(obj); + closepar = chr(']'); + } else { + put_char(out, chr('(')); + } + for (iter = obj; consp(iter); iter = cdr(iter)) { obj_pprint(car(iter), out); if (nullp(cdr(iter))) { - put_char(out, chr(')')); + put_char(out, closepar); } else if (consp(cdr(iter))) { put_char(out, chr(' ')); } else { put_string(out, lit(" . ")); obj_pprint(cdr(iter), out); - put_char(out, chr(')')); + put_char(out, closepar); } } } @@ -358,12 +358,12 @@ UONLY {U2}{U}|{U3}{U}{U}|{U4}{U}{U}{U} return FINALLY; } -<NESTED>@\( | -<SPECIAL,NESTED>\{|\( { +<NESTED>@[\(\[] | +<SPECIAL,NESTED>[{(\[] { yy_push_state(NESTED); if (yytext[0] == '@') { - yylval.chr = '('; - return METAPAR; + yylval.chr = yytext[1]; + return yytext[1] == '(' ? METAPAR : METABKT; } yylval.lineno = lineno; return yytext[0]; @@ -379,7 +379,7 @@ UONLY {U2}{U}|{U3}{U}{U}|{U4}{U}{U}{U} return yytext[0]; } -<SPECIAL,NESTED>\}|\) { +<SPECIAL,NESTED>[})\]] { yy_pop_state(); if (yy_top_state() == INITIAL || yy_top_state() == QSILIT) @@ -77,7 +77,7 @@ static val parsed_spec; %token <val> NUMBER %token <chr> REGCHAR LITCHAR -%token <chr> METAPAR SPLICE +%token <chr> METAPAR METABKT SPLICE %type <val> spec clauses clauses_opt clause %type <val> all_clause some_clause none_clause maybe_clause @@ -93,7 +93,7 @@ static val parsed_spec; %type <val> regterm regclass regclassterm regrange %type <val> strlit chrlit quasilit quasi_items quasi_item litchars %type <chr> regchar -%type <lineno> '(' +%type <lineno> '(' '[' %nonassoc LOW /* used for precedence assertion */ %right IDENT '{' '}' @@ -656,6 +656,8 @@ vector : '#' list { $$ = rlcp(vector_list($2), $2); } list : '(' exprs ')' { $$ = rl($2, num($1)); } | '(' ')' { $$ = nil; } + | '[' exprs ']' { $$ = rl(cons(dwim_s, $2), num($1)); } + | '[' ']' { $$ = rl(cons(dwim_s, nil), num($1)); } | ',' expr { val expr = $2; if (consp(expr) && first(expr) == qquote_s) expr = cons(quote_s, rest(expr)); @@ -668,10 +670,17 @@ list : '(' exprs ')' { $$ = rl($2, num($1)); } $$ = rlcp(list(splice_s, expr, nao), $2); } | '(' error { $$ = nil; yybadtoken(yychar, lit("list expression")); } + | '[' error { $$ = nil; + yybadtoken(yychar, lit("DWIM expression")); } ; meta_expr : METAPAR exprs ')' { $$ = rlcp(cons(expr_s, expand($2)), $2); } + | METABKT exprs ']' { $$ = rlcp(cons(expr_s, + cons(dwim_s, + expand($2))), $2); } | METAPAR ')' { $$ = rl(cons(expr_s, nil), num(lineno)); } + | METABKT ']' { $$ = rl(cons(expr_s, cons(dwim_s, nil)), + num(lineno)); } | METAPAR error { $$ = nil; yybadtoken(yychar, lit("meta expression")); } ; @@ -1054,6 +1063,7 @@ void yybadtoken(int tok, val context) case REGCHAR: problem = lit("regular expression character"); break; case LITCHAR: problem = lit("string literal character"); break; case METAPAR: problem = lit("@("); break; + case METABKT: problem = lit("@["); break; } if (problem != 0) @@ -4273,10 +4273,10 @@ Define several Lisp functions using @(do): TXR Lisp is a small and simple dialect, like Scheme, but much more similar to Common Lisp than Scheme. It has separate value and function binding namespaces, -like Common Lisp, and represents boolean true and false with the symbols t and -nil (but note the case sensitivity of identifiers denoting symbols!) -Furthermore, the symbol nil is also the empty list, which terminates nonempty -lists. +like Common Lisp (and thus is a Lisp-2 type dialect), and represents boolean +true and false with the symbols t and nil (but note the case sensitivity of +identifiers denoting symbols!) Furthermore, the symbol nil is also the empty +list, which terminates nonempty lists. Function and variable Bindings are dynamically scoped in TXR Lisp. However, closures do capture variables. @@ -4363,6 +4363,21 @@ with the innermost quote. The quote between the commas protects the (+ 1 2) from repeated evaluations: the two unquotes call for two evaluations, but we only want (+ 1 2) to be evaluated once. +.SS The DWIM Brackets + +.IP [...] + +TXR Lisp has a square bracket notation. The syntax [...] is a shorthand +way of writing (dwim ...). The [] syntax is useful for situations +where the expressive style of a Lisp-1 dialect is useful. + +For instance if foo is a variable which holds a function object, then [foo 3] +can be used to call it, instead of (call foo 3). If foo is a vector, then +[foo 3] retrieves the fourth element, like (vecref foo 3). Indexing over lists, +strings and hash tables is possible, and the notation is assignable. + +More details are given in the documentation for the dwim operator. + .SS Lisp Operators When the first element of a compound expression is an operator symbol, @@ -4753,6 +4768,11 @@ Currently, these forms are recognized as places: (vecref <vector> <index>) + (dwim <obj> ...) + + [<obj> ...] ;; equivalent to (dwim <obj> ...) + + A <symbol> place denotes a variable. If the variable does not exist, it is an error. @@ -4769,6 +4789,86 @@ determine the initial value of the place. Otherwise it is ignored. The vecref place denotes a vector element, allowing vector elements to be treated as assignment places. +The dwim/[] place denotes a vector element, list element, or hash table, +depending on the type of obj. + +.SS Operator dwim + +.TP +Syntax: + + (dwim <argument>*) + + [<argument>*] + +.TP +Description: + +The dwim operator's name is an acronym: DWIM may be taken to mean +"Do What I Mean", or alternatively, "Dispatch, in a Way that is +Intelligent and Meaningful". + +The notation [...] is a shorthand equivalent to (dwim ...) and is the preferred +way for writing dwim expressions. + +The dwim operator takes a variable number of arguments, which are +all evaluated in the same way. How many are required depends on the type of +object to which the first argument expression evaluates: of the first argument. +The possibilities are: + +.IP [<function> <argument>*] +Call the given the function object to the given arguments. + +.IP [<symbol> <argument>*] +If the first expression evaluates to a symbol, that symbol +is resolved in the function namespace, and then +the resulting function, if found, is called with the +given arguments. + +.IP [<list> <index>] +Retrieve the specified element from the specified list. Index zero +refers to the first element. Indexed list access does not throw exceptions. +Negative indices yield nil, and indices beyond the end of a list +yield nil. (However assignment to a nonexistent list element throws.) + +.IP [<vector> <index>] +Retrieve the specified element of a vector. This is equivalent to +(vecref <vector> <index>). + +.IP [<string> <index>] +Retrieve the specified element of a string. This is equivalent to +(chr-str <string> <index>). + +.IP [<hash-table> <key> <default-value>] +Retrieve a value from the hash table corresponding to <key>, +or <default-value> if there is no such entry. + +The list, vector and hash table forms of dwim denote places +that can be assigned. + +.TP +Notes: + +The dwim operator allows for a Lisp-1 flavor of programming in TXR Lisp, +which is normally Lisp-2, with some useful extensions. + +A Lisp-1 dialect is one in which an expression like (a b) treats both a and b +as expressions with the same evaluation rules. Thus in a Lisp-1, named +functions do not exist as such: they are just variable bindings. +In a Lisp-1 (car 1 2) means that there is a variable called car, +which holds a function. In a Lisp-2 (car 1 2) means that there is +a function called car, and so (car car car) is possible, because +there can be also a variable called car. + +The Lisp-1 design has certain disadvantages, which are avoided in TXR Lisp by +confining the Lisp-1 expressivity inside the [...] notation. When round +parentheses are used, the normal Lisp-2 rules apply. A "best of both worlds" +situation is achieved. + +Lisp-1 dialects can provide useful extensions by giving a meaning +to objects other than functions in the first position of a form, +and the dwim/[...] syntax does exactly this. + .SS Operators for and for* .TP @@ -101,13 +101,15 @@ syn match txr_ncomment ";.*" contained syn match txr_ident "[a-zA-Z0-9!$%&*+\-<=>?\\^_~]\+" contained syn match txr_num "[+-]\?[0-9]\+" contained -syn region txr_bracevar matchgroup=Delimiter start="@[ \t]*[*]\?{" matchgroup=Delimiter end="}" contains=txr_num,txr_ident,xr_string,txr_list,txr_regex,txr_quasilit,txr_chr +syn region txr_bracevar matchgroup=Delimiter start="@[ \t]*[*]\?{" matchgroup=Delimiter end="}" contains=txr_num,txr_ident,txr_string,txr_dwim,txr_list,txr_regex,txr_quasilit,txr_chr -syn region txr_directive matchgroup=Delimiter start="@[ \t]*(" matchgroup=Delimiter end=")" contains=txr_keyword,txr_string,txr_list,txr_meta,txr_quasilit,txr_num,txr_ident,txr_regex,txr_string,txr_variable,txr_chr,txr_hash,txr_ncomment +syn region txr_directive matchgroup=Delimiter start="@[ \t]*(" matchgroup=Delimiter end=")" contains=txr_keyword,txr_string,txr_list,txr_dwim,txr_meta,txr_quasilit,txr_num,txr_ident,txr_regex,txr_string,txr_variable,txr_chr,txr_hash,txr_ncomment -syn region txr_list contained matchgroup=Delimiter start="(" matchgroup=Delimiter end=")" contains=txl_keyword,txr_string,txr_regex,txr_num,txr_ident,txr_variable,txr_meta,txr_list,txr_quasilit,txr_chr,txr_hash,txr_quote,txr_ncomment +syn region txr_list contained matchgroup=Delimiter start="(" matchgroup=Delimiter end=")" contains=txl_keyword,txr_string,txr_regex,txr_num,txr_ident,txr_variable,txr_meta,txr_list,txr_dwim,txr_quasilit,txr_chr,txr_hash,txr_quote,txr_ncomment -syn region txr_meta contained matchgroup=Delimiter start="@[ \t]*(" matchgroup=Delimiter end=")" contains=txl_keyword,txr_string,txr_list,txr_regex,txr_num,txr_ident,txr_variable,txr_quasilit,txr_chrb,txr_hash,txr_quote,txr_ncomment +syn region txr_dwim contained matchgroup=Delimiter start="\[" matchgroup=Delimiter end="\]" contains=txl_keyword,txr_string,txr_regex,txr_num,txr_ident,txr_variable,txr_meta,txr_list,txr_dwim,txr_dwim,txr_quasilit,txr_chr,txr_hash,txr_quote,txr_ncomment + +syn region txr_meta contained matchgroup=Delimiter start="@[ \t]*(" matchgroup=Delimiter end=")" contains=txl_keyword,txr_string,txr_list,txr_dwim,txr_regex,txr_num,txr_ident,txr_variable,txr_quasilit,txr_chrb,txr_hash,txr_quote,txr_ncomment syn region txr_string contained oneline start=+"+ skip=+\\\\\|\\"+ end=+"+ syn region txr_quasilit contained oneline start=+`+ skip=+\\\\\|\\`+ end=+`+ contains=txr_directive,txr_variable,txr_bracevar |