123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297open!StdLabelsopen!Sexp_convopen!Sexp_conv_errormoduleKind=structtype(_,_)t=|Default:(unit->'a)->('a,Sexp.t->'a)t|Omit_nil:('a,Sexp.t->'a)t|Required:('a,Sexp.t->'a)t|Sexp_array:('aarray,Sexp.t->'a)t|Sexp_bool:(bool,unit)t|Sexp_list:('alist,Sexp.t->'a)t|Sexp_option:('aoption,Sexp.t->'a)tendmoduleFields=structtype_t=|Empty:unitt|Field:{name:string;kind:('a,'conv)Kind.t;conv:'conv;rest:'bt}->('a*'b)tletlength=letreclength_loop:typea.at->int->int=funtacc->matchtwith|Field{rest;_}->length_looprest(acc+1)|Empty->accinfunt->length_loopt0;;endmoduleMalformed=struct(* Represents errors that can occur due to malformed record sexps. Accumulated as a
value so we can report multiple names at once for extra fields, duplicate fields, or
missing fields. *)typet=|Bool_payload|Extrasofstringlist|Dupsofstringlist|Missingofstringlist|Non_pairofSexp.toptionletcombineab=matcha,bwith(* choose the first bool-payload or non-pair error that occurs *)|((Bool_payload|Non_pair_)ast),_->t|_,((Bool_payload|Non_pair_)ast)->t(* combine lists of similar errors *)|Extrasa,Extrasb->Extras(a@b)|Dupsa,Dupsb->Dups(a@b)|Missinga,Missingb->Missing(a@b)(* otherwise, dups > extras > missing *)|(Dups_ast),_|_,(Dups_ast)->t|(Extras_ast),_|_,(Extras_ast)->t;;letraiset~caller~context=matchtwith|Bool_payload->record_sexp_bool_with_payloadcallercontext|Extrasnames->record_extra_fieldscaller(List.revnames)context|Dupsnames->record_duplicate_fieldscaller(List.revnames)context|Missingnames->List.mapnames~f:(funname->true,name)|>record_undefined_elementscallercontext|Non_pairmaybe_context->letcontext=Option.valuemaybe_context~default:contextinrecord_only_pairs_expectedcallercontext;;endexceptionMalformedofMalformed.tmoduleState=struct(* Stores sexps corresponding to record fields, in the order the fields were declared.
Excludes fields already parsed in the fast path.
List sexps represent a field that is present, such as (x 1) for a field named "x".
Atom sexps represent a field that is absent, or at least not yet seen. *)typet={state:Sexp.tarray}[@@unboxed]letunsafe_gettpos=Array.unsafe_gett.stateposletunsafe_settpossexp=Array.unsafe_sett.statepossexpletabsent=Sexp.Atom""letcreatelen={state=Array.makelenabsent}end(* Parsing field values from state. *)letrecparse_value_malformed:typeab.Malformed.t->fields:(a*b)Fields.t->state:State.t->pos:int->a=funmalformed~fields~state~pos->let(Fieldfield)=fieldsinletmalformed=matchparse_values~fields:field.rest~state~pos:(pos+1)with|(_:b)->malformed|exceptionMalformedother->Malformed.combinemalformedotherinraise(Malformedmalformed)andparse_value:typeab.fields:(a*b)Fields.t->state:State.t->pos:int->a*b=fun~fields~state~pos->let(Field{name;kind;conv;rest})=fieldsinletvalue:a=matchkind,State.unsafe_getstateposwith(* well-formed *)|Required,List[_;sexp]->convsexp|Default_,List[_;sexp]->convsexp|Omit_nil,List[_;sexp]->convsexp|Sexp_option,List[_;sexp]->Some(convsexp)|Sexp_list,List[_;sexp]->list_of_sexpconvsexp|Sexp_array,List[_;sexp]->array_of_sexpconvsexp|Sexp_bool,List[_]->true(* ill-formed *)|((Required|Default_|Omit_nil|Sexp_option|Sexp_list|Sexp_array),(List(_::_::_::_)assexp))->parse_value_malformed(Non_pair(Somesexp))~fields~state~pos|((Required|Default_|Omit_nil|Sexp_option|Sexp_list|Sexp_array),List([]|[_]))->parse_value_malformed(Non_pairNone)~fields~state~pos|Sexp_bool,List([]|_::_::_)->parse_value_malformedBool_payload~fields~state~pos(* absent *)|Required,Atom_->parse_value_malformed(Missing[name])~fields~state~pos|Defaultdefault,Atom_->default()|Omit_nil,Atom_->conv(List[])|Sexp_option,Atom_->None|Sexp_list,Atom_->[]|Sexp_array,Atom_->[||]|Sexp_bool,Atom_->falseinvalue,parse_values~fields:rest~state~pos:(pos+1)andparse_values:typea.fields:aFields.t->state:State.t->pos:int->a=fun~fields~state~pos->matchfieldswith|Field_->parse_value~fields~state~pos|Empty->();;(* Populating state. Handles slow path cases where there may be reordered, duplicated,
missing, or extra fields. *)letrecparse_spine_malformedmalformed~index~extra~seen~state~lensexps=letmalformed=matchparse_spine_slow~index~extra~seen~state~lensexpswith|()->malformed|exceptionMalformedother->Malformed.combinemalformedotherinraise(Malformedmalformed)andparse_spine_slow~index~extra~seen~state~lensexps=match(sexps:Sexp.tlist)with|[]->()|(List(Atomname::_)asfield)::sexps->leti=indexnamein(matchseen<=i&&i<lenwith|true->(* valid field for slow-path parsing *)letpos=i-seenin(matchState.unsafe_getstateposwith|Atom_->(* field not seen yet *)State.unsafe_setstateposfield;parse_spine_slow~index~extra~seen~state~lensexps|List_->(* field already seen *)parse_spine_malformed(Dups[name])~index~extra~seen~state~lensexps)|false->(match0<=i&&i<seenwith|true->(* field seen in fast path *)parse_spine_malformed(Dups[name])~index~extra~seen~state~lensexps|false->(* extra field *)(matchextrawith|true->parse_spine_slow~index~extra~seen~state~lensexps|false->parse_spine_malformed(Extras[name])~index~extra~seen~state~lensexps)))|sexp::sexps->parse_spine_malformed(Non_pair(Somesexp))~index~extra~seen~state~lensexps;;(* Slow path for record parsing. Uses state to store fields as they are discovered. *)letparse_record_slow~fields~index~extra~seensexps=letunseen=Fields.lengthfieldsinletstate=State.createunseeninletlen=seen+unseenin(* populate state *)parse_spine_slow~index~extra~seen~state~lensexps;(* parse values from state *)parse_values~fields~state~pos:0;;(* Fast path for record parsing. Directly parses and returns fields in the order they are
declared. Falls back on slow path if any fields are absent, reordered, or malformed. *)letrecparse_field_fast:typeab.fields:(a*b)Fields.t->index:(string->int)->extra:bool->seen:int->Sexp.tlist->a*b=fun~fields~index~extra~seensexps->let(Field{name;kind;conv;rest})=fieldsinmatchsexpswith|List(Atomatom::args)::otherswhenString.equalatomname->(matchkind,argswith|Required,[sexp]->convsexp,parse_spine_fast~fields:rest~index~extra~seen:(seen+1)others|Default_,[sexp]->convsexp,parse_spine_fast~fields:rest~index~extra~seen:(seen+1)others|Omit_nil,[sexp]->convsexp,parse_spine_fast~fields:rest~index~extra~seen:(seen+1)others|Sexp_option,[sexp]->(Some(convsexp),parse_spine_fast~fields:rest~index~extra~seen:(seen+1)others)|Sexp_list,[sexp]->(list_of_sexpconvsexp,parse_spine_fast~fields:rest~index~extra~seen:(seen+1)others)|Sexp_array,[sexp]->(array_of_sexpconvsexp,parse_spine_fast~fields:rest~index~extra~seen:(seen+1)others)|Sexp_bool,[]->true,parse_spine_fast~fields:rest~index~extra~seen:(seen+1)others(* malformed field of some kind, dispatch to slow path *)|_,_->parse_record_slow~fields~index~extra~seensexps)(* malformed or out-of-order field, dispatch to slow path *)|_->parse_record_slow~fields~index~extra~seensexpsandparse_spine_fast:typea.fields:aFields.t->index:(string->int)->extra:bool->seen:int->Sexp.tlist->a=fun~fields~index~extra~seensexps->matchfieldswith|Field_->parse_field_fast~fields~index~extra~seensexps|Empty->(matchsexpswith|[]->()|_::_->(* extra sexps, dispatch to slow path *)parse_record_slow~fields~index~extra~seensexps);;letparse_record_fast~fields~index~extrasexps=parse_spine_fast~fields~index~extra~seen:0sexps;;(* Entry points. *)letrecord_of_sexps~caller~context~fields~index_of_field~allow_extra_fields~createsexps=letallow_extra_fields=allow_extra_fields||not!Sexp_conv.record_check_extra_fieldsinmatchparse_record_fast~fields~index:index_of_field~extra:allow_extra_fieldssexpswith|value->createvalue|exceptionMalformedmalformed->Malformed.raisemalformed~caller~context;;letrecord_of_sexp~caller~fields~index_of_field~allow_extra_fields~createsexp=match(sexp:Sexp.t)with|Atom_ascontext->record_list_instead_atomcallercontext|Listsexpsascontext->record_of_sexps~caller~context~fields~index_of_field~allow_extra_fields~createsexps;;