diff --git a/ivette/api/kernel/ast/index.ts b/ivette/api/kernel/ast/index.ts index 27e84f57231fa9f2e70cbbfc52dd0f381dffbf4a..a5bfaf9032e5e6800aa93703af31ab2e5d9bb0af 100644 --- a/ivette/api/kernel/ast/index.ts +++ b/ivette/api/kernel/ast/index.ts @@ -114,10 +114,10 @@ export const byMarkerInfoData: Compare.Order<markerInfoData> = Compare.byFields <{ key: Json.key<'#markerInfo'>, kind: markerKind, name: string, descr: string }>({ - key: Compare.primitive, + key: Compare.string, kind: byMarkerKind, name: Compare.alpha, - descr: Compare.primitive, + descr: Compare.string, }); /** Signal for array [`markerInfo`](#markerinfo) */ @@ -242,9 +242,9 @@ export const jFunctionsDataSafe: Json.Safe<functionsData> = export const byFunctionsData: Compare.Order<functionsData> = Compare.byFields <{ key: Json.key<'#functions'>, name: string, signature: string }>({ - key: Compare.primitive, + key: Compare.string, name: Compare.alpha, - signature: Compare.primitive, + signature: Compare.string, }); /** Signal for array [`functions`](#functions) */ diff --git a/ivette/api/kernel/data/index.ts b/ivette/api/kernel/data/index.ts index ffd93535e849479a40c0683f83c6bdf65ed6c5fe..f3bd97722e658b170d5a5e6dbae991eae25396c2 100644 --- a/ivette/api/kernel/data/index.ts +++ b/ivette/api/kernel/data/index.ts @@ -27,7 +27,7 @@ export const jMarkdownSafe: Json.Safe<markdown> = Json.jFail(Json.jString,'String expected'); /** Natural order for `markdown` */ -export const byMarkdown: Compare.Order<markdown> = Compare.primitive; +export const byMarkdown: Compare.Order<markdown> = Compare.string; /** Rich text format uses `[tag; …text ]` to apply the tag `tag` to the enclosed text. Empty tag `""` can also used to simply group text together. */ export type text = null | string | text[]; diff --git a/ivette/api/kernel/project/index.ts b/ivette/api/kernel/project/index.ts index 6674b144671357e35b45ac7648ca0b0798841b7d..c1dba98e07b19f1bdd52e2c9d1db91ccdbc981c0 100644 --- a/ivette/api/kernel/project/index.ts +++ b/ivette/api/kernel/project/index.ts @@ -36,9 +36,9 @@ export const jProjectInfoSafe: Json.Safe<projectInfo> = export const byProjectInfo: Compare.Order<projectInfo> = Compare.byFields <{ id: Json.key<'#project'>, name: string, current: boolean }>({ - id: Compare.primitive, + id: Compare.string, name: Compare.alpha, - current: Compare.primitive, + current: Compare.boolean, }); /** Request to be executed on the specified project. */ @@ -62,8 +62,8 @@ export const jProjectRequestSafe: Json.Safe<projectRequest> = export const byProjectRequest: Compare.Order<projectRequest> = Compare.byFields <{ project: Json.key<'#project'>, request: string, data: Json.json }>({ - project: Compare.primitive, - request: Compare.primitive, + project: Compare.string, + request: Compare.string, data: Compare.structural, }); diff --git a/ivette/api/kernel/properties/index.ts b/ivette/api/kernel/properties/index.ts index 92d7cc4a130b483eb06e5e3267031478de08dfce..9f7eeb5e6e49df77718140190f847501a88e5eec 100644 --- a/ivette/api/kernel/properties/index.ts +++ b/ivette/api/kernel/properties/index.ts @@ -275,17 +275,17 @@ export const byStatusData: Compare.Order<statusData> = names: string[], status: propStatus, function?: Json.key<'#fct'>, kinstr?: Json.key<'#stmt'>, source: source, alarm?: string, alarm_descr?: string, predicate?: string }>({ - key: Compare.primitive, - descr: Compare.primitive, + key: Compare.string, + descr: Compare.string, kind: byPropKind, - names: Compare.array(Compare.primitive), + names: Compare.array(Compare.string), status: byPropStatus, - function: Compare.defined(Compare.primitive), - kinstr: Compare.defined(Compare.primitive), + function: Compare.defined(Compare.string), + kinstr: Compare.defined(Compare.string), source: bySource, - alarm: Compare.defined(Compare.primitive), - alarm_descr: Compare.defined(Compare.primitive), - predicate: Compare.defined(Compare.primitive), + alarm: Compare.defined(Compare.string), + alarm_descr: Compare.defined(Compare.string), + predicate: Compare.defined(Compare.string), }); /** Signal for array [`status`](#status) */ diff --git a/ivette/api/kernel/services/index.ts b/ivette/api/kernel/services/index.ts index cdc371d34b5398ba484204c25ad4712e3899d378..2b98b1c59cbc4e138a9ceada0949b5ca6e7c9de7 100644 --- a/ivette/api/kernel/services/index.ts +++ b/ivette/api/kernel/services/index.ts @@ -74,10 +74,10 @@ export const jSourceSafe: Json.Safe<source> = export const bySource: Compare.Order<source> = Compare.byFields <{ dir: string, base: string, file: string, line: number }>({ - dir: Compare.primitive, - base: Compare.primitive, - file: Compare.primitive, - line: Compare.primitive, + dir: Compare.string, + base: Compare.string, + file: Compare.string, + line: Compare.number, }); /** Log messages categories. */ @@ -149,8 +149,8 @@ export const byLog: Compare.Order<log> = source?: source }>({ kind: byLogkind, plugin: Compare.alpha, - message: Compare.primitive, - category: Compare.defined(Compare.primitive), + message: Compare.string, + category: Compare.defined(Compare.string), source: Compare.defined(bySource), }); diff --git a/ivette/api/server_tsc.ml b/ivette/api/server_tsc.ml index 8f5eb6e829b7992c706487f59d1b23b226f80421..b303c841281597b3db9ab019a3b6e695c7ac595f 100644 --- a/ivette/api/server_tsc.ml +++ b/ivette/api/server_tsc.ml @@ -74,6 +74,7 @@ let makeJtype ?self ~names = | Jnumber -> Format.pp_print_string fmt "number" | Jboolean -> Format.pp_print_string fmt "boolean" | Jstring | Jalpha -> Format.pp_print_string fmt "string" + | Jtag a -> Format.fprintf fmt "\"%s\"" a | Jkey kd -> Format.fprintf fmt "Json.key<'#%s'>" kd | Jindex kd -> Format.fprintf fmt "Json.index<'#%s'>" kd | Jdict(kd,js) -> Format.fprintf fmt "Json.Dict<'#%s',%a>" kd pp js @@ -166,6 +167,7 @@ let rec makeDecoder ~safe ?self ~names fmt js = | Jboolean -> jsafe ~safe "Boolean" jprim fmt "jBoolean" | Jnumber -> jsafe ~safe "Number" jprim fmt "jNumber" | Jstring | Jalpha -> jsafe ~safe "String" jprim fmt "jString" + | Jtag a -> Format.fprintf fmt "jTag(\"%s\")" a | Jkey kd -> jsafe ~safe ("#" ^ kd) jkey fmt kd | Jindex kd -> jsafe ~safe ("#" ^ kd) jindex fmt kd | Jdata id -> jcall names fmt (Pkg.Derived.decode ~safe id) @@ -217,14 +219,13 @@ let makeOrder ~self ~names fmt js = let rec pp fmt = function | Jnull -> Format.pp_print_string fmt "Compare.equal" | Jalpha -> Format.pp_print_string fmt "Compare.alpha" - | Jnumber | Jstring | Jboolean | Jkey _ | Jindex _ - -> Format.pp_print_string fmt "Compare.primitive" + | Jnumber | Jindex _ -> Format.pp_print_string fmt "Compare.number" + | Jstring | Jkey _ -> Format.pp_print_string fmt "Compare.string" + | Jboolean -> Format.pp_print_string fmt "Compare.boolean" | Jself -> jcall names fmt (Pkg.Derived.order self) | Jdata id -> jcall names fmt (Pkg.Derived.order id) | Joption js -> Format.fprintf fmt "@[<hov 2>Compare.defined(@,%a)@]" pp js - | Jany | Junion _ -> (* Can not find a better solution *) - Format.fprintf fmt "Compare.structural" | Jenum id -> Format.fprintf fmt "@[<hov 2>Compare.byEnum(@,%a)@]" (jcall names) id | Jlist js | Jarray js -> @@ -250,6 +251,8 @@ let makeOrder ~self ~names fmt js = Format.fprintf fmt "@[<hov 2>Compare.dictionary<@,Json.dict<'#%s'@,%a>>(@,%a)@]" kd jtype js pp js + | Jany | Junion _ | Jtag _ -> + Format.fprintf fmt "Compare.structural" in pp fmt js (* -------------------------------------------------------------------------- *) diff --git a/ivette/src/dome/src/renderer/data/compare.ts b/ivette/src/dome/src/renderer/data/compare.ts index 31185f33ee006c019b53b8438c9f1b3f390ef10e..b9bcc989b71af9d42a5f7ab74406649a466ad81d 100644 --- a/ivette/src/dome/src/renderer/data/compare.ts +++ b/ivette/src/dome/src/renderer/data/compare.ts @@ -41,28 +41,37 @@ export function isBigNum(x: any): x is bignum { return typeof (x) === 'bigint' || (typeof (x) === 'number' && !Number.isNaN(x)); } -/** - Primitive comparison. - Can only compare arguments that have - comparable primitive type. - - This includes symbols, boolean, non-NaN numbers, bigints and strings. - Numbers and big-ints can also be compared with each others. -*/ -export function primitive(x: symbol, y: symbol): number; -export function primitive(x: boolean, y: boolean): number; -export function primitive(x: bignum, y: bignum): number; -export function primitive(x: string, y: string): number; -export function primitive(x: any, y: any) { +/** @internal */ +function primitive(x: any, y: any) { if (x < y) return -1; if (x > y) return 1; return 0; } /** - Primitive comparison for numbers (NaN included). + Primitive comparison for symbols. +*/ +export const symbol: Order<symbol> = primitive; + +/** + Primitive comparison for booleans. +*/ +export const boolean: Order<boolean> = primitive; + +/** + Primitive comparison for strings. See also [[alpha]]. +*/ +export const string: Order<string> = primitive; + +/** + Primitive comparison for (big) integers (non NaN numbers included). + */ +export const bignum: Order<bignum> = primitive; + +/** + Primitive comparison for number (NaN included). */ -export function float(x: number, y: number) { +export function number(x: number, y: number) { const nx = Number.isNaN(x); const ny = Number.isNaN(y); if (nx && ny) return 0; diff --git a/ivette/src/dome/src/renderer/data/json.ts b/ivette/src/dome/src/renderer/data/json.ts index ba0be2fc66fcef5b3a37e5e436633d1f4ebfba3f..e79d55155979fcf83fa1bfe84019b97991b17bbd 100644 --- a/ivette/src/dome/src/renderer/data/json.ts +++ b/ivette/src/dome/src/renderer/data/json.ts @@ -118,6 +118,15 @@ export const jString: Loose<string> = (js: json) => ( typeof js === 'string' ? js : undefined ); +/** JSON constant. + Capture the tag or returns `undefined`. + Can be used with [[jUnion]], although [[jEnum]] + might be more efficient. +*/ +export function jTag<A>(tg: A): Loose<A> { + return (js: json) => Object.is(js, tg) ? tg : undefined; +} + /** Lookup tags in a dictionary. Can be used directly for enum types, eg. `jEnum(myEnumType)`. diff --git a/ivette/src/renderer/Properties.tsx b/ivette/src/renderer/Properties.tsx index e2a434f7a7e0743ff27b63b55aa4ac0d0f9d5fa5..22acbafdf641891b0c6c7adb7a0ccfd6ca5a2880 100644 --- a/ivette/src/renderer/Properties.tsx +++ b/ivette/src/renderer/Properties.tsx @@ -202,7 +202,7 @@ function ColumnTag<Row>(props: ColumnProps<Row, States.Tag>) { // ------------------------------------------------------------------------- const bySource = - Compare.byFields<SourceLoc>({ file: Compare.alpha, line: Compare.primitive }); + Compare.byFields<SourceLoc>({ file: Compare.alpha, line: Compare.number }); const byStatus = Compare.byRank( @@ -227,7 +227,7 @@ const byProperty: Compare.ByFields<Property> = { alarm: Compare.defined(Compare.alpha), names: Compare.array(Compare.alpha), predicate: Compare.defined(Compare.alpha), - key: Compare.primitive, + key: Compare.string, kinstr: Compare.structural, }; diff --git a/src/plugins/server/data.ml b/src/plugins/server/data.ml index a298e490e43ceced5223d92f7713cb087ff1d4f2..0dc576f61ca97541dd4e906cac794a5827f24732 100644 --- a/src/plugins/server/data.ml +++ b/src/plugins/server/data.ml @@ -332,9 +332,21 @@ struct if s.published then raise (Invalid_argument "Server.Data.Record: already published") + let check_field_name s name = + begin + if List.exists (fun f -> f.Package.fd_name = name) s.fields then + (let msg = Printf.sprintf "Server.Data.Record: duplicate field %S" name + in raise (Invalid_argument msg)); + if not (Str.string_match (Str.regexp "[a-zA-Z0-9 _-]+$") name 0) then + (let msg = Printf.sprintf + "Server.Data.Record: invalid characters for field %S" name in + raise (Invalid_argument msg)); + end + let field (type a r) (s : r signature) ~name ~descr ?default (d : a data) : (r,a) field = not_published s ; + check_field_name s name ; let module D = (val d) in begin match default with | None -> () @@ -354,6 +366,7 @@ struct let option (type a r) (s : r signature) ~name ~descr (d : a data) : (r,a option) field = not_published s ; + check_field_name s name ; let module D = (val d) in let field = Package.{ fd_name = name ; diff --git a/src/plugins/server/package.ml b/src/plugins/server/package.ml index 6f2e56f956b5cafbb98573b3d8712d95206ed9d6..cb716919cf55fec5794d311fc940b8acd195717c 100644 --- a/src/plugins/server/package.ml +++ b/src/plugins/server/package.ml @@ -166,6 +166,7 @@ type jtype = | Jnumber | Jstring | Jalpha (* string primarily compared without case *) + | Jtag of string (* single constant string *) | Jkey of string (* kind of a string used for indexing *) | Jindex of string (* kind of an integer used for indexing *) | Joption of jtype @@ -289,14 +290,14 @@ let rec isRecursive = function | Jself -> true | Jdata _ | Jenum _ | Jany | Jnull | Jboolean | Jnumber - | Jstring | Jalpha | Jkey _ | Jindex _ -> false + | Jstring | Jalpha | Jkey _ | Jindex _ | Jtag _ -> false | Joption js | Jdict(_,js) | Jarray js | Jlist js -> isRecursive js | Jtuple js | Junion js -> List.exists isRecursive js | Jrecord fjs -> List.exists (fun (_,js) -> isRecursive js) fjs let rec visit_jtype fn = function | Jany | Jself | Jnull | Jboolean | Jnumber - | Jstring | Jalpha | Jkey _ | Jindex _ -> () + | Jstring | Jalpha | Jkey _ | Jindex _ | Jtag _ -> () | Joption js | Jdict(_,js) | Jarray js | Jlist js -> visit_jtype fn js | Jtuple js | Junion js -> List.iter (visit_jtype fn) js | Jrecord fjs -> List.iter (fun (_,js) -> visit_jtype fn js) fjs @@ -447,7 +448,7 @@ let iter f = let key kd = Md.plain (Printf.sprintf "`#%s`" kd) let index kd = Md.plain (Printf.sprintf "`#0%s`" kd) -let escaped tag = Md.plain (Printf.sprintf "`\"%s\"`" @@ String.escaped tag) +let litteral tag = Md.plain (Printf.sprintf "`\"%s\"`" tag) type pp = { self: Md.text ; @@ -461,6 +462,7 @@ let rec md_jtype pp = function | Jnumber -> Md.emph "number" | Jboolean -> Md.emph "boolean" | Jstring | Jalpha -> Md.emph "string" + | Jtag a -> litteral a | Jkey kd -> key kd | Jindex kd -> index kd | Jdata id | Jenum id -> pp.ident id @@ -478,7 +480,7 @@ and md_jlist pp sep js = and fields pp fjs = Md.glue ~sep:(Md.plain ",") @@ List.map (fun (fd,js) -> - escaped fd @ + litteral fd @ match js with | Joption js -> Md.code ":?" @ md_jtype pp js | _ -> Md.code ":" @ md_jtype pp js @@ -501,7 +503,7 @@ let md_tags ?(title="Tags") (tags : tagInfo list) = ] in let row tg = [ tg.tg_label ; - escaped tg.tg_name ; + litteral tg.tg_name ; tg.tg_descr ; ] in Md.{ caption = None ; header ; content = List.map row tags } @@ -515,12 +517,12 @@ let md_fields ?(title="Field") pp (fields : fieldInfo list) = let row f = match f.fd_type with | Joption js -> [ - escaped (f.fd_name ^ "?") ; + litteral f.fd_name @ Md.plain "(opt.)" ; md_jtype pp js ; f.fd_descr ; ] | _ -> [ - escaped f.fd_name ; + litteral f.fd_name ; md_jtype pp f.fd_type ; f.fd_descr ; ] diff --git a/src/plugins/server/package.mli b/src/plugins/server/package.mli index c040c985bc85129a3c15bb222e1afdffaf2262a4..85af7f86923ac5df9fda9ad6fc5b066800ace281 100644 --- a/src/plugins/server/package.mli +++ b/src/plugins/server/package.mli @@ -34,6 +34,7 @@ type jtype = | Jnumber | Jstring | Jalpha (** string primarily compared without case *) + | Jtag of string (** single constant string *) | Jkey of string (** kind of a string used for indexing *) | Jindex of string (** kind of an integer used for indexing *) | Joption of jtype @@ -222,7 +223,8 @@ type pp = { ident: ident -> Markdown.text ; } -val escaped : string -> Markdown.text +(** Quoted string *) +val litteral : string -> Markdown.text val md_jtype : pp -> jtype -> Markdown.text val md_tags : ?title:string -> tagInfo list -> Markdown.table