Skip to content

Commit 76aad9e

Browse files
authored
Merge pull request #35 from reside-ic/issue-32
Add io utilities
2 parents c2a942c + f1873b6 commit 76aad9e

2 files changed

Lines changed: 298 additions & 0 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { DimUtils } from "./array";
2+
3+
const isNullish = (x: unknown) => x === null || x === undefined;
4+
5+
const errorIfMissingValueAndDefault = (par: unknown, parName: string, defaultValue?: unknown) => {
6+
if (isNullish(par) && isNullish(defaultValue)) {
7+
throw new Error(`'${parName}' must not be a missing value`);
8+
}
9+
};
10+
11+
const checkScalar = (x: unknown, name: string) => {
12+
if (typeof x !== "number") {
13+
throw new Error(`'${name}' must be a scalar`);
14+
}
15+
};
16+
17+
const readReal = <TParams extends Record<string, unknown>, ParName extends string>(
18+
params: TParams,
19+
parName: ParName,
20+
defaultValue?: TParams[ParName]
21+
) => {
22+
const par = params[parName];
23+
errorIfMissingValueAndDefault(par, parName, defaultValue);
24+
25+
if (isNullish(par)) {
26+
checkScalar(defaultValue, parName);
27+
return defaultValue!;
28+
}
29+
30+
checkScalar(par, parName);
31+
return par;
32+
};
33+
34+
const readInt = <TParams extends Record<string, unknown>, ParName extends string>(
35+
params: TParams,
36+
parName: ParName,
37+
defaultValue?: TParams[ParName]
38+
) => {
39+
const par = readReal(params, parName, defaultValue);
40+
if (!Number.isInteger(par)) {
41+
throw new Error(`'${parName}' must be an integer`);
42+
}
43+
return par;
44+
};
45+
46+
const readSize = <TParams extends Record<string, unknown>, ParName extends string>(
47+
params: TParams,
48+
parName: ParName,
49+
defaultValue?: TParams[ParName]
50+
) => {
51+
const par = readReal(params, parName, defaultValue);
52+
const intPar = Math.round(par as number);
53+
if (intPar < 0) {
54+
throw new Error(`'${parName}' must be non-negative`);
55+
}
56+
};
57+
58+
const readBool = <TParams extends Record<string, unknown>, ParName extends string>(
59+
params: TParams,
60+
parName: ParName,
61+
defaultValue?: TParams[ParName]
62+
) => {
63+
const par = params[parName];
64+
errorIfMissingValueAndDefault(par, parName, defaultValue);
65+
66+
if (isNullish(par)) return defaultValue!;
67+
68+
return Boolean(par);
69+
};
70+
71+
const checkDims = (par: number[], dims: number[], name: string) => {
72+
const size = dims.reduce((agg, dim) => agg * dim, 1);
73+
if (par.length !== size) {
74+
throw new Error(`Expected '${name}' to have size ${size} but got ${par.length}`);
75+
}
76+
};
77+
78+
const readRealArray = <TParams extends Record<string, unknown>, ParName extends string>(
79+
params: TParams,
80+
parName: ParName,
81+
dim: DimUtils
82+
) => {
83+
const par = params[parName] as number[];
84+
checkDims(par, dim.dim, parName);
85+
return par;
86+
};
87+
88+
const readIntArray = <TParams extends Record<string, unknown>, ParName extends string>(
89+
params: TParams,
90+
parName: ParName,
91+
dim: DimUtils
92+
) => {
93+
const par = params[parName] as number[];
94+
checkDims(par, dim.dim, parName);
95+
return par.map(Math.round);
96+
};
97+
98+
const checkMinScalar = (par: number, min: number, name: string) => {
99+
if (par < min) {
100+
throw new Error(`'${name}' must be at least ${min}`);
101+
}
102+
};
103+
104+
const checkMaxScalar = (par: number, max: number, name: string) => {
105+
if (par > max) {
106+
throw new Error(`'${name}' must be at most ${max}`);
107+
}
108+
};
109+
110+
const checkMinArray = (par: number[], min: number, name: string) => {
111+
for (let i = 0; i < par.length; i++) {
112+
if (par[i] < min) {
113+
throw new Error(`All values of '${name}' must be at least ${min}`);
114+
}
115+
}
116+
};
117+
118+
const checkMaxArray = (par: number[], max: number, name: string) => {
119+
for (let i = 0; i < par.length; i++) {
120+
if (par[i] > max) {
121+
throw new Error(`All values of '${name}' must be at most ${max}`);
122+
}
123+
}
124+
};
125+
126+
export const io = {
127+
readReal,
128+
readInt,
129+
readSize,
130+
readBool,
131+
readRealArray,
132+
readIntArray,
133+
checkMinScalar,
134+
checkMaxScalar,
135+
checkMinArray,
136+
checkMaxArray
137+
};

