1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/nsprpub/pr/src/io/prscanf.c Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,634 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +/* 1.10 + * Scan functions for NSPR types 1.11 + * 1.12 + * Author: Wan-Teh Chang 1.13 + * 1.14 + * Acknowledgment: The implementation is inspired by the source code 1.15 + * in P.J. Plauger's "The Standard C Library," Prentice-Hall, 1992. 1.16 + */ 1.17 + 1.18 +#include <limits.h> 1.19 +#include <ctype.h> 1.20 +#include <string.h> 1.21 +#include <stdlib.h> 1.22 +#include "prprf.h" 1.23 +#include "prdtoa.h" 1.24 +#include "prlog.h" 1.25 +#include "prerror.h" 1.26 + 1.27 +/* 1.28 + * A function that reads a character from 'stream'. 1.29 + * Returns the character read, or EOF if end of stream is reached. 1.30 + */ 1.31 +typedef int (*_PRGetCharFN)(void *stream); 1.32 + 1.33 +/* 1.34 + * A function that pushes the character 'ch' back to 'stream'. 1.35 + */ 1.36 +typedef void (*_PRUngetCharFN)(void *stream, int ch); 1.37 + 1.38 +/* 1.39 + * The size specifier for the integer and floating point number 1.40 + * conversions in format control strings. 1.41 + */ 1.42 +typedef enum { 1.43 + _PR_size_none, /* No size specifier is given */ 1.44 + _PR_size_h, /* The 'h' specifier, suggesting "short" */ 1.45 + _PR_size_l, /* The 'l' specifier, suggesting "long" */ 1.46 + _PR_size_L, /* The 'L' specifier, meaning a 'long double' */ 1.47 + _PR_size_ll /* The 'll' specifier, suggesting "long long" */ 1.48 +} _PRSizeSpec; 1.49 + 1.50 +/* 1.51 + * The collection of data that is passed between the scan function 1.52 + * and its subordinate functions. The fields of this structure 1.53 + * serve as the input or output arguments for these functions. 1.54 + */ 1.55 +typedef struct { 1.56 + _PRGetCharFN get; /* get a character from input stream */ 1.57 + _PRUngetCharFN unget; /* unget (push back) a character */ 1.58 + void *stream; /* argument for get and unget */ 1.59 + va_list ap; /* the variable argument list */ 1.60 + int nChar; /* number of characters read from 'stream' */ 1.61 + 1.62 + PRBool assign; /* assign, or suppress assignment? */ 1.63 + int width; /* field width */ 1.64 + _PRSizeSpec sizeSpec; /* 'h', 'l', 'L', or 'll' */ 1.65 + 1.66 + PRBool converted; /* is the value actually converted? */ 1.67 +} ScanfState; 1.68 + 1.69 +#define GET(state) ((state)->nChar++, (state)->get((state)->stream)) 1.70 +#define UNGET(state, ch) \ 1.71 + ((state)->nChar--, (state)->unget((state)->stream, ch)) 1.72 + 1.73 +/* 1.74 + * The following two macros, GET_IF_WITHIN_WIDTH and WITHIN_WIDTH, 1.75 + * are always used together. 1.76 + * 1.77 + * GET_IF_WITHIN_WIDTH calls the GET macro and assigns its return 1.78 + * value to 'ch' only if we have not exceeded the field width of 1.79 + * 'state'. Therefore, after GET_IF_WITHIN_WIDTH, the value of 1.80 + * 'ch' is valid only if the macro WITHIN_WIDTH evaluates to true. 1.81 + */ 1.82 + 1.83 +#define GET_IF_WITHIN_WIDTH(state, ch) \ 1.84 + if (--(state)->width >= 0) { \ 1.85 + (ch) = GET(state); \ 1.86 + } 1.87 +#define WITHIN_WIDTH(state) ((state)->width >= 0) 1.88 + 1.89 +/* 1.90 + * _pr_strtoull: 1.91 + * Convert a string to an unsigned 64-bit integer. The string 1.92 + * 'str' is assumed to be a representation of the integer in 1.93 + * base 'base'. 1.94 + * 1.95 + * Warning: 1.96 + * - Only handle base 8, 10, and 16. 1.97 + * - No overflow checking. 1.98 + */ 1.99 + 1.100 +static PRUint64 1.101 +_pr_strtoull(const char *str, char **endptr, int base) 1.102 +{ 1.103 + static const int BASE_MAX = 16; 1.104 + static const char digits[] = "0123456789abcdef"; 1.105 + char *digitPtr; 1.106 + PRUint64 x; /* return value */ 1.107 + PRInt64 base64; 1.108 + const char *cPtr; 1.109 + PRBool negative; 1.110 + const char *digitStart; 1.111 + 1.112 + PR_ASSERT(base == 0 || base == 8 || base == 10 || base == 16); 1.113 + if (base < 0 || base == 1 || base > BASE_MAX) { 1.114 + if (endptr) { 1.115 + *endptr = (char *) str; 1.116 + return LL_ZERO; 1.117 + } 1.118 + } 1.119 + 1.120 + cPtr = str; 1.121 + while (isspace(*cPtr)) { 1.122 + ++cPtr; 1.123 + } 1.124 + 1.125 + negative = PR_FALSE; 1.126 + if (*cPtr == '-') { 1.127 + negative = PR_TRUE; 1.128 + cPtr++; 1.129 + } else if (*cPtr == '+') { 1.130 + cPtr++; 1.131 + } 1.132 + 1.133 + if (base == 16) { 1.134 + if (*cPtr == '0' && (cPtr[1] == 'x' || cPtr[1] == 'X')) { 1.135 + cPtr += 2; 1.136 + } 1.137 + } else if (base == 0) { 1.138 + if (*cPtr != '0') { 1.139 + base = 10; 1.140 + } else if (cPtr[1] == 'x' || cPtr[1] == 'X') { 1.141 + base = 16; 1.142 + cPtr += 2; 1.143 + } else { 1.144 + base = 8; 1.145 + } 1.146 + } 1.147 + PR_ASSERT(base != 0); 1.148 + LL_I2L(base64, base); 1.149 + digitStart = cPtr; 1.150 + 1.151 + /* Skip leading zeros */ 1.152 + while (*cPtr == '0') { 1.153 + cPtr++; 1.154 + } 1.155 + 1.156 + LL_I2L(x, 0); 1.157 + while ((digitPtr = (char*)memchr(digits, tolower(*cPtr), base)) != NULL) { 1.158 + PRUint64 d; 1.159 + 1.160 + LL_I2L(d, (digitPtr - digits)); 1.161 + LL_MUL(x, x, base64); 1.162 + LL_ADD(x, x, d); 1.163 + cPtr++; 1.164 + } 1.165 + 1.166 + if (cPtr == digitStart) { 1.167 + if (endptr) { 1.168 + *endptr = (char *) str; 1.169 + } 1.170 + return LL_ZERO; 1.171 + } 1.172 + 1.173 + if (negative) { 1.174 +#ifdef HAVE_LONG_LONG 1.175 + /* The cast to a signed type is to avoid a compiler warning */ 1.176 + x = -(PRInt64)x; 1.177 +#else 1.178 + LL_NEG(x, x); 1.179 +#endif 1.180 + } 1.181 + 1.182 + if (endptr) { 1.183 + *endptr = (char *) cPtr; 1.184 + } 1.185 + return x; 1.186 +} 1.187 + 1.188 +/* 1.189 + * The maximum field width (in number of characters) that is enough 1.190 + * (may be more than necessary) to represent a 64-bit integer or 1.191 + * floating point number. 1.192 + */ 1.193 +#define FMAX 31 1.194 +#define DECIMAL_POINT '.' 1.195 + 1.196 +static PRStatus 1.197 +GetInt(ScanfState *state, int code) 1.198 +{ 1.199 + char buf[FMAX + 1], *p; 1.200 + int ch; 1.201 + static const char digits[] = "0123456789abcdefABCDEF"; 1.202 + PRBool seenDigit = PR_FALSE; 1.203 + int base; 1.204 + int dlen; 1.205 + 1.206 + switch (code) { 1.207 + case 'd': case 'u': 1.208 + base = 10; 1.209 + break; 1.210 + case 'i': 1.211 + base = 0; 1.212 + break; 1.213 + case 'x': case 'X': case 'p': 1.214 + base = 16; 1.215 + break; 1.216 + case 'o': 1.217 + base = 8; 1.218 + break; 1.219 + default: 1.220 + return PR_FAILURE; 1.221 + } 1.222 + if (state->width == 0 || state->width > FMAX) { 1.223 + state->width = FMAX; 1.224 + } 1.225 + p = buf; 1.226 + GET_IF_WITHIN_WIDTH(state, ch); 1.227 + if (WITHIN_WIDTH(state) && (ch == '+' || ch == '-')) { 1.228 + *p++ = ch; 1.229 + GET_IF_WITHIN_WIDTH(state, ch); 1.230 + } 1.231 + if (WITHIN_WIDTH(state) && ch == '0') { 1.232 + seenDigit = PR_TRUE; 1.233 + *p++ = ch; 1.234 + GET_IF_WITHIN_WIDTH(state, ch); 1.235 + if (WITHIN_WIDTH(state) 1.236 + && (ch == 'x' || ch == 'X') 1.237 + && (base == 0 || base == 16)) { 1.238 + base = 16; 1.239 + *p++ = ch; 1.240 + GET_IF_WITHIN_WIDTH(state, ch); 1.241 + } else if (base == 0) { 1.242 + base = 8; 1.243 + } 1.244 + } 1.245 + if (base == 0 || base == 10) { 1.246 + dlen = 10; 1.247 + } else if (base == 8) { 1.248 + dlen = 8; 1.249 + } else { 1.250 + PR_ASSERT(base == 16); 1.251 + dlen = 16 + 6; /* 16 digits, plus 6 in uppercase */ 1.252 + } 1.253 + while (WITHIN_WIDTH(state) && memchr(digits, ch, dlen)) { 1.254 + *p++ = ch; 1.255 + GET_IF_WITHIN_WIDTH(state, ch); 1.256 + seenDigit = PR_TRUE; 1.257 + } 1.258 + if (WITHIN_WIDTH(state)) { 1.259 + UNGET(state, ch); 1.260 + } 1.261 + if (!seenDigit) { 1.262 + return PR_FAILURE; 1.263 + } 1.264 + *p = '\0'; 1.265 + if (state->assign) { 1.266 + if (code == 'd' || code == 'i') { 1.267 + if (state->sizeSpec == _PR_size_ll) { 1.268 + PRInt64 llval = _pr_strtoull(buf, NULL, base); 1.269 + *va_arg(state->ap, PRInt64 *) = llval; 1.270 + } else { 1.271 + long lval = strtol(buf, NULL, base); 1.272 + 1.273 + if (state->sizeSpec == _PR_size_none) { 1.274 + *va_arg(state->ap, PRIntn *) = lval; 1.275 + } else if (state->sizeSpec == _PR_size_h) { 1.276 + *va_arg(state->ap, PRInt16 *) = (PRInt16)lval; 1.277 + } else if (state->sizeSpec == _PR_size_l) { 1.278 + *va_arg(state->ap, PRInt32 *) = lval; 1.279 + } else { 1.280 + return PR_FAILURE; 1.281 + } 1.282 + } 1.283 + } else { 1.284 + if (state->sizeSpec == _PR_size_ll) { 1.285 + PRUint64 llval = _pr_strtoull(buf, NULL, base); 1.286 + *va_arg(state->ap, PRUint64 *) = llval; 1.287 + } else { 1.288 + unsigned long lval = strtoul(buf, NULL, base); 1.289 + 1.290 + if (state->sizeSpec == _PR_size_none) { 1.291 + *va_arg(state->ap, PRUintn *) = lval; 1.292 + } else if (state->sizeSpec == _PR_size_h) { 1.293 + *va_arg(state->ap, PRUint16 *) = (PRUint16)lval; 1.294 + } else if (state->sizeSpec == _PR_size_l) { 1.295 + *va_arg(state->ap, PRUint32 *) = lval; 1.296 + } else { 1.297 + return PR_FAILURE; 1.298 + } 1.299 + } 1.300 + } 1.301 + state->converted = PR_TRUE; 1.302 + } 1.303 + return PR_SUCCESS; 1.304 +} 1.305 + 1.306 +static PRStatus 1.307 +GetFloat(ScanfState *state) 1.308 +{ 1.309 + char buf[FMAX + 1], *p; 1.310 + int ch; 1.311 + PRBool seenDigit = PR_FALSE; 1.312 + 1.313 + if (state->width == 0 || state->width > FMAX) { 1.314 + state->width = FMAX; 1.315 + } 1.316 + p = buf; 1.317 + GET_IF_WITHIN_WIDTH(state, ch); 1.318 + if (WITHIN_WIDTH(state) && (ch == '+' || ch == '-')) { 1.319 + *p++ = ch; 1.320 + GET_IF_WITHIN_WIDTH(state, ch); 1.321 + } 1.322 + while (WITHIN_WIDTH(state) && isdigit(ch)) { 1.323 + *p++ = ch; 1.324 + GET_IF_WITHIN_WIDTH(state, ch); 1.325 + seenDigit = PR_TRUE; 1.326 + } 1.327 + if (WITHIN_WIDTH(state) && ch == DECIMAL_POINT) { 1.328 + *p++ = ch; 1.329 + GET_IF_WITHIN_WIDTH(state, ch); 1.330 + while (WITHIN_WIDTH(state) && isdigit(ch)) { 1.331 + *p++ = ch; 1.332 + GET_IF_WITHIN_WIDTH(state, ch); 1.333 + seenDigit = PR_TRUE; 1.334 + } 1.335 + } 1.336 + 1.337 + /* 1.338 + * This is not robust. For example, "1.2e+" would confuse 1.339 + * the code below to read 'e' and '+', only to realize that 1.340 + * it should have stopped at "1.2". But we can't push back 1.341 + * more than one character, so there is nothing I can do. 1.342 + */ 1.343 + 1.344 + /* Parse exponent */ 1.345 + if (WITHIN_WIDTH(state) && (ch == 'e' || ch == 'E') && seenDigit) { 1.346 + *p++ = ch; 1.347 + GET_IF_WITHIN_WIDTH(state, ch); 1.348 + if (WITHIN_WIDTH(state) && (ch == '+' || ch == '-')) { 1.349 + *p++ = ch; 1.350 + GET_IF_WITHIN_WIDTH(state, ch); 1.351 + } 1.352 + while (WITHIN_WIDTH(state) && isdigit(ch)) { 1.353 + *p++ = ch; 1.354 + GET_IF_WITHIN_WIDTH(state, ch); 1.355 + } 1.356 + } 1.357 + if (WITHIN_WIDTH(state)) { 1.358 + UNGET(state, ch); 1.359 + } 1.360 + if (!seenDigit) { 1.361 + return PR_FAILURE; 1.362 + } 1.363 + *p = '\0'; 1.364 + if (state->assign) { 1.365 + PRFloat64 dval = PR_strtod(buf, NULL); 1.366 + 1.367 + state->converted = PR_TRUE; 1.368 + if (state->sizeSpec == _PR_size_l) { 1.369 + *va_arg(state->ap, PRFloat64 *) = dval; 1.370 + } else if (state->sizeSpec == _PR_size_L) { 1.371 +#if defined(OSF1) || defined(IRIX) 1.372 + *va_arg(state->ap, double *) = dval; 1.373 +#else 1.374 + *va_arg(state->ap, long double *) = dval; 1.375 +#endif 1.376 + } else { 1.377 + *va_arg(state->ap, float *) = (float) dval; 1.378 + } 1.379 + } 1.380 + return PR_SUCCESS; 1.381 +} 1.382 + 1.383 +/* 1.384 + * Convert, and return the end of the conversion spec. 1.385 + * Return NULL on error. 1.386 + */ 1.387 + 1.388 +static const char * 1.389 +Convert(ScanfState *state, const char *fmt) 1.390 +{ 1.391 + const char *cPtr; 1.392 + int ch; 1.393 + char *cArg = NULL; 1.394 + 1.395 + state->converted = PR_FALSE; 1.396 + cPtr = fmt; 1.397 + if (*cPtr != 'c' && *cPtr != 'n' && *cPtr != '[') { 1.398 + do { 1.399 + ch = GET(state); 1.400 + } while (isspace(ch)); 1.401 + UNGET(state, ch); 1.402 + } 1.403 + switch (*cPtr) { 1.404 + case 'c': 1.405 + if (state->assign) { 1.406 + cArg = va_arg(state->ap, char *); 1.407 + } 1.408 + if (state->width == 0) { 1.409 + state->width = 1; 1.410 + } 1.411 + for (; state->width > 0; state->width--) { 1.412 + ch = GET(state); 1.413 + if (ch == EOF) { 1.414 + return NULL; 1.415 + } else if (state->assign) { 1.416 + *cArg++ = ch; 1.417 + } 1.418 + } 1.419 + if (state->assign) { 1.420 + state->converted = PR_TRUE; 1.421 + } 1.422 + break; 1.423 + case 'p': 1.424 + case 'd': case 'i': case 'o': 1.425 + case 'u': case 'x': case 'X': 1.426 + if (GetInt(state, *cPtr) == PR_FAILURE) { 1.427 + return NULL; 1.428 + } 1.429 + break; 1.430 + case 'e': case 'E': case 'f': 1.431 + case 'g': case 'G': 1.432 + if (GetFloat(state) == PR_FAILURE) { 1.433 + return NULL; 1.434 + } 1.435 + break; 1.436 + case 'n': 1.437 + /* do not consume any input */ 1.438 + if (state->assign) { 1.439 + switch (state->sizeSpec) { 1.440 + case _PR_size_none: 1.441 + *va_arg(state->ap, PRIntn *) = state->nChar; 1.442 + break; 1.443 + case _PR_size_h: 1.444 + *va_arg(state->ap, PRInt16 *) = state->nChar; 1.445 + break; 1.446 + case _PR_size_l: 1.447 + *va_arg(state->ap, PRInt32 *) = state->nChar; 1.448 + break; 1.449 + case _PR_size_ll: 1.450 + LL_I2L(*va_arg(state->ap, PRInt64 *), state->nChar); 1.451 + break; 1.452 + default: 1.453 + PR_ASSERT(0); 1.454 + } 1.455 + } 1.456 + break; 1.457 + case 's': 1.458 + if (state->width == 0) { 1.459 + state->width = INT_MAX; 1.460 + } 1.461 + if (state->assign) { 1.462 + cArg = va_arg(state->ap, char *); 1.463 + } 1.464 + for (; state->width > 0; state->width--) { 1.465 + ch = GET(state); 1.466 + if ((ch == EOF) || isspace(ch)) { 1.467 + UNGET(state, ch); 1.468 + break; 1.469 + } 1.470 + if (state->assign) { 1.471 + *cArg++ = ch; 1.472 + } 1.473 + } 1.474 + if (state->assign) { 1.475 + *cArg = '\0'; 1.476 + state->converted = PR_TRUE; 1.477 + } 1.478 + break; 1.479 + case '%': 1.480 + ch = GET(state); 1.481 + if (ch != '%') { 1.482 + UNGET(state, ch); 1.483 + return NULL; 1.484 + } 1.485 + break; 1.486 + case '[': 1.487 + { 1.488 + PRBool complement = PR_FALSE; 1.489 + const char *closeBracket; 1.490 + size_t n; 1.491 + 1.492 + if (*++cPtr == '^') { 1.493 + complement = PR_TRUE; 1.494 + cPtr++; 1.495 + } 1.496 + closeBracket = strchr(*cPtr == ']' ? cPtr + 1 : cPtr, ']'); 1.497 + if (closeBracket == NULL) { 1.498 + return NULL; 1.499 + } 1.500 + n = closeBracket - cPtr; 1.501 + if (state->width == 0) { 1.502 + state->width = INT_MAX; 1.503 + } 1.504 + if (state->assign) { 1.505 + cArg = va_arg(state->ap, char *); 1.506 + } 1.507 + for (; state->width > 0; state->width--) { 1.508 + ch = GET(state); 1.509 + if ((ch == EOF) 1.510 + || (!complement && !memchr(cPtr, ch, n)) 1.511 + || (complement && memchr(cPtr, ch, n))) { 1.512 + UNGET(state, ch); 1.513 + break; 1.514 + } 1.515 + if (state->assign) { 1.516 + *cArg++ = ch; 1.517 + } 1.518 + } 1.519 + if (state->assign) { 1.520 + *cArg = '\0'; 1.521 + state->converted = PR_TRUE; 1.522 + } 1.523 + cPtr = closeBracket; 1.524 + } 1.525 + break; 1.526 + default: 1.527 + return NULL; 1.528 + } 1.529 + return cPtr; 1.530 +} 1.531 + 1.532 +static PRInt32 1.533 +DoScanf(ScanfState *state, const char *fmt) 1.534 +{ 1.535 + PRInt32 nConverted = 0; 1.536 + const char *cPtr; 1.537 + int ch; 1.538 + 1.539 + state->nChar = 0; 1.540 + cPtr = fmt; 1.541 + while (1) { 1.542 + if (isspace(*cPtr)) { 1.543 + /* white space: skip */ 1.544 + do { 1.545 + cPtr++; 1.546 + } while (isspace(*cPtr)); 1.547 + do { 1.548 + ch = GET(state); 1.549 + } while (isspace(ch)); 1.550 + UNGET(state, ch); 1.551 + } else if (*cPtr == '%') { 1.552 + /* format spec: convert */ 1.553 + cPtr++; 1.554 + state->assign = PR_TRUE; 1.555 + if (*cPtr == '*') { 1.556 + cPtr++; 1.557 + state->assign = PR_FALSE; 1.558 + } 1.559 + for (state->width = 0; isdigit(*cPtr); cPtr++) { 1.560 + state->width = state->width * 10 + *cPtr - '0'; 1.561 + } 1.562 + state->sizeSpec = _PR_size_none; 1.563 + if (*cPtr == 'h') { 1.564 + cPtr++; 1.565 + state->sizeSpec = _PR_size_h; 1.566 + } else if (*cPtr == 'l') { 1.567 + cPtr++; 1.568 + if (*cPtr == 'l') { 1.569 + cPtr++; 1.570 + state->sizeSpec = _PR_size_ll; 1.571 + } else { 1.572 + state->sizeSpec = _PR_size_l; 1.573 + } 1.574 + } else if (*cPtr == 'L') { 1.575 + cPtr++; 1.576 + state->sizeSpec = _PR_size_L; 1.577 + } 1.578 + cPtr = Convert(state, cPtr); 1.579 + if (cPtr == NULL) { 1.580 + return (nConverted > 0 ? nConverted : EOF); 1.581 + } 1.582 + if (state->converted) { 1.583 + nConverted++; 1.584 + } 1.585 + cPtr++; 1.586 + } else { 1.587 + /* others: must match */ 1.588 + if (*cPtr == '\0') { 1.589 + return nConverted; 1.590 + } 1.591 + ch = GET(state); 1.592 + if (ch != *cPtr) { 1.593 + UNGET(state, ch); 1.594 + return nConverted; 1.595 + } 1.596 + cPtr++; 1.597 + } 1.598 + } 1.599 +} 1.600 + 1.601 +static int 1.602 +StringGetChar(void *stream) 1.603 +{ 1.604 + char *cPtr = *((char **) stream); 1.605 + 1.606 + if (*cPtr == '\0') { 1.607 + return EOF; 1.608 + } else { 1.609 + *((char **) stream) = cPtr + 1; 1.610 + return (unsigned char) *cPtr; 1.611 + } 1.612 +} 1.613 + 1.614 +static void 1.615 +StringUngetChar(void *stream, int ch) 1.616 +{ 1.617 + char *cPtr = *((char **) stream); 1.618 + 1.619 + if (ch != EOF) { 1.620 + *((char **) stream) = cPtr - 1; 1.621 + } 1.622 +} 1.623 + 1.624 +PR_IMPLEMENT(PRInt32) 1.625 +PR_sscanf(const char *buf, const char *fmt, ...) 1.626 +{ 1.627 + PRInt32 rv; 1.628 + ScanfState state; 1.629 + 1.630 + state.get = &StringGetChar; 1.631 + state.unget = &StringUngetChar; 1.632 + state.stream = (void *) &buf; 1.633 + va_start(state.ap, fmt); 1.634 + rv = DoScanf(&state, fmt); 1.635 + va_end(state.ap); 1.636 + return rv; 1.637 +}