123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796(*
Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2016, 2017 Anton Lavrik
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*)moduleC=Piqi_commonopenCopenPiqobj_common(*
(* "unknown field" warnings will not be printed for the fields from this list *)
let ignored_fields = ref []
let add_ignored_field x =
ignored_fields := x :: !ignored_fields
let is_ignored_field (ast :piq_ast) =
match ast with
| `name x | `named {T.Named.name = x} -> (* field or flag *)
List.mem x !ignored_fields
| _ -> false
let load_piq_ignore_field = function
| `word x ->
add_ignored_field x
| x ->
error x "invalid .piq-ignore entry"
let load_piq_ignore_node (l :piq_ast list) =
try
let ignore_node = List.find
(function
| `named {T.Named.name = "piq-ignore"; T.Named.value = `list l} ->
add_ignored_field "piq-ignore"; (* add piq-ignore itself *)
List.iter load_piq_ignore_field l;
true
| (`named {T.Named.name = "piq-ignore"} as x) ->
error x "invalid .piq-ignore specification"
| _ -> false) l
in ignore (ignore_node)
with
Not_found -> ()
let load_piq_ignore (ast : piq_ast) =
ignored_fields := []; (* reset ignored fields *)
match ast with
| `list l -> load_piq_ignore_node l
| _ -> ()
*)letunknown_fields=ref[]letadd_unknown_fieldx=unknown_fields:=x::!unknown_fieldsletget_unknown_fields()=letres=List.rev!unknown_fieldsin(* reset unknown field list state *)unknown_fields:=[];res(* ------------------------------------------------------------------- *)(* ------------------------------------------------------------------- *)(* ------------------------------------------------------------------- *)letdepth=ref0(* depth, description, object *)exceptionErrorofint*string*Obj.t(* TODO: trace errors *)lettrace_errorobjs=letloc=C.locationobjintrace"piqobj_of_piq error: %s\n"(strerrlocs)leterrorobjs=(*
trace_error obj s;
*)raise(Error(!depth,s,Obj.reprobj))(* TODO, XXX: handle integer overflows *)letrecparse_int(obj:piq_ast)=matchobjwith|`int(x,_)->`int(Piqloc.addrefretobjx)|`uint(x,_)->`uint(Piqloc.addrefretobjx)|o->erroro"int constant expected"letuint64_to_floatx=ifInt64.comparex0L<0(* big unsigned? *)thenlets=Printf.sprintf"%Lu"xinfloat_of_stringselseInt64.to_floatxletrecparse_float(obj:piq_ast)=matchobjwith|`int(x,_)->Int64.to_floatx|`uint(x,_)->uint64_to_floatx|`float(x,_)->x|o->erroro"float constant expected"letparse_bool(x:piq_ast)=matchxwith|`boolx->x|o->erroro"boolean constant expected"letparse_string?piq_format(x:piq_ast)=letunicode_errors=errors"string contains non-unicode binary data"inletcheck_piq_format()=matchpiq_format,xwith|Some`word,`word_->();(* ok *)|Some`word,`text_->errorx"word literal expected instead of verbatim text"|Some`word,_(* various string literals *)->warningx"word literal expected instead of quoted string"|None,`word_->errorx"quoted string literal expected instead of word"|Some`text,`word_->errorx"verbatim text or string literal expected instead of word"|_->()inmatchxwith|`wordswhen!Config.piq_relaxed_parsing->s|`int(_,s)|`uint(_,s)|`float(_,s)when!Config.piq_relaxed_parsing->s|`boolbwhen!Config.piq_relaxed_parsing->lets=matchbwithtrue->"true"|false->"false"inPiqloc.addrefretxs|`string(s,_)|`texts|`words->check_piq_format();s|`raw_strings->ifPiq_lexer.is_utf8_stringsthenselseunicode_errors|`binary(s,_)->unicode_errors|o->erroro"string expected"letparse_binary(x:piq_ast)=matchxwith|`binary(s,_)|`raw_strings->s|`string(s,_)->ifPiq_lexer.is_ascii_stringsthenselseerrors"binary contains unicode characters or code points"|o->erroro"binary expected"(* some common errors *)leterror_exp_listobj=errorobj"list expected"letcheck_duplicatenametail=matchtailwith|[]->()|l->if!Config.flag_strictthenletobj=List.hdlinerrorobj("duplicate field "^U.quotename)elseList.iter(funobj->warningobj("duplicate field "^U.quotename))l(* truncate the string till the first newline or to max_len *)lettruncate_stringsmax_len=letmax_len=tryString.indexs'\n'withNot_found->max_leninifString.lengths<=max_lenthenselselets=String.subs0max_lenins^" ..."letstring_of_piqastx=matchxwith|`names->s|`named{Piq_ast.Named.name=n}->n|_->lets=Piq_gen.to_stringx~nl:falseintruncate_strings50letwarn_unknown_fieldx=warningx("unknown field: "^string_of_piqastx)lethandle_unknown_field(x:piq_ast)=if!Config.flag_strictthenerrorx("unknown field: "^string_of_piqastx)elseif!C.is_inside_parse_piqithenadd_unknown_fieldxelsewarn_unknown_fieldxlethandle_unknown_variant(x:piq_ast)=errorx("unknown variant: "^string_of_piqastx)exceptionUnknown_variantletfind_piqtypename=tryPiqi_db.find_piqtypenamewithNot_found->Piqi_common.errorname("unknown type: "^U.quotename)(* idtable implemented as map: string -> 'a *)letrecparse_obj0?(piq_format:T.piq_formatoption)~try_mode~nested_variant~labeled(t:T.piqtype)(x:piq_ast):Piqobj.obj=(* fill the location DB *)letrfx=referencefxinletrrftx=reference(ft)xinmatchtwith(* built-in types *)|`int->parse_intx|`float->`float(rparse_floatx)|`bool->`bool(rparse_boolx)|`string->`string(reference(parse_string?piq_format)x)|`binary->`binary(rparse_binaryx)|`any->`any(rparse_anyx)(* custom types *)|`recordt->`record(rr(parse_record~labeled)tx)|`variantt->`variant(rr(parse_variant~try_mode~nested:nested_variant)tx)|`enumt->`enum(rr(parse_enum~try_mode~nested:nested_variant)tx)|`listt->`list(rrparse_listtx)|`aliast->`alias(reference(parse_aliast?piq_format~try_mode~nested_variant~labeled)x)andparse_obj?(try_mode=false)?(nested_variant=false)?(labeled=false)?piq_formattx=reference(parse_obj0~try_mode~nested_variant~labeled?piq_formatt)xandparse_typed_obj?piqtypex=matchpiqtype,xwith|None,`typed{Piq_ast.Typed.typename=n;value=ast}->lett=find_piqtypeninparse_objtast|Somet,`typed{Piq_ast.Typed.value=ast}->(* XXX: if both piqtype and `typed are defined, supplied type overrides
* object type *)(* XXX: produce warning if they are not equal? *)parse_objtast|Somet,_->(* it is not a typed object, but we can use a supplied type *)parse_objtx|_->errorx"typed object expected"andtry_parse_field_objftx=(* unwind alias to obtain its real type *)matchC.unaliastwith|_whenf.T.Field.piq_positional=Somefalse->(* this field must be always labeled according to the explicit
* ".piq-positional false" setting *)None|`record_|`list_whenf.T.Field.piq_positional<>Sometrue->(* all records and lists should be labeled (i.e. can't be positional)
* unless explicitly overridden in the piqi spec by the .piq-positional
* setting *)None|`anywhenf.T.Field.name<>None->(* NOTE, XXX: try-parsing of labeled `any always failes *)None(* NOTE, XXX: try-parsing of unlabeled `any always succeeds *)|_->letdepth'=!depthintrySome(parse_objtx~try_mode:true?piq_format:f.T.Field.piq_format)with(* ignore errors which occur at the same parse depth, i.e. when
* parsing everything except for lists and records which increment
* depth *)Error(depth'',_,_)whendepth''=depth'->(depth:=depth';None)(* restore the original depth *)andparse_anyx:Piqobj.any=(* NOTE: the object is not fully resolved during this stage; at least
* "obj" should be obtained by parsing "piqtype.ast" at later stages (see
* Piqi.resolve_defaults for example *)matchxwith|`anyref->(* in internal mode, returning the exact Piqobj.any object passed via a
* reference *)C.debug"Piqobj_of_piq.parse_any: recovering any from existing ref %d\n"ref;letany=Piqobj.get_anyrefin(* prevent adding a location reference; if we attempt to add a location
* referene here, we end up with a circular reference *)Piqloc.pause_once();any|`typed{Piq_ast.Typed.typename=typename;value=ast}->letany=Any.({Piqobj.default_anywithtypename=Sometypename;piq_ast=Someast;})inPiqloc.addrefretastany(* read untyped JSON form (json ...) as piqi-any *)|`form(`word"json",[`texts])->letjson_ast=!Piqobj.json_of_stringsinletany=Any.({Piqobj.default_anywithjson_ast=Somejson_ast;json_string=Somes;})inPiqloc.addrefretsany|`form(`word"json",_)->errorx"verbatim text literal with JSON value expected after \"json\""(* read untyped XML form (xml ...) as piqi-any *)|`form(`word"xml",[`texts])->letxml_list=!Piqobj.xml_of_stringsinletany=Any.({Piqobj.default_anywithxml_ast=Some("undefined",xml_list);})inPiqloc.addrefretsany|`form(`word"xml",_)->errorx"verbatim text literal with XML value expected after \"xml\""|ast->letany=Any.({Piqobj.default_anywithpiq_ast=Someast;})inPiqloc.addrefretastanyandparse_record~labeledtx=letl=matchxwith|`listl->l|xwhenlabeled&&t.T.Record.piq_allow_unnesting=Sometrue->(* allow field unnesting for a labeled record *)[x]|o->error_exp_listoinincrdepth;(* NOTE: pass locating information as a separate parameter since empty
* list is unboxed and doesn't provide correct location information *)letloc=xinletres=do_parse_recordloctlindecrdepth;res(*
* 1. parse required fields first by label, type or (sub)type = anonymous
* 2. parse the rest in the order they are listed in the original specification
*
*
*)anddo_parse_recordloctl=letrequired_spec,other_spec=List.partitionis_required_fieldt.T.Record.fieldin(* parse required fields first *)letfields,rem=List.fold_left(parse_fieldloc)([],l)(required_spec@other_spec)in(* issue warnings on unparsed fields *)List.iterhandle_unknown_fieldrem;letunparsed_piq_fields_ref=ifrem<>[]&&!C.is_inside_parse_piqithenSome(Piqi_objstore.putrem)(* FIXME: potential memory leak *)elseNonein(* put required fields back at the top *)R.({t=t;field=List.revfields;unparsed_piq_fields_ref=unparsed_piq_fields_ref})andis_required_fieldt=(t.T.Field.mode=`required)andparse_fieldloc(accu,rem)t=letfields,rem=do_parse_fieldloctremin(List.rev_appendfieldsaccu,rem)anddo_parse_fieldloctl=letopenT.Fieldinletname=name_of_fieldtindebug"do_parse_field: %s\n"name;letfield_type=some_oft.piqtypeinletvalues,rem=matcht.modewith|`required->letx,rem=parse_required_fieldtlocnamefield_typelin[x],rem|`optional->letx,rem=parse_optional_fieldtnamefield_typet.defaultlinletres=(matchxwithSomex->[x]|None->[])inres,rem|`repeated->parse_repeated_fieldtnamefield_typelinletfields=List.map(funx->letres=F.({t=t;obj=Somex})inPiqloc.addrefretxres)valuesinfields,remandparse_required_fieldflocnamefield_typel=letres,rem=find_fieldsnamef.T.Field.piq_aliaslinmatchreswith|[]->(* try finding the first field which is successfully parsed by
* 'parse_obj' for a given field type *)beginletres,rem=find_first_parsed_fieldffield_typelinmatchreswith|Somex->x,rem|None->errorloc("missing field "^U.quotename)end|x::tail->check_duplicatenametail;letobj=parse_field_objffield_typexinobj,remandparse_field_objffield_typex=matchxwith|`namednamed->letvalue=named.Piq_ast.Named.valueinparse_objfield_typevalue?piq_format:f.T.Field.piq_format~labeled:true|`namename->(matchf.T.Field.piq_flag_defaultwith|Somepiqi_any->letany=Piqobj.any_of_piqi_anypiqi_anyin(* NOTE: obj must be resolved already *)letobj=some_ofany.Any.objinPiqloc.addrefretxobj(* XXX *)|None->errorx("value must be specified for field "^U.quotename))|_->(* NOTE: find_fields can return only `name | `named ... *)assertfalseandequals_namenamealt_namex=ifx=namethentrueelsematchalt_namewith|Somename->x=name|None->false(* find field by name, return found fields and remaining fields *)andfind_fields(name:string)(alt_name:stringoption)(l:piq_astlist):(piq_astlist*piq_astlist)=letequals_name=equals_namenamealt_nameinletrecauxaccurem=function|[]->List.revaccu,List.revrem|((`namednamed)ash)::twhenequals_namenamed.Piq_ast.Named.name->aux(h::accu)remt|((`namen)ash)::twhenequals_namen->aux(h::accu)remt|h::t->auxaccu(h::rem)tinaux[][]landfind_first_parsed_fieldffield_typel=letrecauxrem=function|[]->None,l|h::t->matchtry_parse_field_objffield_typehwith|None->aux(h::rem)t|x->x,(List.revrem)@tinaux[]landparse_optional_fieldfnamefield_typedefaultl=letres,rem=find_fieldsnamef.T.Field.piq_aliaslinmatchreswith|[]->(* try finding the first field which is successfully parsed by
* 'parse_obj for a given field_type' *)beginletres,rem=find_first_parsed_fieldffield_typelinmatchreswith|Some_->res,rem|None->letres=Piqobj_common.parse_defaultfield_typedefaultinres,lend|x::tail->check_duplicatenametail;letobj=Some(parse_field_objffield_typex)inobj,rem(* parse repeated variant field allowing variant names if field name is
* unspecified *)andparse_repeated_fieldfnamefield_typel=letres,rem=find_fieldsnamef.T.Field.piq_aliaslinmatchreswith|[]->(* XXX: ignore errors occurring when unknown element is present in the
* list allowing other fields to find their members among the list of
* elements *)letaccu,rem=(List.fold_left(fun(accu,rem)x->matchtry_parse_field_objffield_typexwith|None->accu,x::rem|Somex->x::accu,rem)([],[])l)inList.revaccu,List.revrem|l->(* use strict parsing *)letres=List.map(parse_field_objffield_type)resinres,remandparse_variant~try_mode~nestedtx=debug"parse_variant: %s\n"(some_oft.T.Variant.name);letvalue=parse_optionst.T.Variant.optionx~try_mode~nestedinV.({t=t;option=value})andparse_options~try_mode~nestedoptionsx=matchoptionswith|[]->ifnestedthenraiseUnknown_variantelsehandle_unknown_variantx|o::options->matchparse_optionox~try_modewith|Somevalue->(* success *)Piqloc.addrefretxvalue|None->(* need to try other options *)(matchparse_nested_optionox~try_modewith|Somevalue->Piqloc.addrefretxvalue|None->(* continue with other options *)parse_optionsoptionsx~try_mode~nested)andparse_nested_option~try_modeox=(* recursively descent into non-terminal (i.e. nameless variant and enum)
* options
*
* NOTE: recurse into aliased nested variants as well *)letopenT.Optioninmatcho.name,o.piqtypewith|None,Somet->letis_nested_variant=matchC.unaliastwith|`variantv->debug"parse_nested_variant: %s\n"(some_ofv.T.Variant.name);true|`enume->debug"parse_nested_enum: %s\n"(some_ofe.T.Enum.name);true|_->falseinifis_nested_variantthentryletobj=parse_objtx~try_mode~nested_variant:trueinSomeO.({t=o;obj=Someobj})withUnknown_variant->NoneelseNone|_->Noneandparse_option~try_modeox=matchxwith|`namen->parse_name_optionon|`named{Piq_ast.Named.name=n;value=x}->parse_named_optiononx|_->parse_option_by_typeox~try_modeandparse_option_by_type~try_modeox=letopenT.Optioninmatcho.name,o.piqtypewith|None,None->assertfalse|Somen,None->(* try parsing word as a name, but only when the label is exact, i.e.
* try_mode = false
*
* by doing this, we allow using --foo bar instead of --foo.bar in
* relaxed Piq parsing and getopt modes *)(matchxwith|`wordswhenequals_nameno.piq_aliass&&!Config.piq_relaxed_parsing&¬try_mode->SomeO.({t=o;obj=None})|_->None)(* TODO: do not support this behavior by default, only when
* piq-allow-omit-label is specified for options
*
* TODO: unify .piq-allow-omit-label and .piq-positional
*)|None,Sometwheno.piq_alias=None->letdo_parse()=letobj=Some(parse_objtx?piq_format:o.piq_format)inSomeO.({t=o;obj=obj})in(matchC.unaliast,xwith|`bool,`bool_|`int,`int_|`int,`uint_|`float,`int_|`float,`uint_|`float,`float_|`record_,`list_|`list_,`list_->do_parse()|`string,`text_wheno.piq_format=Some`text->do_parse()|`string,`word_wheno.piq_format=Some`word->do_parse()(* XXX, TODO: do we need it?
| `string, `string _ when o.piq_format = Some `string -> do_parse ()
*)|`string,`string_|`string,`raw_string_|`string,`text_wheno.piq_format=None->do_parse()|`string,`int_|`string,`uint_|`string,`float_|`string,`bool_|`string,`word_wheno.piq_format=None&&!Config.piq_relaxed_parsing->do_parse()|`binary,`binary_|`binary,`raw_string_->do_parse()|`binary,`string(s,_)whenPiq_lexer.is_ascii_strings->do_parse()|_->None)|_,Some_->(* either name or piq_alias are defined *)Noneandparse_name_optiononame=letopenT.Optioninletn=C.name_of_optionoinifequals_nameno.piq_aliasnamethenmatcho.piqtypewith|Some_->errorname("value expected for option "^U.quoten)|_->SomeO.({t=o;obj=None})elseNoneandparse_named_optiononamex=letopenT.Optioninletn=C.name_of_optionoinifequals_nameno.piq_aliasnamethenmatcho.name,o.piqtypewith|_,None->errorx("value can not be specified for option "^U.quoten)|_,Somet->letobj=Some(parse_objtx?piq_format:o.piq_format~labeled:true)inSomeO.({t=o;obj=obj})elseNoneandparse_enum~try_mode~nestedtx=debug"parse_enum: %s\n"(some_oft.T.Enum.name);letvalue=parse_optionst.T.Enum.optionx~try_mode~nestedinE.({t=t;option=value})andparse_listt=function|`listl->incrdepth;letres=do_parse_listtlindecrdepth;res|o->error_exp_listoanddo_parse_listtl=letobj_type=some_oft.T.Piqi_list.piqtypeinletcontents=List.map(parse_objobj_type?piq_format:t.T.Piqi_list.piq_format)linL.({t=t;obj=contents})(* XXX: roll-up multiple enclosed aliases into one? *)andparse_alias?(piq_format:T.piq_formatoption)~try_mode~nested_variant~labeledtx=(* upper-level setting overrides lower-level setting *)letthis_piq_format=t.T.Alias.piq_formatinletpiq_format=ifthis_piq_format<>Nonethenthis_piq_formatelsepiq_formatinletpiqtype=some_oft.T.Alias.piqtypeinletobj=parse_objpiqtypex?piq_format~try_mode~nested_variant~labeledinA.({t=t;obj=obj})(*
* External interface:
* resolve parse_obj errors into common parse error format
*)letwrapfx=depth:=0;(* reset the parser's depth *)(*
load_piq_ignore x; (* load ignored fields from the toplevel list *)
*)tryfxwithError(_depth,s,obj)->(* print delayed warnings in case of error *)List.iterwarn_unknown_field(get_unknown_fields());Piqi_common.errorobjsletparse_objtx=wrap(parse_objt)xletparse_typed_obj?piqtypex=wrap(parse_typed_obj?piqtype)xlet_=Piqobj.of_piq:=parse_obj