More Option refactoring, and some tests
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
This commit is contained in:
parent
4313b923bf
commit
090d6262c3
@ -3,7 +3,7 @@ import Buffer from './buffer.ts';
|
|||||||
import Document from './document.ts';
|
import Document from './document.ts';
|
||||||
import Editor from './editor.ts';
|
import Editor from './editor.ts';
|
||||||
import { highlightToColor, HighlightType } from './highlight.ts';
|
import { highlightToColor, HighlightType } from './highlight.ts';
|
||||||
import _Option, { None, Some } from './option.ts';
|
import Option, { None, Some } from './option.ts';
|
||||||
import Position from './position.ts';
|
import Position from './position.ts';
|
||||||
import Row from './row.ts';
|
import Row from './row.ts';
|
||||||
|
|
||||||
@ -417,7 +417,19 @@ const EditorTest = {
|
|||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
const OptionTest = {
|
const OptionTest = {
|
||||||
// @TODO implement Option tests
|
'Option.from()': () => {
|
||||||
|
assertTrue(Option.from(null).isNone());
|
||||||
|
assertTrue(Option.from().isNone());
|
||||||
|
assertEquivalent(Option.from(undefined), None);
|
||||||
|
|
||||||
|
assertEquivalent(Option.from(Some('foo')), Some('foo'));
|
||||||
|
assertEquivalent(Some(Some('bar')), Some('bar'));
|
||||||
|
},
|
||||||
|
'.toString': () => {
|
||||||
|
assertEquals(Some({}).toString(), 'Some ({})');
|
||||||
|
assertEquals(Some([1, 2, 3]).toString(), 'Some ([1,2,3])');
|
||||||
|
assertEquals(None.toString(), 'None');
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -7,8 +7,27 @@ enum OptionType {
|
|||||||
None = 'None',
|
None = 'None',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Typeguards to handle Some/None difference
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
const isOption = <T>(v: any): v is Option<T> => v instanceof Option;
|
const isOption = <T>(v: any): v is Option<T> => v instanceof Option;
|
||||||
|
|
||||||
|
class OptionInnerNone<T> {
|
||||||
|
public type: OptionType = OptionType.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
class OptionInnerSome<T> {
|
||||||
|
public type: OptionType = OptionType.Some;
|
||||||
|
|
||||||
|
constructor(public value: T) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptionInnerType<T> = OptionInnerNone<T> | OptionInnerSome<T>;
|
||||||
|
|
||||||
|
const isSome = <T>(v: OptionInnerType<T>): v is OptionInnerSome<T> =>
|
||||||
|
'value' in v && v.type === OptionType.Some;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rust-style optional type
|
* Rust-style optional type
|
||||||
*
|
*
|
||||||
@ -18,50 +37,41 @@ export class Option<T> {
|
|||||||
/**
|
/**
|
||||||
* The placeholder for the 'None' value type
|
* The placeholder for the 'None' value type
|
||||||
*/
|
*/
|
||||||
private static _noneInstance: Option<any> = new Option();
|
private static _None: Option<any> = new Option(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is this a 'Some' or a 'None'?
|
* Is this a 'Some' or a 'None'?
|
||||||
*/
|
*/
|
||||||
private optionType: OptionType;
|
private readonly inner: OptionInnerType<T>;
|
||||||
|
|
||||||
/**
|
private constructor(v?: T) {
|
||||||
* The value for the 'Some' type
|
this.inner = (v !== undefined && v !== null)
|
||||||
*/
|
? new OptionInnerSome(v)
|
||||||
private value?: T;
|
: new OptionInnerNone();
|
||||||
|
|
||||||
private constructor(v?: T | null) {
|
|
||||||
if (v !== undefined && v !== null) {
|
|
||||||
this.optionType = OptionType.Some;
|
|
||||||
this.value = v;
|
|
||||||
} else {
|
|
||||||
this.optionType = OptionType.None;
|
|
||||||
this.value = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static get None(): Option<any> {
|
public static get None(): Option<any> {
|
||||||
return <Option<any>> Option._noneInstance;
|
return Option._None;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Some<X>(v: X): Option<X> {
|
public static Some<X>(v: any): Option<X> {
|
||||||
return new Option(v);
|
return Option.from(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static from<X>(v: any): Option<X> {
|
public static from<X>(v?: any): Option<X> {
|
||||||
return (isOption(v)) ? Option.from(v.unwrap()) : new Option(v);
|
return (isOption(v)) ? Option.from(v.unwrap()) : new Option(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
isSome(): boolean {
|
isSome(): boolean {
|
||||||
return this.optionType === OptionType.Some && this.value !== undefined;
|
return isSome(this.inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
isNone(): boolean {
|
isNone(): boolean {
|
||||||
return this.optionType === OptionType.None;
|
return !this.isSome();
|
||||||
}
|
}
|
||||||
|
|
||||||
isSomeAnd(fn: (a: T) => boolean): boolean {
|
isSomeAnd(fn: (a: T) => boolean): boolean {
|
||||||
return this.isSome() ? fn(this.unwrap()) : false;
|
return isSome(this.inner) ? fn(this.inner.value) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
isNoneAnd(fn: () => boolean): boolean {
|
isNoneAnd(fn: () => boolean): boolean {
|
||||||
@ -69,20 +79,20 @@ export class Option<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
map<U>(fn: (a: T) => U): Option<U> {
|
map<U>(fn: (a: T) => U): Option<U> {
|
||||||
return this.isSome() ? new Option(fn(this.unwrap())) : Option._noneInstance;
|
return isSome(this.inner) ? Option.from(fn(this.inner.value)) : Option.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapOr<U>(def: U, f: (a: T) => U): U {
|
mapOr<U>(def: U, f: (a: T) => U): U {
|
||||||
return this.isSome() ? f(this.unwrap()) : def;
|
return isSome(this.inner) ? f(this.inner.value) : def;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapOrElse<U>(def: () => U, f: (a: T) => U): U {
|
mapOrElse<U>(def: () => U, f: (a: T) => U): U {
|
||||||
return this.isSome() ? f(this.unwrap()) : def();
|
return isSome(this.inner) ? f(this.inner.value) : def();
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrap(): T | never {
|
unwrap(): T | never {
|
||||||
if (this.isSome() && this.value !== undefined) {
|
if (isSome(this.inner)) {
|
||||||
return this.value;
|
return this.inner.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error('None.unwrap()');
|
console.error('None.unwrap()');
|
||||||
@ -90,19 +100,19 @@ export class Option<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unwrapOr(def: T): T {
|
unwrapOr(def: T): T {
|
||||||
return this.isSome() ? this.unwrap() : def;
|
return isSome(this.inner) ? this.inner.value : def;
|
||||||
}
|
}
|
||||||
|
|
||||||
unwrapOrElse(f: () => T): T {
|
unwrapOrElse(f: () => T): T {
|
||||||
return this.isSome() ? this.unwrap() : f();
|
return isSome(this.inner) ? this.inner.value : f();
|
||||||
}
|
}
|
||||||
|
|
||||||
and<U>(optb: Option<U>): Option<U> {
|
and<U>(optb: Option<U>): Option<U> {
|
||||||
return this.isSome() ? optb : Option._noneInstance;
|
return isSome(this.inner) ? optb : Option.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
andThen<U>(f: (a: T) => Option<U>): Option<U> {
|
andThen<U>(f: (a: T) => Option<U>): Option<U> {
|
||||||
return this.isSome() ? f(this.unwrap()) : Option._noneInstance;
|
return isSome(this.inner) ? f(this.inner.value) : Option.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
or(optb: Option<T>): Option<T> {
|
or(optb: Option<T>): Option<T> {
|
||||||
@ -114,10 +124,10 @@ export class Option<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
const innerValue = (this.value !== undefined)
|
const innerValue = (isSome(this.inner))
|
||||||
? JSON.stringify(this.value)
|
? JSON.stringify(this.inner.value)
|
||||||
: '';
|
: '';
|
||||||
const prefix = this.optionType.valueOf();
|
const prefix = this.inner.type.valueOf();
|
||||||
|
|
||||||
return (innerValue.length > 0) ? `${prefix} (${innerValue})` : prefix;
|
return (innerValue.length > 0) ? `${prefix} (${innerValue})` : prefix;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user