diff options
author | Sam Bingner <sam@bingner.com> | 2018-12-13 15:11:52 -1000 |
---|---|---|
committer | Sam Bingner <sam@bingner.com> | 2018-12-13 15:11:52 -1000 |
commit | 957aa75d05c00731d7112bed7b68ce4568667d0c (patch) | |
tree | 0445216818495a7864eaa3acde1a1570d34b958d /data/vim/patches/8.1.0252 | |
parent | c54a909c8b5a8519130803cf55f68603c0ad3682 (diff) |
Update vim
Diffstat (limited to 'data/vim/patches/8.1.0252')
-rw-r--r-- | data/vim/patches/8.1.0252 | 1540 |
1 files changed, 1540 insertions, 0 deletions
diff --git a/data/vim/patches/8.1.0252 b/data/vim/patches/8.1.0252 new file mode 100644 index 000000000..f72f55a0d --- /dev/null +++ b/data/vim/patches/8.1.0252 @@ -0,0 +1,1540 @@ +To: vim_dev@googlegroups.com +Subject: Patch 8.1.0252 +Fcc: outbox +From: Bram Moolenaar <Bram@moolenaar.net> +Mime-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +------------ + +Patch 8.1.0252 +Problem: Quickfix functions are too long. +Solution: Refactor. (Yegappan Lakshmanan, closes #2950) +Files: src/quickfix.c + + +*** ../vim-8.1.0251/src/quickfix.c 2018-08-07 19:47:46.746434541 +0200 +--- src/quickfix.c 2018-08-07 21:52:51.654369210 +0200 +*************** +*** 201,306 **** + + /* + * Convert an errorformat pattern to a regular expression pattern. +! * See fmt_pat definition above for the list of supported patterns. + */ + static char_u * +! fmtpat_to_regpat( +! char_u *efmp, +! efm_T *fmt_ptr, + int idx, + int round, +- char_u *ptr, + char_u *errmsg) + { + char_u *srcptr; + +! if (fmt_ptr->addr[idx]) + { + /* Each errorformat pattern can occur only once */ + sprintf((char *)errmsg, +! _("E372: Too many %%%c in format string"), *efmp); + EMSG(errmsg); + return NULL; + } + if ((idx && idx < 6 +! && vim_strchr((char_u *)"DXOPQ", fmt_ptr->prefix) != NULL) + || (idx == 6 +! && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL)) + { + sprintf((char *)errmsg, +! _("E373: Unexpected %%%c in format string"), *efmp); + EMSG(errmsg); + return NULL; + } +! fmt_ptr->addr[idx] = (char_u)++round; +! *ptr++ = '\\'; +! *ptr++ = '('; + #ifdef BACKSLASH_IN_FILENAME +! if (*efmp == 'f') + { + /* Also match "c:" in the file name, even when + * checking for a colon next: "%f:". + * "\%(\a:\)\=" */ +! STRCPY(ptr, "\\%(\\a:\\)\\="); +! ptr += 10; + } + #endif +! if (*efmp == 'f' && efmp[1] != NUL) + { +! if (efmp[1] != '\\' && efmp[1] != '%') + { + /* A file name may contain spaces, but this isn't + * in "\f". For "%f:%l:%m" there may be a ":" in + * the file name. Use ".\{-1,}x" instead (x is + * the next character), the requirement that :999: + * follows should work. */ +! STRCPY(ptr, ".\\{-1,}"); +! ptr += 7; + } + else + { + /* File name followed by '\\' or '%': include as + * many file name chars as possible. */ +! STRCPY(ptr, "\\f\\+"); +! ptr += 4; + } + } + else + { + srcptr = (char_u *)fmt_pat[idx].pattern; +! while ((*ptr = *srcptr++) != NUL) +! ++ptr; + } +! *ptr++ = '\\'; +! *ptr++ = ')'; + +! return ptr; + } + + /* + * Convert a scanf like format in 'errorformat' to a regular expression. + */ + static char_u * + scanf_fmt_to_regpat( + char_u *efm, + int len, +! char_u **pefmp, +! char_u *ptr, + char_u *errmsg) + { + char_u *efmp = *pefmp; + +! if (*++efmp == '[' || *efmp == '\\') + { +! if ((*ptr++ = *efmp) == '[') /* %*[^a-z0-9] etc. */ + { + if (efmp[1] == '^') +! *ptr++ = *++efmp; + if (efmp < efm + len) + { +! *ptr++ = *++efmp; /* could be ']' */ + while (efmp < efm + len +! && (*ptr++ = *++efmp) != ']') + /* skip */; + if (efmp == efm + len) + { +--- 201,309 ---- + + /* + * Convert an errorformat pattern to a regular expression pattern. +! * See fmt_pat definition above for the list of supported patterns. The +! * pattern specifier is supplied in "efmpat". The converted pattern is stored +! * in "regpat". Returns a pointer to the location after the pattern. + */ + static char_u * +! efmpat_to_regpat( +! char_u *efmpat, +! char_u *regpat, +! efm_T *efminfo, + int idx, + int round, + char_u *errmsg) + { + char_u *srcptr; + +! if (efminfo->addr[idx]) + { + /* Each errorformat pattern can occur only once */ + sprintf((char *)errmsg, +! _("E372: Too many %%%c in format string"), *efmpat); + EMSG(errmsg); + return NULL; + } + if ((idx && idx < 6 +! && vim_strchr((char_u *)"DXOPQ", efminfo->prefix) != NULL) + || (idx == 6 +! && vim_strchr((char_u *)"OPQ", efminfo->prefix) == NULL)) + { + sprintf((char *)errmsg, +! _("E373: Unexpected %%%c in format string"), *efmpat); + EMSG(errmsg); + return NULL; + } +! efminfo->addr[idx] = (char_u)++round; +! *regpat++ = '\\'; +! *regpat++ = '('; + #ifdef BACKSLASH_IN_FILENAME +! if (*efmpat == 'f') + { + /* Also match "c:" in the file name, even when + * checking for a colon next: "%f:". + * "\%(\a:\)\=" */ +! STRCPY(regpat, "\\%(\\a:\\)\\="); +! regpat += 10; + } + #endif +! if (*efmpat == 'f' && efmpat[1] != NUL) + { +! if (efmpat[1] != '\\' && efmpat[1] != '%') + { + /* A file name may contain spaces, but this isn't + * in "\f". For "%f:%l:%m" there may be a ":" in + * the file name. Use ".\{-1,}x" instead (x is + * the next character), the requirement that :999: + * follows should work. */ +! STRCPY(regpat, ".\\{-1,}"); +! regpat += 7; + } + else + { + /* File name followed by '\\' or '%': include as + * many file name chars as possible. */ +! STRCPY(regpat, "\\f\\+"); +! regpat += 4; + } + } + else + { + srcptr = (char_u *)fmt_pat[idx].pattern; +! while ((*regpat = *srcptr++) != NUL) +! ++regpat; + } +! *regpat++ = '\\'; +! *regpat++ = ')'; + +! return regpat; + } + + /* + * Convert a scanf like format in 'errorformat' to a regular expression. ++ * Returns a pointer to the location after the pattern. + */ + static char_u * + scanf_fmt_to_regpat( ++ char_u **pefmp, + char_u *efm, + int len, +! char_u *regpat, + char_u *errmsg) + { + char_u *efmp = *pefmp; + +! if (*efmp == '[' || *efmp == '\\') + { +! if ((*regpat++ = *efmp) == '[') /* %*[^a-z0-9] etc. */ + { + if (efmp[1] == '^') +! *regpat++ = *++efmp; + if (efmp < efm + len) + { +! *regpat++ = *++efmp; /* could be ']' */ + while (efmp < efm + len +! && (*regpat++ = *++efmp) != ']') + /* skip */; + if (efmp == efm + len) + { +*************** +*** 310,318 **** + } + } + else if (efmp < efm + len) /* %*\D, %*\s etc. */ +! *ptr++ = *++efmp; +! *ptr++ = '\\'; +! *ptr++ = '+'; + } + else + { +--- 313,321 ---- + } + } + else if (efmp < efm + len) /* %*\D, %*\s etc. */ +! *regpat++ = *++efmp; +! *regpat++ = '\\'; +! *regpat++ = '+'; + } + else + { +*************** +*** 325,360 **** + + *pefmp = efmp; + +! return ptr; + } + + /* + * Analyze/parse an errorformat prefix. + */ +! static int +! efm_analyze_prefix(char_u **pefmp, efm_T *fmt_ptr, char_u *errmsg) + { +- char_u *efmp = *pefmp; +- + if (vim_strchr((char_u *)"+-", *efmp) != NULL) +! fmt_ptr->flags = *efmp++; + if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) +! fmt_ptr->prefix = *efmp; + else + { + sprintf((char *)errmsg, + _("E376: Invalid %%%c in format string prefix"), *efmp); + EMSG(errmsg); +! return FAIL; + } + +! *pefmp = efmp; +! +! return OK; + } + + /* +! * Converts a 'errorformat' string to regular expression pattern + */ + static int + efm_to_regpat( +--- 328,362 ---- + + *pefmp = efmp; + +! return regpat; + } + + /* + * Analyze/parse an errorformat prefix. + */ +! static char_u * +! efm_analyze_prefix(char_u *efmp, efm_T *efminfo, char_u *errmsg) + { + if (vim_strchr((char_u *)"+-", *efmp) != NULL) +! efminfo->flags = *efmp++; + if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) +! efminfo->prefix = *efmp; + else + { + sprintf((char *)errmsg, + _("E376: Invalid %%%c in format string prefix"), *efmp); + EMSG(errmsg); +! return NULL; + } + +! return efmp; + } + + /* +! * Converts a 'errorformat' string part in 'efm' to a regular expression +! * pattern. The resulting regex pattern is returned in "regpat". Additional +! * information about the 'erroformat' pattern is returned in "fmt_ptr". +! * Returns OK or FAIL. + */ + static int + efm_to_regpat( +*************** +*** 370,376 **** + int idx = 0; + + /* +! * Build regexp pattern from current 'errorformat' option + */ + ptr = regpat; + *ptr++ = '^'; +--- 372,378 ---- + int idx = 0; + + /* +! * Build a regexp pattern for a 'errorformat' option part + */ + ptr = regpat; + *ptr++ = '^'; +*************** +*** 385,401 **** + break; + if (idx < FMT_PATTERNS) + { +! ptr = fmtpat_to_regpat(efmp, fmt_ptr, idx, round, ptr, + errmsg); + if (ptr == NULL) +! return -1; + round++; + } + else if (*efmp == '*') + { +! ptr = scanf_fmt_to_regpat(efm, len, &efmp, ptr, errmsg); + if (ptr == NULL) +! return -1; + } + else if (vim_strchr((char_u *)"%\\.^$~[", *efmp) != NULL) + *ptr++ = *efmp; /* regexp magic characters */ +--- 387,404 ---- + break; + if (idx < FMT_PATTERNS) + { +! ptr = efmpat_to_regpat(efmp, ptr, fmt_ptr, idx, round, + errmsg); + if (ptr == NULL) +! return FAIL; + round++; + } + else if (*efmp == '*') + { +! ++efmp; +! ptr = scanf_fmt_to_regpat(&efmp, efm, len, ptr, errmsg); + if (ptr == NULL) +! return FAIL; + } + else if (vim_strchr((char_u *)"%\\.^$~[", *efmp) != NULL) + *ptr++ = *efmp; /* regexp magic characters */ +*************** +*** 405,419 **** + fmt_ptr->conthere = TRUE; + else if (efmp == efm + 1) /* analyse prefix */ + { +! if (efm_analyze_prefix(&efmp, fmt_ptr, errmsg) == FAIL) +! return -1; + } + else + { + sprintf((char *)errmsg, + _("E377: Invalid %%%c in format string"), *efmp); + EMSG(errmsg); +! return -1; + } + } + else /* copy normal character */ +--- 408,427 ---- + fmt_ptr->conthere = TRUE; + else if (efmp == efm + 1) /* analyse prefix */ + { +! /* +! * prefix is allowed only at the beginning of the errorformat +! * option part +! */ +! efmp = efm_analyze_prefix(efmp, fmt_ptr, errmsg); +! if (efmp == NULL) +! return FAIL; + } + else + { + sprintf((char *)errmsg, + _("E377: Invalid %%%c in format string"), *efmp); + EMSG(errmsg); +! return FAIL; + } + } + else /* copy normal character */ +*************** +*** 429,437 **** + *ptr++ = '$'; + *ptr = NUL; + +! return 0; + } + + static void + free_efm_list(efm_T **efm_first) + { +--- 437,448 ---- + *ptr++ = '$'; + *ptr = NUL; + +! return OK; + } + ++ /* ++ * Free the 'errorformat' information list ++ */ + static void + free_efm_list(efm_T **efm_first) + { +*************** +*** 446,452 **** + fmt_start = NULL; + } + +! /* Parse 'errorformat' option */ + static efm_T * + parse_efm_option(char_u *efm) + { +--- 457,504 ---- + fmt_start = NULL; + } + +! /* +! * Compute the size of the buffer used to convert a 'errorformat' pattern into +! * a regular expression pattern. +! */ +! static int +! efm_regpat_bufsz(char_u *efm) +! { +! int sz; +! int i; +! +! sz = (FMT_PATTERNS * 3) + ((int)STRLEN(efm) << 2); +! for (i = FMT_PATTERNS; i > 0; ) +! sz += (int)STRLEN(fmt_pat[--i].pattern); +! #ifdef BACKSLASH_IN_FILENAME +! sz += 12; /* "%f" can become twelve chars longer (see efm_to_regpat) */ +! #else +! sz += 2; /* "%f" can become two chars longer */ +! #endif +! +! return sz; +! } +! +! /* +! * Return the length of a 'errorformat' option part (separated by ","). +! */ +! static int +! efm_option_part_len(char_u *efm) +! { +! int len; +! +! for (len = 0; efm[len] != NUL && efm[len] != ','; ++len) +! if (efm[len] == '\\' && efm[len + 1] != NUL) +! ++len; +! +! return len; +! } +! +! /* +! * Parse the 'errorformat' option. Multiple parts in the 'errorformat' option +! * are parsed and converted to regular expressions. Returns information about +! * the parsed 'errorformat' option. +! */ + static efm_T * + parse_efm_option(char_u *efm) + { +*************** +*** 457,464 **** + efm_T *fmt_last = NULL; + char_u *fmtstr = NULL; + int len; +! int i; +! int round; + + errmsglen = CMDBUFFSIZE + 1; + errmsg = alloc_id(errmsglen, aid_qf_errmsg); +--- 509,515 ---- + efm_T *fmt_last = NULL; + char_u *fmtstr = NULL; + int len; +! int sz; + + errmsglen = CMDBUFFSIZE + 1; + errmsg = alloc_id(errmsglen, aid_qf_errmsg); +*************** +*** 473,487 **** + /* + * Get some space to modify the format string into. + */ +! i = (FMT_PATTERNS * 3) + ((int)STRLEN(efm) << 2); +! for (round = FMT_PATTERNS; round > 0; ) +! i += (int)STRLEN(fmt_pat[--round].pattern); +! #ifdef BACKSLASH_IN_FILENAME +! i += 12; /* "%f" can become twelve chars longer (see efm_to_regpat) */ +! #else +! i += 2; /* "%f" can become two chars longer */ +! #endif +! if ((fmtstr = alloc(i)) == NULL) + goto parse_efm_error; + + while (efm[0] != NUL) +--- 524,531 ---- + /* + * Get some space to modify the format string into. + */ +! sz = efm_regpat_bufsz(efm); +! if ((fmtstr = alloc(sz)) == NULL) + goto parse_efm_error; + + while (efm[0] != NUL) +*************** +*** 501,511 **** + /* + * Isolate one part in the 'errorformat' option + */ +! for (len = 0; efm[len] != NUL && efm[len] != ','; ++len) +! if (efm[len] == '\\' && efm[len + 1] != NUL) +! ++len; + +! if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg) == -1) + goto parse_efm_error; + if ((fmt_ptr->prog = vim_regcomp(fmtstr, RE_MAGIC + RE_STRING)) == NULL) + goto parse_efm_error; +--- 545,553 ---- + /* + * Isolate one part in the 'errorformat' option + */ +! len = efm_option_part_len(efm); + +! if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg) == FAIL) + goto parse_efm_error; + if ((fmt_ptr->prog = vim_regcomp(fmtstr, RE_MAGIC + RE_STRING)) == NULL) + goto parse_efm_error; +*************** +*** 539,544 **** +--- 581,590 ---- + QF_MULTISCAN = 5, + }; + ++ /* ++ * State information used to parse lines and add entries to a quickfix/location ++ * list. ++ */ + typedef struct { + char_u *linebuf; + int linelen; +*************** +*** 554,559 **** +--- 600,608 ---- + vimconv_T vc; + } qfstate_T; + ++ /* ++ * Allocate more memory for the line buffer used for parsing lines. ++ */ + static char_u * + qf_grow_linebuf(qfstate_T *state, int newsz) + { +*************** +*** 861,870 **** + } qffields_T; + + /* +! * Parse the error format matches in 'regmatch' and set the values in 'fields'. +! * fmt_ptr contains the 'efm' format specifiers/prefixes that have a match. +! * Returns QF_OK if all the matches are successfully parsed. On failure, +! * returns QF_FAIL or QF_NOMEM. + */ + static int + qf_parse_match( +--- 910,1160 ---- + } qffields_T; + + /* +! * Parse the match for filename ('%f') pattern in regmatch. +! * Return the matched value in "fields->namebuf". +! */ +! static int +! qf_parse_fmt_f(regmatch_T *rmp, int midx, qffields_T *fields, int prefix) +! { +! int c; +! +! if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) +! return QF_FAIL; +! +! /* Expand ~/file and $HOME/file to full path. */ +! c = *rmp->endp[midx]; +! *rmp->endp[midx] = NUL; +! expand_env(rmp->startp[midx], fields->namebuf, CMDBUFFSIZE); +! *rmp->endp[midx] = c; +! +! /* +! * For separate filename patterns (%O, %P and %Q), the specified file +! * should exist. +! */ +! if (vim_strchr((char_u *)"OPQ", prefix) != NULL +! && mch_getperm(fields->namebuf) == -1) +! return QF_FAIL; +! +! return QF_OK; +! } +! +! /* +! * Parse the match for error number ('%n') pattern in regmatch. +! * Return the matched value in "fields->enr". +! */ +! static int +! qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields) +! { +! if (rmp->startp[midx] == NULL) +! return QF_FAIL; +! fields->enr = (int)atol((char *)rmp->startp[midx]); +! return QF_OK; +! } +! +! /* +! * Parse the match for line number (%l') pattern in regmatch. +! * Return the matched value in "fields->lnum". +! */ +! static int +! qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields) +! { +! if (rmp->startp[midx] == NULL) +! return QF_FAIL; +! fields->lnum = atol((char *)rmp->startp[midx]); +! return QF_OK; +! } +! +! /* +! * Parse the match for column number ('%c') pattern in regmatch. +! * Return the matched value in "fields->col". +! */ +! static int +! qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields) +! { +! if (rmp->startp[midx] == NULL) +! return QF_FAIL; +! fields->col = (int)atol((char *)rmp->startp[midx]); +! return QF_OK; +! } +! +! /* +! * Parse the match for error type ('%t') pattern in regmatch. +! * Return the matched value in "fields->type". +! */ +! static int +! qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields) +! { +! if (rmp->startp[midx] == NULL) +! return QF_FAIL; +! fields->type = *rmp->startp[midx]; +! return QF_OK; +! } +! +! /* +! * Parse the match for '%+' format pattern. The whole matching line is included +! * in the error string. Return the matched line in "fields->errmsg". +! */ +! static int +! qf_parse_fmt_plus(char_u *linebuf, int linelen, qffields_T *fields) +! { +! char_u *p; +! +! if (linelen >= fields->errmsglen) +! { +! /* linelen + null terminator */ +! if ((p = vim_realloc(fields->errmsg, linelen + 1)) == NULL) +! return QF_NOMEM; +! fields->errmsg = p; +! fields->errmsglen = linelen + 1; +! } +! vim_strncpy(fields->errmsg, linebuf, linelen); +! return QF_OK; +! } +! +! /* +! * Parse the match for error message ('%m') pattern in regmatch. +! * Return the matched value in "fields->errmsg". +! */ +! static int +! qf_parse_fmt_m(regmatch_T *rmp, int midx, qffields_T *fields) +! { +! char_u *p; +! int len; +! +! if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) +! return QF_FAIL; +! len = (int)(rmp->endp[midx] - rmp->startp[midx]); +! if (len >= fields->errmsglen) +! { +! /* len + null terminator */ +! if ((p = vim_realloc(fields->errmsg, len + 1)) == NULL) +! return QF_NOMEM; +! fields->errmsg = p; +! fields->errmsglen = len + 1; +! } +! vim_strncpy(fields->errmsg, rmp->startp[midx], len); +! return QF_OK; +! } +! +! /* +! * Parse the match for rest of a single-line file message ('%r') pattern. +! * Return the matched value in "tail". +! */ +! static int +! qf_parse_fmt_r(regmatch_T *rmp, int midx, char_u **tail) +! { +! if (rmp->startp[midx] == NULL) +! return QF_FAIL; +! *tail = rmp->startp[midx]; +! return QF_OK; +! } +! +! /* +! * Parse the match for the pointer line ('%p') pattern in regmatch. +! * Return the matched value in "fields->col". +! */ +! static int +! qf_parse_fmt_p(regmatch_T *rmp, int midx, qffields_T *fields) +! { +! char_u *match_ptr; +! +! if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) +! return QF_FAIL; +! fields->col = 0; +! for (match_ptr = rmp->startp[midx]; match_ptr != rmp->endp[midx]; +! ++match_ptr) +! { +! ++fields->col; +! if (*match_ptr == TAB) +! { +! fields->col += 7; +! fields->col -= fields->col % 8; +! } +! } +! ++fields->col; +! fields->use_viscol = TRUE; +! return QF_OK; +! } +! +! /* +! * Parse the match for the virtual column number ('%v') pattern in regmatch. +! * Return the matched value in "fields->col". +! */ +! static int +! qf_parse_fmt_v(regmatch_T *rmp, int midx, qffields_T *fields) +! { +! if (rmp->startp[midx] == NULL) +! return QF_FAIL; +! fields->col = (int)atol((char *)rmp->startp[midx]); +! fields->use_viscol = TRUE; +! return QF_OK; +! } +! +! /* +! * Parse the match for the search text ('%s') pattern in regmatch. +! * Return the matched value in "fields->pattern". +! */ +! static int +! qf_parse_fmt_s(regmatch_T *rmp, int midx, qffields_T *fields) +! { +! int len; +! +! if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) +! return QF_FAIL; +! len = (int)(rmp->endp[midx] - rmp->startp[midx]); +! if (len > CMDBUFFSIZE - 5) +! len = CMDBUFFSIZE - 5; +! STRCPY(fields->pattern, "^\\V"); +! STRNCAT(fields->pattern, rmp->startp[midx], len); +! fields->pattern[len + 3] = '\\'; +! fields->pattern[len + 4] = '$'; +! fields->pattern[len + 5] = NUL; +! return QF_OK; +! } +! +! /* +! * Parse the match for the module ('%o') pattern in regmatch. +! * Return the matched value in "fields->module". +! */ +! static int +! qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields) +! { +! int len; +! +! if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) +! return QF_FAIL; +! len = (int)(rmp->endp[midx] - rmp->startp[midx]); +! if (len > CMDBUFFSIZE) +! len = CMDBUFFSIZE; +! STRNCAT(fields->module, rmp->startp[midx], len); +! return QF_OK; +! } +! +! /* +! * 'errorformat' format pattern parser functions. +! * The '%f' and '%r' formats are parsed differently from other formats. +! * See qf_parse_match() for details. +! */ +! static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = +! { +! NULL, +! qf_parse_fmt_n, +! qf_parse_fmt_l, +! qf_parse_fmt_c, +! qf_parse_fmt_t, +! qf_parse_fmt_m, +! NULL, +! qf_parse_fmt_p, +! qf_parse_fmt_v, +! qf_parse_fmt_s, +! qf_parse_fmt_o +! }; +! +! /* +! * Parse the error format pattern matches in "regmatch" and set the values in +! * "fields". fmt_ptr contains the 'efm' format specifiers/prefixes that have a +! * match. Returns QF_OK if all the matches are successfully parsed. On +! * failure, returns QF_FAIL or QF_NOMEM. + */ + static int + qf_parse_match( +*************** +*** 877,886 **** + int qf_multiscan, + char_u **tail) + { +- char_u *p; + int idx = fmt_ptr->prefix; + int i; +! int len; + + if ((idx == 'C' || idx == 'Z') && !qf_multiline) + return QF_FAIL; +--- 1167,1176 ---- + int qf_multiscan, + char_u **tail) + { + int idx = fmt_ptr->prefix; + int i; +! int midx; +! int status; + + if ((idx == 'C' || idx == 'Z') && !qf_multiline) + return QF_FAIL; +*************** +*** 893,1020 **** + * We check for an actual submatch, because "\[" and "\]" in + * the 'errorformat' may cause the wrong submatch to be used. + */ +! if ((i = (int)fmt_ptr->addr[0]) > 0) /* %f */ + { +! int c; + +! if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) +! return QF_FAIL; +! +! /* Expand ~/file and $HOME/file to full path. */ +! c = *regmatch->endp[i]; +! *regmatch->endp[i] = NUL; +! expand_env(regmatch->startp[i], fields->namebuf, CMDBUFFSIZE); +! *regmatch->endp[i] = c; +! +! if (vim_strchr((char_u *)"OPQ", idx) != NULL +! && mch_getperm(fields->namebuf) == -1) +! return QF_FAIL; +! } +! if ((i = (int)fmt_ptr->addr[1]) > 0) /* %n */ +! { +! if (regmatch->startp[i] == NULL) +! return QF_FAIL; +! fields->enr = (int)atol((char *)regmatch->startp[i]); +! } +! if ((i = (int)fmt_ptr->addr[2]) > 0) /* %l */ +! { +! if (regmatch->startp[i] == NULL) +! return QF_FAIL; +! fields->lnum = atol((char *)regmatch->startp[i]); +! } +! if ((i = (int)fmt_ptr->addr[3]) > 0) /* %c */ +! { +! if (regmatch->startp[i] == NULL) +! return QF_FAIL; +! fields->col = (int)atol((char *)regmatch->startp[i]); +! } +! if ((i = (int)fmt_ptr->addr[4]) > 0) /* %t */ +! { +! if (regmatch->startp[i] == NULL) +! return QF_FAIL; +! fields->type = *regmatch->startp[i]; +! } +! if (fmt_ptr->flags == '+' && !qf_multiscan) /* %+ */ +! { +! if (linelen >= fields->errmsglen) +! { +! /* linelen + null terminator */ +! if ((p = vim_realloc(fields->errmsg, linelen + 1)) == NULL) +! return QF_NOMEM; +! fields->errmsg = p; +! fields->errmsglen = linelen + 1; +! } +! vim_strncpy(fields->errmsg, linebuf, linelen); +! } +! else if ((i = (int)fmt_ptr->addr[5]) > 0) /* %m */ +! { +! if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) +! return QF_FAIL; +! len = (int)(regmatch->endp[i] - regmatch->startp[i]); +! if (len >= fields->errmsglen) +! { +! /* len + null terminator */ +! if ((p = vim_realloc(fields->errmsg, len + 1)) == NULL) +! return QF_NOMEM; +! fields->errmsg = p; +! fields->errmsglen = len + 1; +! } +! vim_strncpy(fields->errmsg, regmatch->startp[i], len); +! } +! if ((i = (int)fmt_ptr->addr[6]) > 0) /* %r */ +! { +! if (regmatch->startp[i] == NULL) +! return QF_FAIL; +! *tail = regmatch->startp[i]; +! } +! if ((i = (int)fmt_ptr->addr[7]) > 0) /* %p */ +! { +! char_u *match_ptr; +! +! if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) +! return QF_FAIL; +! fields->col = 0; +! for (match_ptr = regmatch->startp[i]; +! match_ptr != regmatch->endp[i]; ++match_ptr) +! { +! ++fields->col; +! if (*match_ptr == TAB) +! { +! fields->col += 7; +! fields->col -= fields->col % 8; +! } +! } +! ++fields->col; +! fields->use_viscol = TRUE; +! } +! if ((i = (int)fmt_ptr->addr[8]) > 0) /* %v */ +! { +! if (regmatch->startp[i] == NULL) +! return QF_FAIL; +! fields->col = (int)atol((char *)regmatch->startp[i]); +! fields->use_viscol = TRUE; +! } +! if ((i = (int)fmt_ptr->addr[9]) > 0) /* %s */ +! { +! if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) +! return QF_FAIL; +! len = (int)(regmatch->endp[i] - regmatch->startp[i]); +! if (len > CMDBUFFSIZE - 5) +! len = CMDBUFFSIZE - 5; +! STRCPY(fields->pattern, "^\\V"); +! STRNCAT(fields->pattern, regmatch->startp[i], len); +! fields->pattern[len + 3] = '\\'; +! fields->pattern[len + 4] = '$'; +! fields->pattern[len + 5] = NUL; +! } +! if ((i = (int)fmt_ptr->addr[10]) > 0) /* %o */ +! { +! if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) +! return QF_FAIL; +! len = (int)(regmatch->endp[i] - regmatch->startp[i]); +! if (len > CMDBUFFSIZE) +! len = CMDBUFFSIZE; +! STRNCAT(fields->module, regmatch->startp[i], len); + } + + return QF_OK; +--- 1183,1208 ---- + * We check for an actual submatch, because "\[" and "\]" in + * the 'errorformat' may cause the wrong submatch to be used. + */ +! for (i = 0; i < FMT_PATTERNS; i++) + { +! status = QF_OK; +! midx = (int)fmt_ptr->addr[i]; +! if (i == 0 && midx > 0) /* %f */ +! status = qf_parse_fmt_f(regmatch, midx, fields, idx); +! else if (i == 5) +! { +! if (fmt_ptr->flags == '+' && !qf_multiscan) /* %+ */ +! status = qf_parse_fmt_plus(linebuf, linelen, fields); +! else if (midx > 0) /* %m */ +! status = qf_parse_fmt_m(regmatch, midx, fields); +! } +! else if (i == 6 && midx > 0) /* %r */ +! status = qf_parse_fmt_r(regmatch, midx, tail); +! else if (midx > 0) /* others */ +! status = (qf_parse_fmt[i])(regmatch, midx, fields); + +! if (status != QF_OK) +! return status; + } + + return QF_OK; +*************** +*** 1308,1313 **** +--- 1496,1513 ---- + } + + /* ++ * Returns TRUE if the specified quickfix/location list is empty. ++ */ ++ static int ++ qf_list_empty(qf_info_T *qi, int qf_idx) ++ { ++ if (qi == NULL || qf_idx < 0 || qf_idx >= LISTCOUNT) ++ return TRUE; ++ return qi->qf_lists[qf_idx].qf_count <= 0; ++ } ++ ++ ++ /* + * Allocate the fields used for parsing lines and populating a quickfix list. + */ + static int +*************** +*** 1450,1456 **** + { + /* Adding to existing list, use last entry. */ + adding = TRUE; +! if (qi->qf_lists[qf_idx].qf_count > 0) + old_last = qi->qf_lists[qf_idx].qf_last; + } + +--- 1650,1656 ---- + { + /* Adding to existing list, use last entry. */ + adding = TRUE; +! if (!qf_list_empty(qi, qf_idx)) + old_last = qi->qf_lists[qf_idx].qf_last; + } + +*************** +*** 1777,1784 **** + qfp->qf_valid = valid; + + lastp = &qi->qf_lists[qf_idx].qf_last; +! if (qi->qf_lists[qf_idx].qf_count == 0) +! /* first element in the list */ + { + qi->qf_lists[qf_idx].qf_start = qfp; + qi->qf_lists[qf_idx].qf_ptr = qfp; +--- 1977,1983 ---- + qfp->qf_valid = valid; + + lastp = &qi->qf_lists[qf_idx].qf_last; +! if (qf_list_empty(qi, qf_idx)) /* first element in the list */ + { + qi->qf_lists[qf_idx].qf_start = qfp; + qi->qf_lists[qf_idx].qf_ptr = qfp; +*************** +*** 1875,1881 **** + to->w_llist->qf_listcount = qi->qf_listcount; + + /* Copy the location lists one at a time */ +! for (idx = 0; idx < qi->qf_listcount; idx++) + { + qf_list_T *from_qfl; + qf_list_T *to_qfl; +--- 2074,2080 ---- + to->w_llist->qf_listcount = qi->qf_listcount; + + /* Copy the location lists one at a time */ +! for (idx = 0; idx < qi->qf_listcount; ++idx) + { + qf_list_T *from_qfl; + qf_list_T *to_qfl; +*************** +*** 2907,2913 **** + qi = &ql_info; + + if (qi->qf_curlist >= qi->qf_listcount +! || qi->qf_lists[qi->qf_curlist].qf_count == 0) + { + EMSG(_(e_quickfix)); + return; +--- 3106,3112 ---- + qi = &ql_info; + + if (qi->qf_curlist >= qi->qf_listcount +! || qf_list_empty(qi, qi->qf_curlist)) + { + EMSG(_(e_quickfix)); + return; +*************** +*** 3033,3056 **** + } + + /* + * ":clist": list all errors + * ":llist": list all locations + */ + void + qf_list(exarg_T *eap) + { +- buf_T *buf; +- char_u *fname; + qfline_T *qfp; + int i; + int idx1 = 1; + int idx2 = -1; + char_u *arg = eap->arg; + int plus = FALSE; +- int qfFileAttr; +- int qfSepAttr; +- int qfLineAttr; +- int filter_entry; + int all = eap->forceit; /* if not :cl!, only show + recognised errors */ + qf_info_T *qi = &ql_info; +--- 3232,3336 ---- + } + + /* ++ * Highlight attributes used for displaying entries from the quickfix list. ++ */ ++ static int qfFileAttr; ++ static int qfSepAttr; ++ static int qfLineAttr; ++ ++ /* ++ * Display information about a single entry from the quickfix/location list. ++ * Used by ":clist/:llist" commands. ++ */ ++ static void ++ qf_list_entry(qf_info_T *qi, qfline_T *qfp, int qf_idx) ++ { ++ char_u *fname; ++ buf_T *buf; ++ int filter_entry; ++ ++ fname = NULL; ++ if (qfp->qf_module != NULL && *qfp->qf_module != NUL) ++ vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", qf_idx, ++ (char *)qfp->qf_module); ++ else { ++ if (qfp->qf_fnum != 0 ++ && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) ++ { ++ fname = buf->b_fname; ++ if (qfp->qf_type == 1) /* :helpgrep */ ++ fname = gettail(fname); ++ } ++ if (fname == NULL) ++ sprintf((char *)IObuff, "%2d", qf_idx); ++ else ++ vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", ++ qf_idx, (char *)fname); ++ } ++ ++ // Support for filtering entries using :filter /pat/ clist ++ // Match against the module name, file name, search pattern and ++ // text of the entry. ++ filter_entry = TRUE; ++ if (qfp->qf_module != NULL && *qfp->qf_module != NUL) ++ filter_entry &= message_filtered(qfp->qf_module); ++ if (filter_entry && fname != NULL) ++ filter_entry &= message_filtered(fname); ++ if (filter_entry && qfp->qf_pattern != NULL) ++ filter_entry &= message_filtered(qfp->qf_pattern); ++ if (filter_entry) ++ filter_entry &= message_filtered(qfp->qf_text); ++ if (filter_entry) ++ return; ++ ++ msg_putchar('\n'); ++ msg_outtrans_attr(IObuff, qf_idx == qi->qf_lists[qi->qf_curlist].qf_index ++ ? HL_ATTR(HLF_QFL) : qfFileAttr); ++ ++ if (qfp->qf_lnum != 0) ++ msg_puts_attr((char_u *)":", qfSepAttr); ++ if (qfp->qf_lnum == 0) ++ IObuff[0] = NUL; ++ else if (qfp->qf_col == 0) ++ sprintf((char *)IObuff, "%ld", qfp->qf_lnum); ++ else ++ sprintf((char *)IObuff, "%ld col %d", ++ qfp->qf_lnum, qfp->qf_col); ++ sprintf((char *)IObuff + STRLEN(IObuff), "%s", ++ (char *)qf_types(qfp->qf_type, qfp->qf_nr)); ++ msg_puts_attr(IObuff, qfLineAttr); ++ msg_puts_attr((char_u *)":", qfSepAttr); ++ if (qfp->qf_pattern != NULL) ++ { ++ qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); ++ msg_puts(IObuff); ++ msg_puts_attr((char_u *)":", qfSepAttr); ++ } ++ msg_puts((char_u *)" "); ++ ++ /* Remove newlines and leading whitespace from the text. For an ++ * unrecognized line keep the indent, the compiler may mark a word ++ * with ^^^^. */ ++ qf_fmt_text((fname != NULL || qfp->qf_lnum != 0) ++ ? skipwhite(qfp->qf_text) : qfp->qf_text, ++ IObuff, IOSIZE); ++ msg_prt_line(IObuff, FALSE); ++ out_flush(); /* show one line at a time */ ++ } ++ ++ /* + * ":clist": list all errors + * ":llist": list all locations + */ + void + qf_list(exarg_T *eap) + { + qfline_T *qfp; + int i; + int idx1 = 1; + int idx2 = -1; + char_u *arg = eap->arg; + int plus = FALSE; + int all = eap->forceit; /* if not :cl!, only show + recognised errors */ + qf_info_T *qi = &ql_info; +*************** +*** 3066,3072 **** + } + + if (qi->qf_curlist >= qi->qf_listcount +! || qi->qf_lists[qi->qf_curlist].qf_count == 0) + { + EMSG(_(e_quickfix)); + return; +--- 3346,3352 ---- + } + + if (qi->qf_curlist >= qi->qf_listcount +! || qf_list_empty(qi, qi->qf_curlist)) + { + EMSG(_(e_quickfix)); + return; +*************** +*** 3123,3197 **** + if (got_int) + break; + +! fname = NULL; +! if (qfp->qf_module != NULL && *qfp->qf_module != NUL) +! vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, (char *)qfp->qf_module); +! else { +! if (qfp->qf_fnum != 0 +! && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) +! { +! fname = buf->b_fname; +! if (qfp->qf_type == 1) /* :helpgrep */ +! fname = gettail(fname); +! } +! if (fname == NULL) +! sprintf((char *)IObuff, "%2d", i); +! else +! vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", +! i, (char *)fname); +! } +! +! // Support for filtering entries using :filter /pat/ clist +! // Match against the module name, file name, search pattern and +! // text of the entry. +! filter_entry = TRUE; +! if (qfp->qf_module != NULL && *qfp->qf_module != NUL) +! filter_entry &= message_filtered(qfp->qf_module); +! if (filter_entry && fname != NULL) +! filter_entry &= message_filtered(fname); +! if (filter_entry && qfp->qf_pattern != NULL) +! filter_entry &= message_filtered(qfp->qf_pattern); +! if (filter_entry) +! filter_entry &= message_filtered(qfp->qf_text); +! if (filter_entry) +! goto next_entry; +! +! msg_putchar('\n'); +! msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index +! ? HL_ATTR(HLF_QFL) : qfFileAttr); +! +! if (qfp->qf_lnum != 0) +! msg_puts_attr((char_u *)":", qfSepAttr); +! if (qfp->qf_lnum == 0) +! IObuff[0] = NUL; +! else if (qfp->qf_col == 0) +! sprintf((char *)IObuff, "%ld", qfp->qf_lnum); +! else +! sprintf((char *)IObuff, "%ld col %d", +! qfp->qf_lnum, qfp->qf_col); +! sprintf((char *)IObuff + STRLEN(IObuff), "%s", +! (char *)qf_types(qfp->qf_type, qfp->qf_nr)); +! msg_puts_attr(IObuff, qfLineAttr); +! msg_puts_attr((char_u *)":", qfSepAttr); +! if (qfp->qf_pattern != NULL) +! { +! qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); +! msg_puts(IObuff); +! msg_puts_attr((char_u *)":", qfSepAttr); +! } +! msg_puts((char_u *)" "); +! +! /* Remove newlines and leading whitespace from the text. For an +! * unrecognized line keep the indent, the compiler may mark a word +! * with ^^^^. */ +! qf_fmt_text((fname != NULL || qfp->qf_lnum != 0) +! ? skipwhite(qfp->qf_text) : qfp->qf_text, +! IObuff, IOSIZE); +! msg_prt_line(IObuff, FALSE); +! out_flush(); /* show one line at a time */ + } + +- next_entry: + qfp = qfp->qf_next; + if (qfp == NULL) + break; +--- 3403,3411 ---- + if (got_int) + break; + +! qf_list_entry(qi, qfp, i); + } + + qfp = qfp->qf_next; + if (qfp == NULL) + break; +*************** +*** 3320,3326 **** + if (eap->cmdidx == CMD_lhistory) + qi = GET_LOC_LIST(curwin); + if (qi == NULL || (qi->qf_listcount == 0 +! && qi->qf_lists[qi->qf_curlist].qf_count == 0)) + MSG(_("No entries")); + else + for (i = 0; i < qi->qf_listcount; ++i) +--- 3534,3540 ---- + if (eap->cmdidx == CMD_lhistory) + qi = GET_LOC_LIST(curwin); + if (qi == NULL || (qi->qf_listcount == 0 +! && qf_list_empty(qi, qi->qf_curlist))) + MSG(_("No entries")); + else + for (i = 0; i < qi->qf_listcount; ++i) +*************** +*** 3421,3427 **** + } + + for (idx = 0; idx < qi->qf_listcount; ++idx) +! if (qi->qf_lists[idx].qf_count) + for (i = 0, qfp = qi->qf_lists[idx].qf_start; + i < qi->qf_lists[idx].qf_count && qfp != NULL; + ++i, qfp = qfp->qf_next) +--- 3635,3641 ---- + } + + for (idx = 0; idx < qi->qf_listcount; ++idx) +! if (!qf_list_empty(qi, idx)) + for (i = 0, qfp = qi->qf_lists[idx].qf_start; + i < qi->qf_lists[idx].qf_count && qfp != NULL; + ++i, qfp = qfp->qf_next) +*************** +*** 3552,3558 **** + * it if we have errors; otherwise, leave it closed. + */ + if (qi->qf_lists[qi->qf_curlist].qf_nonevalid +! || qi->qf_lists[qi->qf_curlist].qf_count == 0 + || qi->qf_curlist >= qi->qf_listcount) + { + if (win != NULL) +--- 3766,3772 ---- + * it if we have errors; otherwise, leave it closed. + */ + if (qi->qf_lists[qi->qf_curlist].qf_nonevalid +! || qf_list_empty(qi, qi->qf_curlist) + || qi->qf_curlist >= qi->qf_listcount) + { + if (win != NULL) +*************** +*** 5154,5160 **** + qi->qf_curlist = qf_id2nr(qi, save_qfid); + + /* Jump to first match. */ +! if (qi->qf_lists[qi->qf_curlist].qf_count > 0) + { + if ((flags & VGR_NOJUMP) == 0) + vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, +--- 5368,5374 ---- + qi->qf_curlist = qf_id2nr(qi, save_qfid); + + /* Jump to first match. */ +! if (!qf_list_empty(qi, qi->qf_curlist)) + { + if ((flags & VGR_NOJUMP) == 0) + vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, +*************** +*** 5387,5394 **** + if (qf_idx == INVALID_QFIDX) + qf_idx = qi->qf_curlist; + +! if (qf_idx >= qi->qf_listcount +! || qi->qf_lists[qf_idx].qf_count == 0) + return FAIL; + + qfp = qi->qf_lists[qf_idx].qf_start; +--- 5601,5607 ---- + if (qf_idx == INVALID_QFIDX) + qf_idx = qi->qf_curlist; + +! if (qf_idx >= qi->qf_listcount || qf_list_empty(qi, qf_idx)) + return FAIL; + + qfp = qi->qf_lists[qf_idx].qf_start; +*************** +*** 5709,5715 **** + qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict) + { + int idx = qi->qf_lists[qf_idx].qf_index; +! if (qi->qf_lists[qf_idx].qf_count == 0) + /* For empty lists, qf_index is set to 1 */ + idx = 0; + return dict_add_number(retdict, "idx", idx); +--- 5922,5928 ---- + qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict) + { + int idx = qi->qf_lists[qf_idx].qf_index; +! if (qf_list_empty(qi, qf_idx)) + /* For empty lists, qf_index is set to 1 */ + idx = 0; + return dict_add_number(retdict, "idx", idx); +*************** +*** 5798,5804 **** + qf_new_list(qi, title); + qf_idx = qi->qf_curlist; + } +! else if (action == 'a' && qi->qf_lists[qf_idx].qf_count > 0) + /* Adding to existing list, use last entry. */ + old_last = qi->qf_lists[qf_idx].qf_last; + else if (action == 'r') +--- 6011,6017 ---- + qf_new_list(qi, title); + qf_idx = qi->qf_curlist; + } +! else if (action == 'a' && !qf_list_empty(qi, qf_idx)) + /* Adding to existing list, use last entry. */ + old_last = qi->qf_lists[qf_idx].qf_last; + else if (action == 'r') +*************** +*** 5887,5893 **** + { + qi->qf_lists[qf_idx].qf_ptr = + qi->qf_lists[qf_idx].qf_start; +! if (qi->qf_lists[qf_idx].qf_count > 0) + qi->qf_lists[qf_idx].qf_index = 1; + } + +--- 6100,6106 ---- + { + qi->qf_lists[qf_idx].qf_ptr = + qi->qf_lists[qf_idx].qf_start; +! if (!qf_list_empty(qi, qf_idx)) + qi->qf_lists[qf_idx].qf_index = 1; + } + +*************** +*** 6746,6752 **** + } + + /* Jump to first match. */ +! if (qi->qf_lists[qi->qf_curlist].qf_count > 0) + qf_jump(qi, 0, 0, FALSE); + else + EMSG2(_(e_nomatch2), eap->arg); +--- 6959,6965 ---- + } + + /* Jump to first match. */ +! if (!qf_list_empty(qi, qi->qf_curlist)) + qf_jump(qi, 0, 0, FALSE); + else + EMSG2(_(e_nomatch2), eap->arg); +*** ../vim-8.1.0251/src/version.c 2018-08-07 21:39:09.251060096 +0200 +--- src/version.c 2018-08-07 21:43:08.981711510 +0200 +*************** +*** 796,797 **** +--- 796,799 ---- + { /* Add new patch number below this line */ ++ /**/ ++ 252, + /**/ + +-- +Q: How do you tell the difference between a female cat and a male cat? +A: You ask it a question and if HE answers, it's a male but, if SHE + answers, it's a female. + + /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ +/// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ +\\\ an exciting new programming language -- http://www.Zimbu.org /// + \\\ help me help AIDS victims -- http://ICCF-Holland.org /// |