diff --git a/src/tokenizer.ts b/src/tokenizer.ts index b931a98438..829a6f4581 100644 --- a/src/tokenizer.ts +++ b/src/tokenizer.ts @@ -32,8 +32,10 @@ import { isDecimal, isOctal, isHexBase, + isHexOrDecimal, isHighSurrogate, - isLowSurrogate + isLowSurrogate, + parseHexFloat } from "./util"; /** Named token types. */ @@ -1259,18 +1261,34 @@ export class Tokenizer extends DiagnosticEmitter { var text = this.source.text; var pos = this.pos; var end = this.end; - if (pos + 1 < end && text.charCodeAt(pos) == CharCode._0) { - switch (text.charCodeAt(pos + 2) | 32) { - case CharCode.x: + var hex = false; + if (pos + 2 < end && text.charCodeAt(pos) == CharCode._0) { + switch (text.charCodeAt(pos + 1) | 32) { + case CharCode.x: { + // Don't early return for CharCode.x + // It possible a hexadecimal float. + hex = true; + pos += 2; + break; + } case CharCode.b: case CharCode.o: return true; + case CharCode.DOT: return false; } } while (pos < end) { let c = text.charCodeAt(pos); - if (c == CharCode.DOT || (c | 32) == CharCode.e) return false; - if (c != CharCode._ && (c < CharCode._0 || c > CharCode._9)) break; + if (c == CharCode.DOT) return false; // does not validate separator placement (this is done in readXYInteger) + if (c != CharCode._) { + if (hex) { + if ((c | 32) == CharCode.p) return false; + if (!isHexOrDecimal(c)) break; + } else { + if ((c | 32) == CharCode.e) return false; + if (!isDecimal(c)) break; + } + } pos++; } return true; @@ -1389,7 +1407,7 @@ export class Tokenizer extends DiagnosticEmitter { while (pos < end) { let c = text.charCodeAt(pos); if (isDecimal(c)) { - // value = value * 10 + c - CharCode._0; + // value * 10 + c - CharCode._0 nextValue = i64_add( i64_mul(value, i64_10), i64_new(c - CharCode._0) @@ -1563,16 +1581,12 @@ export class Tokenizer extends DiagnosticEmitter { } readFloat(): f64 { - // var text = this.source.text; - // if (text.charCodeAt(this.pos) == CharCode._0 && this.pos + 2 < this.end) { - // switch (text.charCodeAt(this.pos + 1)) { - // case CharCode.X: - // case CharCode.x: { - // this.pos += 2; - // return this.readHexFloat(); - // } - // } - // } + var text = this.source.text; + if (text.charCodeAt(this.pos) == CharCode._0 && this.pos + 2 < this.end) { + if ((text.charCodeAt(this.pos + 1) | 32) == CharCode.x) { + return this.readHexFloat(); + } + } return this.readDecimalFloat(); } @@ -1580,10 +1594,10 @@ export class Tokenizer extends DiagnosticEmitter { var text = this.source.text; var end = this.end; var start = this.pos; - var sepCount = this.readDecimalFloatPartial(false); + var sepCount = this.readFloatPartial(false, false); if (this.pos < end && text.charCodeAt(this.pos) == CharCode.DOT) { ++this.pos; - sepCount += this.readDecimalFloatPartial(); + sepCount += this.readFloatPartial(true, false); } if (this.pos < end) { let c = text.charCodeAt(this.pos); @@ -1595,7 +1609,7 @@ export class Tokenizer extends DiagnosticEmitter { ) { ++this.pos; } - sepCount += this.readDecimalFloatPartial(); + sepCount += this.readFloatPartial(true, false); } } let result = text.substring(start, this.pos); @@ -1604,7 +1618,7 @@ export class Tokenizer extends DiagnosticEmitter { } /** Reads past one section of a decimal float literal. Returns the number of separators encountered. */ - private readDecimalFloatPartial(allowLeadingZeroSep: bool = true): u32 { + private readFloatPartial(allowLeadingZeroSep: bool, isHexadecimal: bool): u32 { var text = this.source.text; var pos = this.pos; var start = pos; @@ -1614,7 +1628,6 @@ export class Tokenizer extends DiagnosticEmitter { while (pos < end) { let c = text.charCodeAt(pos); - if (c == CharCode._) { if (sepEnd == pos) { this.error( @@ -1631,8 +1644,12 @@ export class Tokenizer extends DiagnosticEmitter { } sepEnd = pos + 1; ++sepCount; - } else if (!isDecimal(c)) { - break; + } else { + if (isHexadecimal) { + if (!isHexOrDecimal(c)) break; + } else { + if (!isDecimal(c)) break; + } } ++pos; } @@ -1649,7 +1666,33 @@ export class Tokenizer extends DiagnosticEmitter { } readHexFloat(): f64 { - throw new Error("not implemented"); // TBD + var text = this.source.text; + var pos = this.pos; + var start = pos; + var end = this.end; + + this.pos += 2; // skip 0x + var sepCount = this.readFloatPartial(false, true); + if (this.pos < end && text.charCodeAt(this.pos) == CharCode.DOT) { + ++this.pos; + sepCount += this.readFloatPartial(true, true); + } + if (this.pos < end) { + let c = text.charCodeAt(this.pos); + if ((c | 32) == CharCode.p) { + if ( + ++this.pos < end && + (c = text.charCodeAt(this.pos)) == CharCode.MINUS || c == CharCode.PLUS && + isHexOrDecimal(text.charCodeAt(this.pos + 1)) + ) { + ++this.pos; + } + sepCount += this.readFloatPartial(true, false); + } + } + let result = text.substring(start, this.pos); + if (sepCount) result = result.replaceAll("_", ""); + return parseHexFloat(result); } readHexadecimalEscape(remain: i32 = 2, startIfTaggedTemplate: i32 = -1): string { diff --git a/src/util/math.ts b/src/util/math.ts index 25759f35d1..bcc161b3f4 100644 --- a/src/util/math.ts +++ b/src/util/math.ts @@ -24,3 +24,38 @@ export function accuratePow64(x: f64, y: f64): f64 { } return Math.pow(x, y); } + +// see: https://git.musl-libc.org/cgit/musl/tree/src/math/scalbn.c +/** Equivalent of `x * (2 ** n)` */ +export function scalbn(x: f64, n: i32): f64 { + const + Ox1p1023 = 8.98846567431158e+307, // 0x1p1023 + Ox1p_969 = 2.00416836000897278e-292; // 0x1p-1022 * 0x1p53 + + var y = x; + if (n > 1023) { + y *= Ox1p1023; + n -= 1023; + if (n > 1023) { + y *= Ox1p1023; + n -= 1023; + if (n > 1023) n = 1023; + } + } else if (n < -1022) { + // make sure final n < -53 to avoid double + // rounding in the subnormal range + y *= Ox1p_969; + n += 1022 - 53; + if (n < -1022) { + y *= Ox1p_969; + n += 1022 - 53; + if (n < -1022) n = -1022; + } + } + if (!ASC_TARGET) { // ASC_TARGET == JS + return y * i64_as_f64(i64_new(0, 0x3FF + n << 20)); + } else { + // @ts-ignore + return y * reinterpret(u64(0x3FF + n) << 52); + } +} diff --git a/src/util/text.ts b/src/util/text.ts index 60fe6a5410..725087ecfb 100644 --- a/src/util/text.ts +++ b/src/util/text.ts @@ -3,6 +3,8 @@ * @license Apache-2.0 */ +import { scalbn } from "./math"; + /** An enum of named character codes. */ export const enum CharCode { @@ -564,3 +566,33 @@ export function escapeString(str: string, quote: CharCode): string { if (i > off) sb.push(str.substring(off, i)); return sb.join(""); } + +export function parseHexFloat(str: string): f64 { + var sign = 1, pPos = -1, dotPos = -1; + for (let i = 0, k = str.length; i < k; ++i) { + const c = str.charCodeAt(i); + if (i == 0 && c == CharCode.MINUS) { + sign = -1; + } else if ((c | 32) == CharCode.p) { + pPos = i; + } else if (c == CharCode.DOT) { + dotPos = i; + } + } + var mant: f64; + var mantissa = ~pPos ? str.substring(0, pPos) : str; + if (~dotPos) { + const integer = mantissa.substring(0, dotPos); + const fraction = mantissa.substring(dotPos + 1); + const intVal = parseInt(integer, 16); + const fracVal = fraction.length + ? scalbn(parseInt(fraction, 16), -(fraction.length << 2)) + : 0; + mant = intVal + sign * fracVal; + } else { + mant = parseInt(mantissa, 16); + } + return ~pPos + ? scalbn(mant, parseInt(str.substring(pPos + 1))) + : mant; +} diff --git a/tests/compiler/literals-hexfloat.debug.wat b/tests/compiler/literals-hexfloat.debug.wat new file mode 100644 index 0000000000..23d4c9706f --- /dev/null +++ b/tests/compiler/literals-hexfloat.debug.wat @@ -0,0 +1,183 @@ +(module + (type $none_=>_none (func)) + (global $~lib/memory/__data_end i32 (i32.const 8)) + (global $~lib/memory/__stack_pointer (mut i32) (i32.const 16392)) + (global $~lib/memory/__heap_base i32 (i32.const 16392)) + (memory $0 0) + (table $0 1 1 funcref) + (elem $0 (i32.const 1)) + (export "memory" (memory $0)) + (start $~start) + (func $start:literals-hexfloat + (local $var$0 f64) + f64.const 0 + f64.const 0 + f64.eq + drop + f64.const 0 + f64.const 0 + f64.eq + drop + f64.const 0 + f64.const 0 + f64.eq + drop + f64.const 0.5 + f64.const 0.5 + f64.eq + drop + f64.const 2 + f64.const 2 + f64.eq + drop + f64.const 256 + f64.const 256 + f64.eq + drop + f64.const 0.015625 + f64.const 0.015625 + f64.eq + drop + f64.const 0.857421875 + f64.const 0.857421875 + f64.eq + drop + f64.const 3.1415926 + f64.const 3.1415926 + f64.eq + drop + f64.const 0.1 + f64.const 0.1 + f64.eq + drop + f64.const 6.283185307179586 + f64.const 6.283185307179586 + f64.eq + drop + f64.const 5e-324 + f64.const 5e-324 + f64.eq + drop + f64.const 2.2250738585072014e-308 + f64.const 2.2250738585072014e-308 + f64.eq + drop + f64.const 2.225073858507201e-308 + f64.const 2.225073858507201e-308 + f64.eq + drop + f64.const 1797693134862315708145274e284 + f64.const 1797693134862315708145274e284 + f64.eq + drop + f64.const 1267650600228229401496703e6 + f64.const 1267650600228229401496703e6 + f64.eq + drop + f64.const 0.1 + f64.const 0.1 + f64.eq + drop + f64.const 0.1 + f64.const 0.1 + f64.eq + drop + f64.const -0 + local.set $var$0 + local.get $var$0 + i64.reinterpret_f64 + i64.const 63 + i64.shr_u + i64.const 0 + i64.ne + i32.const 0 + i32.ne + drop + f64.const -0 + local.set $var$0 + local.get $var$0 + i64.reinterpret_f64 + i64.const 63 + i64.shr_u + i64.const 0 + i64.ne + i32.const 0 + i32.ne + drop + f64.const -1 + f64.const -1 + f64.eq + drop + f64.const -1.0625 + f64.const -1.0625 + f64.eq + drop + f64.const -0.0625 + f64.const -0.0625 + f64.eq + drop + f64.const -0.5 + f64.const -0.5 + f64.eq + drop + f64.const -2 + f64.const -2 + f64.eq + drop + f64.const -256 + f64.const -256 + f64.eq + drop + f64.const -0.015625 + f64.const -0.015625 + f64.eq + drop + f64.const -0.857421875 + f64.const -0.857421875 + f64.eq + drop + f64.const -3.1415926 + f64.const -3.1415926 + f64.eq + drop + f64.const -0.1 + f64.const -0.1 + f64.eq + drop + f64.const -6.283185307179586 + f64.const -6.283185307179586 + f64.eq + drop + f64.const -5e-324 + f64.const -5e-324 + f64.eq + drop + f64.const -2.2250738585072014e-308 + f64.const -2.2250738585072014e-308 + f64.eq + drop + f64.const -2.225073858507201e-308 + f64.const -2.225073858507201e-308 + f64.eq + drop + f64.const -1797693134862315708145274e284 + f64.const -1797693134862315708145274e284 + f64.eq + drop + f64.const -1267650600228229401496703e6 + f64.const -1267650600228229401496703e6 + f64.eq + drop + f64.const 5e-324 + f64.const 5e-324 + f64.eq + drop + f64.const -1.509761204943061e-11 + f64.const -1.509761204943061e-11 + f64.eq + drop + ) + (func $~start + call $start:literals-hexfloat + ) +) diff --git a/tests/compiler/literals-hexfloat.json b/tests/compiler/literals-hexfloat.json new file mode 100644 index 0000000000..1bdd02b1be --- /dev/null +++ b/tests/compiler/literals-hexfloat.json @@ -0,0 +1,4 @@ +{ + "asc_flags": [ + ] +} diff --git a/tests/compiler/literals-hexfloat.release.wat b/tests/compiler/literals-hexfloat.release.wat new file mode 100644 index 0000000000..23da3862e2 --- /dev/null +++ b/tests/compiler/literals-hexfloat.release.wat @@ -0,0 +1,4 @@ +(module + (memory $0 0) + (export "memory" (memory $0)) +) diff --git a/tests/compiler/literals-hexfloat.ts b/tests/compiler/literals-hexfloat.ts new file mode 100644 index 0000000000..f1cf42fa82 --- /dev/null +++ b/tests/compiler/literals-hexfloat.ts @@ -0,0 +1,46 @@ +// @ts-nocheck + +assert(+0x0.0 == 0.0); +assert(+0x0.0p0 == 0.0); +assert(+0x0p1 == 0.0); + +assert(+0x1P-1 == 0.5); +assert(0x1p1 == 2.0); +assert(0X1P+8 == 256.0); +assert(0x1p-6 == 0.015625); +assert(0x1.b7p-1 == 0.857421875); + +assert(0X1.921FB4D12D84AP+1 == 3.1415926); +assert(0x1.999999999999ap-4 == 0.1); +assert(0x1.921fb54442d18P+2 == 6.283185307179586); +assert(0x0.0000000000001p-1022 == 5e-324); +assert(0x1p-1022 == 2.2250738585072014e-308); +assert(0x0.fffffffffffffp-1022 == 2.225073858507201e-308); +assert(0x1.fffffffffffffp+1023 == 1.7976931348623157e308); +assert(0x1.p100 == 1.2676506002282294e30); + +assert(0x1.999999999999ap-4 == 0x3.3333333333334p-5); +assert(0xcc.ccccccccccdp-11 == 0x1.999999999999ap-4); + +assert(-0x0p0 == -0.0 && Math.signbit(-0x0p0)); +assert(-0x0.0p2 == -0.0 && Math.signbit(-0x0.0p2)); +assert(-0x0.1p4 == -1.0); +assert(-0x1.1 == -1.0625); +assert(-0x0.1 == -0.0625); +assert(-0x1p-1 == -0.5); +assert(-0x1p1 == -2.0); +assert(-0x1p+8 == -256); +assert(-0x1p-6 == -0.015625); +assert(-0x1.b7p-1 == -0.857421875); +assert(-0X1.921FB4D12D84AP+1 == -3.1415926); +assert(-0x1.999999999999ap-4 == -0.1); +assert(-0x1.921fb54442d18p+2 == -6.283185307179586); +assert(-0x0.0000000000001p-1022 == -5e-324); +assert(-0x1p-1022 == -2.2250738585072014e-308); +assert(-0x0.fffffffffffffp-1022 == -2.225073858507201e-308); +assert(-0x1.fffffffffffffp+1023 == -1.7976931348623157e308); +assert(-0x1.p100 == -1.2676506002282294e30); +assert(0x1p-1074 == 5e-324); + +// test separators +assert(-0x1_0.9999999_9999_9ap-4_0 == -1.509761204943061e-11);