From cdbc0dcd1640240a919bd01a31988a22841bdb4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr> Date: Wed, 24 Jun 2020 16:02:42 +0200 Subject: [PATCH] [ivette] fix in decoders --- ivette/api/server_tsc.ml | 2 +- ivette/src/dome/src/renderer/data/json.ts | 103 +++++++++++++++++----- 2 files changed, 83 insertions(+), 22 deletions(-) diff --git a/ivette/api/server_tsc.ml b/ivette/api/server_tsc.ml index a50958b176c..378dfc49d94 100644 --- a/ivette/api/server_tsc.ml +++ b/ivette/api/server_tsc.ml @@ -168,7 +168,7 @@ let rec makeDecoder ~safe ?self ~names fmt js = | Jself -> jcall names fmt (Pkg.Derived.decode ~safe (getSelf self)) | Joption js -> makeLoose fmt js | Jdict(kd,js) -> - Format.fprintf fmt "@[<hov 2>Json.jDict('#%s',@,%a)@]" kd makeLoose js + Format.fprintf fmt "@[<hov 2>Json.jDictionary('#%s',@,%a)@]" kd makeLoose js | Jlist js -> Format.fprintf fmt "@[<hov 2>Json.jList(%a)@]" makeLoose js | Jarray js -> diff --git a/ivette/src/dome/src/renderer/data/json.ts b/ivette/src/dome/src/renderer/data/json.ts index 2e28501d599..7339577a393 100644 --- a/ivette/src/dome/src/renderer/data/json.ts +++ b/ivette/src/dome/src/renderer/data/json.ts @@ -71,6 +71,12 @@ export function identity<A>(v: A): A { return v; }; // --- Primitives // -------------------------------------------------------------------------- +/** Always returns `undefined` on any input. */ +export const jNull: Safe<undefined> = (_: json) => undefined; + +/** Identity. */ +export const jAny: Safe<json> = (js: json) => js; + /** Primitive JSON number or `undefined`. */ export const jNumber: Loose<number> = (js: json) => ( typeof js === 'number' && !Number.isNaN(js) ? js : undefined @@ -106,13 +112,21 @@ export const jString: Loose<string> = (js: json) => ( typeof js === 'string' ? js : undefined ); +/** + Lookup tags in a dictionary. + Can be used directly for enum types, eg. `jEnum(myEnumType)`. + */ +export function jEnum<A>(d: { [tag: string]: A }): Loose<A> { + return (v: json) => typeof v === 'string' ? d[v] : undefined; +} + /** One of the enumerated _constants_ or `undefined`. The typechecker will prevent you from listing values that are not in type `A`. However, it will not protected you from missings constants in `A`. */ -export function jEnum<A>(...values: ((string | number) & A)[]): Loose<A> { +export function jTags<A>(...values: ((string | number) & A)[]): Loose<A> { var m = new Map<string | number, A>(); values.forEach(v => m.set(v, v)); return (v: json) => (typeof v === 'string' ? m.get(v) : undefined); @@ -131,18 +145,10 @@ export function jDefault<A>( } /** - Force returning `undefined` or a default value for `undefined` JSON input. + Force returning `undefined` or a default value for `undefined` _or_ `null` JSON input. Typically usefull to leverage an existing `Safe<A>` decoder. */ export function jOption<A>(fn: Safe<A>, defaultValue?: A): Loose<A> { - return (js: json) => (js === undefined ? defaultValue : fn(js)); -} - -/** - Force returning `undefined` or a default value for `undefined` _or_ `null` - JSON input. Typically usefull to leverage an existing `Safe<A>` decoder. - */ -export function jNull<A>(fn: Safe<A>, defaultValue?: A): Loose<A> { return (js: json) => (js === undefined || js === null ? defaultValue : fn(js)); } @@ -349,6 +355,19 @@ export function jObject<A>(fp: Props<A>): Loose<A> { }; } +/** + Returns the first decoder result that is not undefined. + */ +export function jUnion<A>(...cases: Loose<A>[]): Loose<A> { + return (js: json) => { + for (var fn of cases) { + const fv = fn(js); + if (fv !== undefined) return fv; + } + return undefined; + }; +} + /** Encoders for each property of object type `A`. */ @@ -378,22 +397,64 @@ export function eObject<A>(fp: EProps<A>): Encoder<A> { } } -/** Type of dictionaries. */ -export type dict<A> = { [key: string]: A }; +// Intentionnaly internal and only declared +declare const tag: unique symbol; + +/** Phantom type. */ +export type phantom<K, A> = A & { tag: K }; + +export function forge<K, A>(_tag: K, data: A): phantom<K, A> { + return data as any; +} + +/** String key with kind. Can be used as a `string` but shall be created with [forge]. */ +export type key<K> = phantom<K, string>; + +/** Number index with kind. Can be used as a `number` but shall be created with [forge]. */ +export type index<K> = phantom<K, number>; + +/** Decoder for `key<K>` strings. */ +export function jKey<K>(kd: K): Loose<key<K>> { + return (js: json) => typeof js === 'string' ? forge(kd, js) : undefined; +} + +/** Decoder for `index<K>` numbers. */ +export function jIndex<K>(kd: K): Loose<index<K>> { + return (js: json) => typeof js === 'number' ? forge(kd, js) : undefined; +} + +/** Dictionaries with « typed » keys. */ +export type dict<K, A> = phantom<K, { [key: string]: A }> + +/** Lookup into dictionary. + Better than a direct access to `d[k]` for undefined values. */ +export function lookup<K, A>(d: dict<K, A>, k: key<K>): A | undefined { + return d[k]; +} + +/** Empty dictionary. */ +export function empty<K, A>(kd: K): dict<K, A> { + return forge(kd, {} as any); +} + +/** Dictionary extension. */ +export function index<K, A>(d: dict<K, A>, key: key<K>, value: A) { + d[key] = value; +} /** Decode a JSON dictionary, dicarding all inconsistent entries. If the JSON contains no valid entry, still returns `{}`. */ -export function jDictionary<A>(fn: Loose<A>): Safe<dict<A>> { +export function jDictionary<K, A>(kd: K, fn: Loose<A>): Safe<dict<K, A>> { return (js: json) => { - const buffer: dict<A> = {}; + const buffer: dict<K, A> = empty(kd); if (js !== null && typeof js === 'object' && !Array.isArray(js)) { - for (var k of Object.keys(js)) { - const fd = js[k]; + for (var key of Object.keys(js)) { + const fd = js[key]; if (fd !== undefined) { const fv = fn(fd); - if (fv !== undefined) buffer[k] = fv; + if (fv !== undefined) index(buffer, forge(kd, key), fv); } } } @@ -405,14 +466,14 @@ export function jDictionary<A>(fn: Loose<A>): Safe<dict<A>> { Encode a dictionary into JSON, dicarding all inconsistent entries. If the dictionary contains no valid entry, still returns `{}`. */ -export function eDictionary<A>(fn: Encoder<A>): Encoder<dict<A>> { - return (d: dict<A>) => { +export function eDictionary<K, A>(fn: Encoder<A>): Encoder<dict<K, A>> { + return (d: dict<K, A>) => { const js: json = {}; for (var k of Object.keys(d)) { const fv = d[k]; if (fv !== undefined) { - const fv = fn(d[k]); - if (fv !== undefined) js[k] = fv; + const fr = fn(fv); + if (fr !== undefined) js[k] = fr; } } return js; -- GitLab