Skip to content

Commit 94e2d95

Browse files
re-implements MultiCheckNames with bit-level byte array, introduces SMapRepeat
- MultiCheck now uses bit-packed byte arrays instead of tuple-of-bools, reducing encoded buffer size and eliminating redundant framing overhead. - Added SMapRepeat to support repeated map structures with cleaner semantics and more efficient encoding.
1 parent d092b48 commit 94e2d95

File tree

5 files changed

+295
-79
lines changed

5 files changed

+295
-79
lines changed

packable/packable_primitives.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,19 @@ func (v PackByteArrayRef) PackInto(p *access.PutAccess) {
168168
func PackByteArray(b []byte) PackByteArrayRef {
169169
return PackByteArrayRef{ref: &b}
170170
}
171+
172+
// PackFlags packs a variadic list of bools into a bit-packed byte array
173+
func PackFlags(flags ...bool) PackByteArrayRef {
174+
byteCount := (len(flags) + 7) / 8
175+
buf := make([]byte, byteCount)
176+
177+
for i, f := range flags {
178+
if f {
179+
byteIndex := i / 8
180+
bitIndex := uint(i % 8)
181+
buf[byteIndex] |= 1 << bitIndex
182+
}
183+
}
184+
185+
return PackByteArrayRef{ref: &buf}
186+
}

scheme/scheme.go

Lines changed: 197 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ type RangeErrorDetails struct {
102102
}
103103

104104
func (r RangeErrorDetails) Error() string {
105+
if r.Max == math.MaxInt64 || r.Max < 0 {
106+
return fmt.Sprintf("%d < %d", r.Actual, r.Min)
107+
}
108+
if r.Min == math.MinInt64 || r.Min < 0 {
109+
return fmt.Sprintf("%d > %d", r.Actual, r.Max)
110+
}
105111
return fmt.Sprintf("%d != [%d , %d]", r.Actual, r.Min, r.Max)
106112
}
107113

@@ -170,6 +176,7 @@ const (
170176
TupleSchemeName = "TupleScheme"
171177
TupleSchemeNamedName = "TupleSchemeNamed"
172178
SRepeatSchemeName = "SRepeatScheme"
179+
SchemeMapRepeatName = "SchemeMapRepeat"
173180
)
174181

175182
type SchemeGeneric struct {
@@ -1798,8 +1805,12 @@ func (s SRepeatScheme) Validate(seq *access.SeqGetAccess) error {
17981805
argCount := seq.ArgCount() - pos
17991806

18001807
if s.min != -1 && argCount < s.min {
1801-
return NewSchemeError(ErrConstraintViolated, SRepeatSchemeName, "", pos,
1802-
fmt.Errorf("expected minimum %d elements, but only %d remain", s.min, argCount))
1808+
return NewSchemeError(ErrConstraintViolated, SRepeatSchemeName, "", pos, RangeErrorDetails{
1809+
Min: int64(s.min),
1810+
Max: int64(s.max),
1811+
Actual: int64(argCount),
1812+
})
1813+
18031814
}
18041815

18051816
maxIter := argCount
@@ -1829,7 +1840,11 @@ func (s SRepeatScheme) Decode(seq *access.SeqGetAccess) (any, error) {
18291840

18301841
if s.min != -1 && argCount < s.min {
18311842
return nil, NewSchemeError(ErrConstraintViolated, SRepeatSchemeName, "", pos,
1832-
fmt.Errorf("expected minimum %d elements, but only %d remain", s.min, argCount))
1843+
RangeErrorDetails{
1844+
Min: int64(s.min),
1845+
Max: int64(s.max),
1846+
Actual: int64(argCount),
1847+
})
18331848
}
18341849

18351850
maxIter := argCount
@@ -1864,8 +1879,11 @@ func (s SRepeatScheme) Encode(put *access.PutAccess, val any) error {
18641879
}
18651880
argCount := len(valArr)
18661881
if s.min != -1 && argCount < s.min {
1867-
return NewSchemeError(ErrConstraintViolated, SRepeatSchemeName, "", -1,
1868-
fmt.Errorf("expected minimum %d elements, but only %d remain", s.min, argCount))
1882+
return NewSchemeError(ErrConstraintViolated, SRepeatSchemeName, "", -1, RangeErrorDetails{
1883+
Min: int64(s.min),
1884+
Max: int64(s.max),
1885+
Actual: int64(argCount),
1886+
})
18691887
}
18701888
maxIter := argCount
18711889
if s.max != -1 && s.max < argCount {
@@ -1907,63 +1925,43 @@ func (s SchemeMultiCheckNamesScheme) IsNullable() bool {
19071925

19081926
func (s SchemeMultiCheckNamesScheme) Validate(seq *access.SeqGetAccess) error {
19091927
pos := seq.CurrentIndex()
1910-
_, err := precheck(SchemeMultiCheckNamesSchemeNamed, pos, seq, types.TypeTuple, -1, s.IsNullable())
1911-
if err != nil {
1912-
return err
1913-
}
1914-
sub, err := seq.PeekNestedSeq()
1928+
byteCount := (len(s.FieldNames) + 7) / 8
1929+
1930+
// Direct primitive validation: expect a bytes value of exact width
1931+
err := validatePrimitive(SchemeBytesName, seq, types.TypeString, byteCount, s.IsNullable())
19151932
if err != nil {
19161933
return NewSchemeError(ErrInvalidFormat, SchemeMultiCheckNamesSchemeNamed, "", pos, err)
19171934
}
1918-
if sub.ArgCount() != len(s.FieldNames) {
1919-
return NewSchemeError(ErrConstraintViolated, SchemeMultiCheckNamesSchemeNamed, "", pos,
1920-
SizeExact{Actual: sub.ArgCount(), Exact: len(s.FieldNames)})
1921-
}
1922-
for range s.FieldNames {
1923-
err := SchemeBool{}.Validate(sub)
1924-
if err != nil {
1925-
return NewSchemeError(ErrInvalidFormat, SchemeMultiCheckNamesSchemeNamed, "", pos, err)
1926-
}
1927-
}
1928-
if err := seq.Advance(); err != nil {
1929-
return NewSchemeError(ErrUnexpectedEOF, SchemeMultiCheckNamesSchemeNamed, "", pos, err)
1930-
}
1935+
19311936
return nil
19321937
}
19331938

19341939
func (s SchemeMultiCheckNamesScheme) Decode(seq *access.SeqGetAccess) (any, error) {
19351940
pos := seq.CurrentIndex()
1936-
_, err := precheck(SchemeMultiCheckNamesSchemeNamed, pos, seq, types.TypeTuple, -1, s.IsNullable())
1937-
if err != nil {
1938-
return nil, err
1939-
}
1940-
1941-
selected := make([]string, 0)
1941+
byteCount := (len(s.FieldNames) + 7) / 8
19421942

1943-
sub, err := seq.PeekNestedSeq()
1943+
payload, err := validatePrimitiveAndGetPayload(SchemeMultiCheckNamesSchemeNamed, seq, types.TypeByteArray, byteCount, s.IsNullable())
19441944
if err != nil {
19451945
return nil, NewSchemeError(ErrInvalidFormat, SchemeMultiCheckNamesSchemeNamed, "", pos, err)
19461946
}
1947-
if sub.ArgCount() != len(s.FieldNames) {
1948-
return nil, NewSchemeError(ErrConstraintViolated, SchemeMultiCheckNamesSchemeNamed, "", pos,
1949-
SizeExact{Actual: sub.ArgCount(), Exact: len(s.FieldNames)})
1950-
}
1947+
if payload == nil {
1948+
if s.Nullable {
1949+
return nil, nil // allow nullable
1950+
} else {
19511951

1952-
for _, name := range s.FieldNames {
1953-
v, err := SchemeBool{}.Decode(sub)
1954-
if err != nil {
1955-
return nil, NewSchemeError(ErrInvalidFormat, SchemeMultiCheckNamesSchemeNamed, name, pos, err)
1956-
}
1957-
if b, ok := v.(bool); ok && b {
1958-
selected = append(selected, name)
1952+
return nil, NewSchemeError(ErrInvalidFormat, SchemeMultiCheckNamesSchemeNamed, "", pos, nil)
19591953
}
19601954
}
19611955

1962-
if err := seq.Advance(); err != nil {
1963-
return nil, NewSchemeError(ErrUnexpectedEOF, SchemeMultiCheckNamesSchemeNamed, "", pos, err)
1956+
selected := make([]string, 0)
1957+
for i, name := range s.FieldNames {
1958+
byteIndex := i / 8
1959+
bitIndex := uint(i % 8)
1960+
if payload[byteIndex]&(1<<bitIndex) != 0 {
1961+
selected = append(selected, name)
1962+
}
19641963
}
19651964

1966-
// Return only the slice of selected names
19671965
return selected, nil
19681966
}
19691967

@@ -1987,16 +1985,17 @@ func (s SchemeMultiCheckNamesScheme) Encode(put *access.PutAccess, val any) erro
19871985
return NewSchemeError(ErrEncode, SchemeMultiCheckNamesSchemeNamed, "", -1, ErrTypeMisMatch)
19881986
}
19891987

1990-
nested := put.BeginTuple()
1991-
defer put.EndNested(nested)
1988+
byteCount := (len(s.FieldNames) + 7) / 8
1989+
buf := make([]byte, byteCount)
19921990

1993-
for _, key := range s.FieldNames {
1994-
_, checked := set[key]
1995-
err := SchemeBool{}.Encode(nested, checked)
1996-
if err != nil {
1997-
return NewSchemeError(ErrInvalidFormat, SchemeMultiCheckNamesSchemeNamed, key, -1, err)
1991+
for i, key := range s.FieldNames {
1992+
if _, ok := set[key]; ok {
1993+
byteIndex := i / 8
1994+
bitIndex := uint(i % 8)
1995+
buf[byteIndex] |= 1 << bitIndex
19981996
}
19991997
}
1998+
put.AddBytes(buf)
20001999
return nil
20012000
}
20022001

@@ -2197,3 +2196,150 @@ func SColor(nullable bool) Scheme {
21972196
}
21982197
return s.Pattern(`^#(?:[0-9a-fA-F]{3}){1,2}$`)
21992198
}
2199+
2200+
type SchemeMapRepeat struct {
2201+
Key Scheme
2202+
Value Scheme
2203+
min int
2204+
max int
2205+
}
2206+
2207+
func SMapRepeat(key Scheme, value Scheme) SchemeMapRepeat {
2208+
return SchemeMapRepeat{Key: key, Value: value, min: -1, max: -1}
2209+
}
2210+
2211+
func SMapRepeatRange(key Scheme, value Scheme, min, max int) SchemeMapRepeat {
2212+
return SchemeMapRepeat{Key: key, Value: value, min: min, max: max}
2213+
}
2214+
2215+
func (s SchemeMapRepeat) IsNullable() bool {
2216+
return s.min <= 0
2217+
}
2218+
2219+
func (s SchemeMapRepeat) Validate(seq *access.SeqGetAccess) error {
2220+
pos := seq.CurrentIndex()
2221+
_, err := precheck(SchemeMapRepeatName, pos, seq, types.TypeMap, -1, s.IsNullable())
2222+
if err != nil {
2223+
return err
2224+
}
2225+
subseq, err := seq.PeekNestedSeq()
2226+
if err != nil {
2227+
return NewSchemeError(ErrInvalidFormat, SchemeMapRepeatName, "", pos, err)
2228+
}
2229+
pairCount := subseq.ArgCount() / 2
2230+
maxIter := pairCount
2231+
if s.max != -1 && s.max < pairCount {
2232+
maxIter = s.max
2233+
}
2234+
if s.min != -1 && pairCount < s.min {
2235+
return NewSchemeError(ErrConstraintViolated, SchemeMapRepeatName, "", pos,
2236+
RangeErrorDetails{
2237+
Min: int64(s.min),
2238+
Max: int64(s.max),
2239+
Actual: int64(pairCount),
2240+
})
2241+
}
2242+
2243+
for i := 0; i < maxIter; i++ {
2244+
2245+
if err := s.Key.Validate(subseq); err != nil {
2246+
return NewSchemeError(ErrInvalidFormat, SchemeMapRepeatName, "", pos, err)
2247+
}
2248+
if err := s.Value.Validate(subseq); err != nil {
2249+
return NewSchemeError(ErrInvalidFormat, SchemeMapRepeatName, "", pos, err)
2250+
}
2251+
}
2252+
2253+
if err := seq.Advance(); err != nil {
2254+
return NewSchemeError(ErrUnexpectedEOF, SchemeMapRepeatName, "", pos, err)
2255+
}
2256+
return nil
2257+
}
2258+
2259+
func (s SchemeMapRepeat) Decode(seq *access.SeqGetAccess) (any, error) {
2260+
pos := seq.CurrentIndex()
2261+
_, err := precheck(SchemeMapRepeatName, pos, seq, types.TypeMap, 0, s.IsNullable())
2262+
if err != nil {
2263+
return nil, err
2264+
}
2265+
subseq, err := seq.PeekNestedSeq()
2266+
if err != nil {
2267+
return nil, NewSchemeError(ErrInvalidFormat, SchemeMapRepeatName, "", pos, err)
2268+
}
2269+
pairCount := subseq.ArgCount() / 2
2270+
maxIter := pairCount
2271+
if s.max != -1 && s.max < pairCount {
2272+
maxIter = s.max
2273+
}
2274+
if s.min != -1 && pairCount < s.min {
2275+
return nil, NewSchemeError(ErrConstraintViolated, SchemeMapRepeatName, "", pos,
2276+
RangeErrorDetails{
2277+
Min: int64(s.min),
2278+
Max: int64(s.max),
2279+
Actual: int64(pairCount),
2280+
})
2281+
}
2282+
out := make(map[string]any)
2283+
for i := 0; i < maxIter; i++ {
2284+
k, err := s.Key.Decode(subseq)
2285+
if err != nil {
2286+
return nil, NewSchemeError(ErrInvalidFormat, SchemeMapRepeatName, "", pos, err)
2287+
}
2288+
v, err := s.Value.Decode(subseq)
2289+
if err != nil {
2290+
return nil, NewSchemeError(ErrInvalidFormat, SchemeMapRepeatName, "", pos, err)
2291+
}
2292+
if keyStr, ok := k.(string); ok {
2293+
out[keyStr] = v
2294+
} else {
2295+
return nil, NewSchemeError(ErrInvalidFormat, SchemeMapRepeatName, "", pos-1, ErrUnsupportedType)
2296+
}
2297+
}
2298+
2299+
if err := seq.Advance(); err != nil {
2300+
return nil, NewSchemeError(ErrUnexpectedEOF, SchemeMapRepeatName, "", pos, err)
2301+
}
2302+
return out, nil
2303+
}
2304+
2305+
func (s SchemeMapRepeat) Encode(put *access.PutAccess, val any) error {
2306+
mapKV, ok := val.(map[string]any)
2307+
if !ok {
2308+
return NewSchemeError(ErrEncode, SchemeMapRepeatName, "", -1, ErrTypeMisMatch)
2309+
}
2310+
2311+
nested := put.BeginMap()
2312+
defer put.EndNested(nested)
2313+
2314+
count := 0
2315+
for key, v := range mapKV {
2316+
// Encode key
2317+
if err := s.Key.Encode(nested, key); err != nil {
2318+
return NewSchemeError(ErrEncode, SchemeMapRepeatName, key, -1, err)
2319+
}
2320+
// Encode value
2321+
if err := s.Value.Encode(nested, v); err != nil {
2322+
return NewSchemeError(ErrEncode, SchemeMapRepeatName, key, -1, err)
2323+
}
2324+
count++
2325+
}
2326+
2327+
if s.min != -1 && count < s.min {
2328+
return NewSchemeError(ErrConstraintViolated, SchemeMapRepeatName, "", -1,
2329+
RangeErrorDetails{
2330+
Min: int64(s.min),
2331+
Max: int64(s.max),
2332+
Actual: int64(count),
2333+
})
2334+
}
2335+
if s.max != -1 && count > s.max {
2336+
return NewSchemeError(ErrConstraintViolated, SchemeMapRepeatName, "", -1,
2337+
RangeErrorDetails{
2338+
Min: int64(s.min),
2339+
Max: int64(s.max),
2340+
Actual: int64(count),
2341+
})
2342+
}
2343+
2344+
return nil
2345+
}

scheme/scheme_test.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,11 +1218,7 @@ func TestSchemeMultiCheckNamesScheme(t *testing.T) {
12181218

12191219
// Pack a tuple of bools: read=true, write=false, execute=true
12201220
actual := pack.Pack(
1221-
pack.PackTuple(
1222-
pack.PackBool(true),
1223-
pack.PackBool(false),
1224-
pack.PackBool(true),
1225-
),
1221+
pack.PackFlags(true, false, true),
12261222
)
12271223

12281224
// Validate

scheme/schemebuilder_json.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package scheme
22

3-
import "time"
3+
import (
4+
"fmt"
5+
"time"
6+
)
47

58
type SchemeJSON struct {
69
Type string `json:"type"`
@@ -153,6 +156,12 @@ func BuildScheme(js SchemeJSON) Scheme {
153156
return SMapUnorderedOptional(mapped)
154157
}
155158
return SMapUnordered(mapped)
159+
case "mapRepeat":
160+
if len(js.Schema) == 2 {
161+
return SMapRepeatRange(BuildScheme(js.Schema[0]), BuildScheme(js.Schema[1]), js.Min, js.Max)
162+
} else {
163+
panic(fmt.Sprintf("should be 2 schemes %v", len(js.FieldNames)))
164+
}
156165
case "multicheck":
157166
if len(js.FieldNames) > 0 {
158167
return SMultiCheckNames(js.FieldNames)

0 commit comments

Comments
 (0)