Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/framework/Verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
3 changes: 2 additions & 1 deletion src/framework/scenario/Invoker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -34,6 +35,6 @@ export function returns<T extends Type>(n: Value<T>): Expectation[] {
if (n.type == Special.nothing) {
return [{'value': {kind: 'primitive', value: undefined} as Expected<undefined>}]
}
type R = T extends Float ? number : bigint;
type R = T extends Float ? number : WasmInt;
return [{'value': {kind: 'primitive', value: n.value} as Expected<R>}]
}
9 changes: 5 additions & 4 deletions src/messaging/Message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface Request<R> {
export namespace Message {
import Inspect = WARDuino.Inspect;
import Float = WASM.Float;
import isFloat = WASM.isFloat;
export const run: Request<Ack> = {
type: Interrupt.run,
parser: (line: string) => {
Expand Down Expand Up @@ -176,12 +177,12 @@ export namespace Message {
function convert(args: Value<Type>[]) {
let payload: string = '';
args.forEach((arg: Value<Type>) => {
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(<number>arg.value);
}
});
return payload;
Expand Down
36 changes: 17 additions & 19 deletions src/messaging/Parsers.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
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';
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);
}
Expand All @@ -19,7 +21,7 @@
if (exception(text)) {
return {text: text};
}
const stack: {value: any, type: any}[] = stateParser(text).stack!;
const stack: { value: any, type: any }[] = stateParser(text).stack!;

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.
if (stack.length == 0) {
return nothing;
}
Expand Down Expand Up @@ -67,42 +69,38 @@

}

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 {

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.
return WASM.typing.get(object.type.toLowerCase()) ?? WASM.Special.unknown;
}

function stacking(objects: {value: bigint | number, type: any}[]): WASM.Value<Type>[] {
function stacking(objects: { value: string, type: any }[]): WASM.Value<Type>[] {

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.
const stacked: WASM.Value<Type>[] = [];
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:
Expand Down
103 changes: 84 additions & 19 deletions src/sourcemap/Wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ export namespace WASM {

export enum Special {
nothing = 'nothing',
nan = 'nan',
infinity = 'infinity',
unknown = 'unknown'
}

Expand All @@ -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<T extends Type> {
type: T;
value: T extends Integer ? bigint : number;
value: T extends Integer ? WasmInt : number;
}

export function equals<T extends Type>(a: Value<T>, b: Value<T>): 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:
Expand All @@ -56,38 +115,44 @@ export namespace WASM {
type: Special.nothing, value: 0
}

export const nan: WASM.Value<Special> = {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<Special> = {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<Special> = {value: Infinity, type: Special.infinity};
export function nan(type: WASM.Type, positive: boolean = true): WASM.Value<Type> {
return {value: isInteger(type) ? WasmInt.nan(positive): (positive ? NaN : -NaN), type};
}

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

export function u32(n: bigint): WASM.Value<Integer> {
return {value: n, type: Integer.u32};
return {value: WasmInt.finite(n), type: Integer.u32};
}

export function i32(n: bigint): WASM.Value<Integer> {
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<Type> {
return {value: n, type: determineType(n)};
return {value: n, type: Float.f32};
}

export function f64(n: number): WASM.Value<Type> {
return {value: n, type: determineType(n)};
return {value: n, type: Float.f64};
}

export function u64(n: bigint): WASM.Value<Integer> {
return {value: n, type: Integer.u64};
return {value: WasmInt.finite(n), type: Integer.u64};
}

export function i64(n: bigint): WASM.Value<Integer> {
return {value: n, type: Integer.i64};
return {value: WasmInt.finite(n), type: Integer.i64};
}

export interface Frame {
Expand Down
39 changes: 20 additions & 19 deletions tests/unit/parsing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Type> | Exception = invokeParser('{"stack": [{"idx":0,"type":"i64","value":18446744073709551615}]}\n');

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

Expand All @@ -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<Type> | 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(<number>result.value));
}
});
Loading