diff --git a/src/framework/Verifier.ts b/src/framework/Verifier.ts index b233575..86dd711 100644 --- a/src/framework/Verifier.ts +++ b/src/framework/Verifier.ts @@ -123,5 +123,5 @@ export class Verifier { /* eslint @typescript-eslint/no-explicit-any: off */ function deepEqual(a: any, b: any): boolean { - return a === b || (isNaN(Number(a)) && isNaN(Number(b))); + return a === b || (isNaN(Number(a)) && isNaN(Number(b))) || a.equals(b); } diff --git a/src/framework/scenario/Invoker.ts b/src/framework/scenario/Invoker.ts index d04f373..7bd6ef4 100644 --- a/src/framework/scenario/Invoker.ts +++ b/src/framework/scenario/Invoker.ts @@ -7,6 +7,7 @@ import Type = WASM.Type; import nothing = WASM.nothing; import Special = WASM.Special; import Float = WASM.Float; +import WasmInt = WASM.WasmInt; export class Invoker implements Step { readonly title: string; @@ -34,6 +35,6 @@ export function returns(n: Value): Expectation[] { if (n.type == Special.nothing) { return [{'value': {kind: 'primitive', value: undefined} as Expected}] } - type R = T extends Float ? number : bigint; + type R = T extends Float ? number : WasmInt; return [{'value': {kind: 'primitive', value: n.value} as Expected}] } diff --git a/src/messaging/Message.ts b/src/messaging/Message.ts index 62c54dd..ba7e439 100644 --- a/src/messaging/Message.ts +++ b/src/messaging/Message.ts @@ -30,6 +30,7 @@ export interface Request { export namespace Message { import Inspect = WARDuino.Inspect; import Float = WASM.Float; + import isFloat = WASM.isFloat; export const run: Request = { type: Interrupt.run, parser: (line: string) => { @@ -176,12 +177,12 @@ export namespace Message { function convert(args: Value[]) { let payload: string = ''; args.forEach((arg: Value) => { - if (arg.type === Float.f32 || arg.type === Float.f64 || arg.type === Special.nan || arg.type === Special.infinity) { - const buff = Buffer.alloc(4); - write(buff, Number(arg.value), 0, true, 23, buff.length); // todo fix precision loss + if (isFloat(arg.type)) { + const buff = Buffer.alloc(arg.type === Float.f32 ? 4 : 8); + write(buff, Number(arg.value), 0, true, arg.type === Float.f32 ? 23 : 52, buff.length); // todo fix precision loss payload += buff.toString('hex'); } else { - payload += WASM.leb128(arg.value); + payload += WASM.leb128(arg.value); } }); return payload; diff --git a/src/messaging/Parsers.ts b/src/messaging/Parsers.ts index 0f93629..558f499 100644 --- a/src/messaging/Parsers.ts +++ b/src/messaging/Parsers.ts @@ -1,5 +1,4 @@ import {WASM} from '../sourcemap/Wasm'; -import * as ieee754 from 'ieee754'; import {Ack, Exception} from './Message'; import {Breakpoint} from '../debug/Breakpoint'; import {WARDuino} from '../debug/WARDuino'; @@ -7,6 +6,9 @@ import {JSONParse} from 'json-with-bigint'; import State = WARDuino.State; import nothing = WASM.nothing; import Type = WASM.Type; +import WasmInt = WASM.WasmInt; +import ieee754 from "ieee754"; + export function identityParser(text: string) { return stripEnd(text); } @@ -19,7 +21,7 @@ export function invokeParser(text: string): WASM.Value | Exception { if (exception(text)) { return {text: text}; } - const stack: {value: any, type: any}[] = stateParser(text).stack!; + const stack: { value: any, type: any }[] = stateParser(text).stack!; if (stack.length == 0) { return nothing; } @@ -67,42 +69,38 @@ export function signed(value: bigint, bits = 32) { } -function extractType(object: {value: bigint | number, type: any}): Type { - if (typeof object.value === 'number') { - if (Number.isNaN(object.value)) return WASM.Special.nan; - if (object.value === Infinity) return WASM.Special.infinity; - } +function extractType(object: { value: string, type: any }): Type { return WASM.typing.get(object.type.toLowerCase()) ?? WASM.Special.unknown; } -function stacking(objects: {value: bigint | number, type: any}[]): WASM.Value[] { +function stacking(objects: { value: string, type: any }[]): WASM.Value[] { const stacked: WASM.Value[] = []; for (const object of objects) { const type: WASM.Type = extractType(object); let buff; switch (type) { - case WASM.Special.nan: - stacked.push({value: NaN, type: type}); - break; - case WASM.Special.infinity: - stacked.push({value: Infinity, type: type}); - break; case WASM.Integer.u32: case WASM.Integer.u64: - stacked.push({value: object.value, type: type}); + stacked.push({ + value: isNaN(Number(object.value)) ? WasmInt.nan() + : object.value === 'inf' ? WasmInt.infinity() + : object.value === '-inf' ? WasmInt.infinity(false) + : WasmInt.finite(BigInt(object.value)), + type: type + }); break; case WASM.Integer.i32: - stacked.push({value: signed(BigInt(object.value), 32), type: type}); + stacked.push({value: WasmInt.finite(signed(BigInt(object.value), 32)), type: type}); break; case WASM.Integer.i64: - stacked.push({value: signed(BigInt(object.value), 64), type: type}); + stacked.push({value: WasmInt.finite(signed(BigInt(object.value), 64)), type: type}); break; case WASM.Float.f32: - buff = Buffer.from(Number(object.value.toString(16)).toString(16), 'hex'); + buff = Buffer.from(Number(object.value).toString(16), 'hex'); stacked.push({value: ieee754.read(buff, 0, false, 23, buff.length), type: type}); break; case WASM.Float.f64: - buff = Buffer.from(BigInt(object.value.toString(16)).toString(16), 'hex'); + buff = Buffer.from(BigInt(object.value).toString(16), 'hex'); stacked.push({value: ieee754.read(buff, 0, false, 52, buff.length), type: type}); break; case WASM.Special.unknown: diff --git a/src/sourcemap/Wasm.ts b/src/sourcemap/Wasm.ts index e99e5a4..e3515fb 100644 --- a/src/sourcemap/Wasm.ts +++ b/src/sourcemap/Wasm.ts @@ -13,8 +13,6 @@ export namespace WASM { export enum Special { nothing = 'nothing', - nan = 'nan', - infinity = 'infinity', unknown = 'unknown' } @@ -29,17 +27,78 @@ export namespace WASM { ['i64', Integer.i64] ]); + export class WasmInt { + private constructor( + private readonly kind: 'finite' | 'inf' | 'nan', + private readonly finiteValue?: bigint, + private readonly positive: boolean = true + ) { + } + + static finite(value: bigint): WasmInt { + return new WasmInt('finite', value, value >= 0); + } + + static infinity(positive: boolean = true): WasmInt { + return new WasmInt('inf', undefined, positive); + } + + static nan(positive: boolean = true): WasmInt { + return new WasmInt('nan', undefined, positive); + } + + isFinite(): boolean { + return this.kind === 'finite'; + } + + isInfinity(): boolean { + return this.kind === 'inf'; + } + + isNaN(): boolean { + return this.kind === 'nan'; + } + + toBigInt(): bigint { + if (this.finiteValue !== undefined) { + return this.finiteValue; + } + throw new Error(`Cannot convert ${this.toString()} to bigint.`); + } + + toNumber(): number { + switch (this.kind) { + case 'finite': + return Number(this.finiteValue!); + case 'inf': + return this.positive ? Infinity : -Infinity; + case 'nan': + return this.positive ? NaN : -NaN; + } + } + + equals(other: WasmInt): boolean { + return this.kind === 'finite' + ? this.finiteValue === other.finiteValue + : this.kind === other.kind && this.positive === other.positive; + } + + toString(): string { + if (this.kind === 'finite') { + return this.finiteValue!.toString(); + } + return `${this.positive ? '' : '-'}${this.kind}`; + } + } + + export interface Value { type: T; - value: T extends Integer ? bigint : number; + value: T extends Integer ? WasmInt : number; } export function equals(a: Value, b: Value): boolean { switch (a.type) { - case Special.nan: - return b.type === Special.nan; - case Special.infinity: - return b.type === Special.infinity; case Special.nothing: return b.type === Special.nothing; case Special.unknown: @@ -56,38 +115,44 @@ export namespace WASM { type: Special.nothing, value: 0 } - export const nan: WASM.Value = {value: NaN, type: Special.nan}; + export function isInteger(type: Type): type is Integer { + return type === Integer.u32 || type === Integer.i32 || type === Integer.u64 || type === Integer.i64; + } - export const negnan: WASM.Value = {value: -NaN, type: Special.nan}; + export function isFloat(type: Type): type is Float { + return type === Float.f32 || type === Float.f64; + } - export const infinity: WASM.Value = {value: Infinity, type: Special.infinity}; + export function nan(type: WASM.Type, positive: boolean = true): WASM.Value { + return {value: isInteger(type) ? WasmInt.nan(positive): (positive ? NaN : -NaN), type}; + } - export const neginfinity: WASM.Value = {value: -Infinity, type: Special.infinity}; + export function inf(type: WASM.Type, positive: boolean = true): WASM.Value { + return {value: isInteger(type) ? WasmInt.infinity(positive): (positive ? Infinity : -Infinity), type}; + } export function u32(n: bigint): WASM.Value { - return {value: n, type: Integer.u32}; + return {value: WasmInt.finite(n), type: Integer.u32}; } export function i32(n: bigint): WASM.Value { - return {value: n, type: Integer.i32}; + return {value: WasmInt.finite(n), type: Integer.i32}; } - const determineType: (n: number) => WASM.Type = (n: number) => n === Infinity || n === -Infinity ? Special.infinity : (isNaN(n) ? Special.nan : Float.f64); - export function f32(n: number): WASM.Value { - return {value: n, type: determineType(n)}; + return {value: n, type: Float.f32}; } export function f64(n: number): WASM.Value { - return {value: n, type: determineType(n)}; + return {value: n, type: Float.f64}; } export function u64(n: bigint): WASM.Value { - return {value: n, type: Integer.u64}; + return {value: WasmInt.finite(n), type: Integer.u64}; } export function i64(n: bigint): WASM.Value { - return {value: n, type: Integer.i64}; + return {value: WasmInt.finite(n), type: Integer.i64}; } export interface Frame { diff --git a/tests/unit/parsing.test.ts b/tests/unit/parsing.test.ts index 8001c76..e32aa99 100644 --- a/tests/unit/parsing.test.ts +++ b/tests/unit/parsing.test.ts @@ -4,6 +4,7 @@ import {Exception, WARDuino} from "../../src"; import {WASM} from "../../src/sourcemap/Wasm"; import State = WARDuino.State; import Type = WASM.Type; +import WasmInt = WASM.WasmInt; /** * Check unsigned 32-bit integer to signed conversion @@ -41,27 +42,9 @@ const equality = (a: bigint | number | undefined, b: bigint) => (typeof a === 'bigint' && a === b) || // both bigint (a !== undefined && Number.isInteger(a) && BigInt(a) === b); // compare integer number with bigint -test('[state] : 32-bit integer precision', t => { - const values: bigint[] = [1n, 127n, 2147483648n, 4294967294n]; - for (const value of values) { - const state: State = stateParser(`{\"stack\": [{\"idx\":0,\"type\":\"i32\",\"value\":${value}}]}\n`); - t.true(equality(state.stack?.[0].value, value)); - } -}); - -test('[state parser] : 64-bit integer precision', t => { - const values: bigint[] = [1n, 127n, 2147483648n, 4294967294n, 18446744073709551615n, 18446744073709551489n]; - for (const value of values) { - const state: State = stateParser(`{\"stack\": [{\"idx\":0,\"type\":\"i32\",\"value\":${value}}]}\n`); - t.true(equality(state.stack?.[0].value, value)); - } -}); - test('[invoke parser] : 64-bit signed conversion', t => { const values = [[1n, 1n], [127n, 127n], [2147483648n, 2147483648n], [4294967294n, 4294967294n], [18446744073709551615n, -1n], [18446744073709551489n, -127n]]; - const result: WASM.Value | Exception = invokeParser('{"stack": [{"idx":0,"type":"i64","value":18446744073709551615}]}\n'); - for (const [value, expected] of values) { const result: WASM.Value | Exception = invokeParser(`{\"stack\": [{\"idx\":0,\"type\":\"i64\",\"value\":${value}}]}\n`); @@ -71,6 +54,24 @@ test('[invoke parser] : 64-bit signed conversion', t => { } t.is(result.type, WASM.Integer.i64); - t.is(result.value, expected); + t.is(typeof result.value, 'object'); + t.is((result.value as WasmInt).toBigInt(), expected); + } +}); + +test('[invoke parser] : 64-bit float', t => { + const values = [[9221120237041090560n, NaN]]; + + for (const [value, expected] of values) { + const result: WASM.Value | Exception = invokeParser(`{\"stack\": [{\"idx\":0,"type":"F64","value": 9221120237041090560}]}\n`); + + if ('text' in result) { // check if exception + t.fail(`Expected parsed value, got exception: ${result.text}`); + return; + } + + t.is(result.type, WASM.Float.f64); + t.is(typeof result.value, 'number'); + t.true(isNaN(result.value)); } });