tests/importsIo.test.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { describe, expect, test } from "vitest";
2+
import { io } from "../src/interfaces/generators/imports/io";
3+
import { DimUtils } from "../src/interfaces/generators/imports/array";
4+
5+
describe("io", () => {
6+
const dimUtils2d = { dim: [2, 3] } as DimUtils;
7+
const params = {
8+
a: 10.1,
9+
b: "b",
10+
c: null,
11+
d: undefined,
12+
e: -20.1,
13+
f: true,
14+
g: false,
15+
h: [1.1, 2.2, 3.3, 4.4, 5.5, 6.6],
16+
i: 2
17+
} as const;
18+
type Par = keyof typeof params;
19+
20+
test("readReal works as expected", () => {
21+
const par: Par = "a";
22+
expect(io.readReal(params, par)).toBe(params[par]);
23+
});
24+
25+
test("readReal throws if arg is not scalar", () => {
26+
const par: Par = "b";
27+
expect(() => {
28+
io.readReal(params, par);
29+
}).toThrow(`'${par}' must be a scalar`);
30+
});
31+
32+
test("readReal throws if arg is nullish with no default", () => {
33+
let par: Par = "c";
34+
expect(() => {
35+
io.readReal(params, par);
36+
}).toThrow(`'${par}' must not be a missing value`);
37+
par = "d";
38+
expect(() => {
39+
io.readReal(params, par);
40+
}).toThrow(`'${par}' must not be a missing value`);
41+
});
42+
43+
test("readReal returns default if arg is nullish", () => {
44+
const def = 1.1;
45+
let par: Par = "c";
46+
expect(io.readReal(params, par, def as any)).toBe(def);
47+
par = "d";
48+
expect(io.readReal(params, par, def as any)).toBe(def);
49+
});
50+
51+
test("readReal throws if default is not scalar", () => {
52+
const par: Par = "c";
53+
expect(() => {
54+
io.readReal(params, par, "hello" as any);
55+
}).toThrow(`'${par}' must be a scalar`);
56+
});
57+
58+
test("readInt throws if number is not an integer", () => {
59+
const par: Par = "a";
60+
expect(() => {
61+
io.readInt(params, par);
62+
}).toThrow(`'${par}' must be an integer`);
63+
});
64+
65+
test("readInt returns number if integer", () => {
66+
const par: Par = "i";
67+
expect(io.readInt(params, par)).toBe(2);
68+
});
69+
70+
test("readSize errors if number is negative", () => {
71+
const par: Par = "e";
72+
expect(() => {
73+
io.readSize(params, par);
74+
}).toThrow(`'${par}' must be non-negative`);
75+
});
76+
77+
test("readBool works as expected", () => {
78+
let par: Par = "f";
79+
expect(io.readBool(params, par)).toBe(true);
80+
par = "g";
81+
expect(io.readBool(params, par)).toBe(false);
82+
});
83+
84+
test("readBool throws if param is nullish with no default", () => {
85+
let par: Par = "c";
86+
expect(() => {
87+
io.readBool(params, par);
88+
}).toThrow(`'${par}' must not be a missing value`);
89+
par = "d";
90+
expect(() => {
91+
io.readBool(params, par);
92+
}).toThrow(`'${par}' must not be a missing value`);
93+
});
94+
95+
test("readBool returns default if arg is nullish", () => {
96+
const def = false;
97+
let par: Par = "c";
98+
expect(io.readBool(params, par, def as any)).toBe(def);
99+
par = "d";
100+
expect(io.readBool(params, par, def as any)).toBe(def);
101+
});
102+
103+
test("readRealArray works as expected", () => {
104+
const par: Par = "h";
105+
expect(io.readRealArray(params, par, dimUtils2d)).toStrictEqual(params[par]);
106+
});
107+
108+
test("readRealArray throws if size isn't at least dim prod", () => {
109+
const par: Par = "h";
110+
expect(() => {
111+
io.readRealArray(params, par, { dim: [1, 2] } as DimUtils);
112+
}).toThrow(`Expected '${par}' to have size 2 but got 6`);
113+
});
114+
115+
test("readIntArray works as expected", () => {
116+
const par: Par = "h";
117+
expect(io.readIntArray(params, par, dimUtils2d)).toStrictEqual(params[par].map(Math.round));
118+
});
119+
120+
test("checkMinScalar works as expected", () => {
121+
const name = "foo";
122+
expect(() => {
123+
io.checkMinScalar(10.2, 5.1, name);
124+
}).not.toThrow();
125+
expect(() => {
126+
io.checkMinScalar(10.2, 10.3, name);
127+
}).toThrow(`'${name}' must be at least 10.3`);
128+
});
129+
130+
test("checkMaxScalar works as expected", () => {
131+
const name = "foo";
132+
expect(() => {
133+
io.checkMaxScalar(10.2, 10.3, name);
134+
}).not.toThrow();
135+
expect(() => {
136+
io.checkMaxScalar(10.2, 5.1, name);
137+
}).toThrow(`'${name}' must be at most 5.1`);
138+
});
139+
140+
test("checkMinArray works as expected", () => {
141+
const name = "foo";
142+
const arr = [10.1, 5.2];
143+
expect(() => {
144+
io.checkMinArray(arr, 5.1, name);
145+
}).not.toThrow();
146+
expect(() => {
147+
io.checkMinArray(arr, 5.3, name);
148+
}).toThrow(`All values of '${name}' must be at least 5.3`);
149+
});
150+
151+
test("checkMaxArray works as expected", () => {
152+
const name = "foo";
153+
const arr = [10.1, 5.2];
154+
expect(() => {
155+
io.checkMaxArray(arr, 10.2, name);
156+
}).not.toThrow();
157+
expect(() => {
158+
io.checkMaxArray(arr, 5.3, name);
159+
}).toThrow(`All values of '${name}' must be at most 5.3`);
160+
});
161+
});

0 commit comments

Comments
 (0)