Skip to content

Commit 2853a0b

Browse files
authored
Fix/nan (#84)
* Fix nan and inf * Undo float parsing change * Fix unit tests
1 parent 8ba39e2 commit 2853a0b

6 files changed

Lines changed: 129 additions & 63 deletions

File tree

src/framework/Verifier.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,5 @@ export class Verifier {
123123

124124
/* eslint @typescript-eslint/no-explicit-any: off */
125125
function deepEqual(a: any, b: any): boolean {
126-
return a === b || (isNaN(Number(a)) && isNaN(Number(b)));
126+
return a === b || (isNaN(Number(a)) && isNaN(Number(b))) || a.equals(b);
127127
}

src/framework/scenario/Invoker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Type = WASM.Type;
77
import nothing = WASM.nothing;
88
import Special = WASM.Special;
99
import Float = WASM.Float;
10+
import WasmInt = WASM.WasmInt;
1011

1112
export class Invoker implements Step {
1213
readonly title: string;
@@ -34,6 +35,6 @@ export function returns<T extends Type>(n: Value<T>): Expectation[] {
3435
if (n.type == Special.nothing) {
3536
return [{'value': {kind: 'primitive', value: undefined} as Expected<undefined>}]
3637
}
37-
type R = T extends Float ? number : bigint;
38+
type R = T extends Float ? number : WasmInt;
3839
return [{'value': {kind: 'primitive', value: n.value} as Expected<R>}]
3940
}

src/messaging/Message.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export interface Request<R> {
3030
export namespace Message {
3131
import Inspect = WARDuino.Inspect;
3232
import Float = WASM.Float;
33+
import isFloat = WASM.isFloat;
3334
export const run: Request<Ack> = {
3435
type: Interrupt.run,
3536
parser: (line: string) => {
@@ -176,12 +177,12 @@ export namespace Message {
176177
function convert(args: Value<Type>[]) {
177178
let payload: string = '';
178179
args.forEach((arg: Value<Type>) => {
179-
if (arg.type === Float.f32 || arg.type === Float.f64 || arg.type === Special.nan || arg.type === Special.infinity) {
180-
const buff = Buffer.alloc(4);
181-
write(buff, Number(arg.value), 0, true, 23, buff.length); // todo fix precision loss
180+
if (isFloat(arg.type)) {
181+
const buff = Buffer.alloc(arg.type === Float.f32 ? 4 : 8);
182+
write(buff, Number(arg.value), 0, true, arg.type === Float.f32 ? 23 : 52, buff.length); // todo fix precision loss
182183
payload += buff.toString('hex');
183184
} else {
184-
payload += WASM.leb128(arg.value);
185+
payload += WASM.leb128(<number>arg.value);
185186
}
186187
});
187188
return payload;

src/messaging/Parsers.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import {WASM} from '../sourcemap/Wasm';
2-
import * as ieee754 from 'ieee754';
32
import {Ack, Exception} from './Message';
43
import {Breakpoint} from '../debug/Breakpoint';
54
import {WARDuino} from '../debug/WARDuino';
65
import {JSONParse} from 'json-with-bigint';
76
import State = WARDuino.State;
87
import nothing = WASM.nothing;
98
import Type = WASM.Type;
9+
import WasmInt = WASM.WasmInt;
10+
import ieee754 from "ieee754";
11+
1012
export function identityParser(text: string) {
1113
return stripEnd(text);
1214
}
@@ -19,7 +21,7 @@ export function invokeParser(text: string): WASM.Value<Type> | Exception {
1921
if (exception(text)) {
2022
return {text: text};
2123
}
22-
const stack: {value: any, type: any}[] = stateParser(text).stack!;
24+
const stack: { value: any, type: any }[] = stateParser(text).stack!;
2325
if (stack.length == 0) {
2426
return nothing;
2527
}
@@ -67,42 +69,38 @@ export function signed(value: bigint, bits = 32) {
6769

6870
}
6971

70-
function extractType(object: {value: bigint | number, type: any}): Type {
71-
if (typeof object.value === 'number') {
72-
if (Number.isNaN(object.value)) return WASM.Special.nan;
73-
if (object.value === Infinity) return WASM.Special.infinity;
74-
}
72+
function extractType(object: { value: string, type: any }): Type {
7573
return WASM.typing.get(object.type.toLowerCase()) ?? WASM.Special.unknown;
7674
}
7775

78-
function stacking(objects: {value: bigint | number, type: any}[]): WASM.Value<Type>[] {
76+
function stacking(objects: { value: string, type: any }[]): WASM.Value<Type>[] {
7977
const stacked: WASM.Value<Type>[] = [];
8078
for (const object of objects) {
8179
const type: WASM.Type = extractType(object);
8280
let buff;
8381
switch (type) {
84-
case WASM.Special.nan:
85-
stacked.push({value: NaN, type: type});
86-
break;
87-
case WASM.Special.infinity:
88-
stacked.push({value: Infinity, type: type});
89-
break;
9082
case WASM.Integer.u32:
9183
case WASM.Integer.u64:
92-
stacked.push({value: object.value, type: type});
84+
stacked.push({
85+
value: isNaN(Number(object.value)) ? WasmInt.nan()
86+
: object.value === 'inf' ? WasmInt.infinity()
87+
: object.value === '-inf' ? WasmInt.infinity(false)
88+
: WasmInt.finite(BigInt(object.value)),
89+
type: type
90+
});
9391
break;
9492
case WASM.Integer.i32:
95-
stacked.push({value: signed(BigInt(object.value), 32), type: type});
93+
stacked.push({value: WasmInt.finite(signed(BigInt(object.value), 32)), type: type});
9694
break;
9795
case WASM.Integer.i64:
98-
stacked.push({value: signed(BigInt(object.value), 64), type: type});
96+
stacked.push({value: WasmInt.finite(signed(BigInt(object.value), 64)), type: type});
9997
break;
10098
case WASM.Float.f32:
101-
buff = Buffer.from(Number(object.value.toString(16)).toString(16), 'hex');
99+
buff = Buffer.from(Number(object.value).toString(16), 'hex');
102100
stacked.push({value: ieee754.read(buff, 0, false, 23, buff.length), type: type});
103101
break;
104102
case WASM.Float.f64:
105-
buff = Buffer.from(BigInt(object.value.toString(16)).toString(16), 'hex');
103+
buff = Buffer.from(BigInt(object.value).toString(16), 'hex');
106104
stacked.push({value: ieee754.read(buff, 0, false, 52, buff.length), type: type});
107105
break;
108106
case WASM.Special.unknown:

src/sourcemap/Wasm.ts

Lines changed: 84 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ export namespace WASM {
1313

1414
export enum Special {
1515
nothing = 'nothing',
16-
nan = 'nan',
17-
infinity = 'infinity',
1816
unknown = 'unknown'
1917
}
2018

@@ -29,17 +27,78 @@ export namespace WASM {
2927
['i64', Integer.i64]
3028
]);
3129

30+
export class WasmInt {
31+
private constructor(
32+
private readonly kind: 'finite' | 'inf' | 'nan',
33+
private readonly finiteValue?: bigint,
34+
private readonly positive: boolean = true
35+
) {
36+
}
37+
38+
static finite(value: bigint): WasmInt {
39+
return new WasmInt('finite', value, value >= 0);
40+
}
41+
42+
static infinity(positive: boolean = true): WasmInt {
43+
return new WasmInt('inf', undefined, positive);
44+
}
45+
46+
static nan(positive: boolean = true): WasmInt {
47+
return new WasmInt('nan', undefined, positive);
48+
}
49+
50+
isFinite(): boolean {
51+
return this.kind === 'finite';
52+
}
53+
54+
isInfinity(): boolean {
55+
return this.kind === 'inf';
56+
}
57+
58+
isNaN(): boolean {
59+
return this.kind === 'nan';
60+
}
61+
62+
toBigInt(): bigint {
63+
if (this.finiteValue !== undefined) {
64+
return this.finiteValue;
65+
}
66+
throw new Error(`Cannot convert ${this.toString()} to bigint.`);
67+
}
68+
69+
toNumber(): number {
70+
switch (this.kind) {
71+
case 'finite':
72+
return Number(this.finiteValue!);
73+
case 'inf':
74+
return this.positive ? Infinity : -Infinity;
75+
case 'nan':
76+
return this.positive ? NaN : -NaN;
77+
}
78+
}
79+
80+
equals(other: WasmInt): boolean {
81+
return this.kind === 'finite'
82+
? this.finiteValue === other.finiteValue
83+
: this.kind === other.kind && this.positive === other.positive;
84+
}
85+
86+
toString(): string {
87+
if (this.kind === 'finite') {
88+
return this.finiteValue!.toString();
89+
}
90+
return `${this.positive ? '' : '-'}${this.kind}`;
91+
}
92+
}
93+
94+
3295
export interface Value<T extends Type> {
3396
type: T;
34-
value: T extends Integer ? bigint : number;
97+
value: T extends Integer ? WasmInt : number;
3598
}
3699

37100
export function equals<T extends Type>(a: Value<T>, b: Value<T>): boolean {
38101
switch (a.type) {
39-
case Special.nan:
40-
return b.type === Special.nan;
41-
case Special.infinity:
42-
return b.type === Special.infinity;
43102
case Special.nothing:
44103
return b.type === Special.nothing;
45104
case Special.unknown:
@@ -56,38 +115,44 @@ export namespace WASM {
56115
type: Special.nothing, value: 0
57116
}
58117

59-
export const nan: WASM.Value<Special> = {value: NaN, type: Special.nan};
118+
export function isInteger(type: Type): type is Integer {
119+
return type === Integer.u32 || type === Integer.i32 || type === Integer.u64 || type === Integer.i64;
120+
}
60121

61-
export const negnan: WASM.Value<Special> = {value: -NaN, type: Special.nan};
122+
export function isFloat(type: Type): type is Float {
123+
return type === Float.f32 || type === Float.f64;
124+
}
62125

63-
export const infinity: WASM.Value<Special> = {value: Infinity, type: Special.infinity};
126+
export function nan(type: WASM.Type, positive: boolean = true): WASM.Value<Type> {
127+
return {value: isInteger(type) ? WasmInt.nan(positive): (positive ? NaN : -NaN), type};
128+
}
64129

65-
export const neginfinity: WASM.Value<Special> = {value: -Infinity, type: Special.infinity};
130+
export function inf(type: WASM.Type, positive: boolean = true): WASM.Value<Type> {
131+
return {value: isInteger(type) ? WasmInt.infinity(positive): (positive ? Infinity : -Infinity), type};
132+
}
66133

67134
export function u32(n: bigint): WASM.Value<Integer> {
68-
return {value: n, type: Integer.u32};
135+
return {value: WasmInt.finite(n), type: Integer.u32};
69136
}
70137

71138
export function i32(n: bigint): WASM.Value<Integer> {
72-
return {value: n, type: Integer.i32};
139+
return {value: WasmInt.finite(n), type: Integer.i32};
73140
}
74141

75-
const determineType: (n: number) => WASM.Type = (n: number) => n === Infinity || n === -Infinity ? Special.infinity : (isNaN(n) ? Special.nan : Float.f64);
76-
77142
export function f32(n: number): WASM.Value<Type> {
78-
return {value: n, type: determineType(n)};
143+
return {value: n, type: Float.f32};
79144
}
80145

81146
export function f64(n: number): WASM.Value<Type> {
82-
return {value: n, type: determineType(n)};
147+
return {value: n, type: Float.f64};
83148
}
84149

85150
export function u64(n: bigint): WASM.Value<Integer> {
86-
return {value: n, type: Integer.u64};
151+
return {value: WasmInt.finite(n), type: Integer.u64};
87152
}
88153

89154
export function i64(n: bigint): WASM.Value<Integer> {
90-
return {value: n, type: Integer.i64};
155+
return {value: WasmInt.finite(n), type: Integer.i64};
91156
}
92157

93158
export interface Frame {

tests/unit/parsing.test.ts

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {Exception, WARDuino} from "../../src";
44
import {WASM} from "../../src/sourcemap/Wasm";
55
import State = WARDuino.State;
66
import Type = WASM.Type;
7+
import WasmInt = WASM.WasmInt;
78

89
/**
910
* Check unsigned 32-bit integer to signed conversion
@@ -41,27 +42,9 @@ const equality = (a: bigint | number | undefined, b: bigint) =>
4142
(typeof a === 'bigint' && a === b) || // both bigint
4243
(a !== undefined && Number.isInteger(a) && BigInt(a) === b); // compare integer number with bigint
4344

44-
test('[state] : 32-bit integer precision', t => {
45-
const values: bigint[] = [1n, 127n, 2147483648n, 4294967294n];
46-
for (const value of values) {
47-
const state: State = stateParser(`{\"stack\": [{\"idx\":0,\"type\":\"i32\",\"value\":${value}}]}\n`);
48-
t.true(equality(state.stack?.[0].value, value));
49-
}
50-
});
51-
52-
test('[state parser] : 64-bit integer precision', t => {
53-
const values: bigint[] = [1n, 127n, 2147483648n, 4294967294n, 18446744073709551615n, 18446744073709551489n];
54-
for (const value of values) {
55-
const state: State = stateParser(`{\"stack\": [{\"idx\":0,\"type\":\"i32\",\"value\":${value}}]}\n`);
56-
t.true(equality(state.stack?.[0].value, value));
57-
}
58-
});
59-
6045
test('[invoke parser] : 64-bit signed conversion', t => {
6146
const values = [[1n, 1n], [127n, 127n], [2147483648n, 2147483648n], [4294967294n, 4294967294n], [18446744073709551615n, -1n], [18446744073709551489n, -127n]];
6247

63-
const result: WASM.Value<Type> | Exception = invokeParser('{"stack": [{"idx":0,"type":"i64","value":18446744073709551615}]}\n');
64-
6548
for (const [value, expected] of values) {
6649
const result: WASM.Value<Type> | Exception = invokeParser(`{\"stack\": [{\"idx\":0,\"type\":\"i64\",\"value\":${value}}]}\n`);
6750

@@ -71,6 +54,24 @@ test('[invoke parser] : 64-bit signed conversion', t => {
7154
}
7255

7356
t.is(result.type, WASM.Integer.i64);
74-
t.is(result.value, expected);
57+
t.is(typeof result.value, 'object');
58+
t.is((result.value as WasmInt).toBigInt(), expected);
59+
}
60+
});
61+
62+
test('[invoke parser] : 64-bit float', t => {
63+
const values = [[9221120237041090560n, NaN]];
64+
65+
for (const [value, expected] of values) {
66+
const result: WASM.Value<Type> | Exception = invokeParser(`{\"stack\": [{\"idx\":0,"type":"F64","value": 9221120237041090560}]}\n`);
67+
68+
if ('text' in result) { // check if exception
69+
t.fail(`Expected parsed value, got exception: ${result.text}`);
70+
return;
71+
}
72+
73+
t.is(result.type, WASM.Float.f64);
74+
t.is(typeof result.value, 'number');
75+
t.true(isNaN(<number>result.value));
7576
}
7677
});

0 commit comments

Comments
 (0)