1 /* 2 * Copyright 2008 Jacek Caban for CodeWeavers 3 * 4 * This library is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU Lesser General Public 6 * License as published by the Free Software Foundation; either 7 * version 2.1 of the License, or (at your option) any later version. 8 * 9 * This library is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 * Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public 15 * License along with this library; if not, write to the Free Software 16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 17 */ 18 19 #include "jscript.h" 20 21 typedef struct { 22 jsdisp_t dispex; 23 24 double value; 25 } NumberInstance; 26 27 static const WCHAR toStringW[] = {'t','o','S','t','r','i','n','g',0}; 28 static const WCHAR toLocaleStringW[] = {'t','o','L','o','c','a','l','e','S','t','r','i','n','g',0}; 29 static const WCHAR toFixedW[] = {'t','o','F','i','x','e','d',0}; 30 static const WCHAR toExponentialW[] = {'t','o','E','x','p','o','n','e','n','t','i','a','l',0}; 31 static const WCHAR toPrecisionW[] = {'t','o','P','r','e','c','i','s','i','o','n',0}; 32 static const WCHAR valueOfW[] = {'v','a','l','u','e','O','f',0}; 33 34 #define NUMBER_TOSTRING_BUF_SIZE 64 35 #define NUMBER_DTOA_SIZE 18 36 37 static inline NumberInstance *number_from_jsdisp(jsdisp_t *jsdisp) 38 { 39 return CONTAINING_RECORD(jsdisp, NumberInstance, dispex); 40 } 41 42 static inline NumberInstance *number_from_vdisp(vdisp_t *vdisp) 43 { 44 return number_from_jsdisp(vdisp->u.jsdisp); 45 } 46 47 static inline NumberInstance *number_this(vdisp_t *jsthis) 48 { 49 return is_vclass(jsthis, JSCLASS_NUMBER) ? number_from_vdisp(jsthis) : NULL; 50 } 51 52 static inline void number_to_str(double d, WCHAR *buf, int size, int *dec_point) 53 { 54 ULONGLONG l; 55 int i; 56 57 /* TODO: this function should print doubles with bigger precision */ 58 assert(size>=2 && size<=NUMBER_DTOA_SIZE && d>=0); 59 60 if(d == 0) 61 *dec_point = 0; 62 else 63 *dec_point = floor(log10(d)); 64 l = d*pow(10, size-*dec_point-1); 65 66 if(l%10 >= 5) 67 l = l/10+1; 68 else 69 l /= 10; 70 71 buf[size-1] = 0; 72 for(i=size-2; i>=0; i--) { 73 buf[i] = '0'+l%10; 74 l /= 10; 75 } 76 77 /* log10 was wrong by 1 or rounding changed number of digits */ 78 if(l) { 79 (*dec_point)++; 80 memmove(buf+1, buf, size-2); 81 buf[0] = '0'+l; 82 }else if(buf[0]=='0' && buf[1]>='1' && buf[1]<='9') { 83 (*dec_point)--; 84 memmove(buf, buf+1, size-2); 85 buf[size-2] = '0'; 86 } 87 } 88 89 static inline jsstr_t *number_to_fixed(double val, int prec) 90 { 91 WCHAR buf[NUMBER_DTOA_SIZE]; 92 int dec_point, size, buf_size, buf_pos; 93 BOOL neg = FALSE; 94 jsstr_t *ret; 95 WCHAR *str; 96 97 TRACE("%lf %d\n", val, prec); 98 99 if(val < 0) { 100 neg = TRUE; 101 val = -val; 102 } 103 104 if(val >= 1) 105 buf_size = log10(val)+prec+2; 106 else 107 buf_size = prec ? prec+1 : 2; 108 if(buf_size > NUMBER_DTOA_SIZE) 109 buf_size = NUMBER_DTOA_SIZE; 110 111 number_to_str(val, buf, buf_size, &dec_point); 112 dec_point++; 113 size = 0; 114 if(neg) 115 size++; 116 if(dec_point > 0) 117 size += dec_point; 118 else 119 size++; 120 if(prec) 121 size += prec+1; 122 123 ret = jsstr_alloc_buf(size, &str); 124 if(!ret) 125 return NULL; 126 127 size = buf_pos = 0; 128 if(neg) 129 str[size++] = '-'; 130 if(dec_point > 0) { 131 for(;buf_pos<buf_size-1 && dec_point; dec_point--) 132 str[size++] = buf[buf_pos++]; 133 }else { 134 str[size++] = '0'; 135 } 136 for(; dec_point>0; dec_point--) 137 str[size++] = '0'; 138 if(prec) { 139 str[size++] = '.'; 140 141 for(; dec_point<0 && prec; dec_point++, prec--) 142 str[size++] = '0'; 143 for(; buf_pos<buf_size-1 && prec; prec--) 144 str[size++] = buf[buf_pos++]; 145 for(; prec; prec--) { 146 str[size++] = '0'; 147 } 148 } 149 str[size++] = 0; 150 return ret; 151 } 152 153 static inline jsstr_t *number_to_exponential(double val, int prec) 154 { 155 WCHAR buf[NUMBER_DTOA_SIZE], *pbuf; 156 int dec_point, size, buf_size, exp_size = 1; 157 BOOL neg = FALSE; 158 jsstr_t *ret; 159 WCHAR *str; 160 161 if(val < 0) { 162 neg = TRUE; 163 val = -val; 164 } 165 166 buf_size = prec+2; 167 if(buf_size<2 || buf_size>NUMBER_DTOA_SIZE) 168 buf_size = NUMBER_DTOA_SIZE; 169 number_to_str(val, buf, buf_size, &dec_point); 170 buf_size--; 171 if(prec == -1) 172 for(; buf_size>1 && buf[buf_size-1]=='0'; buf_size--) 173 buf[buf_size-1] = 0; 174 175 size = 10; 176 while(dec_point>=size || dec_point<=-size) { 177 size *= 10; 178 exp_size++; 179 } 180 181 if(buf_size == 1) 182 size = buf_size+2+exp_size; /* 2 = strlen(e+) */ 183 else if(prec == -1) 184 size = buf_size+3+exp_size; /* 3 = strlen(.e+) */ 185 else 186 size = prec+4+exp_size; /* 4 = strlen(0.e+) */ 187 if(neg) 188 size++; 189 190 ret = jsstr_alloc_buf(size, &str); 191 if(!ret) 192 return NULL; 193 194 size = 0; 195 pbuf = buf; 196 if(neg) 197 str[size++] = '-'; 198 str[size++] = *pbuf++; 199 if(buf_size != 1) { 200 str[size++] = '.'; 201 while(*pbuf) 202 str[size++] = *pbuf++; 203 for(; prec>buf_size-1; prec--) 204 str[size++] = '0'; 205 } 206 str[size++] = 'e'; 207 if(dec_point >= 0) { 208 str[size++] = '+'; 209 }else { 210 str[size++] = '-'; 211 dec_point = -dec_point; 212 } 213 size += exp_size; 214 do { 215 str[--size] = '0'+dec_point%10; 216 dec_point /= 10; 217 }while(dec_point>0); 218 size += exp_size; 219 str[size] = 0; 220 221 return ret; 222 } 223 224 /* ECMA-262 3rd Edition 15.7.4.2 */ 225 static HRESULT Number_toString(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, 226 jsval_t *r) 227 { 228 NumberInstance *number; 229 INT radix = 10; 230 DOUBLE val; 231 jsstr_t *str; 232 HRESULT hres; 233 234 TRACE("\n"); 235 236 if(!(number = number_this(jsthis))) 237 return throw_type_error(ctx, JS_E_NUMBER_EXPECTED, NULL); 238 239 if(argc) { 240 hres = to_int32(ctx, argv[0], &radix); 241 if(FAILED(hres)) 242 return hres; 243 244 if(radix<2 || radix>36) 245 return throw_type_error(ctx, JS_E_INVALIDARG, NULL); 246 } 247 248 val = number->value; 249 250 if(radix==10 || !is_finite(val)) { 251 hres = to_string(ctx, jsval_number(val), &str); 252 if(FAILED(hres)) 253 return hres; 254 }else { 255 INT idx = 0; 256 DOUBLE integ, frac, log_radix = 0; 257 WCHAR buf[NUMBER_TOSTRING_BUF_SIZE+16]; 258 BOOL exp = FALSE; 259 260 if(val<0) { 261 val = -val; 262 buf[idx++] = '-'; 263 } 264 265 while(1) { 266 integ = floor(val); 267 frac = val-integ; 268 269 if(integ == 0) 270 buf[idx++] = '0'; 271 while(integ>=1 && idx<NUMBER_TOSTRING_BUF_SIZE) { 272 buf[idx] = fmod(integ, radix); 273 if(buf[idx]<10) buf[idx] += '0'; 274 else buf[idx] += 'a'-10; 275 integ /= radix; 276 idx++; 277 } 278 279 if(idx<NUMBER_TOSTRING_BUF_SIZE) { 280 INT beg = buf[0]=='-'?1:0; 281 INT end = idx-1; 282 WCHAR wch; 283 284 while(end > beg) { 285 wch = buf[beg]; 286 buf[beg++] = buf[end]; 287 buf[end--] = wch; 288 } 289 } 290 291 if(idx != NUMBER_TOSTRING_BUF_SIZE) buf[idx++] = '.'; 292 293 while(frac>0 && idx<NUMBER_TOSTRING_BUF_SIZE) { 294 frac *= radix; 295 buf[idx] = fmod(frac, radix); 296 frac -= buf[idx]; 297 if(buf[idx]<10) buf[idx] += '0'; 298 else buf[idx] += 'a'-10; 299 idx++; 300 } 301 302 if(idx==NUMBER_TOSTRING_BUF_SIZE && !exp) { 303 exp = TRUE; 304 idx = (buf[0]=='-') ? 1 : 0; 305 log_radix = floor(log(val)/log(radix)); 306 val *= pow(radix, -log_radix); 307 continue; 308 } 309 310 break; 311 } 312 313 while(buf[idx-1] == '0') idx--; 314 if(buf[idx-1] == '.') idx--; 315 316 if(exp) { 317 if(log_radix==0) 318 buf[idx] = 0; 319 else { 320 static const WCHAR formatW[] = {'(','e','%','c','%','d',')',0}; 321 WCHAR ch; 322 323 if(log_radix<0) { 324 log_radix = -log_radix; 325 ch = '-'; 326 } 327 else ch = '+'; 328 sprintfW(&buf[idx], formatW, ch, (int)log_radix); 329 } 330 } 331 else buf[idx] = '\0'; 332 333 str = jsstr_alloc(buf); 334 if(!str) 335 return E_OUTOFMEMORY; 336 } 337 338 if(r) 339 *r = jsval_string(str); 340 else 341 jsstr_release(str); 342 return S_OK; 343 } 344 345 static HRESULT Number_toLocaleString(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, 346 jsval_t *r) 347 { 348 FIXME("\n"); 349 return E_NOTIMPL; 350 } 351 352 static HRESULT Number_toFixed(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, 353 jsval_t *r) 354 { 355 NumberInstance *number; 356 DOUBLE val; 357 INT prec = 0; 358 jsstr_t *str; 359 HRESULT hres; 360 361 TRACE("\n"); 362 363 if(!(number = number_this(jsthis))) 364 return throw_type_error(ctx, JS_E_NUMBER_EXPECTED, NULL); 365 366 if(argc) { 367 hres = to_int32(ctx, argv[0], &prec); 368 if(FAILED(hres)) 369 return hres; 370 371 if(prec<0 || prec>20) 372 return throw_range_error(ctx, JS_E_FRACTION_DIGITS_OUT_OF_RANGE, NULL); 373 } 374 375 val = number->value; 376 if(!is_finite(val)) { 377 hres = to_string(ctx, jsval_number(val), &str); 378 if(FAILED(hres)) 379 return hres; 380 }else { 381 str = number_to_fixed(val, prec); 382 if(!str) 383 return E_OUTOFMEMORY; 384 } 385 386 if(r) 387 *r = jsval_string(str); 388 else 389 jsstr_release(str); 390 return S_OK; 391 } 392 393 static HRESULT Number_toExponential(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, 394 jsval_t *r) 395 { 396 NumberInstance *number; 397 DOUBLE val; 398 INT prec = 0; 399 jsstr_t *str; 400 HRESULT hres; 401 402 TRACE("\n"); 403 404 if(!(number = number_this(jsthis))) 405 return throw_type_error(ctx, JS_E_NUMBER_EXPECTED, NULL); 406 407 if(argc) { 408 hres = to_int32(ctx, argv[0], &prec); 409 if(FAILED(hres)) 410 return hres; 411 412 if(prec<0 || prec>20) 413 return throw_range_error(ctx, JS_E_FRACTION_DIGITS_OUT_OF_RANGE, NULL); 414 } 415 416 val = number->value; 417 if(!is_finite(val)) { 418 hres = to_string(ctx, jsval_number(val), &str); 419 if(FAILED(hres)) 420 return hres; 421 }else { 422 if(!prec) 423 prec--; 424 str = number_to_exponential(val, prec); 425 if(!str) 426 return E_OUTOFMEMORY; 427 } 428 429 if(r) 430 *r = jsval_string(str); 431 else 432 jsstr_release(str); 433 return S_OK; 434 } 435 436 static HRESULT Number_toPrecision(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, 437 jsval_t *r) 438 { 439 NumberInstance *number; 440 INT prec = 0, size; 441 jsstr_t *str; 442 DOUBLE val; 443 HRESULT hres; 444 445 if(!(number = number_this(jsthis))) 446 return throw_type_error(ctx, JS_E_NUMBER_EXPECTED, NULL); 447 448 if(argc) { 449 hres = to_int32(ctx, argv[0], &prec); 450 if(FAILED(hres)) 451 return hres; 452 453 if(prec<1 || prec>21) 454 return throw_range_error(ctx, JS_E_PRECISION_OUT_OF_RANGE, NULL); 455 } 456 457 val = number->value; 458 if(!is_finite(val) || !prec) { 459 hres = to_string(ctx, jsval_number(val), &str); 460 if(FAILED(hres)) 461 return hres; 462 }else { 463 if(val != 0) 464 size = floor(log10(val>0 ? val : -val)) + 1; 465 else 466 size = 1; 467 468 if(size > prec) 469 str = number_to_exponential(val, prec-1); 470 else 471 str = number_to_fixed(val, prec-size); 472 if(!str) 473 return E_OUTOFMEMORY; 474 } 475 476 if(r) 477 *r = jsval_string(str); 478 else 479 jsstr_release(str); 480 return S_OK; 481 } 482 483 static HRESULT Number_valueOf(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, 484 jsval_t *r) 485 { 486 NumberInstance *number; 487 488 TRACE("\n"); 489 490 if(!(number = number_this(jsthis))) 491 return throw_type_error(ctx, JS_E_NUMBER_EXPECTED, NULL); 492 493 if(r) 494 *r = jsval_number(number->value); 495 return S_OK; 496 } 497 498 static HRESULT Number_get_value(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r) 499 { 500 NumberInstance *number = number_from_jsdisp(jsthis); 501 502 TRACE("(%p)\n", number); 503 504 *r = jsval_number(number->value); 505 return S_OK; 506 } 507 508 static const builtin_prop_t Number_props[] = { 509 {toExponentialW, Number_toExponential, PROPF_METHOD|1}, 510 {toFixedW, Number_toFixed, PROPF_METHOD}, 511 {toLocaleStringW, Number_toLocaleString, PROPF_METHOD}, 512 {toPrecisionW, Number_toPrecision, PROPF_METHOD|1}, 513 {toStringW, Number_toString, PROPF_METHOD|1}, 514 {valueOfW, Number_valueOf, PROPF_METHOD} 515 }; 516 517 static const builtin_info_t Number_info = { 518 JSCLASS_NUMBER, 519 {NULL, NULL,0, Number_get_value}, 520 sizeof(Number_props)/sizeof(*Number_props), 521 Number_props, 522 NULL, 523 NULL 524 }; 525 526 static const builtin_info_t NumberInst_info = { 527 JSCLASS_NUMBER, 528 {NULL, NULL,0, Number_get_value}, 529 0, NULL, 530 NULL, 531 NULL 532 }; 533 534 static HRESULT NumberConstr_value(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, 535 jsval_t *r) 536 { 537 double n; 538 HRESULT hres; 539 540 TRACE("\n"); 541 542 switch(flags) { 543 case INVOKE_FUNC: 544 if(!argc) { 545 if(r) 546 *r = jsval_number(0); 547 return S_OK; 548 } 549 550 hres = to_number(ctx, argv[0], &n); 551 if(FAILED(hres)) 552 return hres; 553 554 if(r) 555 *r = jsval_number(n); 556 break; 557 558 case DISPATCH_CONSTRUCT: { 559 jsdisp_t *obj; 560 561 if(argc) { 562 hres = to_number(ctx, argv[0], &n); 563 if(FAILED(hres)) 564 return hres; 565 }else { 566 n = 0; 567 } 568 569 hres = create_number(ctx, n, &obj); 570 if(FAILED(hres)) 571 return hres; 572 573 *r = jsval_obj(obj); 574 break; 575 } 576 default: 577 FIXME("unimplemented flags %x\n", flags); 578 return E_NOTIMPL; 579 } 580 581 return S_OK; 582 } 583 584 static HRESULT alloc_number(script_ctx_t *ctx, jsdisp_t *object_prototype, NumberInstance **ret) 585 { 586 NumberInstance *number; 587 HRESULT hres; 588 589 number = heap_alloc_zero(sizeof(NumberInstance)); 590 if(!number) 591 return E_OUTOFMEMORY; 592 593 if(object_prototype) 594 hres = init_dispex(&number->dispex, ctx, &Number_info, object_prototype); 595 else 596 hres = init_dispex_from_constr(&number->dispex, ctx, &NumberInst_info, ctx->number_constr); 597 if(FAILED(hres)) { 598 heap_free(number); 599 return hres; 600 } 601 602 *ret = number; 603 return S_OK; 604 } 605 606 HRESULT create_number_constr(script_ctx_t *ctx, jsdisp_t *object_prototype, jsdisp_t **ret) 607 { 608 NumberInstance *number; 609 HRESULT hres; 610 611 static const WCHAR NumberW[] = {'N','u','m','b','e','r',0}; 612 613 hres = alloc_number(ctx, object_prototype, &number); 614 if(FAILED(hres)) 615 return hres; 616 617 number->value = 0; 618 hres = create_builtin_constructor(ctx, NumberConstr_value, NumberW, NULL, 619 PROPF_CONSTR|1, &number->dispex, ret); 620 621 jsdisp_release(&number->dispex); 622 return hres; 623 } 624 625 HRESULT create_number(script_ctx_t *ctx, double value, jsdisp_t **ret) 626 { 627 NumberInstance *number; 628 HRESULT hres; 629 630 hres = alloc_number(ctx, NULL, &number); 631 if(FAILED(hres)) 632 return hres; 633 634 number->value = value; 635 636 *ret = &number->dispex; 637 return S_OK; 638 } 639