Skip to content

Commit cde8834

Browse files
committed
add iterable variant to array result
1 parent 25e4197 commit cde8834

File tree

6 files changed

+216
-25
lines changed

6 files changed

+216
-25
lines changed

dictionary/build.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
// deno-lint-ignore-file no-console
44

5-
import { ArrayResultError } from "../src/array_result.ts";
5+
import { ResultError } from "../src/array_result.ts";
66
import { PositionedError } from "../src/parser/parser_lib.ts";
77
import { dictionaryParser } from "./parser.ts";
88
import { Dictionary } from "./type.ts";
@@ -50,7 +50,7 @@ export async function build(): Promise<boolean> {
5050
);
5151
return true;
5252
}
53-
function displayError(source: string, errors: ReadonlyArray<ArrayResultError>) {
53+
function displayError(source: string, errors: ReadonlyArray<ResultError>) {
5454
let color: boolean;
5555
try {
5656
color = Deno.env.get("NO_COLOR") !== "1";

src/array_result.ts

Lines changed: 202 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,28 @@ export type ArrayResultOptions = {
44
cause: unknown;
55
isHtml: boolean;
66
};
7-
export class ArrayResultError extends Error {
7+
export class ResultError extends Error {
88
isHtml: boolean;
99
override name = "ArrayResultError";
1010
constructor(message: string, options: Partial<ArrayResultOptions> = {}) {
1111
super(message, { cause: options.cause });
1212
this.isHtml = options.isHtml ?? false;
1313
}
1414
}
15-
export class TodoError extends ArrayResultError {
15+
export class TodoError extends ResultError {
1616
override name = "TodoError";
1717
constructor(functionality: string) {
1818
super(`${functionality} is not yet implemented`);
1919
}
2020
}
2121
export class ArrayResult<const T> {
2222
constructor(array?: ReadonlyArray<T>);
23-
constructor(array: undefined, errors: ReadonlyArray<ArrayResultError>);
23+
constructor(array: undefined, errors: ReadonlyArray<ResultError>);
2424
constructor(
2525
public readonly array: ReadonlyArray<T> = [],
26-
public readonly errors: ReadonlyArray<ArrayResultError> = [],
26+
public readonly errors: ReadonlyArray<ResultError> = [],
2727
) {}
28-
static errors(errors: ReadonlyArray<ArrayResultError>): ArrayResult<never> {
28+
static errors(errors: ReadonlyArray<ResultError>): ArrayResult<never> {
2929
return new ArrayResult(undefined, errors);
3030
}
3131
static empty(): ArrayResult<never> {
@@ -75,13 +75,20 @@ export class ArrayResult<const T> {
7575
sortBy(mapper: (value: T) => number): ArrayResult<T> {
7676
return this.sort((left, right) => mapper(left) - mapper(right));
7777
}
78-
addErrorWhenNone(error: () => ArrayResultError): ArrayResult<T> {
78+
addErrorWhenNone(error: () => ResultError): ArrayResult<T> {
7979
if (this.isError() && this.errors.length === 0) {
8080
return ArrayResult.errors([error()]);
8181
} else {
8282
return this;
8383
}
8484
}
85+
asIterableResult(): IterableResult<T> {
86+
if (this.isError()) {
87+
return IterableResult.errors(this.errors);
88+
} else {
89+
return IterableResult.fromArray(this.unwrap());
90+
}
91+
}
8592
static concat<T>(
8693
...arrayResults: ReadonlyArray<ArrayResult<T>>
8794
): ArrayResult<T> {
@@ -118,20 +125,204 @@ export class ArrayResult<const T> {
118125
try {
119126
return arrayResult();
120127
} catch (error) {
121-
return ArrayResult.errors(extractArrayResultError(error));
128+
return ArrayResult.errors(extractResultError(error));
129+
}
130+
}
131+
}
132+
export type Result<T> =
133+
| Readonly<{ type: "value"; value: T }>
134+
| Readonly<{ type: "error"; error: ResultError }>;
135+
136+
export class IterableResult<const T> {
137+
constructor(public readonly iterable: () => Generator<Result<T>>) {}
138+
static fromArray<const T>(array: ReadonlyArray<T>): IterableResult<T> {
139+
return new IterableResult(function* () {
140+
for (const value of array) {
141+
yield { type: "value", value };
142+
}
143+
});
144+
}
145+
static single<const T>(value: T): IterableResult<T> {
146+
return new IterableResult(function* () {
147+
yield { type: "value", value };
148+
});
149+
}
150+
static errors(errors: ReadonlyArray<ResultError>): IterableResult<never> {
151+
return new IterableResult(function* () {
152+
for (const error of errors) {
153+
yield { type: "error", error };
154+
}
155+
});
156+
}
157+
static empty(): IterableResult<never> {
158+
return new IterableResult(function* () {});
159+
}
160+
filter(mapper: (value: T) => boolean): IterableResult<T> {
161+
return this.flatMap((value) =>
162+
mapper(value) ? IterableResult.single(value) : IterableResult.empty()
163+
);
164+
}
165+
map<const U>(mapper: (value: T) => U): IterableResult<U> {
166+
return this.flatMap((value) => IterableResult.single(mapper(value)));
167+
}
168+
flatMap<const U>(mapper: (value: T) => IterableResult<U>): IterableResult<U> {
169+
const iterable = this.iterable;
170+
return new IterableResult(function* () {
171+
const errors: Array<ResultError> = [];
172+
let yielded = false;
173+
for (const result of iterable()) {
174+
switch (result.type) {
175+
case "value": {
176+
const more = IterableResult.from(() => mapper(result.value));
177+
for (const result of more.iterable()) {
178+
switch (result.type) {
179+
case "value":
180+
yielded = false;
181+
yield result;
182+
break;
183+
case "error":
184+
errors.push(result.error);
185+
}
186+
}
187+
break;
188+
}
189+
case "error":
190+
yield result;
191+
}
192+
}
193+
if (!yielded) {
194+
for (const error of errors) {
195+
yield { type: "error", error } as const;
196+
}
197+
}
198+
});
199+
}
200+
filterMap<const U>(mapper: (value: T) => U): IterableResult<NonNullable<U>> {
201+
return this.flatMap((value) => {
202+
const newValue = mapper(value);
203+
if (newValue == null) {
204+
return IterableResult.empty();
205+
} else {
206+
return IterableResult.single(newValue);
207+
}
208+
});
209+
}
210+
addErrorWhenNone(error: () => ResultError): IterableResult<T> {
211+
const iterable = this.iterable;
212+
return new IterableResult(function* () {
213+
let yielded = false;
214+
for (const result of iterable()) {
215+
yielded = true;
216+
yield result;
217+
}
218+
if (!yielded) {
219+
yield { type: "error", error: error() };
220+
}
221+
});
222+
}
223+
static concat<T>(
224+
...iterableResults: ReadonlyArray<IterableResult<T>>
225+
): IterableResult<T> {
226+
return new IterableResult(function* () {
227+
const errors: Array<ResultError> = [];
228+
let yielded = false;
229+
for (const iterable of iterableResults) {
230+
for (const result of iterable.iterable()) {
231+
switch (result.type) {
232+
case "value":
233+
yielded = true;
234+
yield result;
235+
break;
236+
case "error":
237+
errors.push(result.error);
238+
break;
239+
}
240+
}
241+
}
242+
if (!yielded) {
243+
for (const error of errors) {
244+
yield { type: "error", error } as const;
245+
}
246+
}
247+
});
248+
}
249+
static combine<T extends ReadonlyArray<unknown>>(
250+
...iterableResults:
251+
& Readonly<{ [I in keyof T]: IterableResult<T[I]> }>
252+
& Readonly<{ length: T["length"] }>
253+
): IterableResult<T> {
254+
// we resorted to using `any` types here, make sure it works properly
255+
return iterableResults.reduce(
256+
(left: IterableResult<any>, right) =>
257+
new IterableResult(function* () {
258+
let rightAggregate: null | Array<any> = null;
259+
let yielded = false;
260+
for (const leftResult of left.iterable()) {
261+
switch (leftResult.type) {
262+
case "value":
263+
if (rightAggregate == null) {
264+
rightAggregate = [];
265+
for (const rightResult of right.iterable()) {
266+
switch (rightResult.type) {
267+
case "value": {
268+
const right = rightResult.value;
269+
rightAggregate.push(right);
270+
yielded = true;
271+
yield {
272+
type: "value",
273+
value: [...leftResult.value, right],
274+
};
275+
break;
276+
}
277+
case "error":
278+
yield rightResult;
279+
break;
280+
}
281+
}
282+
} else {
283+
for (const right of rightAggregate) {
284+
yielded = true;
285+
yield {
286+
type: "value",
287+
value: [...leftResult.value, right],
288+
};
289+
}
290+
}
291+
break;
292+
case "error":
293+
yield leftResult;
294+
break;
295+
}
296+
}
297+
if (!yielded && rightAggregate == null) {
298+
for (const result of right.iterable()) {
299+
if (result.type === "error") {
300+
yield result;
301+
}
302+
}
303+
}
304+
}),
305+
IterableResult.single([]) as IterableResult<any>,
306+
) as IterableResult<T>;
307+
}
308+
static from<T>(iterableResult: () => IterableResult<T>): IterableResult<T> {
309+
try {
310+
return iterableResult();
311+
} catch (error) {
312+
return IterableResult.errors(extractResultError(error));
122313
}
123314
}
124315
}
125-
export function extractArrayResultError(
316+
export function extractResultError(
126317
error: unknown,
127-
): ReadonlyArray<ArrayResultError> {
128-
if (error instanceof ArrayResultError) {
318+
): ReadonlyArray<ResultError> {
319+
if (error instanceof ResultError) {
129320
return [error];
130321
} else if (error instanceof AggregateError) {
131322
const { errors } = error;
132323
if (
133324
errors.length > 0 &&
134-
errors.every((error) => error instanceof ArrayResultError)
325+
errors.every((error) => error instanceof ResultError)
135326
) {
136327
return errors;
137328
}

src/parser/filter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { compound, throwError } from "../../misc/misc.ts";
2-
import { extractArrayResultError } from "../array_result.ts";
2+
import { extractResultError } from "../array_result.ts";
33
import { settings } from "../settings.ts";
44
import {
55
Clause,
@@ -376,7 +376,7 @@ export function filter<const T>(
376376
return [];
377377
}
378378
} catch (error) {
379-
return extractArrayResultError(error);
379+
return extractResultError(error);
380380
}
381381
},
382382
);

src/parser/parser_lib.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { assertGreater } from "@std/assert/greater";
22
import { MemoizationCacheResult, memoize } from "@std/cache/memoize";
33
import { lazy as lazyEval } from "../../misc/misc.ts";
4-
import { ArrayResult, ArrayResultError } from "../array_result.ts";
4+
import { ArrayResult, ResultError } from "../array_result.ts";
55

66
type ParserResult<T> = ArrayResult<Readonly<{ value: T; length: number }>>;
77
type InnerParser<T> = (input: number) => ParserResult<T>;
@@ -82,7 +82,7 @@ export class Parser<const T> {
8282
}
8383
}
8484
export type Position = Readonly<{ position: number; length: number }>;
85-
export class PositionedError extends ArrayResultError {
85+
export class PositionedError extends ResultError {
8686
public position: null | Position;
8787
override name = "PositionedError";
8888
constructor(message: string, position?: Position) {
@@ -116,7 +116,7 @@ export class UnrecognizedError extends PositionedError {
116116
super(`${element} is unrecognized`, position);
117117
}
118118
}
119-
export function error(error: ArrayResultError): Parser<never> {
119+
export function error(error: ResultError): Parser<never> {
120120
return new Parser(() => ArrayResult.errors([error]));
121121
}
122122
export const empty: Parser<never> = new Parser(() => ArrayResult.empty());
@@ -365,7 +365,7 @@ export function choiceWithCheck<const T>(
365365
...choices: ReadonlyArray<CheckedParser<T>>
366366
): Parser<T> {
367367
return new Parser((position) => {
368-
const errors: Array<ArrayResultError> = [];
368+
const errors: Array<ResultError> = [];
369369
for (const { check, parser } of choices) {
370370
const result = check.rawParser(position);
371371
if (result.isError()) {

src/translator/error.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
import { ArrayResultError, TodoError } from "../array_result.ts";
1+
import { ResultError, TodoError } from "../array_result.ts";
22

33
export class TranslationTodoError extends TodoError {
44
override name = "TranslationTodoError";
55
constructor(type: string) {
66
super(`translation of ${type}`);
77
}
88
}
9-
export class ExhaustedError extends ArrayResultError {
9+
export class ExhaustedError extends ResultError {
1010
override name = "ExhaustedError";
1111
constructor(text: string) {
1212
super(`no possible translation found for "${text}"`);
1313
}
1414
}
15-
export class FilteredError extends ArrayResultError {
15+
export class FilteredError extends ResultError {
1616
override name = "FilteredOutError";
1717
constructor(element: string) {
1818
super(`${element} is filtered out`);
1919
}
2020
}
21-
export class UntranslatableError extends ArrayResultError {
21+
export class UntranslatableError extends ResultError {
2222
override name = "UntranslatableError";
2323
constructor(source: string, target: string) {
2424
super(`cannot translate ${source} into ${target}`);

src/translator/translator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { distinct } from "@std/collections/distinct";
22
import { distinctBy } from "@std/collections/distinct-by";
33
import { shuffle } from "@std/random/shuffle";
44
import { errors } from "../../telo_misikeke/telo_misikeke.js";
5-
import { ArrayResult, ArrayResultError } from "../array_result.ts";
5+
import { ArrayResult, ResultError } from "../array_result.ts";
66
import { parser } from "../parser/parser.ts";
77
import { settings } from "../settings.ts";
88
import * as EnglishComposer from "./composer.ts";
@@ -23,7 +23,7 @@ export function translate(tokiPona: string): ArrayResult<string> {
2323
} else {
2424
const teloMisikekeErrors = settings.teloMisikeke
2525
? errors(tokiPona)
26-
.map((message) => new ArrayResultError(message, { isHtml: true }))
26+
.map((message) => new ResultError(message, { isHtml: true }))
2727
: [];
2828
const error = teloMisikekeErrors.length === 0
2929
? deduplicateErrors(arrayResult.errors)

0 commit comments

Comments
 (0)