From: Sergey Matveev Date: Fri, 20 Jun 2025 10:20:03 +0000 (+0300) Subject: Denser schemes, any kind of keys in tcl X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=2843e4b06e2230d64aeeaf6217e875dff442b244a92f381fd02352435c859675;p=keks.git Denser schemes, any kind of keys in tcl * Single-character types for TYPE command * Ability to use digits or "." for the TAKE command in Tcl schemas * {of type} allows multiple types specification --- diff --git a/c/lib/schema.c b/c/lib/schema.c index 5b2474c..ace4b58 100644 --- a/c/lib/schema.c +++ b/c/lib/schema.c @@ -39,17 +39,17 @@ static const char CmdTimePrec[] = "TP"; static const char CmdType[] = "T"; static const char CmdUTC[] = "UTC"; -static const char TypeBin[] = "BIN"; -static const char TypeBlob[] = "BLOB"; -static const char TypeBool[] = "BOOL"; -static const char TypeHexlet[] = "HEXLET"; -static const char TypeInt[] = "INT"; -static const char TypeList[] = "LIST"; -static const char TypeMagic[] = "MAGIC"; -static const char TypeMap[] = "MAP"; -static const char TypeNIL[] = "NIL"; -static const char TypeStr[] = "STR"; -static const char TypeTAI[] = "TAI"; +static const char TypeBin[] = "B"; +static const char TypeBlob[] = "O"; +static const char TypeBool[] = "?"; +static const char TypeHexlet[] = "H"; +static const char TypeInt[] = "I"; +static const char TypeList[] = "L"; +static const char TypeMagic[] = "K"; +static const char TypeMap[] = "M"; +static const char TypeNIL[] = "N"; +static const char TypeStr[] = "S"; +static const char TypeTAI[] = "T"; static struct KEKSSchemaErr keksSchemaCmd( @@ -202,24 +202,22 @@ Eached: } err.offSchema = schema->offsets[idxSchema]; switch (schema->list[idxSchema].atom.typ) { + case KEKSItemNIL: + (*taken) = idxData; + break; case KEKSItemStr: - if ((schema->list[idxSchema].atom.v.str.len == 1) && - (schema->list[idxSchema].atom.v.str.ptr[0] == '.')) { - (*taken) = idxData; - } else { - if (data->list[idxData].atom.typ != KEKSItemMap) { - err.code = KEKSSchemaErrUnexpectedState; - err.msg = "non-map TAKE target"; - return err; - } - (*taken) = KEKSItemsGetByKeyLen( - data, - idxData, - (const char *)schema->list[idxSchema].atom.v.str.ptr, - schema->list[idxSchema].atom.v.str.len); - if ((*taken) == 0) { - (*taken) = SIZE_MAX; - } + if (data->list[idxData].atom.typ != KEKSItemMap) { + err.code = KEKSSchemaErrUnexpectedState; + err.msg = "non-map TAKE target"; + return err; + } + (*taken) = KEKSItemsGetByKeyLen( + data, + idxData, + (const char *)schema->list[idxSchema].atom.v.str.ptr, + schema->list[idxSchema].atom.v.str.len); + if ((*taken) == 0) { + (*taken) = SIZE_MAX; } break; case KEKSItemPint: @@ -241,7 +239,6 @@ Eached: break; case KEKSItemInvalid: case KEKSItemEOC: - case KEKSItemNIL: case KEKSItemFalse: case KEKSItemTrue: case KEKSItemHexlet: diff --git a/go/schema/check.go b/go/schema/check.go index 7daf7fd..d88cc16 100644 --- a/go/schema/check.go +++ b/go/schema/check.go @@ -134,28 +134,27 @@ func Check(schemaName string, schemas map[string][][]any, data any) error { }} } switch k := cmd[1].(type) { + case nil: + taken = "." + vs = []any{data} case string: - taken = k - if k == "." { - vs = []any{data} - } else { - var m map[string]any - m, ok = data.(map[string]any) - if !ok { - return &SchemaErr{BaseErr: BaseErr{ - SchemaName: schemaName, - CmdIdx: cmdIdx, - CmdName: cmdName, - Msg: "non map", - Data: data, - }} - } - v, exists := m[k] - if !exists { - continue - } - vs = []any{v} + taken = ":" + k + var m map[string]any + m, ok = data.(map[string]any) + if !ok { + return &SchemaErr{BaseErr: BaseErr{ + SchemaName: schemaName, + CmdIdx: cmdIdx, + CmdName: cmdName, + Msg: "non map", + Data: data, + }} + } + v, exists := m[k] + if !exists { + continue } + vs = []any{v} case uint64: taken = strconv.FormatUint(k, 10) var l []any @@ -293,27 +292,27 @@ func Check(schemaName string, schemas map[string][][]any, data any) error { }} } switch t { - case "NIL": + case "N": expected = append(expected, types.NIL) - case "BOOL": + case "?": expected = append(expected, types.Bool) - case "HEXLET": + case "H": expected = append(expected, types.Hexlet) - case "INT": + case "I": expected = append(expected, types.UInt, types.Int) - case "LIST": + case "L": expected = append(expected, types.List) - case "MAP": + case "M": expected = append(expected, types.Map) - case "BLOB": + case "O": expected = append(expected, types.Blob) - case "TAI": + case "T": expected = append(expected, types.TAI64, types.TAI64N, types.TAI64NA) - case "MAGIC": + case "K": expected = append(expected, types.Magic) - case "BIN": + case "B": expected = append(expected, types.Bin) - case "STR": + case "S": expected = append(expected, types.Str) default: return &SchemaErr{BaseErr: BaseErr{ diff --git a/spec/schema/cmds b/spec/schema/cmds index eecc30c..6fed256 100644 --- a/spec/schema/cmds +++ b/spec/schema/cmds @@ -8,12 +8,13 @@ Here is full list of structure validation commands, that should be generated from higher level schema descriptions. TAKE | [".", k] - Take/choose the value of the "k" key in the map, if "k" is a string. - If "k" is integer, then choose the k-th value in a list. - If "k" equals to ".", then choose the element you are currently in. - Command never fails, but key can be non-existent. - All following commands will be applied to the taken value. - By default analogue of [".", "."] command is executed when schema + Take/choose/consider element "k". All following commands will be + applied to the taken value. + If "k" is NIL, then choose the element your scheme is currently in. + If "k" is an integer, then choose the k-th value in a list, starting + from the zero. + If "k" is a string, then choose the value of the "k" key in the map. + By default analogue of [".", NIL] command is executed when schema check is called. EXISTS | ["E"] @@ -28,8 +29,19 @@ EACH | ["*"] TYPE | ["T", T0 [, T1, ...]] Check that chosen (if it exists) element's type is in (T0, T1 ...) - set. Possible types: "BIN", "BLOB", "BOOL", "HEXLET", "INT", "LIST", - "MAGIC", "MAP", "NIL", "STR", "TAI". + set. Possible single-character types are: + + "B"(IN) + (BL)"O"(B) + "?" for BOOL + "H"(EXLET) + "I"(NT) + "L"(IST) + "K"(EKS) MAGIC + "M"(AP) + "N"(IL) + "S"(TR) + "T"(AI) GT | [">", n] Check that chosen (if it exists) integer value is greater than "n". @@ -76,21 +88,21 @@ Corresponding schema can be: {"our": [ [".", "a"], ["E"], - ["T", "STR"], + ["T", "S"], [">", 0], [".", "v"], ["E"], - ["T", "BIN", "STR"], + ["T", "B", "S"], [".", "fpr"], ["E"], - ["T", "BIN"], + ["T", "B"], [">", 31], ["<", 33], [".", "comment"], - ["T", "STR"], + ["T", "S"], ]} Here is an example with multiple schemas: @@ -102,11 +114,11 @@ Here is an example with multiple schemas: { "where": [ - ["T", "LIST"], + ["T", "L"], [">", 1], ["<", 3], ["*"], - ["T", "INT"], + ["T", "I"], [".", 0], [">", -91], @@ -117,7 +129,7 @@ Here is an example with multiple schemas: ["<", 181], ], "wheres": [ - ["T", "LIST"], + ["T", "L"], [">", 0], ["*"], ["S", "where"], diff --git a/spec/schema/tcl b/spec/schema/tcl index 96fb3dc..1eb0c2e 100644 --- a/spec/schema/tcl +++ b/spec/schema/tcl @@ -37,13 +37,22 @@ an encoded map with "cmds*" commands for "s*" schemas. "field" command helps creation of commands related to the field. -Its first argument is either field's name in the map, or list's index or -dot, meaning the self-structure itself. - -Second argument is a list of allowable types, written in lowercase. -If that list equals to {with S}, then {SCHEMA s} command will be called -instead of TYPE checking. If list equals to {set}, then it is checked -to be a MAP with EACH value of NIL. + {field N {T} [optional] [!exists] [{of type T}] [{of S}] + [>n] [n" and "0" assures that either list/map or strings are not -empty. +">n" and "0" assures that either list/map or +strings are not empty. -"len=n" checks the exact length. +"len=n" checks the exact length of bin/str/list/map, or integer's value. -"=v" checks that given element has specified string/binary value -(use "len=" for integers). +"=v" checks that given bin/str/hexlet/magic has specified binary value. "prec=p" issues TIMEPREC command, but instead of specifying the raw integer values, you choose one of: s, ms, us, ns, ps, fs. "utc" issues UTC command. -{of s} argument issues checking of EACH element of the list or map -against the specified schema "s". +{of S} argument issues checking of EACH element of the list or map +against the specified schema "S". -{of type t} argument issues checking of EACH element of the list or map -against the specified type "t". +{of type T [T ...]} argument issues checking of EACH element of the list +or map against the specified types. "schema-include filename.tcl" command used instead of "field" allows inclusion of the specified file with the path relative to given schema diff --git a/tcl/schema.t/generic.t b/tcl/schema.t/generic.t index 9297741..7a88013 100755 --- a/tcl/schema.t/generic.t +++ b/tcl/schema.t/generic.t @@ -640,9 +640,9 @@ test_expect_success "tai64 prec=fs" "$SCHEMA_VALIDATE schema.keks e schema.keks.hex <} {INT 100}}} {LIST {{STR <} {INT 300}}} }}} diff --git a/tcl/schema.tcl b/tcl/schema.tcl index d8b9cf2..c1b6d36 100755 --- a/tcl/schema.tcl +++ b/tcl/schema.tcl @@ -20,33 +20,42 @@ namespace eval KEKS::Schema { set version 0.1.0 +proc EXISTS {} {return {LIST {{STR E}}}} +proc !EXISTS {} {return {LIST {{STR !E}}}} +proc EACH {} {return {LIST {{STR *}}}} +proc EQ {v} {subst {LIST {{STR =} {BIN $v}}}} +proc GT {n} {subst {LIST {{STR >} {INT $n}}}} +proc LT {n} {subst {LIST {{STR <} {INT $n}}}} +proc SCHEMA {s} {subst {LIST {{STR S} {STR $s}}}} +proc TIMEPREC {p} {subst {LIST {{STR TP} {INT $p}}}} +proc UTC {} {return {LIST {{STR UTC}}}} + proc TAKE {k} { - if {[string is digit $k]} { + if {$k == "."} { + set v [list NIL] + } elseif {[string first ":" $k] == 0} { + set v [list STR [string range $k 1 end]] + } elseif {[string is digit $k]} { set v [list INT $k] - } { + } else { set v [list STR $k] } subst {LIST {{STR .} {$v}}} } -proc EXISTS {} {return {LIST {{STR E}}}} -proc !EXISTS {} {return {LIST {{STR !E}}}} -proc EACH {} {return {LIST {{STR *}}}} -proc EQ {v} {subst {LIST {{STR =} {BIN $v}}}} + +set knownTypes [dict create \ + bin B blob O bool ? hexlet H int I list L magic K map M nil N str S tai T] + proc TYPE {types} { + variable knownTypes set l {{STR T}} foreach t $types { - lappend l [list STR [string toupper $t]] + lappend l [list STR [dict get $knownTypes [string tolower $t]]] } subst {LIST {$l}} } -proc GT {n} {subst {LIST {{STR >} {INT $n}}}} -proc LT {n} {subst {LIST {{STR <} {INT $n}}}} -proc SCHEMA {s} {subst {LIST {{STR S} {STR $s}}}} -proc TIMEPREC {p} {subst {LIST {{STR TP} {INT $p}}}} -proc UTC {} {return {LIST {{STR UTC}}}} set timeprecArgs [dict create s 0 ms 3 us 6 ns 9 ps 12 fs 15] -set knownTypes {bin blob bool hexlet int list magic map nil set str tai} proc field {k types args} { upvar _cmds _cmds buf buf @@ -106,7 +115,7 @@ proc field {k types args} { lappend _cmds [EACH] set s [lindex $args $i] if {[llength $s] > 2} { - lappend _cmds [TYPE [lindex $s 2]] + lappend _cmds [TYPE [lrange $s 2 end]] } else { lappend _cmds [SCHEMA [lindex $s 1]] }