]> Cypherpunks repositories - keks.git/commitdiff
Denser schemes, any kind of keys in tcl
authorSergey Matveev <stargrave@stargrave.org>
Fri, 20 Jun 2025 10:20:03 +0000 (13:20 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Fri, 20 Jun 2025 11:25:38 +0000 (14:25 +0300)
* Single-character types for TYPE command
* Ability to use digits or "." for the TAKE command in Tcl schemas
* {of type} allows multiple types specification

c/lib/schema.c
go/schema/check.go
spec/schema/cmds
spec/schema/tcl
tcl/schema.t/generic.t
tcl/schema.tcl

index 5b2474c2321a74d992b09ca9219686d9be313bfdebf5417936d1f49ca7f0add8..ace4b58ad9521ea5019bd68c1650c4e89d0edb969b6728cdd4b8b68d1953b8b8 100644 (file)
@@ -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:
index 7daf7fdfde354ca3dd0882a5d9d6ce0d0426b8033e2b35db7a7d5000f5912ac4..d88cc16210d0e2463ccacf95b03ff69cee34585a94172a284f63db956f834fca 100644 (file)
@@ -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{
index eecc30c08e783a80ef8feb442eba3b707d6b9c14de381d9f8e04df273f0ea453..6fed2563558ce7d6fc584125efd4c489075b11b7ea2513f40c16594586bc3ccb 100644 (file)
@@ -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"],
index 96fb3dc40b7d48fde55a9b789ed0edb551197e82f69f15a599c365dea6c5b5be..1eb0c2ee31543def19ca9d927c77f07355b08ff7af2f0355f2e341134dc90599 100644 (file)
@@ -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] [len=n] [=v] [prec=p] [utc]}
+
+"N" required argument is the name of the field to consider. Either it is
+".", meaning the current taken element by schema. Or it is a digit,
+meaning the n-th element of the list. Otherwise it is a name of the key
+in map. If N starts with ":", then its remaining part is a string
+anyway, giving you ability to specify ":2" for choosing the "2" key of
+map for example.
+
+"T" required argument is:
+* either a list of whitespace-separated allowable types
+  (bin, blob, bool, hexlet, int, list, magic, map, nil, str, tai)
+* or {set}, that will assure the field is a map with NIL values
+* or {with S} string, that will issue the {SCHEMA S} command instead of
+  type checking the field
 
 All other arguments are optional.
 
@@ -52,25 +61,24 @@ EXISTS check is called for the field. If "!exists" argument is
 specified, then it is explicitly checked to be non-existent and
 you can specify empty list of types in second argument.
 
-">n" and "<n" arguments allow checking of the integer value or
-the lengths. ">0" assures that either list/map or strings are not
-empty.
+">n" and "<n" arguments allow checking of the integer's value or
+the bin/str/list/map lengths. ">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
index 92977417a4a9d74f1779f07652e7344e61c2ab803a2ce0b779223de3914b3c69..7a88013bd8b7aa1bfec65aeaf203059bffb8fb70f8add999e09dc1d0e2bb39c4 100755 (executable)
@@ -640,9 +640,9 @@ test_expect_success "tai64 prec=fs" "$SCHEMA_VALIDATE schema.keks e <data.keks"
 $root/keks.tcl >schema.keks.hex <<EOF
 MAGIC schema
 MAP {e {LIST {
-    {LIST {{STR T} {STR LIST}}}
+    {LIST {{STR T} {STR L}}}
     {LIST {{STR *}}}
-    {LIST {{STR T} {STR INT}}}
+    {LIST {{STR T} {STR I}}}
     {LIST {{STR >} {INT 100}}}
     {LIST {{STR <} {INT 300}}}
 }}}
index d8b9cf2feb1643e7f231355c52e6463799d9d3aed399fc5f585ce5cda1824af0..c1b6d36b5a3def51416d3e0159141b1d505df41905dfa51a4d23fc1b00b20aa6 100755 (executable)
@@ -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]]
         }