1 import Base 2 import ./decode_data.bend as D 3 import ./entity_key.bend as Key 4 import ./snapshot_data.bend as Snapshot 5 import ./restore_raw_number.bend as Number 6 7 # Detached structural parsing failure or unsupported raw version. Schema-specific 8 # name/value/construction errors are a later typed restore stage, not these errors. 9 type Error is Data: 10 Invalid{error:D.Error} 11 UnsupportedVersion{version:D.Raw} 12 # Accepted contains a structurally parsed typed Snapshot; it is not yet schema 13 # validated or applied. Rejected retains complete original Raw and parsing error. 14 type Parsed is Data: 15 Accepted{snapshot:Snapshot.Snapshot} 16 Rejected{original:D.Raw,error:Error} 17 # Internal parser step result, not a second public restore/admission outcome. 18 type Decoded is Data: 19 Value{value:T} 20 Failed{error:Error} 21 def invalid(~T:Data,path:String,expected:String,raw:D.Raw) -> Decoded: 22 Failed{Invalid{D.Invalid{path,expected,raw}}} 23 def numeric_positive(path:String,raw:D.Raw,positive:Bool,value:U32,nonzero:Bool) -> Decoded: 24 match positive nonzero: 25 case True{} False{}:invalid(~U32,path,"positive unsigned 32-bit integer",raw) 26 case _ _:Value{value} 27 def numeric_checked(+path:String,+raw:D.Raw,positive:Bool,result:Number.Checked) -> Decoded: 28 match result: 29 case Number.Invalid{}:invalid(~U32,path,"unsigned 32-bit integer",raw) 30 case Number.Value{+value}:numeric_positive(path,raw,positive,value,U32.is_gt(value,0)) 31 def number(+path:String,+raw:D.Raw,positive:Bool) -> Decoded: 32 numeric_checked(path,raw,positive,Number.checked(raw)) 33 def key_generation(index:U32,result:Decoded) -> Decoded: 34 match result: 35 case Value{generation}:Value{Key.EntityKey{index,generation}} 36 case Failed{error}:Failed{error} 37 def key_index(+path:String,+fields:List<&2,D.Field>,result:Decoded) -> Decoded: 38 match result: 39 case Value{index}:key_generation(index,number(path ++ ".generation",D.field("generation",fields),False{})) 40 case Failed{error}:Failed{error} 41 def key(+path:String,raw:D.Raw) -> Decoded: 42 match raw: 43 case D.Object{+fields}:key_index(path,fields,number(path ++ ".index",D.field("index",fields),False{})) 44 case other:invalid(~Key.Key,path,"object",other) 45 def entity_fields(saved:Key.Key,path:String,raw:D.Raw) -> Decoded: 46 match raw: 47 case D.Object{fields}:Value{Snapshot.Entity{saved,fields}} 48 case other:invalid(~Snapshot.Entity,path,"object",other) 49 def entity_key(+path:String,+fields:List<&2,D.Field>,result:Decoded) -> Decoded: 50 match result: 51 case Value{saved}:entity_fields(saved,path ++ ".components",D.field("components",fields)) 52 case Failed{error}:Failed{error} 53 def entity(+path:String,raw:D.Raw) -> Decoded: 54 match raw: 55 case D.Object{+fields}:entity_key(path,fields,key(path ++ ".id",D.field("id",fields))) 56 case other:invalid(~Snapshot.Entity,path,"object",other) 57 def entities_tail(head:Snapshot.Entity,result:Decoded>) -> Decoded>: 58 match result: 59 case Value{tail}:Value{head <> tail} 60 case Failed{error}:Failed{error} 61 def entities_head(next:Unit -> Decoded>,result:Decoded) -> Decoded>: 62 match result: 63 case Value{head}:entities_tail(head,next(Unit{})) 64 case Failed{error}:Failed{error} 65 def entities(values:List<&2,D.Raw>,+index:U32) -> Decoded>: 66 match values: 67 case []:Value{[]} 68 case raw <> rest:entities_head((_ => entities(rest,U32.add(index,1))),entity("$.entities[" ++ U32.show(index) ++ "]",raw)) 69 def resources(next:U32,values:List<&2,Snapshot.Entity>,raw:D.Raw) -> Decoded: 70 match raw: 71 case D.Object{fields}:Value{Snapshot.Snapshot{1,next,values,fields}} 72 case other:invalid(~Snapshot.Snapshot,"$.resources","object",other) 73 def entities_checked(next:U32,fields:List<&2,D.Field>,result:Decoded>) -> Decoded: 74 match result: 75 case Value{values}:resources(next,values,D.field("resources",fields)) 76 case Failed{error}:Failed{error} 77 def entity_array(next:U32,fields:List<&2,D.Field>,raw:D.Raw) -> Decoded: 78 match raw: 79 case D.Array{values}:entities_checked(next,fields,entities(values,0)) 80 case other:invalid(~Snapshot.Snapshot,"$.entities","array",other) 81 def progress(+fields:List<&2,D.Field>,result:Decoded) -> Decoded: 82 match result: 83 case Value{next}:entity_array(next,fields,D.field("entities",fields)) 84 case Failed{error}:Failed{error} 85 def version_checked(+fields:List<&2,D.Field>,+raw:D.Raw,result:Number.Checked) -> Decoded: 86 match result: 87 case Number.Value{1}:progress(fields,number("$.nextEntity",D.field("nextEntity",fields),True{})) 88 case _:Failed{UnsupportedVersion{raw}} 89 def object(+raw:D.Raw) -> Decoded: 90 match raw: 91 case D.Object{+fields}:version_checked(fields,D.field("version",fields),Number.checked(D.field("version",fields))) 92 case other:invalid(~Snapshot.Snapshot,"$","object",other) 93 def finished(original:D.Raw,result:Decoded) -> Parsed: 94 match result: 95 case Value{snapshot}:Accepted{snapshot} 96 case Failed{error}:Rejected{original,error} 97 # Parse the four-field snapshot object into typed detached Data without touching 98 # World. Version must be 1; nextEntity is positive U32; saved index/generation 99 # accept full U32 including zero as opaque tokens, not target allocator slots. 100 # Exact numeric conversion rejects fractions/nonfinite/out-of-range input before 101 # narrowing. Entities/components/resources shapes are checked in authored order; 102 # D.field keeps existing first-match semantics, not a new duplicate-property rule. 103 # Complete-key duplicates, schema dispatch and capacity are checked by typed restore. 104 def parse(+raw:D.Raw) -> Parsed:finished(raw,object(raw)) 105 def saved_key(value:Key.Key) -> D.Raw: 106 match value: 107 case Key.EntityKey{index,generation}:D.Object{[D.Field{"index",D.Number{index}},D.Field{"generation",D.Number{generation}}]} 108 def saved_entities(values:List<&2,Snapshot.Entity>) -> List<&2,D.Raw>: 109 match values: 110 case []:[] 111 case Snapshot.Entity{key,components} <> rest:D.Object{[D.Field{"id",saved_key(key)},D.Field{"components",D.Object{components}}]} <> saved_entities(rest) 112 # Encode typed Snapshot to the four-field Raw representation preserving entity and 113 # field order. This is not schema validation, JSON serialization or a Runtime 114 # checkpoint; an arbitrary typed Snapshot may still be refused by restore. 115 def encode(value:Snapshot.Snapshot) -> D.Raw: 116 match value: 117 case Snapshot.Snapshot{version,next,entities,resources}:D.Object{[D.Field{"version",D.Number{version}},D.Field{"nextEntity",D.Number{next}},D.Field{"entities",D.Array{saved_entities(entities)}},D.Field{"resources",D.Object{resources}}]}