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