diff --git a/.gitignore b/.gitignore index f0a5812..9ec4b74 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,6 @@ coverage !.vscode/extensions.json !.vscode/settings.json !.vscode/tasks.json +!.vscode/launch.json .idea -.DS_Store \ No newline at end of file +.DS_Store diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d826373..9002534 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,6 @@ { "recommendations": [ + "connor4312.nodejs-testing", "oxc.oxc-vscode", "streetsidesoftware.code-spell-checker", "typescriptteam.native-preview", diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..668656c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug current test file", + "runtimeExecutable": "node", + "runtimeArgs": ["--test", "--strip-types"], + "args": ["${file}"], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 4473f94..2021886 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,14 @@ { + "nodejs-testing.extensions": [ + { + "extensions": ["mjs", "cjs", "js"], + "parameters": [] + }, + { + "extensions": ["ts"], + "parameters": ["--strip-types"] + } + ], "[typescript]": { "editor.defaultFormatter": "oxc.oxc-vscode", "editor.formatOnSave": true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d69176..7c62e64 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,6 +67,12 @@ before the implementation. The loop is **type → red → green → refactor**: `npm run verify` as the definition-of-done gate. Every test pairs an `expectTypeOf(...)` with an `assert.*`; keep them together. +Each test body follows **AAA (Arrange–Act–Assert)** with labeled blocks +separated by a blank line: `// Arrange` sets up the inputs (e.g. the matcher +factory), `// Act` exercises the subject once from them (not a second +throwaway call), `// Assert` holds every check — type expectations first, +runtime assertions last; an empty block drops its label (see +[development/testing.md § AAA ordering](./development/testing.md#aaa-ordering)). Type-first is enforced structurally: `npm test` runs `check:tsc` before the test runner, so a wrong type can never be papered over by a passing assertion. Per [AGENTS.md § Never do](./AGENTS.md#never-do), reach green honestly — fix the diff --git a/backlog.tasks b/backlog.tasks index 6ec352e..2e5197b 100644 --- a/backlog.tasks +++ b/backlog.tasks @@ -6,6 +6,10 @@ Backlog and tracking for tiny-pattern-ts. Managed in vscode-todotasks format. Setup: ☐ Split off template into separate package => pi --session 01a07dde-7050-7054-bb36-1606d7eb2bc3 @high +☐ straighten oxc rules + ☐ oxc forbids ternary => remove oxc rule + ☐ oxc wants comments to start comments with a capital letter => remove oxc rule + ☐ there already exists a /* */ block comment in primitive.ts, => change to multi-line comment v1.0: ☐ API surface is stable and fully typed @@ -20,6 +24,8 @@ v1.0: Bugs: Enhancements: +☐ Allow boolean literals in primitive union patterns (e.g. `true: () => "yes"`) @medium +☐ Are there other primitive types that should be supported in union patterns? (e.g. `bigint`, `symbol`) @medium Documentation: ☐ Create `examples/` directory with runnable snippets diff --git a/cspell.json b/cspell.json index bebc052..f0e5dff 100644 --- a/cspell.json +++ b/cspell.json @@ -42,7 +42,8 @@ "bestikk", "silverwind", "idris", - "todotasks" + "todotasks", + "connor" ], "ignorePaths": ["dist", "node_modules", "coverage", "*.svg", ".gitignore"] } diff --git a/development/testing.md b/development/testing.md index d4ef8be..06a1986 100644 --- a/development/testing.md +++ b/development/testing.md @@ -36,10 +36,38 @@ The runner is `node --test --strip-types "src/**/*.test.ts"` and the tiers are i build step, and the runner relies on the `.ts` import-extension convention (see [tooling.md](./tooling.md#source-imports-use-ts-extensions)). +## AAA ordering + +The rule is in +[CONTRIBUTING.md § Testing discipline (type-driven)](../CONTRIBUTING.md#testing-discipline-type-driven). + +#### Decision (2026-09) + +Test bodies read arrange → act → assert: inputs (the factory) set up first, the +subject exercised once from them, all checks last — types then runtime. The +blocks are labeled with `// Arrange` / `// Act` / `// Assert` comments and +separated by a blank line; an empty block drops its label. + +#### Why + +- Interleaved setup/checks hide what runs vs. what is observed; the eye + re-reads the block to find the seams. +- A factory built mid-test invites a second throwaway call of the subject; + arranging it once makes the positive construction and the negative + `Parameters<…>` check share one source of truth. +- Labels make the seams explicit, not inferred — grep-able and reviewable + without reading the statements. + +#### Rejected + +- Unlabeled ordering (bare blank lines): the seams still have to be found by + reading; the labels cost nothing. + ## Known issues - The type-aware linter misidentifies `expectTypeOf()` as a floating promise, so - `src/index.test.ts` carries a file-level `oxlint-disable + test files that use it (`src/index.test.ts`, `src/primitive.test.ts`) carry a + file-level `oxlint-disable typescript/no-floating-promises` with an explanatory comment. It is a known false positive, not a rule worth disabling project-wide (see [tooling.md § oxlint-disable directives live next to the code](./tooling.md#oxlint-disable-directives-live-next-to-the-code)). diff --git a/package-lock.json b/package-lock.json index 16cfebb..a112ee6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "tiny-pattern-ts", "version": "0.1.8", "license": "MIT", + "dependencies": { + "type-fest": "^5.9.0" + }, "devDependencies": { "@arethetypeswrong/cli": "^0.18.5", "@runwisp/pubv": "^1.5.1", @@ -4622,6 +4625,18 @@ "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/test-exclude": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", @@ -4705,6 +4720,21 @@ "license": "0BSD", "optional": true }, + "node_modules/type-fest": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.9.0.tgz", + "integrity": "sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", diff --git a/package.json b/package.json index b68565c..16be35b 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,9 @@ "setup": "npm run setup:git-commit-message", "setup:git-commit-message": "git config commit.template commit-message-template" }, + "dependencies": { + "type-fest": "^5.9.0" + }, "devDependencies": { "@arethetypeswrong/cli": "^0.18.5", "@runwisp/pubv": "^1.5.1", diff --git a/src/index.test.ts b/src/index.test.ts index 5a1131b..ef6a58e 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -7,44 +7,59 @@ import { expectTypeOf } from "expect-type"; import { type Matcher, P, match } from "./index.ts"; test("match returns a builder", () => { + // Act const builder = match("x"); + + // Assert expectTypeOf(builder).toHaveProperty("with"); expectTypeOf(builder).toHaveProperty("exhaustive"); expectTypeOf(builder).toHaveProperty("otherwise"); }); test("P.literal narrows to its literal type", () => { + // Act const matcher = P.literal("yes"); - expectTypeOf(matcher).toMatchTypeOf>(); + + // Assert + expectTypeOf(matcher).toExtend>(); assert.equal(matcher.matches("yes"), true); assert.equal(matcher.matches("no"), false); }); test("P.type narrows to the typeof target", () => { + // Act const matcher = P.type("string"); - expectTypeOf(matcher).toMatchTypeOf>(); + + // Assert + expectTypeOf(matcher).toExtend>(); assert.equal(matcher.matches("hi"), true); assert.equal(matcher.matches(42), false); }); test("exhaustive() returns the union of handler return types", () => { + // Act const result = match<"a" | "b">("a") .with(P.literal("a"), () => 1 as const) .with(P.literal("b"), () => "two" as const) .exhaustive(); + // Assert expectTypeOf(result).toEqualTypeOf<1 | "two">(); assert.equal(result, 1); }); test("otherwise() falls back when no case matches", () => { + // Act const result = match<"x" | "y" | "z">("z") .with(P.literal("x"), (v): string => `got ${v}`) .otherwise((v): string => `fallback ${v}`); + + // Assert assert.equal(result, "fallback z"); }); test("exhaustive throws when no case matches", () => { + // Assert assert.throws( () => match<"a" | "b" | "c">("c") diff --git a/src/primitive.test.ts b/src/primitive.test.ts new file mode 100644 index 0000000..ee9f57e --- /dev/null +++ b/src/primitive.test.ts @@ -0,0 +1,357 @@ +/* oxlint-disable typescript/no-floating-promises -- expectTypeOf() is a sync type-assertion library that the type-aware linter misidentifies as a promise */ +import { strict as assert } from "node:assert"; +import { test } from "node:test"; + +import { expectTypeOf } from "expect-type"; + +import { + getPrimitiveUnionMatcher, + getPrimitiveUnionMatcherPartial, + getPrimitiveUnionMatcherPartialW, + getPrimitiveUnionMatcherW, +} from "./primitive.ts"; + +// ============================================================================ +// API: getPrimitiveUnionMatcherW — ✔️ Exhaustive / ❌ ReturnsStrict +// ============================================================================ + +test("getPrimitiveUnionMatcherW requires every literal key", () => { + // Arrange + const factory = getPrimitiveUnionMatcherW<"a" | "b">(); + + // Act + const matcher = factory({ + a: (s) => { + expectTypeOf(s).toEqualTypeOf<"a">(); + return 1 as const; + }, + b: (s) => { + expectTypeOf(s).toEqualTypeOf<"b">(); + return "two" as const; + }, + }); + + // Assert + // ❌ ReturnsStrict: mixed handler returns widen to their union. + expectTypeOf(matcher).toEqualTypeOf<(shape: "a" | "b") => 1 | "two">(); + // A pattern missing a key must not satisfy the parameter type. + expectTypeOf<{ + a: () => number; + }>().not.toExtend[0]>(); + assert.equal(matcher("a"), 1); + assert.equal(matcher("b"), "two"); +}); + +test("getPrimitiveUnionMatcherW dispatches on numeric literal keys", () => { + // Arrange + const factory = getPrimitiveUnionMatcherW<1 | 2>(); + + // Act + const matcher = factory({ + 1: (n) => { + expectTypeOf(n).toEqualTypeOf<1>(); + return n + 1; + }, + 2: (n) => { + expectTypeOf(n).toEqualTypeOf<2>(); + return n * 10; + }, + }); + + // Assert + expectTypeOf(matcher).toEqualTypeOf<(shape: 1 | 2) => number>(); + assert.equal(matcher(1), 2); + assert.equal(matcher(2), 20); +}); + +test("getPrimitiveUnionMatcherW dispatches on mixed string and numeric keys", () => { + // Arrange + const factory = getPrimitiveUnionMatcherW<"a" | "b" | 1 | 2>(); + + // Act + const matcher = factory({ + a: (s) => { + expectTypeOf(s).toEqualTypeOf<"a">(); + return "A" as const; + }, + b: (s) => { + expectTypeOf(s).toEqualTypeOf<"b">(); + return "B" as const; + }, + 1: (n) => { + expectTypeOf(n).toEqualTypeOf<1>(); + return 10 as const; + }, + 2: (n) => { + expectTypeOf(n).toEqualTypeOf<2>(); + return 20 as const; + }, + }); + + // Assert + expectTypeOf(matcher).toEqualTypeOf< + (shape: "a" | "b" | 1 | 2) => "A" | "B" | 10 | 20 + >(); + assert.equal(matcher("a"), "A"); + assert.equal(matcher("b"), "B"); + assert.equal(matcher(1), 10); + assert.equal(matcher(2), 20); +}); + +// ============================================================================ +// API: getPrimitiveUnionMatcher — ✔️ Exhaustive / ✔️ ReturnsStrict +// ============================================================================ + +test("getPrimitiveUnionMatcher infers a single return type shared by all handlers", () => { + // Arrange + const factory = getPrimitiveUnionMatcher<"a" | "b">(); + + // Act + const matcher = factory({ + a: (s): number => { + expectTypeOf(s).toEqualTypeOf<"a">(); + return 1; + }, + b: (s): 1 | 2 => { + expectTypeOf(s).toEqualTypeOf<"b">(); + return 2; + }, + }); + + // Assert + // ✔️ ReturnsStrict: R is the best common return type, not a widening union. + expectTypeOf(matcher).toEqualTypeOf<(shape: "a" | "b") => number>(); + assert.equal(matcher("a"), 1); + assert.equal(matcher("b"), 2); +}); + +test("getPrimitiveUnionMatcher handlers receive the matched literal", () => { + // Arrange + const factory = getPrimitiveUnionMatcher<"on" | "off">(); + + // Act + const matcher = factory({ + on: (s) => { + expectTypeOf(s).toEqualTypeOf<"on">(); + return `handler ${s}`; + }, + off: (s) => { + expectTypeOf(s).toEqualTypeOf<"off">(); + return `handler ${s}`; + }, + }); + + // Assert + expectTypeOf(matcher).toEqualTypeOf<(shape: "on" | "off") => string>(); + assert.equal(matcher("on"), "handler on"); + assert.equal(matcher("off"), "handler off"); +}); + +test("getPrimitiveUnionMatcher infers one return type across mixed string and numeric keys", () => { + // Arrange + const factory = getPrimitiveUnionMatcher<"a" | "b" | 1 | 2>(); + + // Act + const matcher = factory({ + a: (s): number => { + expectTypeOf(s).toEqualTypeOf<"a">(); + return 1; + }, + b: (s): 1 | 2 => { + expectTypeOf(s).toEqualTypeOf<"b">(); + return 2; + }, + 1: (n): number => { + expectTypeOf(n).toEqualTypeOf<1>(); + return 10; + }, + 2: (n): number => { + expectTypeOf(n).toEqualTypeOf<2>(); + return 20; + }, + }); + + // Assert + // ✔️ ReturnsStrict: R is the best common return type, not a widening union. + expectTypeOf(matcher).toEqualTypeOf<(shape: "a" | "b" | 1 | 2) => number>(); + assert.equal(matcher("a"), 1); + assert.equal(matcher("b"), 2); + assert.equal(matcher(1), 10); + assert.equal(matcher(2), 20); +}); + +// ============================================================================ +// API: getPrimitiveUnionMatcherPartial — ❌ Exhaustive / ✔️ ReturnsStrict +// ============================================================================ + +test("getPrimitiveUnionMatcherPartial routes shapes without a handler to _", () => { + // Arrange + const factory = getPrimitiveUnionMatcherPartial<"a" | "b" | "c">(); + // Two keys, so `{ a }` lacks only the `_` fallback, nothing else. + const sparseFactory = getPrimitiveUnionMatcherPartial<"a" | "b">(); + + // Act + const matcher = factory({ + a: (s): 1 | 2 => { + expectTypeOf(s).toEqualTypeOf<"a">(); + return 1; + }, + _: (s): 1 | 2 => { + // The fallback sees the whole union, not a single literal. + expectTypeOf(s).toEqualTypeOf<"a" | "b" | "c">(); + return 2; + }, + }); + + // Assert + expectTypeOf(matcher).toEqualTypeOf<(shape: "a" | "b" | "c") => 1 | 2>(); + // ❌ Exhaustive: gaps are allowed, but only with a `_` fallback. + expectTypeOf<{ + a: () => number; + }>().not.toExtend[0]>(); + assert.equal(matcher("a"), 1); + assert.equal(matcher("b"), 2); + assert.equal(matcher("c"), 2); +}); + +test("getPrimitiveUnionMatcherPartial also accepts an exhaustive pattern", () => { + // Arrange + const factory = getPrimitiveUnionMatcherPartial<"a" | "b">(); + + // Act + const matcher = factory({ + a: (s) => { + expectTypeOf(s).toEqualTypeOf<"a">(); + return "A"; + }, + b: (s) => { + expectTypeOf(s).toEqualTypeOf<"b">(); + return "B"; + }, + }); + + // Assert + expectTypeOf(matcher).toEqualTypeOf<(shape: "a" | "b") => string>(); + assert.equal(matcher("a"), "A"); + assert.equal(matcher("b"), "B"); +}); + +test("getPrimitiveUnionMatcherPartial routes mixed string and numeric keys, gaps go to _", () => { + // Arrange + const factory = getPrimitiveUnionMatcherPartial<"a" | "b" | 1 | 2>(); + + // Act + const matcher = factory({ + a: (s): string => { + expectTypeOf(s).toEqualTypeOf<"a">(); + return "A"; + }, + 1: (n): string => { + expectTypeOf(n).toEqualTypeOf<1>(); + return "one"; + }, + _: (s): string => { + // The fallback sees the whole union, not a single literal. + expectTypeOf(s).toEqualTypeOf<"a" | "b" | 1 | 2>(); + return "fallback"; + }, + }); + + // Assert + expectTypeOf(matcher).toEqualTypeOf<(shape: "a" | "b" | 1 | 2) => string>(); + assert.equal(matcher("a"), "A"); + assert.equal(matcher("b"), "fallback"); + assert.equal(matcher(1), "one"); + assert.equal(matcher(2), "fallback"); +}); + +// ============================================================================ +// API: getPrimitiveUnionMatcherPartialW — ❌ Exhaustive / ❌ ReturnsStrict +// ============================================================================ + +test("getPrimitiveUnionMatcherPartialW allows gaps and widens to the union of handler returns", () => { + // Arrange + const factory = getPrimitiveUnionMatcherPartialW<"x" | "y" | "z">(); + // Two keys, so `{ x }` lacks only the `_` fallback, nothing else. + const sparseFactory = getPrimitiveUnionMatcherPartialW<"x" | "y">(); + + // Act + const matcher = factory({ + x: (s) => { + expectTypeOf(s).toEqualTypeOf<"x">(); + return 1 as const; + }, + _: (s) => { + // The fallback sees the whole union, not a single literal. + expectTypeOf(s).toEqualTypeOf<"x" | "y" | "z">(); + return "fallback" as const; + }, + }); + + // Assert + expectTypeOf(matcher).toEqualTypeOf< + (shape: "x" | "y" | "z") => 1 | "fallback" + >(); + // ❌ Exhaustive: gaps are allowed, but only with a `_` fallback. + expectTypeOf<{ + x: () => number; + }>().not.toExtend[0]>(); + assert.equal(matcher("x"), 1); + assert.equal(matcher("y"), "fallback"); + assert.equal(matcher("z"), "fallback"); +}); + +test("getPrimitiveUnionMatcherPartialW widens mixed string and numeric key returns to their union", () => { + // Arrange + const factory = getPrimitiveUnionMatcherPartialW<"a" | "b" | 1 | 2>(); + + // Act + const matcher = factory({ + a: (s) => { + expectTypeOf(s).toEqualTypeOf<"a">(); + return "A" as const; + }, + 1: (n) => { + expectTypeOf(n).toEqualTypeOf<1>(); + return 10 as const; + }, + _: (s) => { + // The fallback sees the whole union, not a single literal. + expectTypeOf(s).toEqualTypeOf<"a" | "b" | 1 | 2>(); + return "fallback" as const; + }, + }); + + // Assert + // ❌ ReturnsStrict: mixed handler returns widen to their union. + expectTypeOf(matcher).toEqualTypeOf< + (shape: "a" | "b" | 1 | 2) => "A" | 10 | "fallback" + >(); + assert.equal(matcher("a"), "A"); + assert.equal(matcher("b"), "fallback"); + assert.equal(matcher(1), 10); + assert.equal(matcher(2), "fallback"); +}); + +test("getPrimitiveUnionMatcherPartialW also accepts an exhaustive pattern", () => { + // Arrange + const factory = getPrimitiveUnionMatcherPartialW<"x" | "y">(); + + // Act + const matcher = factory({ + x: (s) => { + expectTypeOf(s).toEqualTypeOf<"x">(); + return 1 as const; + }, + y: (s) => { + expectTypeOf(s).toEqualTypeOf<"y">(); + return "two" as const; + }, + }); + + // Assert + // ❌ ReturnsStrict: mixed handler returns widen to their union. + expectTypeOf(matcher).toEqualTypeOf<(shape: "x" | "y") => 1 | "two">(); + assert.equal(matcher("x"), 1); + assert.equal(matcher("y"), "two"); +}); diff --git a/src/primitive.ts b/src/primitive.ts new file mode 100644 index 0000000..6759784 --- /dev/null +++ b/src/primitive.ts @@ -0,0 +1,77 @@ +import type { Simplify, ValueOf } from "type-fest"; + +type UnaryFn = (shape: T) => R; + +// ============================================================================ +// ✔️ Exhaustive +// ❌ ReturnsStrict +// ============================================================================ +type PatternPrimitiveUnion = { + [K in T]: UnaryFn; +}; + +type PatternReturns< + P extends Record>, +> = ReturnType>; + +export const getPrimitiveUnionMatcherW: () => < + P extends PatternPrimitiveUnion, +>( + pattern: Simplify

, +) => UnaryFn> = () => (pattern) => (shape) => + /* + Rewrite not to use any is possible, was evaluated and solutions were + more complex than the current solution. + */ + + // oxlint-disable-next-line typescript/no-explicit-any typescript/no-unsafe-type-assertion + (pattern[shape] as any)(shape); + +// ============================================================================ +// ✔️ Exhaustive +// ✔️ ReturnsStrict +// ============================================================================ +export const getPrimitiveUnionMatcher: () => ( + pattern: Simplify>, +) => UnaryFn = getPrimitiveUnionMatcherW; + +// ============================================================================ +// ❌ Exhaustive +// ✔️ ReturnsStrict +// ============================================================================ +type PatternPrimitiveUnionPartial = + | PatternPrimitiveUnion + | (Partial> & { + _: UnaryFn; + }); + +export const getPrimitiveUnionMatcherPartial: () => < + R, +>( + pattern: Simplify>, +) => UnaryFn = () => (pattern) => (shape) => + /* + Rewrite not to use any is possible, was evaluated and solutions were + more complex than the current solution. + */ + + // oxlint-disable-next-line typescript/no-explicit-any typescript/no-unsafe-type-assertion + (pattern[shape] ?? (pattern as any)["_"])(shape); + +// ============================================================================ +// ❌ Exhaustive +// ❌ ReturnsStrict +// ============================================================================ +export const getPrimitiveUnionMatcherPartialW: < + T extends string | number, +>() =>

>( + /* + `Simplify

` is the inference hook: callers infer `P` from the argument. + the second half pins the impl parameter's `R` to `PatternReturns

`. + that makes the `= getPrimitiveUnionMatcherPartial` assignment type-check. + neither half works alone. + without the witness the union's `_` arm demands `_ ∈ keyof P`. + without `Simplify

` the parameter types do not compare. + */ + pattern: Simplify

& PatternPrimitiveUnionPartial, T>, +) => UnaryFn> = getPrimitiveUnionMatcherPartial;