| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880 |
- '''
- Functions for generating source code for the C++ bindings.
- '''
- import io
- import os
- import pickle
- import re
- import textwrap
- import jlib
- from . import classes
- from . import csharp
- from . import parse
- from . import python
- from . import rename
- from . import state
- from . import util
- def _make_top_level( text, top_level='::'):
- if text == 'string':
- # This is a hack; for some reason we often end up with `string` when it
- # it should be `std::string`.
- text = 'std::string'
- initial_prefix = ['']
- def handle_prefix( text, prefix):
- if text.startswith( prefix):
- initial_prefix[0] += prefix
- return text[ len(prefix):]
- return text
- text = handle_prefix( text, 'const ')
- text = handle_prefix( text, 'struct ')
- if text.startswith( ('fz_', 'pdf_')):
- text = f'{top_level}{text}'
- text = f'{initial_prefix[0]}{text}'
- return text
- def declaration_text(
- type_,
- name,
- nest=0,
- name_is_simple=True,
- verbose=False,
- expand_typedef=True,
- top_level='::',
- ):
- '''
- Returns text for C++ declaration of <type_> called <name>.
- type:
- a clang.cindex.Type.
- name:
- name of type; can be empty.
- nest:
- for internal diagnostics.
- name_is_simple:
- true iff <name> is an identifier.
- If name_is_simple is false, we surround <name> with (...) if type is a
- function.
- '''
- # clang can give unhelpful spelling for anonymous structs.
- assert 'struct (unnamed at ' not in type_.spelling, f'type_.spelling={type_.spelling}'
- if verbose:
- jlib.log( '{nest=} {name=} {type_.spelling=} {type_.get_declaration().get_usr()=}')
- jlib.log( '{type_.kind=} {type_.get_array_size()=} {expand_typedef=}')
- array_n = type_.get_array_size()
- if verbose:
- jlib.log( '{array_n=}')
- if array_n >= 0 or type_.kind == state.clang.cindex.TypeKind.INCOMPLETEARRAY:
- if verbose: jlib.log( '{array_n=}')
- if array_n < 0:
- array_n = ''
- ret = declaration_text(
- type_.get_array_element_type(),
- f'{name}[{array_n}]',
- nest+1,
- name_is_simple,
- verbose=verbose,
- expand_typedef=expand_typedef,
- top_level=top_level,
- )
- if verbose:
- jlib.log( 'returning {ret=}')
- return ret
- pointee = type_.get_pointee()
- if pointee and pointee.spelling:
- if type_.kind == state.clang.cindex.TypeKind.LVALUEREFERENCE:
- pointee_type = '&'
- elif type_.kind == state.clang.cindex.TypeKind.POINTER:
- pointee_type = '*'
- else:
- assert 0, f'Unrecognised pointer kind {type_.kind=}.'
- if verbose: jlib.log( '{type_=} {type_.kind=} {pointee.spelling=}')
- ret = declaration_text(
- pointee,
- f'{pointee_type}{name}',
- nest+1,
- name_is_simple=False,
- verbose=verbose,
- expand_typedef=expand_typedef,
- top_level=top_level,
- )
- if verbose:
- jlib.log( 'returning {ret=}')
- return ret
- if expand_typedef and type_.get_typedef_name():
- if verbose: jlib.log( '{type_.get_typedef_name()=}')
- const = 'const ' if type_.is_const_qualified() else ''
- ret = f'{const}{_make_top_level(type_.get_typedef_name(), top_level)} {name}'
- if verbose:
- jlib.log( 'returning {ret=}')
- return ret
- # On MacOS type `size_t` returns true from get_result() and is
- # state.clang.cindex.TypeKind.ELABORATED.
- #
- if ( type_.get_result().spelling
- and type_.kind not in
- (
- state.clang.cindex.TypeKind.FUNCTIONNOPROTO,
- state.clang.cindex.TypeKind.ELABORATED,
- )
- ):
- # <type> is a function. We call ourselves with type=type_.get_result()
- # and name=<name>(<args>).
- #
- assert type_.kind == state.clang.cindex.TypeKind.FUNCTIONPROTO, \
- f'{type_.spelling=} {type_.kind=}'
- ret = ''
- sep = ''
- for arg in type_.argument_types():
- ret += sep
- ret += declaration_text(
- arg,
- '',
- nest+1,
- top_level=top_level,
- verbose=verbose,
- expand_typedef=expand_typedef,
- )
- sep = ', '
- if verbose: jlib.log( '{ret!r=}')
- if not name_is_simple:
- # If name isn't a simple identifier, put it inside braces, e.g.
- # this crudely allows function pointers to work.
- name = f'({name})'
- ret = f'{name}({ret})'
- if verbose: jlib.log( '{type_.get_result()=}')
- ret = declaration_text(
- type_.get_result(),
- ret,
- nest+1,
- name_is_simple=False,
- verbose=verbose,
- expand_typedef=expand_typedef,
- top_level=top_level,
- )
- if verbose:
- jlib.log( 'returning {ret=}')
- return ret
- ret = f'{_make_top_level(type_.spelling, top_level)} {name}'
- assert not 'struct (unnamed at ' in ret, f'Bad clang name for anonymous struct: {ret}'
- if verbose: jlib.log( 'returning {ret=}')
- return ret
- def write_call_arg(
- tu,
- arg,
- classname,
- have_used_this,
- out_cpp,
- verbose=False,
- python=False,
- ):
- '''
- Write an arg of a function call, translating between raw and wrapping
- classes as appropriate.
- If the required type is a fz_ struct that we wrap, we assume that arg.name
- is a reference to an instance of the wrapper class. If the wrapper class
- is the same as <classname>, we use 'this->' instead of <name>. We also
- generate slightly different code depending on whether the wrapper class is
- pod or inline pod.
- arg:
- Arg from get_args().
- classname:
- Name of wrapper class available as 'this'.
- have_used_this:
- If true, we never use 'this->...'.
- out_cpp:
- .
- python:
- If true, we write python code, not C.
- Returns True if we have used 'this->...', else return <have_used_this>.
- '''
- assert isinstance( arg, parse.Arg)
- assert isinstance( arg.cursor, state.clang.cindex.Cursor)
- if not arg.alt:
- # Arg is a normal type; no conversion necessary.
- if python:
- out_cpp.write( arg.name_python)
- else:
- out_cpp.write( arg.name)
- return have_used_this
- if verbose:
- jlib.log( '{=arg.name arg.alt.spelling classname}')
- type_ = state.get_name_canonical( arg.cursor.type)
- ptr = '*'
- #log( '{=arg.name arg.alt.spelling classname type_.spelling}')
- if type_.kind == state.clang.cindex.TypeKind.POINTER:
- type_ = state.get_name_canonical( type_.get_pointee())
- ptr = ''
- #log( '{=arg.name arg.alt.spelling classname type_.spelling}')
- extras = parse.get_fz_extras( tu, type_.spelling)
- assert extras, f'No extras for type_.spelling={type_.spelling}'
- if verbose:
- jlib.log( 'param is fz: {type_.spelling=} {extras2.pod=}')
- assert extras.pod != 'none' \
- 'Cannot pass wrapper for {type_.spelling} as arg because pod is "none" so we cannot recover struct.'
- if python:
- if extras.pod == 'inline':
- out_cpp.write( f'{arg.name_python}.internal()')
- elif extras.pod:
- out_cpp.write( f'{arg.name_python}.m_internal')
- else:
- out_cpp.write( f'{arg.name_python}.m_internal')
- elif extras.pod == 'inline':
- # We use the address of the first class member, casting it to a pointer
- # to the wrapped type. Not sure this is guaranteed safe, but should
- # work in practise.
- name_ = f'{arg.name}.'
- if not have_used_this and rename.class_(arg.alt.type.spelling) == classname:
- have_used_this = True
- name_ = 'this->'
- field0 = parse.get_field0(type_).spelling
- out_cpp.write( f'{ptr} {name_}internal()')
- else:
- if verbose:
- jlib.log( '{=arg state.get_name_canonical(arg.cursor.type).kind classname extras}')
- if extras.pod and state.get_name_canonical( arg.cursor.type).kind == state.clang.cindex.TypeKind.POINTER:
- out_cpp.write( '&')
- elif not extras.pod and state.get_name_canonical( arg.cursor.type).kind != state.clang.cindex.TypeKind.POINTER:
- out_cpp.write( '*')
- elif arg.out_param:
- out_cpp.write( '&')
- if not have_used_this and rename.class_(arg.alt.type.spelling) == classname:
- have_used_this = True
- out_cpp.write( 'this->')
- else:
- out_cpp.write( f'{arg.name}.')
- out_cpp.write( 'm_internal')
- return have_used_this
- def make_fncall( tu, cursor, return_type, fncall, out, refcheck_if, trace_if):
- '''
- Writes a low-level function call to <out>, using fz_context_s from
- internal_context_get() and with fz_try...fz_catch that converts to C++
- exceptions by calling throw_exception().
- return_type:
- Text return type of function, e.g. 'void' or 'double'.
- fncall:
- Text containing function call, e.g. 'function(a, b, 34)'.
- out:
- Stream to which we write generated code.
- '''
- uses_fz_context = False;
- # Setting this to False is a hack to elide all fz_try/fz_catch code. This
- # has a very small effect on mupdfpy test suite performance - e.g. reduce
- # time from 548.1s to 543.2s.
- #
- use_fz_try = True
- if cursor.spelling in (
- 'pdf_specifics',
- ):
- # This fn takes a fz_context* but never throws, so we can omit
- # `fz_try()...fz_catch()`, which might give a small performance
- # improvement.
- use_fz_try = False
- uses_fz_context = True
- else:
- for arg in parse.get_args( tu, cursor, include_fz_context=True):
- if parse.is_pointer_to( arg.cursor.type, 'fz_context'):
- uses_fz_context = True
- break
- if uses_fz_context:
- context_get = rename.internal( 'context_get')
- throw_exception = rename.internal( 'throw_exception')
- out.write( f' fz_context* auto_ctx = {context_get}();\n')
- # Output code that writes diagnostics to std::cerr if $MUPDF_trace is set.
- #
- def varname_enable():
- for t in 'fz_keep_', 'fz_drop_', 'pdf_keep_', 'pdf_drop_':
- if cursor.spelling.startswith( t):
- return 's_trace_keepdrop'
- return 's_trace > 1'
- out.write( f' {trace_if}\n')
- out.write( f' if ({varname_enable()}) {{\n')
- out.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): calling {cursor.spelling}():";\n')
- for arg in parse.get_args( tu, cursor, include_fz_context=True):
- if parse.is_pointer_to( arg.cursor.type, 'fz_context'):
- out.write( f' if ({varname_enable()}) std::cerr << " auto_ctx=" << auto_ctx;\n')
- elif arg.out_param:
- out.write( f' if ({varname_enable()}) std::cerr << " {arg.name}=" << (void*) {arg.name};\n')
- elif arg.alt:
- # If not a pod, there will not be an operator<<, so just show
- # the address of this arg.
- #
- extras = parse.get_fz_extras( tu, arg.alt.type.spelling)
- assert extras.pod != 'none' \
- 'Cannot pass wrapper for {type_.spelling} as arg because pod is "none" so we cannot recover struct.'
- if extras.pod:
- out.write( f' std::cerr << " {arg.name}=" << {arg.name};\n')
- elif arg.cursor.type.kind == state.clang.cindex.TypeKind.POINTER:
- out.write( f' if ({varname_enable()}) std::cerr << " {arg.name}=" << {arg.name};\n')
- elif arg.cursor.type.kind == state.clang.cindex.TypeKind.LVALUEREFERENCE:
- out.write( f' if ({varname_enable()}) std::cerr << " &{arg.name}=" << &{arg.name};\n')
- else:
- out.write( f' std::cerr << " &{arg.name}=" << &{arg.name};\n')
- elif parse.is_pointer_to(arg.cursor.type, 'char') and state.get_name_canonical( arg.cursor.type.get_pointee()).is_const_qualified():
- # 'const char*' is assumed to be zero-terminated string. But we
- # need to protect against trying to write nullptr because this
- # appears to kill std::cerr on Linux.
- out.write( f' if ({arg.name}) std::cerr << " {arg.name}=\'" << {arg.name} << "\'";\n')
- out.write( f' else std::cerr << " {arg.name}:null";\n')
- elif parse.is_( arg.cursor.type, 'va_list'):
- out.write( f' std::cerr << " {arg.name}:va_list";\n')
- elif (0
- or parse.is_( arg.cursor.type, 'signed char')
- or parse.is_( arg.cursor.type, 'unsigned char')
- ):
- # Typically used for raw data, so not safe to treat as text.
- out.write( f' std::cerr << " {arg.name}=" << ((int) {arg.name});\n')
- elif (0
- or parse.is_pointer_to(arg.cursor.type, 'signed char')
- or parse.is_pointer_to(arg.cursor.type, 'unsigned char')
- ):
- # Typically used for raw data, so not safe to treat as text.
- out.write( f' std::cerr << " {arg.name}=" << ((void*) {arg.name});\n')
- elif arg.cursor.type.kind == state.clang.cindex.TypeKind.POINTER:
- # Don't assume non-const 'char*' is a zero-terminated string.
- out.write( f' if ({varname_enable()}) std::cerr << " {arg.name}=" << (void*) {arg.name};\n')
- elif arg.cursor.type.kind == state.clang.cindex.TypeKind.LVALUEREFERENCE:
- out.write( f' if ({varname_enable()}) std::cerr << " &{arg.name}=" << &{arg.name};\n')
- else:
- out.write( f' std::cerr << " {arg.name}=" << {arg.name};\n')
- out.write( f' std::cerr << "\\n";\n')
- out.write( f' }}\n')
- out.write( f' #endif\n')
- if uses_fz_context:
- out.write( f' {refcheck_if}\n')
- out.write( f' long stack0;\n')
- out.write( f' if (s_check_error_stack)\n')
- out.write( f' {{\n')
- out.write( f' stack0 = auto_ctx->error.top - auto_ctx->error.stack_base;\n')
- out.write( f' }}\n')
- out.write( f' #endif\n')
- # Now output the function call.
- #
- if return_type != 'void':
- out.write( f' {return_type} ret;\n')
- if cursor.spelling == 'fz_warn':
- out.write( ' va_list ap;\n')
- out.write( ' fz_var(ap);\n')
- indent = ''
- if uses_fz_context and use_fz_try:
- out.write( f' fz_try(auto_ctx) {{\n')
- indent = ' '
- if cursor.spelling == 'fz_warn':
- out.write( f' {indent}va_start(ap, fmt);\n')
- out.write( f' {indent}fz_vwarn(auto_ctx, fmt, ap);\n')
- else:
- if not uses_fz_context:
- out.write( f' /* No fz_context* arg, so no need for fz_try()/fz_catch() to convert MuPDF exceptions into C++ exceptions. */\n')
- out.write( f' {indent}')
- if return_type != 'void':
- out.write( f'ret = ')
- out.write( f'{fncall};\n')
- if uses_fz_context and use_fz_try:
- out.write( f' }}\n')
- if cursor.spelling == 'fz_warn':
- if use_fz_try:
- out.write( f' fz_always(auto_ctx) {{\n')
- out.write( f' va_end(ap);\n')
- out.write( f' }}\n')
- else:
- out.write( f' va_end(ap);\n')
- if uses_fz_context and use_fz_try:
- out.write( f' fz_catch(auto_ctx) {{\n')
- out.write( f' {trace_if}\n')
- out.write( f' if (s_trace_exceptions) {{\n')
- out.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): fz_catch() has caught exception.\\n";\n')
- out.write( f' }}\n')
- out.write( f' #endif\n')
- out.write( f' {throw_exception}(auto_ctx);\n')
- out.write( f' }}\n')
- if uses_fz_context:
- out.write( f' {refcheck_if}\n')
- out.write( f' if (s_check_error_stack)\n')
- out.write( f' {{\n')
- out.write( f' long stack1 = auto_ctx->error.top - auto_ctx->error.stack_base;\n')
- out.write( f' if (stack1 != stack0)\n')
- out.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): MuPDF error stack size changed by {cursor.spelling}(): " << stack0 << " -> " << stack1 << "\\n";\n')
- out.write( f' }}\n')
- out.write( f' #endif\n')
- if return_type != 'void':
- out.write( f' return ret;\n')
- def to_pickle( obj, path):
- '''
- Pickles <obj> to file <path>.
- '''
- with open( path, 'wb') as f:
- pickle.dump( obj, f)
- def from_pickle( path):
- '''
- Returns contents of file <path> unpickled.
- '''
- with open( path, 'rb') as f:
- return pickle.load( f)
- class Generated:
- '''
- Stores information generated when we parse headers using clang.
- '''
- def __init__( self):
- self.h_files = []
- self.cpp_files = []
- self.fn_usage_filename = None
- self.container_classnames = []
- self.to_string_structnames = []
- self.fn_usage = dict()
- self.output_param_fns = []
- self.c_functions = []
- self.c_globals = []
- self.c_enums = []
- self.c_structs = []
- self.swig_cpp = io.StringIO()
- self.swig_cpp_python = io.StringIO()
- self.swig_python = io.StringIO()
- self.swig_python_exceptions = io.StringIO()
- self.swig_python_set_error_classes = io.StringIO()
- self.swig_csharp = io.StringIO()
- self.virtual_fnptrs = [] # List of extra wrapper class names with virtual fnptrs.
- self.cppyy_extra = ''
- def save( self, dirpath):
- '''
- Saves state to .pickle file, to be loaded later via pickle.load().
- '''
- to_pickle( self, f'{dirpath}/generated.pickle')
- def make_outparam_helper(
- tu,
- cursor,
- fnname,
- fnname_wrapper,
- generated,
- ):
- '''
- Create extra C++, Python and C# code to make tuple-returning wrapper of
- specified function.
- We write Python code to generated.swig_python and C++ code to
- generated.swig_cpp.
- '''
- verbose = False
- main_name = rename.ll_fn(cursor.spelling)
- generated.swig_cpp.write( '\n')
- # Write struct.
- generated.swig_cpp.write( 'namespace mupdf\n')
- generated.swig_cpp.write('{\n')
- generated.swig_cpp.write(f' /* Out-params helper class for {cursor.spelling}(). */\n')
- generated.swig_cpp.write(f' struct {main_name}_outparams\n')
- generated.swig_cpp.write(f' {{\n')
- for arg in parse.get_args( tu, cursor):
- if not arg.out_param:
- continue
- decl = declaration_text( arg.cursor.type, arg.name, verbose=verbose)
- if verbose:
- jlib.log( '{decl=}')
- assert arg.cursor.type.kind == state.clang.cindex.TypeKind.POINTER
- # We use state.get_name_canonical() here because, for example, it
- # converts int64_t to 'long long', which seems to be handled better by
- # swig - swig maps int64_t to mupdf.SWIGTYPE_p_int64_t which can't be
- # treated or converted to an integer.
- #
- # We also value-initialise in case the underlying mupdf function also
- # reads the supplied value - i.e. treats it as an in-parm as well as an
- # out-param; this is particularly important for pointer out-params.
- #
- pointee = state.get_name_canonical( arg.cursor.type.get_pointee())
- generated.swig_cpp.write(f' {declaration_text( pointee, arg.name)} = {{}};\n')
- generated.swig_cpp.write(f' }};\n')
- generated.swig_cpp.write('\n')
- # Write function definition.
- name_args = f'{main_name}_outparams_fn('
- sep = ''
- for arg in parse.get_args( tu, cursor):
- if arg.out_param:
- continue
- name_args += sep
- name_args += declaration_text( arg.cursor.type, arg.name, verbose=verbose)
- sep = ', '
- name_args += f'{sep}{main_name}_outparams* outparams'
- name_args += ')'
- generated.swig_cpp.write(f' /* Out-params function for {cursor.spelling}(). */\n')
- generated.swig_cpp.write(f' {declaration_text( cursor.result_type, name_args)}\n')
- generated.swig_cpp.write( ' {\n')
- return_void = (cursor.result_type.spelling == 'void')
- generated.swig_cpp.write(f' ')
- if not return_void:
- generated.swig_cpp.write(f'{declaration_text(cursor.result_type, "ret")} = ')
- generated.swig_cpp.write(f'{rename.ll_fn(cursor.spelling)}(')
- sep = ''
- for arg in parse.get_args( tu, cursor):
- generated.swig_cpp.write(sep)
- if arg.out_param:
- generated.swig_cpp.write(f'&outparams->{arg.name}')
- else:
- generated.swig_cpp.write(f'{arg.name}')
- sep = ', '
- generated.swig_cpp.write(');\n')
- if not return_void:
- generated.swig_cpp.write(' return ret;\n')
- generated.swig_cpp.write(' }\n')
- generated.swig_cpp.write('}\n')
- # Write Python wrapper.
- python.make_outparam_helper_python(tu, cursor, fnname, fnname_wrapper, generated, main_name)
- # Write C# wrapper.
- csharp.make_outparam_helper_csharp(tu, cursor, fnname, fnname_wrapper, generated, main_name)
- def make_python_class_method_outparam_override(
- tu,
- cursor,
- fnname,
- generated,
- structname,
- classname,
- return_type,
- ):
- '''
- Writes Python code to `generated.swig_python` that monkey-patches Python
- function or method to make it call the underlying MuPDF function's Python
- wrapper, which will return out-params in a tuple.
- This is necessary because C++ doesn't support out-params so the C++ API
- supports wrapper class out-params by taking references to a dummy wrapper
- class instances, whose m_internal is then changed to point to the out-param
- struct (with suitable calls to keep/drop to manage the destruction of the
- dummy instance).
- In Python, we could create dummy wrapper class instances (e.g. passing
- nullptr to constructor) and return them, but instead we make our own call
- to the underlying MuPDF function and wrap the out-params into wrapper
- classes.
- '''
- out = generated.swig_python
- # Underlying fn.
- main_name = rename.ll_fn(cursor.spelling)
- if structname:
- name_new = f'{classname}_{rename.method(structname, cursor.spelling)}_outparams_fn'
- else:
- name_new = f'{rename.fn(cursor.spelling)}_outparams_fn'
- # Define an internal Python function that will become the class method.
- #
- out.write( f'def {name_new}(')
- if structname:
- out.write( ' self')
- comma = ', '
- else:
- comma = ''
- for arg in parse.get_args( tu, cursor):
- if arg.out_param:
- continue
- if structname and parse.is_pointer_to( arg.cursor.type, structname):
- continue
- out.write(f'{comma}{arg.name_python}')
- comma = ', '
- out.write('):\n')
- out.write( ' """\n')
- if structname:
- out.write(f' Helper for out-params of class method {structname}::{main_name}() [{cursor.spelling}()].\n')
- else:
- out.write(f' Class-aware helper for out-params of {fnname}() [{cursor.spelling}()].\n')
- out.write( ' """\n')
- # ret, a, b, ... = foo::bar(self.m_internal, p, q, r, ...)
- out.write(f' ')
- sep = ''
- if cursor.result_type.spelling != 'void':
- out.write( 'ret')
- sep = ', '
- for arg in parse.get_args( tu, cursor):
- if not arg.out_param:
- continue
- out.write( f'{sep}{arg.name_python}')
- sep = ', '
- out.write( f' = {main_name}(')
- sep = ''
- if structname:
- out.write( f' self.m_internal')
- sep = ', '
- for arg in parse.get_args( tu, cursor):
- if arg.out_param:
- continue
- if structname and parse.is_pointer_to( arg.cursor.type, structname):
- continue
- out.write( sep)
- write_call_arg( tu, arg, classname, have_used_this=False, out_cpp=out, python=True)
- sep = ', '
- out.write( ')\n')
- # return ret, a, b.
- #
- # We convert returned items to wrapper classes if they are MuPDF types.
- #
- out.write( ' return ')
- sep = ''
- if cursor.result_type.spelling != 'void':
- if return_type:
- #out.write( f'{return_type}(ret)')
- # Return type is a class wrapper.
- return_ll_type = cursor.result_type
- do_keep = False
- if cursor.result_type.kind == state.clang.cindex.TypeKind.POINTER:
- return_ll_type = return_ll_type.get_pointee()
- if parse.has_refs( tu, return_ll_type):
- return_ll_type = return_ll_type.spelling
- return_ll_type = util.clip( return_ll_type, ('struct ', 'const '))
- assert return_ll_type.startswith( ( 'fz_', 'pdf_'))
- for prefix in ( 'fz_', 'pdf_'):
- if return_ll_type.startswith( prefix):
- break
- else:
- assert 0, f'Unexpected arg type: {return_ll_type}'
- return_extra = classes.classextras.get( tu, return_ll_type)
- if not function_name_implies_kept_references( fnname):
- do_keep = True
- else:
- if 'char' in return_ll_type.spelling:
- jlib.log('### Function returns {cursor.result_type.spelling=} -> {return_ll_type.spelling=}: {fnname}. {function_name_implies_kept_references(fnname)=}')
- if do_keep:
- keepfn = f'{prefix}keep_{return_ll_type[ len(prefix):]}'
- keepfn = rename.ll_fn( keepfn)
- out.write( f'{return_type}( {keepfn}( ret))')
- else:
- out.write( f'{return_type}(ret)')
- else:
- out.write( 'ret')
- sep = ', '
- for arg in parse.get_args( tu, cursor):
- if not arg.out_param:
- continue
- if arg.alt:
- name = util.clip( arg.alt.type.spelling, ('struct ', 'const '))
- for prefix in ( 'fz_', 'pdf_'):
- if name.startswith( prefix):
- break
- else:
- assert 0, f'Unexpected arg type: {name}'
- if function_name_implies_kept_references( fnname):
- out.write( f'{sep}{rename.class_(name)}( {arg.name_python})')
- else:
- keepfn = f'{prefix}keep_{name[ len(prefix):]}'
- keepfn = rename.ll_fn( keepfn)
- out.write( f'{sep}{rename.class_(name)}({keepfn}( {arg.name_python}))')
- else:
- out.write( f'{sep}{arg.name_python}')
- sep = ', '
- out.write('\n')
- out.write('\n')
- # foo.bar = foo_bar_outparams_fn
- if structname:
- out.write(f'{classname}.{rename.method(structname, cursor.spelling)} = {name_new}\n')
- else:
- out.write(f'{rename.fn( cursor.spelling)} = {name_new}\n')
- out.write('\n')
- out.write('\n')
- def make_wrapper_comment(
- tu,
- cursor,
- fnname,
- fnname_wrapper,
- indent,
- is_method,
- is_low_level,
- ):
- ret = io.StringIO()
- def write(text):
- text = text.replace('\n', f'\n{indent}')
- ret.write( text)
- num_out_params = 0
- for arg in parse.get_args(
- tu,
- cursor,
- include_fz_context=False,
- skip_first_alt=is_method,
- ):
- if arg.out_param:
- num_out_params += 1
- if is_low_level:
- write( f'Low-level wrapper for `{rename.c_fn(cursor.spelling)}()`.')
- else:
- write( f'Class-aware wrapper for `{rename.c_fn(cursor.spelling)}()`.')
- if num_out_params:
- tuple_size = num_out_params
- if cursor.result_type.spelling != 'void':
- tuple_size += 1
- write( f'\n')
- write( f'\n')
- write( f'This {"method" if is_method else "function"} has out-params. Python/C# wrappers look like:\n')
- write( f' `{fnname_wrapper}(')
- sep = ''
- for arg in parse.get_args( tu, cursor, include_fz_context=False, skip_first_alt=is_method):
- if arg.alt or not arg.out_param:
- write( f'{sep}{declaration_text( arg.cursor.type, arg.name)}')
- sep = ', '
- write(')` => ')
- if tuple_size > 1:
- write( '`(')
- sep = ''
- if cursor.result_type.spelling != 'void':
- write( f'{cursor.result_type.spelling}')
- sep = ', '
- for arg in parse.get_args( tu, cursor, include_fz_context=False, skip_first_alt=is_method):
- if not arg.alt and arg.out_param:
- write( f'{sep}{declaration_text( arg.cursor.type.get_pointee(), arg.name)}')
- sep = ', '
- if tuple_size > 1:
- write( ')`')
- write( f'\n')
- else:
- write( ' ')
- return ret.getvalue()
- def function_wrapper(
- tu,
- cursor,
- fnname,
- fnname_wrapper,
- out_h,
- out_cpp,
- generated,
- refcheck_if,
- trace_if,
- ):
- '''
- Writes low-level C++ wrapper fn, converting any fz_try..fz_catch exception
- into a C++ exception.
- cursor:
- Clang cursor for function to wrap.
- fnname:
- Name of wrapped function.
- fnname_wrapper:
- Name of function to create.
- out_h:
- Stream to which we write header output.
- out_cpp:
- Stream to which we write cpp output.
- generated:
- A Generated instance.
- refcheck_if:
- A '#if*' statement that determines whether extra checks are compiled
- in.
- trace_if:
- A '#if*' statement that determines whether runtime diagnostics are
- compiled in.
- Example generated function:
- fz_band_writer * mupdf_new_band_writer_of_size(fz_context *ctx, size_t size, fz_output *out)
- {
- fz_band_writer * ret;
- fz_try(ctx) {
- ret = fz_new_band_writer_of_size(ctx, size, out);
- }
- fz_catch(ctx) {
- mupdf_throw_exception(ctx);
- }
- return ret;
- }
- '''
- assert cursor.kind == state.clang.cindex.CursorKind.FUNCTION_DECL
- if cursor.type.is_function_variadic() and fnname != 'fz_warn':
- jlib.log( 'Not writing low-level wrapper because variadic: {fnname=}', 1)
- return
- verbose = state.state_.show_details( fnname)
- if verbose:
- jlib.log( 'Wrapping {fnname}')
- num_out_params = 0
- for arg in parse.get_args( tu, cursor, include_fz_context=True):
- if parse.is_pointer_to(arg.cursor.type, 'fz_context'):
- continue
- if arg.out_param:
- num_out_params += 1
- # Write first line: <result_type> <fnname_wrapper> (<args>...)
- #
- comment = make_wrapper_comment( tu, cursor, fnname, fnname_wrapper, indent='', is_method=False, is_low_level=True)
- comment = f'/** {comment}*/\n'
- for out in out_h, out_cpp:
- out.write( comment)
- # Copy any comment into .h file before declaration.
- if cursor.raw_comment:
- # On Windows, carriage returns can appear in cursor.raw_comment on
- # due to line ending inconsistencies in our generated extra.cpp and
- # extra.h, and can cause spurious differences in our generated C++
- # code, which in turn causes unnecessary rebuilds.
- #
- # It would probably better to fix line endings in our generation of
- # extra.*.
- raw_comment = cursor.raw_comment.replace('\r', '')
- out_h.write(raw_comment)
- if not raw_comment.endswith( '\n'):
- out_h.write( '\n')
- # Write declaration and definition.
- name_args_h = f'{fnname_wrapper}('
- name_args_cpp = f'{fnname_wrapper}('
- comma = ''
- for arg in parse.get_args( tu, cursor, include_fz_context=True):
- if verbose:
- jlib.log( '{arg.cursor=} {arg.name=} {arg.separator=} {arg.alt=} {arg.out_param=}')
- if parse.is_pointer_to(arg.cursor.type, 'fz_context'):
- continue
- decl = declaration_text( arg.cursor.type, arg.name, verbose=verbose)
- if verbose:
- jlib.log( '{decl=}')
- name_args_h += f'{comma}{decl}'
- decl = declaration_text( arg.cursor.type, arg.name)
- name_args_cpp += f'{comma}{decl}'
- comma = ', '
- if cursor.type.is_function_variadic():
- name_args_h += f'{comma}...'
- name_args_cpp += f'{comma}...'
- name_args_h += ')'
- name_args_cpp += ')'
- declaration_h = declaration_text( cursor.result_type, name_args_h, verbose=verbose)
- declaration_cpp = declaration_text( cursor.result_type, name_args_cpp, verbose=verbose)
- out_h.write( f'FZ_FUNCTION {declaration_h};\n')
- out_h.write( '\n')
- # Write function definition.
- #
- out_cpp.write( f'FZ_FUNCTION {declaration_cpp}\n')
- out_cpp.write( '{\n')
- return_type = cursor.result_type.spelling
- fncall = ''
- fncall += f'{rename.c_fn(cursor.spelling)}('
- for arg in parse.get_args( tu, cursor, include_fz_context=True):
- if parse.is_pointer_to( arg.cursor.type, 'fz_context'):
- fncall += f'{arg.separator}auto_ctx'
- else:
- fncall += f'{arg.separator}{arg.name}'
- fncall += ')'
- make_fncall( tu, cursor, return_type, fncall, out_cpp, refcheck_if, trace_if)
- out_cpp.write( '}\n')
- out_cpp.write( '\n')
- if num_out_params:
- make_outparam_helper(
- tu,
- cursor,
- fnname,
- fnname_wrapper,
- generated,
- )
- def make_namespace_open( namespace, out):
- if namespace:
- out.write( '\n')
- out.write( f'namespace {namespace}\n')
- out.write( '{\n')
- def make_namespace_close( namespace, out):
- if namespace:
- out.write( '\n')
- out.write( f'}} /* End of namespace {namespace}. */\n')
- # libclang can't always find headers so we define our own `std::string`
- # and `std::vector<>` that work well enough for the generation of the
- # C++ API.
- #
- # We also define extra raw functions to aid SWIG-generated code. These
- # are implemented in C++, and should be excluded from the generated
- # windows_def file later on, otherwise we get link errors on Windows.
- #
- g_extra_declarations = textwrap.dedent(f'''
- #ifdef MUPDF_WRAP_LIBCLANG
- namespace std
- {{
- template<typename T>
- struct vector
- {{
- }};
- struct string
- {{
- }};
- }}
- #else
- #include <string>
- #include <vector>
- #endif
- #include "mupdf/fitz.h"
- #include "mupdf/pdf.h"
- /**
- C++ alternative to `fz_lookup_metadata()` that returns a `std::string`
- or calls `fz_throw()` if not found.
- */
- FZ_FUNCTION std::string fz_lookup_metadata2(fz_context* ctx, fz_document* doc, const char* key);
- /**
- C++ alternative to `pdf_lookup_metadata()` that returns a `std::string`
- or calls `fz_throw()` if not found.
- */
- FZ_FUNCTION std::string pdf_lookup_metadata2(fz_context* ctx, pdf_document* doc, const char* key);
- /**
- C++ alternative to `fz_md5_pixmap()` that returns the digest by value.
- */
- FZ_FUNCTION std::vector<unsigned char> fz_md5_pixmap2(fz_context* ctx, fz_pixmap* pixmap);
- /**
- C++ alternative to fz_md5_final() that returns the digest by value.
- */
- FZ_FUNCTION std::vector<unsigned char> fz_md5_final2(fz_md5* md5);
- /** */
- FZ_FUNCTION long long fz_pixmap_samples_int(fz_context* ctx, fz_pixmap* pixmap);
- /**
- Provides simple (but slow) access to pixmap data from Python and C#.
- */
- FZ_FUNCTION int fz_samples_get(fz_pixmap* pixmap, int offset);
- /**
- Provides simple (but slow) write access to pixmap data from Python and
- C#.
- */
- FZ_FUNCTION void fz_samples_set(fz_pixmap* pixmap, int offset, int value);
- /**
- C++ alternative to fz_highlight_selection() that returns quads in a
- std::vector.
- */
- FZ_FUNCTION std::vector<fz_quad> fz_highlight_selection2(fz_context* ctx, fz_stext_page* page, fz_point a, fz_point b, int max_quads);
- struct fz_search_page2_hit
- {{
- fz_quad quad;
- int mark;
- }};
- /**
- C++ alternative to fz_search_page() that returns information in a std::vector.
- */
- FZ_FUNCTION std::vector<fz_search_page2_hit> fz_search_page2(fz_context* ctx, fz_document* doc, int number, const char* needle, int hit_max);
- /**
- C++ alternative to fz_string_from_text_language() that returns information in a std::string.
- */
- FZ_FUNCTION std::string fz_string_from_text_language2(fz_text_language lang);
- /**
- C++ alternative to fz_get_glyph_name() that returns information in a std::string.
- */
- FZ_FUNCTION std::string fz_get_glyph_name2(fz_context* ctx, fz_font* font, int glyph);
- /**
- Extra struct containing fz_install_load_system_font_funcs()'s args,
- which we wrap with virtual_fnptrs set to allow use from Python/C# via
- Swig Directors.
- */
- typedef struct fz_install_load_system_font_funcs_args
- {{
- fz_load_system_font_fn* f;
- fz_load_system_cjk_font_fn* f_cjk;
- fz_load_system_fallback_font_fn* f_fallback;
- }} fz_install_load_system_font_funcs_args;
- /**
- Alternative to fz_install_load_system_font_funcs() that takes args in a
- struct, to allow use from Python/C# via Swig Directors.
- */
- FZ_FUNCTION void fz_install_load_system_font_funcs2(fz_context* ctx, fz_install_load_system_font_funcs_args* args);
- /** Internal singleton state to allow Swig Director class to find
- fz_install_load_system_font_funcs_args class wrapper instance. */
- FZ_DATA extern void* fz_install_load_system_font_funcs2_state;
- /** Helper for calling `fz_document_handler::open` function pointer via
- Swig from Python/C#. */
- FZ_FUNCTION fz_document* fz_document_handler_open(fz_context* ctx, const fz_document_handler *handler, fz_stream* stream, fz_stream* accel, fz_archive* dir, void* recognize_state);
- /** Helper for calling a `fz_document_handler::recognize` function
- pointer via Swig from Python/C#. */
- FZ_FUNCTION int fz_document_handler_recognize(fz_context* ctx, const fz_document_handler *handler, const char *magic);
- /** Swig-friendly wrapper for pdf_choice_widget_options(), returns the
- options directly in a vector. */
- FZ_FUNCTION std::vector<std::string> pdf_choice_widget_options2(fz_context* ctx, pdf_annot* tw, int exportval);
- /** Swig-friendly wrapper for fz_new_image_from_compressed_buffer(),
- uses specified `decode` and `colorkey` if they are not null (in which
- case we assert that they have size `2*fz_colorspace_n(colorspace)`). */
- FZ_FUNCTION fz_image* fz_new_image_from_compressed_buffer2(
- fz_context* ctx,
- int w,
- int h,
- int bpc,
- fz_colorspace* colorspace,
- int xres,
- int yres,
- int interpolate,
- int imagemask,
- const std::vector<float>& decode,
- const std::vector<int>& colorkey,
- fz_compressed_buffer* buffer,
- fz_image* mask
- );
- /** Swig-friendly wrapper for pdf_rearrange_pages(). */
- void pdf_rearrange_pages2(
- fz_context* ctx,
- pdf_document* doc,
- const std::vector<int>& pages,
- pdf_clean_options_structure structure
- );
- /** Swig-friendly wrapper for pdf_subset_fonts(). */
- void pdf_subset_fonts2(fz_context *ctx, pdf_document *doc, const std::vector<int>& pages);
- /** Swig-friendly and typesafe way to do fz_snprintf(fmt, value). `fmt`
- must end with one of 'efg' otherwise we throw an exception. */
- std::string fz_format_double(fz_context* ctx, const char* fmt, double value);
- struct fz_font_ucs_gid
- {{
- unsigned long ucs;
- unsigned int gid;
- }};
- /** SWIG-friendly wrapper for fz_enumerate_font_cmap(). */
- std::vector<fz_font_ucs_gid> fz_enumerate_font_cmap2(fz_context* ctx, fz_font* font);
- /** SWIG-friendly wrapper for pdf_set_annot_callout_line(). */
- void pdf_set_annot_callout_line2(fz_context *ctx, pdf_annot *annot, std::vector<fz_point>& callout);
- /** SWIG-friendly wrapper for fz_decode_barcode_from_display_list(),
- avoiding leak of the returned string. */
- std::string fz_decode_barcode_from_display_list2(fz_context *ctx, fz_barcode_type *type, fz_display_list *list, fz_rect subarea, int rotate);
- /** SWIG-friendly wrapper for fz_decode_barcode_from_pixmap(), avoiding
- leak of the returned string. */
- std::string fz_decode_barcode_from_pixmap2(fz_context *ctx, fz_barcode_type *type, fz_pixmap *pix, int rotate);
- /** SWIG-friendly wrapper for fz_decode_barcode_from_page(), avoiding
- leak of the returned string. */
- std::string fz_decode_barcode_from_page2(fz_context *ctx, fz_barcode_type *type, fz_page *page, fz_rect subarea, int rotate);
- ''')
- g_extra_definitions = textwrap.dedent(f'''
- FZ_FUNCTION std::string fz_lookup_metadata2( fz_context* ctx, fz_document* doc, const char* key)
- {{
- /* Find length first. */
- int e = fz_lookup_metadata(ctx, doc, key, NULL /*buf*/, 0 /*size*/);
- if (e < 0)
- {{
- fz_throw(ctx, FZ_ERROR_GENERIC, "key not found: %s", key);
- }}
- assert(e != 0);
- char* buf = (char*) fz_malloc(ctx, e);
- int e2 = fz_lookup_metadata(ctx, doc, key, buf, e);
- assert(e2 = e);
- std::string ret = buf;
- free(buf);
- return ret;
- }}
- FZ_FUNCTION std::string pdf_lookup_metadata2( fz_context* ctx, pdf_document* doc, const char* key)
- {{
- /* Find length first. */
- int e = pdf_lookup_metadata(ctx, doc, key, NULL /*buf*/, 0 /*size*/);
- if (e < 0)
- {{
- fz_throw(ctx, FZ_ERROR_GENERIC, "key not found: %s", key);
- }}
- assert(e != 0);
- char* buf = (char*) fz_malloc(ctx, e);
- int e2 = pdf_lookup_metadata(ctx, doc, key, buf, e);
- assert(e2 = e);
- std::string ret = buf;
- free(buf);
- return ret;
- }}
- FZ_FUNCTION std::vector<unsigned char> fz_md5_pixmap2(fz_context* ctx, fz_pixmap* pixmap)
- {{
- std::vector<unsigned char> ret(16);
- fz_md5_pixmap( ctx, pixmap, &ret[0]);
- return ret;
- }}
- FZ_FUNCTION long long fz_pixmap_samples_int(fz_context* ctx, fz_pixmap* pixmap)
- {{
- long long ret = (intptr_t) pixmap->samples;
- return ret;
- }}
- FZ_FUNCTION int fz_samples_get(fz_pixmap* pixmap, int offset)
- {{
- return pixmap->samples[offset];
- }}
- FZ_FUNCTION void fz_samples_set(fz_pixmap* pixmap, int offset, int value)
- {{
- pixmap->samples[offset] = value;
- }}
- FZ_FUNCTION std::vector<unsigned char> fz_md5_final2(fz_md5* md5)
- {{
- std::vector<unsigned char> ret(16);
- fz_md5_final( md5, &ret[0]);
- return ret;
- }}
- FZ_FUNCTION std::vector<fz_quad> fz_highlight_selection2(fz_context* ctx, fz_stext_page* page, fz_point a, fz_point b, int max_quads)
- {{
- {{
- std::vector<fz_quad> ret(max_quads);
- int n;
- fz_try(ctx)
- {{
- n = fz_highlight_selection(ctx, page, a, b, &ret[0], max_quads);
- }}
- fz_catch(ctx)
- {{
- n = -1;
- }}
- if (n >= 0)
- {{
- ret.resize(n);
- return ret;
- }}
- }}
- /* We are careful to only call `fz_throw()` after `ret`'s
- destructor has been called. */
- fz_throw(ctx, FZ_ERROR_GENERIC, "fz_highlight_selection() failed");
- }}
- FZ_FUNCTION std::vector<fz_search_page2_hit> fz_search_page2(fz_context* ctx, fz_document* doc, int number, const char* needle, int hit_max)
- {{
- std::vector<fz_quad> quads(hit_max);
- std::vector<int> marks(hit_max);
- int n = fz_search_page_number(ctx, doc, number, needle, &marks[0], &quads[0], hit_max);
- std::vector<fz_search_page2_hit> ret(n);
- for (int i=0; i<n; ++i)
- {{
- ret[i].quad = quads[i];
- ret[i].mark = marks[i];
- }}
- return ret;
- }}
- FZ_FUNCTION std::string fz_string_from_text_language2(fz_text_language lang)
- {{
- char str[8];
- fz_string_from_text_language(str, lang);
- return std::string(str);
- }}
- FZ_FUNCTION std::string fz_get_glyph_name2(fz_context* ctx, fz_font* font, int glyph)
- {{
- char name[32];
- fz_get_glyph_name(ctx, font, glyph, name, sizeof(name));
- return std::string(name);
- }}
- void fz_install_load_system_font_funcs2(fz_context* ctx, fz_install_load_system_font_funcs_args* args)
- {{
- fz_install_load_system_font_funcs(ctx, args->f, args->f_cjk, args->f_fallback);
- }}
- void* fz_install_load_system_font_funcs2_state = nullptr;
- FZ_FUNCTION fz_document* fz_document_handler_open(fz_context* ctx, const fz_document_handler *handler, fz_stream* stream, fz_stream* accel, fz_archive* dir, void* recognize_state)
- {{
- return handler->open(ctx, handler, stream, accel, dir, recognize_state);
- }}
- FZ_FUNCTION int fz_document_handler_recognize(fz_context* ctx, const fz_document_handler *handler, const char *magic)
- {{
- return handler->recognize(ctx, handler, magic);
- }}
- FZ_FUNCTION std::vector<std::string> pdf_choice_widget_options2(fz_context* ctx, pdf_annot* tw, int exportval)
- {{
- int n = pdf_choice_widget_options(ctx, tw, exportval, nullptr);
- std::vector<const char*> opts(n);
- int n2 = pdf_choice_widget_options(ctx, tw, exportval, &opts[0]);
- assert(n2 == n);
- std::vector<std::string> ret(n);
- for (int i=0; i<n; ++i)
- {{
- ret[i] = opts[i];
- }}
- return ret;
- }}
- FZ_FUNCTION fz_image* fz_new_image_from_compressed_buffer2(
- fz_context* ctx,
- int w,
- int h,
- int bpc,
- fz_colorspace* colorspace,
- int xres,
- int yres,
- int interpolate,
- int imagemask,
- const std::vector<float>& decode,
- const std::vector<int>& colorkey,
- fz_compressed_buffer* buffer,
- fz_image* mask
- )
- {{
- int n = fz_colorspace_n(ctx, colorspace);
- assert(decode.empty() || decode.size() == 2 * n);
- assert(colorkey.empty() || colorkey.size() == 2 * n);
- const float* decode2 = decode.empty() ? nullptr : &decode[0];
- const int* colorkey2 = colorkey.empty() ? nullptr : &colorkey[0];
- fz_image* ret = fz_new_image_from_compressed_buffer(
- ctx,
- w,
- h,
- bpc,
- colorspace,
- xres,
- yres,
- interpolate,
- imagemask,
- decode2,
- colorkey2,
- fz_keep_compressed_buffer(ctx, buffer),
- mask
- );
- return ret;
- }}
- void pdf_rearrange_pages2(
- fz_context* ctx,
- pdf_document* doc,
- const std::vector<int>& pages,
- pdf_clean_options_structure structure
- )
- {{
- return pdf_rearrange_pages(ctx, doc, pages.size(), &pages[0], structure);
- }}
- void pdf_subset_fonts2(fz_context *ctx, pdf_document *doc, const std::vector<int>& pages)
- {{
- return pdf_subset_fonts(ctx, doc, pages.size(), &pages[0]);
- }}
- static void s_format_check(fz_context* ctx, const char* fmt, const char* specifiers)
- {{
- int length = strlen(fmt);
- if (!length || !strchr(specifiers, fmt[length-1]))
- {{
- fz_throw(ctx, FZ_ERROR_ARGUMENT, "Incorrect fmt '%s' should end with one of '%s'.", fmt, specifiers);
- }}
- }}
- std::string fz_format_double(fz_context* ctx, const char* fmt, double value)
- {{
- char buffer[256];
- s_format_check(ctx, fmt, "efg");
- fz_snprintf(buffer, sizeof(buffer), fmt, value);
- return buffer;
- }}
- static void fz_enumerate_font_cmap2_cb(fz_context* ctx, void* opaque, unsigned long ucs, unsigned int gid)
- {{
- std::vector<fz_font_ucs_gid>& ret = *(std::vector<fz_font_ucs_gid>*) opaque;
- fz_font_ucs_gid item = {{ucs, gid}};
- ret.push_back(item);
- }}
- std::vector<fz_font_ucs_gid> fz_enumerate_font_cmap2(fz_context* ctx, fz_font* font)
- {{
- std::vector<fz_font_ucs_gid> ret;
- fz_enumerate_font_cmap(ctx, font, fz_enumerate_font_cmap2_cb, &ret);
- return ret;
- }}
- void pdf_set_annot_callout_line2(fz_context *ctx, pdf_annot *annot, std::vector<fz_point>& callout)
- {{
- pdf_set_annot_callout_line(ctx, annot, &callout[0], callout.size());
- }}
- std::string fz_decode_barcode_from_display_list2(fz_context *ctx, fz_barcode_type *type, fz_display_list *list, fz_rect subarea, int rotate)
- {{
- char* ret = fz_decode_barcode_from_display_list(ctx, type, list, subarea, rotate);
- std::string ret2 = ret;
- fz_free(ctx, ret);
- return ret2;
- }}
- std::string fz_decode_barcode_from_pixmap2(fz_context *ctx, fz_barcode_type *type, fz_pixmap *pix, int rotate)
- {{
- char* ret = fz_decode_barcode_from_pixmap(ctx, type, pix, rotate);
- std::string ret2 = ret;
- fz_free(ctx, ret);
- return ret2;
- }}
- std::string fz_decode_barcode_from_page2(fz_context *ctx, fz_barcode_type *type, fz_page *page, fz_rect subarea, int rotate)
- {{
- char* ret = fz_decode_barcode_from_page(ctx, type, page, subarea, rotate);
- std::string ret2 = ret;
- fz_free(ctx, ret);
- return ret2;
- }}
- ''')
- def make_extra( out_extra_h, out_extra_cpp):
- '''
- We write extra abstractions here.
- These are written in C++ but are at the same level of abstraction as MuPDF
- C functions, for example they take `fz_context` args. This is done so that
- we automatically generate wrappers as class methods as well as global
- functions.
- '''
- out_extra_h.write( g_extra_declarations)
- out_extra_cpp.write( textwrap.dedent('''
- #include "mupdf/extra.h"
- '''))
- out_extra_cpp.write( g_extra_definitions)
- def make_internal_functions( namespace, out_h, out_cpp, refcheck_if, trace_if):
- '''
- Writes internal support functions.
- out_h:
- Stream to which we write C++ header text.
- out_cpp:
- Stream to which we write C++ text.
- '''
- out_h.write(
- textwrap.dedent(
- f'''
- #define internal_assert(expression) (expression) ? (void) 0 : internal_assert_fail(__FILE__, __LINE__, __FUNCTION__, #expression)
- FZ_FUNCTION void internal_assert_fail(const char* file, int line, const char* fn, const char* expression);
- /** Internal use only. Looks at environmental variable <name>; returns 0 if unset else int value. */
- FZ_FUNCTION int {rename.internal('env_flag')}(const char* name);
- /** Internal use only. Looks at environmental variable <name>; returns 0 if unset else int value. */
- FZ_FUNCTION int {rename.internal('env_flag_check_unset')}( const char* if_, const char* name);
- /** Internal use only. Returns `fz_context*` for use by current thread. */
- FZ_FUNCTION fz_context* {rename.internal('context_get')}();
- '''
- ))
- out_cpp.write(
- textwrap.dedent(
- '''
- #include "mupdf/exceptions.h"
- #include "mupdf/internal.h"
- #include <iostream>
- #include <thread>
- #include <mutex>
- #include <string.h>
- '''))
- make_namespace_open( namespace, out_cpp)
- state_t = rename.internal( 'state')
- thread_state_t = rename.internal( 'thread_state')
- cpp_text = textwrap.dedent(
- f'''
- FZ_FUNCTION void internal_assert_fail(const char* file, int line, const char* fn, const char* expression)
- {{
- std::cerr << file << ":" << line << ":" << fn << "(): "
- << "MuPDF C++ internal assert failure: " << expression
- << "\\n" << std::flush;
- abort();
- }}
- FZ_FUNCTION int {rename.internal('env_flag')}(const char* name)
- {{
- const char* s = getenv( name);
- if (!s) return 0;
- return atoi( s);
- }}
- FZ_FUNCTION int {rename.internal('env_flag_check_unset')}(const char* if_, const char* name)
- {{
- const char* s = getenv( name);
- if (s) std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():"
- << " Warning: ignoring environmental variable because"
- << " '" << if_ << "' is false: " << name << "\\n";
- return false;
- }}
- {trace_if}
- static const int s_trace = mupdf::internal_env_flag("MUPDF_trace");
- #else
- static const int s_trace = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace");
- #endif
- static bool s_state_valid = false;
- struct {rename.internal("state")}
- {{
- /* Constructor. */
- {rename.internal("state")}()
- {{
- m_locks.user = this;
- m_locks.lock = lock;
- m_locks.unlock = unlock;
- m_ctx = nullptr;
- bool multithreaded = true;
- const char* s = getenv( "MUPDF_mt_ctx");
- if ( s && !strcmp( s, "0")) multithreaded = false;
- reinit( multithreaded);
- s_state_valid = true;
- }}
- void reinit( bool multithreaded)
- {{
- if (s_trace)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
- << " calling fz_drop_context()\\n";
- }}
- fz_drop_context( m_ctx);
- m_multithreaded = multithreaded;
- if (s_trace)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
- << " calling fz_new_context()\\n";
- }}
- m_ctx = fz_new_context(NULL /*alloc*/, (multithreaded) ? &m_locks : nullptr, FZ_STORE_DEFAULT);
- if (s_trace)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
- << "fz_new_context() => " << m_ctx << "\\n";
- }}
- if (s_trace)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
- << " calling fz_register_document_handlers()\\n";
- }}
- internal_assert("m_ctx = fz_new_context()" && m_ctx);
- fz_register_document_handlers(m_ctx);
- }}
- static void lock(void *user, int lock)
- {{
- {rename.internal("state")}* self = ({rename.internal("state")}*) user;
- internal_assert( self->m_multithreaded);
- self->m_mutexes[lock].lock();
- }}
- static void unlock(void *user, int lock)
- {{
- {rename.internal("state")}* self = ({rename.internal("state")}*) user;
- internal_assert( self->m_multithreaded);
- self->m_mutexes[lock].unlock();
- }}
- ~{rename.internal("state")}()
- {{
- if (s_trace)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
- << " calling fz_drop_context()\\n";
- }}
- fz_drop_context(m_ctx);
- m_ctx = nullptr;
- s_state_valid = false;
- }}
- bool m_multithreaded;
- fz_context* m_ctx;
- std::mutex m_mutex; /* Serialise access to m_ctx. fixme: not actually necessary. */
- /* Provide thread support to mupdf. */
- std::mutex m_mutexes[FZ_LOCK_MAX];
- fz_locks_context m_locks;
- }};
- static {rename.internal("state")} s_state;
- struct {rename.internal("thread_state")}
- {{
- {rename.internal("thread_state")}()
- :
- m_ctx( nullptr),
- m_constructed( true)
- {{}}
- fz_context* get_context()
- {{
- internal_assert( s_state.m_multithreaded);
- /* The following code checks that we are not being called after
- we have been destructed. This can happen if global mupdf
- wrapper class instances are defined - thread-local objects
- are destructed /before/ globals. */
- if (!m_constructed)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ":\\n"
- << "*** Error - undefined behaviour.\\n"
- << "***\\n"
- << "*** Attempt to get thread-local fz_context after destruction\\n"
- << "*** of thread-local fz_context support instance.\\n"
- << "***\\n"
- << "*** This is undefined behaviour.\\n"
- << "***\\n"
- << "*** This can happen if mupdf wrapper class instances are\\n"
- << "*** created as globals, because in C++ global object\\n"
- << "*** destructors are run after thread_local destructors.\\n"
- << "***\\n"
- ;
- }}
- internal_assert( m_constructed);
- if (!m_ctx)
- {{
- /* Make a context for this thread by cloning the global
- context. */
- /* fixme: we don't actually need to take a lock here. */
- std::lock_guard<std::mutex> lock( s_state.m_mutex);
- if (s_trace)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
- << " calling fz_clone_context()\\n";
- }}
- internal_assert(s_state_valid);
- m_ctx = fz_clone_context(s_state.m_ctx);
- if (s_trace)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
- << "fz_clone_context(" << s_state.m_ctx << ") => " << m_ctx << "\\n";
- }}
- internal_assert("m_ctx = fz_clone_context()" && m_ctx);
- }}
- return m_ctx;
- }}
- ~{rename.internal("thread_state")}()
- {{
- if (m_ctx)
- {{
- internal_assert( s_state.m_multithreaded);
- if (s_trace)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): "
- << " calling fz_drop_context()\\n";
- }}
- fz_drop_context( m_ctx);
- }}
- /* These two statements are an attempt to get useful
- diagnostics in cases of undefined behaviour caused by the
- use of global wrapper class instances, whose destructors
- will be called /after/ destruction of this thread-local
- internal_thread_state instance. See check of m_constructed in
- get_context().
- This probably only works in non-optimised builds -
- optimisation will simply elide both these statements. */
- m_ctx = nullptr;
- m_constructed = false;
- }}
- fz_context* m_ctx;
- bool m_constructed;
- }};
- static thread_local {rename.internal("thread_state")} s_thread_state;
- FZ_FUNCTION fz_context* {rename.internal("context_get")}()
- {{
- if (s_state.m_multithreaded)
- {{
- return s_thread_state.get_context();
- }}
- else
- {{
- /* This gives a small improvement in performance for
- single-threaded use, e.g. from 552.4s to 548.1s. */
- internal_assert(s_state_valid);
- fz_context* ret = s_state.m_ctx;
- internal_assert(ret);
- return ret;
- }}
- }}
- FZ_FUNCTION void reinit_singlethreaded()
- {{
- if (0)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): Reinitialising as single-threaded.\\n";
- }}
- s_state.reinit( false /*multithreaded*/);
- }}
- ''')
- out_cpp.write( cpp_text)
- make_namespace_close( namespace, out_cpp)
- # Generate code that exposes C++ operator new/delete to Memento.
- #
- # Disabled because our generated code makes very few direct calls
- # to operator new, and Memento ends up catching lots of (presumably
- # false-positive) leaks in the Python interpreter, so isn't very useful.
- #
- if 0:
- out_cpp.write( textwrap.dedent(
- '''
- #ifdef MEMENTO
- void* operator new( size_t size)
- {
- return Memento_cpp_new( size);
- }
- void operator delete( void* pointer)
- {
- Memento_cpp_delete( pointer);
- }
- void* operator new[]( size_t size)
- {
- return Memento_cpp_new_array( size);
- }
- void operator delete[]( void* pointer)
- {
- Memento_cpp_delete_array( pointer);
- }
- #endif
- '''
- ))
- def make_function_wrappers(
- tu,
- namespace,
- out_exceptions_h,
- out_exceptions_cpp,
- out_functions_h,
- out_functions_cpp,
- out_internal_h,
- out_internal_cpp,
- out_functions_h2,
- out_functions_cpp2,
- generated,
- refcheck_if,
- trace_if,
- ):
- '''
- Generates C++ source code containing wrappers for all fz_*() functions.
- We also create a function throw_exception(fz_context* ctx) that throws a
- C++ exception appropriate for the error in ctx.
- If a function has first arg fz_context*, extra code is generated that
- converts fz_try..fz_catch exceptions into C++ exceptions by calling
- throw_exception().
- We remove any fz_context* argument and the implementation calls
- internal_get_context() to get a suitable thread-specific fz_context* to
- use.
- We generate a class for each exception type.
- Returned source is just the raw functions text, e.g. it does not contain
- required #include's.
- Args:
- tu:
- Clang translation unit.
- out_exceptions_h:
- Stream to which we write exception class definitions.
- out_exceptions_cpp:
- Stream to which we write exception class implementation.
- out_functions_h:
- Stream to which we write function declarations.
- out_functions_cpp:
- Stream to which we write function definitions.
- generated:
- A Generated instance.
- '''
- # Look for FZ_ERROR_* enums. We generate an exception class for each of
- # these.
- #
- error_name_prefix = 'FZ_ERROR_'
- fz_error_names = []
- fz_error_names_maxlen = 0 # Used for padding so generated code aligns.
- for cursor in parse.get_children(tu.cursor):
- if cursor.kind == state.clang.cindex.CursorKind.ENUM_DECL:
- #log( 'enum: {cursor.spelling=})
- for child in parse.get_members( cursor):
- #log( 'child:{ child.spelling=})
- if child.spelling.startswith( error_name_prefix):
- name = child.spelling[ len(error_name_prefix):]
- fz_error_names.append( name)
- if len( name) > fz_error_names_maxlen:
- fz_error_names_maxlen = len( name)
- def errors(include_error_base=False):
- '''
- Yields (enum, typename, padding) for each error.
- E.g.:
- enum=FZ_ERROR_SYSTEM
- typename=mupdf_error_memory
- padding=' '
- '''
- names = fz_error_names
- if include_error_base:
- names = ['BASE'] + names
- for name in names:
- enum = f'{error_name_prefix}{name}'
- typename = rename.error_class( enum)
- padding = (fz_error_names_maxlen - len(name)) * ' '
- yield enum, typename, padding
- # Declare base exception class and define its methods.
- #
- base_name = rename.error_class('FZ_ERROR_BASE')
- out_exceptions_h.write( textwrap.dedent(
- f'''
- /** Base class for exceptions. */
- struct {base_name} : std::exception
- {{
- int m_code;
- std::string m_text;
- mutable std::string m_what;
- FZ_FUNCTION const char* what() const throw();
- FZ_FUNCTION {base_name}(int code, const char* text);
- }};
- '''))
- out_exceptions_cpp.write( textwrap.dedent(
- f'''
- FZ_FUNCTION {base_name}::{base_name}(int code, const char* text)
- :
- m_code(code),
- m_text(text)
- {{
- {trace_if}
- if (s_trace_exceptions)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): {base_name}: " << m_text << "\\n";
- }}
- #endif
- }};
- FZ_FUNCTION const char* {base_name}::what() const throw()
- {{
- m_what = "code=" + std::to_string(m_code) + ": " + m_text;
- return m_what.c_str();
- }};
- '''))
- # Generate SWIG Python code to allow conversion of our error class
- # exceptions into equivalent Python exceptions.
- error_classes_n = 0
- for enum, typename, padding in errors():
- error_classes_n += 1
- error_classes_n += 1 # Extra space for FzErrorBase.
- generated.swig_python_exceptions.write( textwrap.dedent( f'''
- void internal_set_error_classes(PyObject* classes);
- %{{
- /* A Python list of Error classes, [FzErrorNone, FzErrorMemory, FzErrorGeneric, ...]. */
- static PyObject* s_error_classes[{error_classes_n}] = {{}};
- /* Called on startup by mupdf.py, with a list of error classes
- to be copied into s_error_classes. This will allow us to create
- instances of these error classes in SWIG's `%exception ...`, so
- Python code will see exceptions as instances of Python error
- classes. */
- void internal_set_error_classes(PyObject* classes)
- {{
- assert(PyList_Check(classes));
- int n = PyList_Size(classes);
- assert(n == {error_classes_n});
- for (int i=0; i<n; ++i)
- {{
- PyObject* class_ = PyList_GetItem(classes, i);
- s_error_classes[i] = class_;
- }}
- }}
- /* Sets Python exception to a new mupdf.<name> object constructed
- with `text`. */
- void set_exception(PyObject* class_, int code, const std::string& text)
- {{
- PyObject* args = Py_BuildValue("(s)", text.c_str());
- PyObject* instance = PyObject_CallObject(class_, args);
- PyErr_SetObject(class_, instance);
- Py_XDECREF(instance);
- Py_XDECREF(args);
- }}
- /* Exception handler for swig-generated code. Uses internal
- `throw;` to recover the current C++ exception then uses
- `set_exception()` to set the current Python exception. Caller
- should do `SWIG_fail;` after we return. */
- void handle_exception()
- {{
- try
- {{
- throw;
- }}
- '''
- ))
- # Declare exception class for each FZ_ERROR_*. Also append catch blocks for
- # each of these exception classes to `handle_exception()`.
- #
- for i, (enum, typename, padding) in enumerate(errors()):
- out_exceptions_h.write( textwrap.dedent(
- f'''
- /** For `{enum}`. */
- struct {typename} : {base_name}
- {{
- FZ_FUNCTION {typename}(const char* message);
- }};
- '''))
- generated.swig_python_exceptions.write( textwrap.dedent( f'''
- /**/
- catch (mupdf::{typename}& e)
- {{
- if (g_mupdf_trace_exceptions)
- {{
- std::cerr
- << __FILE__ << ':' << __LINE__ << ':'
- #ifndef _WIN32
- << __PRETTY_FUNCTION__ << ':'
- #endif
- << " Converting C++ std::exception mupdf::{typename} ({i=}) into Python exception:\\n"
- << " e.m_code: " << e.m_code << "\\n"
- << " e.m_text: " << e.m_text << "\\n"
- << " e.what(): " << e.what() << "\\n"
- << " typeid(e).name(): " << typeid(e).name() << "\\n"
- << "\\n";
- }}
- set_exception(s_error_classes[{i}], e.m_code, e.m_text);
- }}'''))
- # Append less specific exception handling.
- generated.swig_python_exceptions.write( textwrap.dedent( f'''
- catch (mupdf::FzErrorBase& e)
- {{
- if (g_mupdf_trace_exceptions)
- {{
- std::cerr
- << __FILE__ << ':' << __LINE__ << ':'
- #ifndef _WIN32
- << __PRETTY_FUNCTION__ << ':'
- #endif
- << " Converting C++ std::exception mupdf::FzErrorBase ({error_classes_n-1=}) into Python exception:\\n"
- << " e.m_code: " << e.m_code << "\\n"
- << " e.m_text: " << e.m_text << "\\n"
- << " e.what(): " << e.what() << "\\n"
- << " typeid(e).name(): " << typeid(e).name() << "\\n"
- << "\\n";
- }}
- PyObject* class_ = s_error_classes[{error_classes_n-1}];
- PyObject* args = Py_BuildValue("is", e.m_code, e.m_text.c_str());
- PyObject* instance = PyObject_CallObject(class_, args);
- PyErr_SetObject(class_, instance);
- Py_XDECREF(instance);
- Py_XDECREF(args);
- }}
- catch (std::exception& e)
- {{
- if (g_mupdf_trace_exceptions)
- {{
- std::cerr
- << __FILE__ << ':' << __LINE__ << ':'
- #ifndef _WIN32
- << __PRETTY_FUNCTION__ << ':'
- #endif
- << " Converting C++ std::exception into Python exception: "
- << e.what()
- << " typeid(e).name(): " << typeid(e).name() << "\\n"
- << "\\n";
- }}
- SWIG_Error(SWIG_RuntimeError, e.what());
- }}
- catch (...)
- {{
- if (g_mupdf_trace_exceptions)
- {{
- std::cerr
- << __FILE__ << ':' << __LINE__ << ':'
- #ifndef _WIN32
- << __PRETTY_FUNCTION__ << ':'
- #endif
- << " Converting unknown C++ exception into Python exception."
- << "\\n";
- }}
- SWIG_Error(SWIG_RuntimeError, "Unknown exception");
- }}
- }}
- %}}
- %exception
- {{
- try
- {{
- $action
- }}
- catch (...)
- {{
- handle_exception();
- SWIG_fail;
- }}
- }}
- '''))
- generated.swig_python_set_error_classes.write( f'# Define __str()__ for each error/exception class, to use self.what().\n')
- for enum, typename, padding in errors(include_error_base=1):
- generated.swig_python_set_error_classes.write( f'{typename}.__str__ = lambda self: self.what()\n')
- generated.swig_python_set_error_classes.write( textwrap.dedent( f'''
- # This must be after the declaration of mupdf::FzError*
- # classes in mupdf/exceptions.h and declaration of
- # `internal_set_error_classes()`, otherwise generated code is
- # before the declaration of the Python class or similar. */
- internal_set_error_classes([
- '''))
- for enum, typename, padding in errors():
- generated.swig_python_set_error_classes.write(f' {typename},\n')
- generated.swig_python_set_error_classes.write( textwrap.dedent( f'''
- FzErrorBase,
- ])
- '''))
- # Define constructor for each exception class.
- #
- for enum, typename, padding in errors():
- out_exceptions_cpp.write( textwrap.dedent(
- f'''
- FZ_FUNCTION {typename}::{typename}(const char* text)
- : {base_name}({enum}, text)
- {{
- {trace_if}
- if (s_trace_exceptions)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): {typename} constructor, text: " << m_text << "\\n";
- }}
- #endif
- }}
- '''))
- # Generate function that throws an appropriate exception from a fz_context.
- #
- throw_exception = rename.internal( 'throw_exception')
- out_exceptions_h.write( textwrap.dedent(
- f'''
- /** Throw exception appropriate for error in `ctx`. */
- FZ_FUNCTION void {throw_exception}(fz_context* ctx);
- '''))
- out_exceptions_cpp.write( textwrap.dedent(
- f'''
- FZ_FUNCTION void {throw_exception}(fz_context* ctx)
- {{
- int code;
- const char* text = fz_convert_error(ctx, &code);
- {trace_if}
- if (s_trace_exceptions)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): code=" << code << "\\n";
- std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): text=" << text << "\\n";
- }}
- #endif
- '''))
- for enum, typename, padding in errors():
- out_exceptions_cpp.write( f' if (code == {enum}) {padding}throw {typename}{padding}(text);\n')
- out_exceptions_cpp.write( f' throw {base_name}(code, text);\n')
- out_exceptions_cpp.write( f'}}\n')
- out_exceptions_cpp.write( '\n')
- make_internal_functions( namespace, out_internal_h, out_internal_cpp, refcheck_if, trace_if)
- # Generate wrappers for each function that we find.
- #
- functions = []
- for fnname, cursor in state.state_.find_functions_starting_with( tu, ('fz_', 'pdf_'), method=False):
- assert fnname not in state.omit_fns
- #jlib.log( '{fnname=} {cursor.spelling=} {cursor.type.spelling=}')
- if ( cursor.type == state.clang.cindex.TypeKind.FUNCTIONPROTO
- and cursor.type.is_function_variadic()
- ):
- # We don't attempt to wrap variadic functions - would need to find
- # the equivalent function that takes a va_list.
- if 0:
- jlib.log( 'Variadic fn: {cursor.type.spelling=}')
- if fnname != 'fz_warn':
- continue
- if fnname == 'fz_push_try':
- # This is partof implementation of fz_try/catch so doesn't make
- # sense to provide a wrapper. Also it is OS-dependent so including
- # it makes our generated code OS-specific.
- continue
- functions.append( (fnname, cursor))
- jlib.log1( '{len(functions)=}')
- # Sort by function-name to make output easier to read.
- functions.sort()
- for fnname, cursor in functions:
- if state.state_.show_details( fnname):
- jlib.log( 'Looking at {fnname}')
- fnname_wrapper = rename.ll_fn( fnname)
- function_wrapper(
- tu,
- cursor,
- fnname,
- fnname_wrapper,
- out_functions_h,
- out_functions_cpp,
- generated,
- refcheck_if,
- trace_if,
- )
- if not fnname.startswith( ( 'fz_keep_', 'fz_drop_', 'pdf_keep_', 'pdf_drop_')):
- function_wrapper_class_aware(
- tu,
- register_fn_use=None,
- struct_name=None,
- class_name=None,
- fn_cursor=cursor,
- refcheck_if=refcheck_if,
- trace_if=trace_if,
- fnname=fnname,
- out_h=out_functions_h2,
- out_cpp=out_functions_cpp2,
- generated=generated,
- )
- python.cppyy_add_outparams_wrapper( tu, fnname, cursor, state.state_, generated)
- if fnname == "pdf_load_field_name": #(fz_context *ctx, pdf_obj *field);
- # Output wrapper that returns std::string instead of buffer that
- # caller needs to free.
- out_functions_h.write(
- textwrap.dedent(
- f'''
- /** Alternative to `{rename.ll_fn('pdf_load_field_name')}()` that returns a std::string. */
- FZ_FUNCTION std::string {rename.ll_fn('pdf_load_field_name2')}(pdf_obj* field);
- '''))
- out_functions_cpp.write(
- textwrap.dedent(
- f'''
- FZ_FUNCTION std::string {rename.ll_fn('pdf_load_field_name2')}(pdf_obj* field)
- {{
- char* buffer = {rename.ll_fn('pdf_load_field_name')}( field);
- std::string ret( buffer);
- {rename.ll_fn('fz_free')}( buffer);
- return ret;
- }}
- '''))
- out_functions_h2.write(
- textwrap.indent(
- textwrap.dedent(
- f'''
- /** Alternative to `{rename.fn('pdf_load_field_name')}()` that returns a std::string. */
- FZ_FUNCTION std::string {rename.fn('pdf_load_field_name2')}({rename.class_('pdf_obj')}& field);
- '''),
- ' ',
- )
- )
- out_functions_cpp2.write(
- textwrap.dedent(
- f'''
- FZ_FUNCTION std::string {rename.fn('pdf_load_field_name2')}({rename.class_('pdf_obj')}& field)
- {{
- return {rename.ll_fn('pdf_load_field_name2')}( field.m_internal);
- }}
- '''))
- # Output custom wrappers for variadic pdf_dict_getl().
- #
- decl = f'''FZ_FUNCTION pdf_obj* {rename.ll_fn('pdf_dict_getlv')}( pdf_obj* dict, va_list keys)'''
- out_functions_h.write( textwrap.dedent( f'''
- /* Low-level wrapper for `pdf_dict_getl()`. `keys` must be null-terminated list of `pdf_obj*`'s. */
- {decl};
- '''))
- out_functions_cpp.write( textwrap.dedent( f'''
- {decl}
- {{
- pdf_obj *key;
- while (dict != NULL && (key = va_arg(keys, pdf_obj *)) != NULL)
- {{
- dict = {rename.ll_fn('pdf_dict_get')}( dict, key);
- }}
- return dict;
- }}
- '''))
- decl = f'''FZ_FUNCTION pdf_obj* {rename.ll_fn('pdf_dict_getl')}( pdf_obj* dict, ...)'''
- out_functions_h.write( textwrap.dedent( f'''
- /* Low-level wrapper for `pdf_dict_getl()`. `...` must be null-terminated list of `pdf_obj*`'s. */
- {decl};
- '''))
- out_functions_cpp.write( textwrap.dedent( f'''
- {decl}
- {{
- va_list keys;
- va_start(keys, dict);
- try
- {{
- dict = {rename.ll_fn('pdf_dict_getlv')}( dict, keys);
- }}
- catch( std::exception&)
- {{
- va_end(keys);
- throw;
- }}
- va_end(keys);
- return dict;
- }}
- '''))
- decl = f'''FZ_FUNCTION {rename.class_('pdf_obj')} {rename.fn('pdf_dict_getlv')}( {rename.class_('pdf_obj')}& dict, va_list keys)'''
- out_functions_h2.write(
- textwrap.indent(
- textwrap.dedent( f'''
- /* Class-aware wrapper for `pdf_dict_getl()`. `keys` must be null-terminated list of
- `pdf_obj*`'s, not `{rename.class_('pdf_obj')}*`'s, so that conventional
- use with `PDF_NAME()` works. */
- {decl};
- '''),
- ' ',
- )
- )
- out_functions_cpp2.write( textwrap.dedent( f'''
- {decl}
- {{
- pdf_obj* ret = {rename.ll_fn('pdf_dict_getlv')}( dict.m_internal, keys);
- return {rename.class_('pdf_obj')}( {rename.ll_fn('pdf_keep_obj')}( ret));
- }}
- '''))
- decl = f'''FZ_FUNCTION {rename.class_('pdf_obj')} {rename.fn('pdf_dict_getl')}( {rename.class_('pdf_obj')}* dict, ...)'''
- out_functions_h2.write(
- textwrap.indent(
- textwrap.dedent( f'''
- /* Class-aware wrapper for `pdf_dict_getl()`. `...` must be null-terminated list of
- `pdf_obj*`'s, not `{rename.class_('pdf_obj')}*`'s, so that conventional
- use with `PDF_NAME()` works. [We use pointer `dict` arg because variadic
- args do not with with reference args.] */
- {decl};
- '''),
- ' ',
- ),
- )
- out_functions_cpp2.write( textwrap.dedent( f'''
- {decl}
- {{
- va_list keys;
- va_start(keys, dict);
- try
- {{
- {rename.class_('pdf_obj')} ret = {rename.fn('pdf_dict_getlv')}( *dict, keys);
- va_end( keys);
- return ret;
- }}
- catch (std::exception&)
- {{
- va_end( keys);
- throw;
- }}
- }}
- '''))
- def class_add_iterator( tu, struct_cursor, struct_name, classname, extras, refcheck_if, trace_if):
- '''
- Add begin() and end() methods so that this generated class is iterable
- from C++ with:
- for (auto i: foo) {...}
- We modify <extras> to create an iterator class and add begin() and end()
- methods that each return an instance of the iterator class.
- '''
- it_begin, it_end = extras.iterator_next
- # Figure out type of what the iterator returns by looking at type of
- # <it_begin>.
- if it_begin:
- c = parse.find_name( struct_cursor, it_begin)
- assert c.type.kind == state.clang.cindex.TypeKind.POINTER
- it_internal_type = state.get_name_canonical( c.type.get_pointee()).spelling
- it_internal_type = util.clip( it_internal_type, 'struct ')
- it_type = rename.class_( it_internal_type)
- else:
- # The container is also the first item in the linked list.
- it_internal_type = struct_name
- it_type = classname
- # We add to extras.methods_extra().
- #
- check_refs = 1 if parse.has_refs( tu, struct_cursor.type) else 0
- extras.methods_extra.append(
- classes.ExtraMethod( f'{classname}Iterator', 'begin()',
- f'''
- {{
- auto ret = {classname}Iterator({'m_internal->'+it_begin if it_begin else '*this'});
- {refcheck_if}
- #if {check_refs}
- if (s_check_refs)
- {{
- s_{classname}_refs_check.check( this, __FILE__, __LINE__, __FUNCTION__);
- }}
- #endif
- #endif
- return ret;
- }}
- ''',
- f'/* Used for iteration over linked list of {it_type} items starting at {it_internal_type}::{it_begin}. */',
- ),
- )
- extras.methods_extra.append(
- classes.ExtraMethod( f'{classname}Iterator', 'end()',
- f'''
- {{
- auto ret = {classname}Iterator({it_type}());
- {refcheck_if}
- #if {check_refs}
- if (s_check_refs)
- {{
- s_{classname}_refs_check.check( this, __FILE__, __LINE__, __FUNCTION__);
- }}
- #endif
- #endif
- return ret;
- }}
- ''',
- f'/* Used for iteration over linked list of {it_type} items starting at {it_internal_type}::{it_begin}. */',
- ),
- )
- extras.class_bottom += f'\n typedef {classname}Iterator iterator;\n'
- extras.class_pre += f'\nstruct {classname}Iterator;\n'
- extras.class_post += f'''
- struct {classname}Iterator
- {{
- FZ_FUNCTION {classname}Iterator(const {it_type}& item);
- FZ_FUNCTION {classname}Iterator& operator++();
- FZ_FUNCTION bool operator==( const {classname}Iterator& rhs);
- FZ_FUNCTION bool operator!=( const {classname}Iterator& rhs);
- FZ_FUNCTION {it_type} operator*();
- FZ_FUNCTION {it_type}* operator->();
- private:
- {it_type} m_item;
- }};
- '''
- keep_text = ''
- if extras.copyable and extras.copyable != 'default':
- # Our operator++ needs to create it_type from m_item.m_internal->next,
- # so we need to call fz_keep_<it_type>().
- #
- # [Perhaps life would be simpler if our generated constructors always
- # called fz_keep_*() as necessary? In some circumstances this would
- # require us to call fz_drop_*() when constructing an instance, but
- # that might be simpler?]
- #
- base_name = util.clip( struct_name, ('fz_', 'pdf_'))
- if struct_name.startswith( 'fz_'):
- keep_name = f'fz_keep_{base_name}'
- elif struct_name.startswith( 'pdf_'):
- keep_name = f'pdf_keep_{base_name}'
- keep_name = rename.ll_fn(keep_name)
- keep_text = f'{keep_name}(m_item.m_internal->next);'
- extras.extra_cpp += f'''
- FZ_FUNCTION {classname}Iterator::{classname}Iterator(const {it_type}& item)
- : m_item( item)
- {{
- }}
- FZ_FUNCTION {classname}Iterator& {classname}Iterator::operator++()
- {{
- {keep_text}
- m_item = {it_type}(m_item.m_internal->next);
- return *this;
- }}
- FZ_FUNCTION bool {classname}Iterator::operator==( const {classname}Iterator& rhs)
- {{
- return m_item.m_internal == rhs.m_item.m_internal;
- }}
- FZ_FUNCTION bool {classname}Iterator::operator!=( const {classname}Iterator& rhs)
- {{
- return m_item.m_internal != rhs.m_item.m_internal;
- }}
- FZ_FUNCTION {it_type} {classname}Iterator::operator*()
- {{
- return m_item;
- }}
- FZ_FUNCTION {it_type}* {classname}Iterator::operator->()
- {{
- return &m_item;
- }}
- '''
- def class_find_constructor_fns( tu, classname, struct_name, base_name, extras):
- '''
- Returns list of functions that could be used as constructors of the
- specified wrapper class.
- For example we look for functions that return a pointer to <struct_name> or
- return a POD <struct_name> by value.
- tu:
- .
- classname:
- Name of our wrapper class.
- struct_name:
- Name of underlying mupdf struct.
- base_name:
- Name of struct without 'fz_' prefix.
- extras:
- .
- '''
- assert struct_name == f'fz_{base_name}' or struct_name == f'pdf_{base_name}'
- verbose = state.state_.show_details( struct_name)
- constructor_fns = []
- if '-' not in extras.constructor_prefixes:
- # Add default constructor fn prefix.
- if struct_name.startswith( 'fz_'):
- extras.constructor_prefixes.insert( 0, f'fz_new_')
- extras.constructor_prefixes.insert( 0, f'pdf_new_')
- elif struct_name.startswith( 'pdf_'):
- extras.constructor_prefixes.insert( 0, f'pdf_new_')
- for fnprefix in extras.constructor_prefixes:
- if verbose:
- jlib.log('{struct_name=} {fnprefix=}')
- for fnname, cursor in state.state_.find_functions_starting_with( tu, fnprefix, method=True):
- # Check whether this has identical signature to any fn we've
- # already found.
- if verbose:
- jlib.log( '{struct_name=} {fnname=}')
- duplicate_type = None
- duplicate_name = False
- for f, c, is_duplicate in constructor_fns:
- if verbose:
- jlib.log( '{struct_name=} {cursor.spelling=} {c.type.spelling=}')
- if f == fnname:
- if verbose:
- jlib.log('setting duplicate_name to true')
- duplicate_name = True
- break
- if c.type == cursor.type:
- if verbose:
- jlib.log( '{struct_name} wrapper: ignoring candidate constructor {fnname}() because prototype is indistinguishable from {f=}()')
- duplicate_type = f
- break
- if duplicate_name:
- continue
- ok = False
- arg, n = parse.get_first_arg( tu, cursor)
- if arg and n == 1 and parse.is_pointer_to( arg.cursor.type, struct_name):
- # This avoids generation of bogus copy constructor wrapping
- # function fz_new_pixmap_from_alpha_channel() introduced
- # 2021-05-07.
- #
- if verbose:
- jlib.log('ignoring possible constructor because looks like copy constructor: {fnname}')
- elif fnname in extras.constructor_excludes:
- if verbose:
- jlib.log('{fnname=} is in {extras.constructor_excludes=}')
- elif extras.pod and extras.pod != 'none' and state.get_name_canonical( cursor.result_type).spelling == f'{struct_name}':
- # Returns POD struct by value.
- ok = True
- elif not extras.pod and parse.is_pointer_to( cursor.result_type, f'{struct_name}'):
- # Returns pointer to struct.
- ok = True
- if ok:
- if duplicate_type and extras.copyable:
- if verbose:
- jlib.log1( 'adding static method wrapper for {fnname}')
- extras.method_wrappers_static.append( fnname)
- else:
- if duplicate_type:
- if verbose:
- jlib.log( 'not able to provide static factory fn {struct_name}::{fnname} because wrapper class is not copyable.')
- if verbose:
- jlib.log( 'adding constructor wrapper for {fnname}')
- constructor_fns.append( (fnname, cursor, duplicate_type))
- else:
- if verbose:
- jlib.log( 'ignoring possible constructor for {classname=} because does not return required type: {fnname=} -> {cursor.result_type.spelling=}')
- constructor_fns.sort()
- return constructor_fns
- def class_find_destructor_fns( tu, struct_name, base_name):
- '''
- Returns list of functions that could be used by destructor - must be called
- 'fz_drop_<typename>', must take a <struct>* arg, may take a fz_context*
- arg.
- '''
- if struct_name.startswith( 'fz_'):
- destructor_prefix = f'fz_drop_{base_name}'
- elif struct_name.startswith( 'pdf_'):
- destructor_prefix = f'pdf_drop_{base_name}'
- destructor_fns = []
- for fnname, cursor in state.state_.find_functions_starting_with( tu, destructor_prefix, method=True):
- arg_struct = False
- arg_context = False
- args_num = 0
- for arg in parse.get_args( tu, cursor):
- if not arg_struct and parse.is_pointer_to( arg.cursor.type, struct_name):
- arg_struct = True
- elif not arg_context and parse.is_pointer_to( arg.cursor.type, 'fz_context'):
- arg_context = True
- args_num += 1
- if arg_struct:
- if args_num == 1 or (args_num == 2 and arg_context):
- # No params other than <struct>* and fz_context* so this is
- # candidate destructor.
- #log( 'adding candidate destructor: {fnname}')
- destructor_fns.append( (fnname, cursor))
- destructor_fns.sort()
- return destructor_fns
- def num_instances(refcheck_if, delta, name):
- '''
- Returns C++ code to embed in a wrapper class constructor/destructor function
- to update the class static `s_num_instances` variable.
- '''
- ret = ''
- ret += f' {refcheck_if}\n'
- if delta == +1:
- ret += ' ++s_num_instances;\n'
- elif delta == -1:
- ret += ' --s_num_instances;\n'
- else:
- assert 0
- ret += ' #endif\n'
- return ret
- def class_constructor_default(
- tu,
- struct_cursor,
- classname,
- extras,
- out_h,
- out_cpp,
- refcheck_if,
- trace_if,
- ):
- '''
- Generates constructor that sets each member to default value.
- '''
- if extras.pod:
- comment = f'Default constructor, sets each member to default value.'
- else:
- comment = f'Default constructor, sets `m_internal` to null.'
- out_h.write( '\n')
- out_h.write( f' /** {comment} */\n')
- out_h.write( f' FZ_FUNCTION {classname}();\n')
- out_cpp.write( f'/** {comment} */\n')
- out_cpp.write( f'FZ_FUNCTION {classname}::{classname}()\n')
- if not extras.pod:
- out_cpp.write( f': m_internal(nullptr)\n')
- out_cpp.write( f'{{\n')
- if extras.pod == 'none':
- pass
- elif extras.pod:
- for c in parse.get_members(struct_cursor):
- if extras.pod == 'inline':
- c_name = f'this->{c.spelling}'
- else:
- c_name = f'this->m_internal.{c.spelling}'
- if c.type.kind == state.clang.cindex.TypeKind.CONSTANTARRAY:
- out_cpp.write( f' memset(&{c_name}, 0, sizeof({c_name}));\n')
- else:
- out_cpp.write( f' {c_name} = {{}};\n')
- else:
- if parse.has_refs( tu, struct_cursor.type):
- out_cpp.write(f' {refcheck_if}\n')
- out_cpp.write( ' if (s_check_refs)\n')
- out_cpp.write( ' {\n')
- out_cpp.write(f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
- out_cpp.write( ' }\n')
- out_cpp.write( ' #endif\n')
- out_cpp.write(num_instances(refcheck_if, +1, classname))
- out_cpp.write( f'}};\n')
- def class_copy_constructor(
- tu,
- functions,
- struct_name,
- struct_cursor,
- base_name,
- classname,
- constructor_fns,
- out_h,
- out_cpp,
- refcheck_if,
- trace_if,
- ):
- '''
- Generate a copy constructor and operator= by finding a suitable fz_keep_*()
- function.
- We raise an exception if we can't find one.
- '''
- if struct_name.startswith( 'fz_'):
- keep_name = f'fz_keep_{base_name}'
- drop_name = f'fz_drop_{base_name}'
- elif struct_name.startswith( 'pdf_'):
- keep_name = f'pdf_keep_{base_name}'
- drop_name = f'pdf_drop_{base_name}'
- for name in keep_name, drop_name:
- cursor = state.state_.find_function( tu, name, method=True)
- if not cursor:
- classextra = classes.classextras.get( tu, struct_name)
- if classextra.copyable:
- if 1 or state.state_.show_details( struct_name):
- jlib.log( 'changing to non-copyable because no function {name}(): {struct_name}')
- classextra.copyable = False
- return
- if name == keep_name:
- pvoid = parse.is_pointer_to( cursor.result_type, 'void')
- assert ( pvoid
- or parse.is_pointer_to( cursor.result_type, struct_name)
- ), (
- f'Function {name}(): result_type not void* or pointer to {struct_name}: {cursor.result_type.spelling}'
- )
- arg, n = parse.get_first_arg( tu, cursor)
- assert n == 1, f'should take exactly one arg: {cursor.spelling}()'
- assert parse.is_pointer_to( arg.cursor.type, struct_name), (
- f'arg0 is not pointer to {struct_name}: {cursor.spelling}(): {arg.cursor.spelling} {arg.name}')
- for fnname, cursor, duplicate_type in constructor_fns:
- fnname2 = rename.ll_fn(fnname)
- if fnname2 == keep_name:
- jlib.log( 'not generating copy constructor with {keep_name=} because already used by a constructor.')
- break
- else:
- functions( keep_name)
- comment = f'Copy constructor using `{keep_name}()`.'
- out_h.write( '\n')
- out_h.write( f' /** {comment} */\n')
- out_h.write( f' FZ_FUNCTION {classname}(const {classname}& rhs);\n')
- out_h.write( '\n')
- cast = ''
- if pvoid:
- # Need to cast the void* to the correct type.
- cast = f'(::{struct_name}*) '
- out_cpp.write( f'/** {comment} */\n')
- out_cpp.write( f'FZ_FUNCTION {classname}::{classname}(const {classname}& rhs)\n')
- out_cpp.write( f': m_internal({cast}{rename.ll_fn(keep_name)}(rhs.m_internal))\n')
- out_cpp.write( '{\n')
- # Write trace code.
- out_cpp.write( f' {trace_if}\n')
- out_cpp.write( f' if (s_trace_keepdrop) {{\n')
- out_cpp.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():"\n')
- out_cpp.write( f' << " have called {rename.ll_fn(keep_name)}(rhs.m_internal)\\n"\n')
- out_cpp.write( f' ;\n')
- out_cpp.write( f' }}\n')
- out_cpp.write( f' #endif\n')
- if parse.has_refs( tu, struct_cursor.type):
- out_cpp.write(f' {refcheck_if}\n')
- out_cpp.write( ' if (s_check_refs)\n')
- out_cpp.write( ' {\n')
- out_cpp.write(f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
- out_cpp.write( ' }\n')
- out_cpp.write( ' #endif\n')
- out_cpp.write(num_instances(refcheck_if, +1, classname))
- out_cpp.write( '}\n')
- out_cpp.write( '\n')
- # Make operator=().
- #
- comment = f'operator= using `{keep_name}()` and `{drop_name}()`.'
- out_h.write( f' /** {comment} */\n')
- out_h.write( f' FZ_FUNCTION {classname}& operator=(const {classname}& rhs);\n')
- out_cpp.write( f'/* {comment} */\n')
- out_cpp.write( f'FZ_FUNCTION {classname}& {classname}::operator=(const {classname}& rhs)\n')
- out_cpp.write( '{\n')
- out_cpp.write( f' {trace_if}\n')
- out_cpp.write( f' if (s_trace_keepdrop) {{\n')
- out_cpp.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():"\n')
- out_cpp.write( f' << " calling {rename.ll_fn(drop_name)}(this->m_internal)"\n')
- out_cpp.write( f' << " and {rename.ll_fn(keep_name)}(rhs.m_internal)\\n"\n')
- out_cpp.write( f' ;\n')
- out_cpp.write( f' }}\n')
- out_cpp.write( f' #endif\n')
- out_cpp.write( f' {rename.ll_fn(drop_name)}(this->m_internal);\n')
- out_cpp.write( f' {rename.ll_fn(keep_name)}(rhs.m_internal);\n')
- if parse.has_refs( tu, struct_cursor.type):
- out_cpp.write(f' {refcheck_if}\n')
- out_cpp.write( ' if (s_check_refs)\n')
- out_cpp.write( ' {\n')
- out_cpp.write(f' s_{classname}_refs_check.remove( this, __FILE__, __LINE__, __FUNCTION__);\n')
- out_cpp.write( ' }\n')
- out_cpp.write( ' #endif\n')
- out_cpp.write( f' this->m_internal = {cast}rhs.m_internal;\n')
- if parse.has_refs( tu, struct_cursor.type):
- out_cpp.write(f' {refcheck_if}\n')
- out_cpp.write( ' if (s_check_refs)\n')
- out_cpp.write( ' {\n')
- out_cpp.write(f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
- out_cpp.write( ' }\n')
- out_cpp.write( ' #endif\n')
- out_cpp.write( f' return *this;\n')
- out_cpp.write( '}\n')
- out_cpp.write( '\n')
- def function_name_implies_kept_references( fnname):
- '''
- Returns true if <fnname> implies the function would return kept
- reference(s).
- '''
- if fnname in (
- 'pdf_page_write',
- 'fz_decomp_image_from_stream',
- 'fz_get_pixmap_from_image',
- ):
- return True
- for i in (
- 'add',
- 'convert',
- 'copy',
- 'create',
- 'deep_copy',
- 'find',
- 'graft',
- 'keep',
- 'load',
- 'new',
- 'open',
- 'parse',
- 'read',
- ):
- if fnname.startswith(f'fz_{i}_') or fnname.startswith(f'pdf_{i}_'):
- if state.state_.show_details(fnname):
- jlib.log('Assuming that {fnname=} returns a kept reference.')
- return True
- return False
- def function_wrapper_class_aware_body(
- tu,
- fnname,
- out_cpp,
- struct_name,
- class_name,
- class_static,
- class_constructor,
- extras,
- struct_cursor,
- fn_cursor,
- return_cursor,
- wrap_return,
- refcheck_if,
- trace_if,
- ):
- '''
- Writes function or method body to <out_cpp> that calls a generated C++ wrapper
- function.
- fnname:
- .
- out_cpp:
- .
- struct_name:
- If false, we write a class-aware wrapping function body. Otherwise name
- of struct such as 'fz_rect' and we write method body for the struct's
- wrapper class.
- class_name:
- class_static:
- If true, this is a static class method.
- class_constructor:
- If true, this is a constructor.
- extras:
- .
- struct_cursor:
- .
- fn_cursor:
- Cursor for the underlying MuPDF function.
- return_cursor:
- If not None, the cursor for definition of returned type.
- wrap_return:
- If 'pointer', the underlying function returns a pointer to a struct
- that we wrap.
- If 'value' the underlying function returns, by value, a
- struct that we wrap, so we need to construct our wrapper from the
- address of this value.
- Otherwise we don't wrap the returned value.
- '''
- verbose = state.state_.show_details( fnname)
- out_cpp.write( f'{{\n')
- return_void = (fn_cursor.result_type.spelling == 'void')
- # Write trace code.
- out_cpp.write( f' {trace_if}\n')
- out_cpp.write( f' if (s_trace) {{\n')
- out_cpp.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():"\n')
- out_cpp.write( f' << " calling mupdf::{rename.ll_fn(fnname)}()\\n";\n')
- out_cpp.write( f' }}\n')
- out_cpp.write( f' #endif\n')
- if fn_cursor.type.is_function_variadic():
- assert fnname == 'fz_warn', f'{fnname=}'
- out_cpp.write( f' va_list ap;\n')
- out_cpp.write( f' va_start( ap, fmt);\n')
- out_cpp.write( f' {rename.ll_fn("fz_vwarn")}( fmt, ap);\n')
- out_cpp.write( f' va_end( ap);\n')
- elif class_constructor or not struct_name:
- # This code can generate a class method, but we choose to not use this,
- # instead method body simply calls the class-aware function (see below).
- def get_keep_drop(arg):
- name = util.clip( arg.alt.type.spelling, 'struct ')
- if name.startswith('fz_'):
- prefix = 'fz'
- name = name[3:]
- elif name.startswith('pdf_'):
- prefix = 'pdf'
- name = name[4:]
- else:
- assert 0
- return rename.ll_fn(f'{prefix}_keep_{name}'), rename.ll_fn(f'{prefix}_drop_{name}')
- # Handle wrapper-class out-params - need to drop .m_internal and set to
- # null.
- #
- # fixme: maybe instead simply call <arg.name>'s destructor directly?
- #
- for arg in parse.get_args( tu, fn_cursor):
- if arg.alt and arg.out_param:
- if parse.has_refs(tu, arg.alt.type):
- keep_fn, drop_fn = get_keep_drop(arg)
- out_cpp.write( f' /* Out-param {arg.name}.m_internal will be overwritten. */\n')
- out_cpp.write( f' {drop_fn}({arg.name}.m_internal);\n')
- out_cpp.write( f' {arg.name}.m_internal = nullptr;\n')
- # Write function call.
- if class_constructor:
- if extras.pod:
- if extras.pod == 'inline':
- out_cpp.write( f' *(::{struct_name}*) &this->{parse.get_field0(struct_cursor.type).spelling} = ')
- else:
- out_cpp.write( f' this->m_internal = ')
- if fn_cursor.result_type.kind == state.clang.cindex.TypeKind.POINTER:
- out_cpp.write( f'*')
- else:
- out_cpp.write( f' this->m_internal = ')
- if fn_cursor.result_type.kind == state.clang.cindex.TypeKind.POINTER:
- pass
- else:
- assert 0, 'cannot handle underlying fn returning by value when not pod.'
- out_cpp.write( f'{rename.ll_fn(fnname)}(')
- elif wrap_return == 'value':
- out_cpp.write( f' {_make_top_level(return_cursor.spelling)} temp = mupdf::{rename.ll_fn(fnname)}(')
- elif wrap_return == 'pointer':
- out_cpp.write( f' {_make_top_level(return_cursor.spelling)}* temp = mupdf::{rename.ll_fn(fnname)}(')
- elif wrap_return == 'const pointer':
- out_cpp.write( f' const {_make_top_level(return_cursor.spelling)}* temp = mupdf::{rename.ll_fn(fnname)}(')
- elif return_void:
- out_cpp.write( f' mupdf::{rename.ll_fn(fnname)}(')
- else:
- out_cpp.write( f' auto ret = mupdf::{rename.ll_fn(fnname)}(')
- have_used_this = False
- sep = ''
- for arg in parse.get_args( tu, fn_cursor):
- arg_classname = class_name
- if class_static or class_constructor:
- arg_classname = None
- out_cpp.write( sep)
- have_used_this = write_call_arg(
- tu,
- arg,
- arg_classname,
- have_used_this,
- out_cpp,
- state.state_.show_details(fnname),
- )
- sep = ', '
- out_cpp.write( f');\n')
- if state.state_.show_details(fnname):
- jlib.log('{=wrap_return}')
- refcounted_return = False
- if wrap_return in ('pointer', 'const pointer') and parse.has_refs( tu, return_cursor.type):
- refcounted_return = True
- refcounted_return_struct_cursor = return_cursor
- elif class_constructor and parse.has_refs( tu, struct_cursor.type):
- refcounted_return = True
- refcounted_return_struct_cursor = struct_cursor
- if refcounted_return:
- # This MuPDF function returns pointer to a struct which uses reference
- # counting. If the function returns a borrowed reference, we need
- # to increment its reference count before passing it to our wrapper
- # class's constructor.
- #
- #jlib.log('Function returns pointer to {return_cursor=}')
- return_struct_name = util.clip( refcounted_return_struct_cursor.spelling, 'struct ')
- if return_struct_name.startswith('fz_'):
- prefix = 'fz_'
- elif return_struct_name.startswith('pdf_'):
- prefix = 'pdf_'
- else:
- prefix = None
- if state.state_.show_details(fnname):
- jlib.log('{=prefix}')
- if prefix:
- if function_name_implies_kept_references( fnname):
- pass
- #out_cpp.write( f' /* We assume that {fnname} returns a kept reference. */\n')
- else:
- if state.state_.show_details(fnname):
- jlib.log('{=classname fnname constructor} Assuming that {fnname=} returns a borrowed reference.')
- # This function returns a borrowed reference.
- suffix = return_struct_name[ len(prefix):]
- keep_fn = f'{prefix}keep_{suffix}'
- #jlib.log('Function assumed to return borrowed reference: {fnname=} => {return_struct_name=} {keep_fn=}')
- #out_cpp.write( f' /* We assume that {fnname} returns a borrowed reference. */\n')
- if class_constructor:
- out_cpp.write( f' {rename.ll_fn(keep_fn)}(this->m_internal);\n')
- else:
- out_cpp.write( f' {rename.ll_fn(keep_fn)}(temp);\n')
- if wrap_return == 'value':
- out_cpp.write( f' auto ret = {rename.class_(return_cursor.spelling)}(&temp);\n')
- elif wrap_return in ('pointer', 'const pointer'):
- out_cpp.write( f' auto ret = {rename.class_(return_cursor.spelling)}(temp);\n')
- # Handle wrapper-class out-params - need to keep arg.m_internal if
- # fnname implies it will be a borrowed reference.
- for arg in parse.get_args( tu, fn_cursor):
- if arg.alt and arg.out_param:
- if parse.has_refs(tu, arg.alt.type):
- if function_name_implies_kept_references( fnname):
- out_cpp.write( f' /* We assume that out-param {arg.name}.m_internal is a kept reference. */\n')
- else:
- keep_fn, drop_fn = get_keep_drop(arg)
- out_cpp.write( f' /* We assume that out-param {arg.name}.m_internal is a borrowed reference. */\n')
- out_cpp.write( f' {keep_fn}({arg.name}.m_internal);\n')
- else:
- # Class method simply calls the class-aware function, which will have
- # been generated elsewhere.
- out_cpp.write( ' ')
- if not return_void:
- out_cpp.write( 'auto ret = ')
- out_cpp.write( f'mupdf::{rename.fn(fnname)}(')
- sep = ''
- for i, arg in enumerate( parse.get_args( tu, fn_cursor)):
- out_cpp.write( sep)
- if i==0 and not class_static:
- out_cpp.write( '*this')
- else:
- out_cpp.write( f'{arg.name}')
- sep = ', '
- out_cpp.write( ');\n')
- if struct_name and not class_static:
- if parse.has_refs( tu, struct_cursor.type):
- # Write code that does runtime checking of reference counts.
- out_cpp.write( f' {refcheck_if}\n')
- out_cpp.write( f' if (s_check_refs)\n')
- out_cpp.write( f' {{\n')
- if class_constructor:
- out_cpp.write( f' s_{class_name}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
- else:
- out_cpp.write( f' s_{class_name}_refs_check.check( this, __FILE__, __LINE__, __FUNCTION__);\n')
- out_cpp.write( f' }}\n')
- out_cpp.write( f' #endif\n')
- if class_constructor:
- out_cpp.write(num_instances(refcheck_if, +1, class_name))
- if not return_void and not class_constructor:
- out_cpp.write( f' return ret;\n')
- out_cpp.write( f'}}\n')
- out_cpp.write( f'\n')
- def function_wrapper_class_aware(
- tu,
- register_fn_use,
- fnname,
- out_h,
- out_cpp,
- struct_name,
- class_name,
- fn_cursor,
- refcheck_if,
- trace_if,
- class_static=False,
- class_constructor=False,
- extras=None,
- struct_cursor=None,
- duplicate_type=None,
- generated=None,
- debug=None,
- ):
- '''
- Writes a function or class method that calls <fnname>.
- Also appends python and C# code to generated.swig_python and
- generated.swig_csharp if <generated> is not None.
- tu
- .
- register_fn_use
- Callback to keep track of what fz_*() fns have been used.
- fnname
- Name of fz_*() fn to wrap, e.g. fz_concat.
- out_h
- out_cpp
- Where to write generated code.
- struct_name
- If false, we generate class-aware wrapping function. Otherwise name
- of struct such as 'fz_rect' and we create a method in the struct's
- wrapper class.
- class_name
- Ignored if struct_name is false.
- Name of wrapper class, e.g. 'Rect'.
- class_static
- Ignored if struct_name is false.
- If true, we generate a static method.
- Otherwise we generate a normal class method, where first arg that
- is type <struct_name> is omitted from the generated method's
- prototype; in the implementation we use <this>.
- class_constructor
- If true, we write a constructor.
- extras
- None or ClassExtras instance.
- Only used if <constructor> is true.
- struct_cursor
- None or cursor for the struct definition.
- Only used if <constructor> is true.
- duplicate_type:
- If true, we have already generated a method with the same args, so
- this generated method will be commented-out.
- generated:
- If not None and there are one or more out-params, we write
- python code to generated.swig_python that overrides the default
- SWIG-generated method to call our *_outparams_fn() alternative.
- debug
- Show extra diagnostics.
- '''
- verbose = state.state_.show_details( fnname)
- if fn_cursor and fn_cursor.type.is_function_variadic() and fnname != 'fz_warn':
- jlib.log( 'Not writing class-aware wrapper because variadic: {fnname=}', 1)
- return
- if verbose:
- jlib.log( 'Writing class-aware wrapper for {fnname=}')
- if struct_name:
- assert fnname not in state.omit_methods, jlib.log_text( '{=fnname}')
- if debug:
- jlib.log( '{class_name=} {fnname=}')
- assert fnname.startswith( ('fz_', 'pdf_'))
- if not fn_cursor:
- fn_cursor = state.state_.find_function( tu, fnname, method=True)
- if not fn_cursor:
- jlib.log( '*** ignoring {fnname=}')
- return
- if fnname.endswith('_drop'):
- # E.g. fz_concat_push_drop() is not safe (or necessary) for us because
- # we need to manage reference counts ourselves.
- #jlib.log('Ignoring because ends with "_drop": {fnname}')
- return
- if struct_name:
- methodname = rename.method( struct_name, fnname)
- else:
- methodname = rename.fn( fnname)
- if verbose:
- jlib.log( 'Writing class-aware wrapper for {fnname=}')
- # Construct prototype fnname(args).
- #
- if class_constructor:
- assert struct_name
- decl_h = f'{class_name}('
- decl_cpp = f'{class_name}('
- else:
- decl_h = f'{methodname}('
- decl_cpp = f'{methodname}('
- have_used_this = False
- num_out_params = 0
- num_class_wrapper_params = 0
- comma = ''
- this_is_const = False
- debug = state.state_.show_details( fnname)
- for arg in parse.get_args( tu, fn_cursor):
- if debug:
- jlib.log( 'Looking at {struct_name=} {fnname=} {fnname_wrapper} {arg=}', 1)
- decl_h += comma
- decl_cpp += comma
- if arg.out_param:
- num_out_params += 1
- if arg.alt:
- # This parameter is a pointer to a struct that we wrap.
- num_class_wrapper_params += 1
- arg_extras = classes.classextras.get( tu, arg.alt.type.spelling)
- assert arg_extras, jlib.log_text( '{=structname fnname arg.alt.type.spelling}')
- const = ''
- if not arg.out_param and (not arg_extras.pod or arg.cursor.type.kind != state.clang.cindex.TypeKind.POINTER):
- const = 'const '
- if (1
- and struct_name
- and not class_static
- and not class_constructor
- and rename.class_(util.clip( arg.alt.type.spelling, 'struct ')) == class_name
- and not have_used_this
- ):
- assert not arg.out_param
- # Omit this arg from the method's prototype - we'll use <this>
- # when calling the underlying fz_ function.
- have_used_this = True
- if not arg_extras.pod:
- this_is_const = const
- continue
- if arg_extras.pod == 'none':
- jlib.log( 'Not wrapping because {arg=} wrapper has {extras.pod=}', 1)
- return
- text = f'{const}{rename.class_(arg.alt.type.spelling)}& {arg.name}'
- decl_h += text
- decl_cpp += text
- else:
- jlib.logx( '{arg.spelling=}')
- decl_text = declaration_text( arg.cursor.type, arg.name)
- decl_h += decl_text
- decl_cpp += decl_text
- comma = ', '
- if fn_cursor.type.is_function_variadic():
- decl_h += f'{comma}...'
- decl_cpp += f'{comma}...'
- decl_h += ')'
- decl_cpp += ')'
- if this_is_const:
- decl_h += ' const'
- decl_cpp += ' const'
- if verbose:
- jlib.log( '{=struct_name class_constructor}')
- if class_constructor:
- comment = f'Constructor using `{fnname}()`.'
- else:
- comment = make_wrapper_comment(
- tu,
- fn_cursor,
- fnname,
- methodname,
- indent=' ',
- is_method=bool(struct_name),
- is_low_level=False,
- )
- if struct_name and not class_static and not class_constructor:
- assert have_used_this, f'error: wrapper for {struct_name}: {fnname}() is not useful - does not have a {struct_name} arg.'
- if struct_name and not duplicate_type:
- register_fn_use( fnname)
- # If this is true, we explicitly construct a temporary from what the
- # wrapped function returns.
- #
- wrap_return = None
- warning_not_copyable = False
- warning_no_raw_constructor = False
- # Figure out return type for our generated function/method.
- #
- if verbose:
- jlib.log( 'Looking at return type...')
- return_cursor = None
- return_type = None
- return_extras = None
- if class_constructor:
- assert struct_name
- fn_h = f'{decl_h}'
- fn_cpp = f'{class_name}::{decl_cpp}'
- else:
- fn_h = declaration_text( fn_cursor.result_type, decl_h)
- if verbose:
- jlib.log( '{fn_cursor.result_type=}')
- if struct_name:
- fn_cpp = declaration_text( fn_cursor.result_type, f'{class_name}::{decl_cpp}')
- else:
- fn_cpp = declaration_text( fn_cursor.result_type, f'{decl_cpp}')
- # See whether we can convert return type to an instance of a wrapper
- # class.
- #
- if verbose:
- jlib.log( '{fn_cursor.result_type.kind=}')
- if fn_cursor.result_type.kind == state.clang.cindex.TypeKind.POINTER:
- # Function returns a pointer.
- t = state.get_name_canonical( fn_cursor.result_type.get_pointee())
- if verbose:
- jlib.log( '{t.spelling=}')
- return_cursor = parse.find_struct( tu, t.spelling, require_definition=False)
- if verbose:
- jlib.log( '{=t.spelling return_cursor}')
- if return_cursor:
- # Function returns a pointer to a struct.
- return_extras = classes.classextras.get( tu, return_cursor.spelling)
- if return_extras:
- # Function returns a pointer to a struct for which we
- # generate a class wrapper, so change return type to be an
- # instance of the class wrapper.
- return_type = rename.class_(return_cursor.spelling)
- if verbose:
- jlib.log( '{=return_type}')
- if 0 and (state.state_.show_details(return_cursor.type.spelling) or state.state_.show_details(struct_name)):
- jlib.log('{return_cursor.type.spelling=}'
- ' {return_cursor.spelling=}'
- ' {struct_name=} {return_extras.copyable=}'
- ' {return_extras.constructor_raw=}'
- )
- fn_h = f'{return_type} {decl_h}'
- if struct_name:
- fn_cpp = f'{return_type} {class_name}::{decl_cpp}'
- else:
- fn_cpp = f'{return_type} {decl_cpp}'
- if t.is_const_qualified():
- wrap_return = 'const pointer'
- else:
- wrap_return = 'pointer'
- else:
- return_pointee = fn_cursor.result_type.get_pointee()
- if 'char' in return_pointee.spelling:
- if function_name_implies_kept_references(fnname):
- # For now we just output a diagnostic, but eventually
- # we might make C++ wrappers return a std::string here,
- # free()-ing the char* before returning.
- jlib.log1( 'Function name implies kept reference and returns char*:'
- ' {fnname}(): {fn_cursor.result_type.spelling=}'
- ' -> {return_pointee.spelling=}.'
- )
- if verbose:
- jlib.log( '{=warning_not_copyable warning_no_raw_constructor}')
- else:
- # The fz_*() function returns by value. See whether we can convert
- # its return type to an instance of a wrapper class.
- #
- # If so, we will use constructor that takes pointer to the fz_
- # struct. C++ doesn't allow us to use address of temporary, so we
- # generate code like this:
- #
- # fz_quad_s ret = mupdf_snap_selection(...);
- # return Quad(&ret);
- #
- t = state.get_name_canonical( fn_cursor.result_type)
- # 2023-02-09: parse.find_struct() will actually find any definition,
- # and we now prefix Fitz headers with a typedef of size_t on Linux,
- # so we need to avoid calling parse.find_struct() unless `t` is for
- # a MuPDF type.
- #
- if t.spelling.startswith( ('fz_', 'pdf_')):
- return_cursor = parse.find_struct( tu, t.spelling)
- if return_cursor:
- tt = state.get_name_canonical( return_cursor.type)
- if tt.kind == state.clang.cindex.TypeKind.ENUM:
- # For now, we return this type directly with no wrapping.
- pass
- else:
- return_extras = classes.classextras.get( tu, return_cursor.spelling)
- return_type = rename.class_(return_cursor.type.spelling)
- fn_h = f'{return_type} {decl_h}'
- if struct_name:
- fn_cpp = f'{return_type} {class_name}::{decl_cpp}'
- else:
- fn_cpp = f'{return_type} {decl_cpp}'
- wrap_return = 'value'
- if return_extras:
- if not return_extras.copyable:
- out_h.write(
- textwrap.indent(
- textwrap.dedent( f'''
- /* Class-aware wrapper for `{fnname}()`
- is not available because returned wrapper class for `{return_cursor.spelling}`
- is non-copyable. */
- '''
- ),
- ' ',
- )
- )
- if verbose:
- jlib.log( 'Not creating class-aware wrapper because returned wrapper class is non-copyable: {fnname=}.')
- return
- if not return_extras.constructor_raw:
- out_h.write(
- textwrap.indent(
- textwrap.dedent( f'''
- /* Class-aware wrapper for `{fnname}()`
- is not available because returned wrapper class for `{return_cursor.spelling}`
- does not have raw constructor. */
- '''
- ),
- ' ',
- )
- )
- if verbose:
- jlib.log( 'Not creating class-aware wrapper because returned wrapper class does not have raw constructor: {fnname=}.')
- return
- out_h.write( '\n')
- out_h.write( f' /** {comment} */\n')
- # Copy any comment (indented) into class definition above method
- # declaration.
- if fn_cursor.raw_comment:
- raw_comment = fn_cursor.raw_comment.replace('\r', '')
- for line in raw_comment.split( '\n'):
- out_h.write( f' {line}\n')
- if duplicate_type:
- out_h.write( f' /* Disabled because same args as {duplicate_type}.\n')
- out_h.write( f' FZ_FUNCTION {"static " if class_static else ""}{fn_h};\n')
- if duplicate_type:
- out_h.write( f' */\n')
- if not struct_name:
- # Use extra spacing between non-class functions. Class methods are
- # grouped together.
- out_cpp.write( f'\n')
- out_cpp.write( f'/* {comment} */\n')
- if duplicate_type:
- out_cpp.write( f'/* Disabled because same args as {duplicate_type}.\n')
- out_cpp.write( f'FZ_FUNCTION {fn_cpp}\n')
- function_wrapper_class_aware_body(
- tu,
- fnname,
- out_cpp,
- struct_name,
- class_name,
- class_static,
- class_constructor,
- extras,
- struct_cursor,
- fn_cursor,
- return_cursor,
- wrap_return,
- refcheck_if,
- trace_if,
- )
- if struct_name:
- if duplicate_type:
- out_cpp.write( f'*/\n')
- # fixme: the test of `struct_name` means that we don't generate outparam override for
- # class-aware fns which don't have any struct/class args, e.g. fz_lookup_cjk_font().
- #
- if generated and num_out_params:
- make_python_class_method_outparam_override(
- tu,
- fn_cursor,
- fnname,
- generated,
- struct_name,
- class_name,
- return_type,
- )
- def class_custom_method(
- tu,
- register_fn_use,
- struct_cursor,
- classname,
- extramethod,
- out_h,
- out_cpp,
- refcheck_if,
- trace_if,
- ):
- '''
- Writes custom method as specified by <extramethod>.
- tu
- .
- register_fn_use
- Callable taking single <fnname> arg.
- struct_cursor
- Cursor for definition of MuPDF struct.
- classname
- Name of wrapper class for <struct_cursor>.
- extramethod
- An ExtraMethod or ExtraConstructor instance.
- out_h
- out_cpp
- Where to write generated code.
- '''
- assert isinstance( extramethod, ( classes.ExtraMethod, classes.ExtraConstructor)), f'{type(extramethod)}'
- is_constructor = False
- is_destructor = False
- is_begin_end = False
- if extramethod.return_:
- name_args = extramethod.name_args
- return_space = f'{extramethod.return_} '
- comment = 'Custom method.'
- if name_args.startswith( 'begin(') or name_args.startswith( 'end('):
- is_begin_end = True
- elif extramethod.name_args == '~()':
- # Destructor.
- name_args = f'~{classname}{extramethod.name_args[1:]}'
- return_space = ''
- comment = 'Custom destructor.'
- is_destructor = True
- elif extramethod.name_args.startswith('operator '):
- name_args = extramethod.name_args
- comment = 'Custom operator.'
- return_space = ''
- else:
- # Constructor.
- assert extramethod.name_args.startswith( '('), f'bad constructor/destructor in {classname=}: {extramethod.name_args=}'
- name_args = f'{classname}{extramethod.name_args}'
- return_space = ''
- comment = 'Custom constructor.'
- is_constructor = True
- out_h.write( f'\n')
- if extramethod.comment:
- for i, line in enumerate( extramethod.comment.strip().split('\n')):
- line = line.replace( '/* ', '/** ')
- out_h.write( f' {line}\n')
- else:
- out_h.write( f' /** {comment} */\n')
- out_h.write( f' FZ_FUNCTION {return_space}{name_args};\n')
- out_cpp.write( f'/** {comment} */\n')
- # Remove any default arg values from <name_args>.
- name_args_no_defaults = re.sub('= *[^(][^),]*', '', name_args)
- if name_args_no_defaults != name_args:
- jlib.log('have changed {name_args=} to {name_args_no_defaults=}', 1)
- out_cpp.write( f'FZ_FUNCTION {return_space}{classname}::{name_args_no_defaults}')
- body = textwrap.dedent(extramethod.body)
- end = body.rfind('}')
- assert end >= 0
- out_cpp.write( body[:end])
- if is_constructor and parse.has_refs( tu, struct_cursor.type):
- # Insert ref checking code into end of custom constructor body.
- out_cpp.write( f' {refcheck_if}\n')
- out_cpp.write( f' if (s_check_refs)\n')
- out_cpp.write( f' {{\n')
- out_cpp.write( f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
- out_cpp.write( f' }}\n')
- out_cpp.write( f' #endif\n')
- if is_constructor:
- out_cpp.write( num_instances(refcheck_if, +1, classname))
- if is_destructor:
- out_cpp.write( num_instances(refcheck_if, -1, classname))
- out_cpp.write( body[end:])
- out_cpp.write( f'\n')
- if 1: # lgtm [py/constant-conditional-expression]
- # Register calls of all fz_* functions. Not necessarily helpful - we
- # might only be interested in calls of fz_* functions that are directly
- # available to uses of class.
- #
- for fnname in re.findall( '(mupdf::[a-zA-Z0-9_]+) *[(]', extramethod.body):
- fnname = util.clip( fnname, 'mupdf::')
- if not fnname.startswith( 'pdf_'):
- fnname = 'fz_' + fnname
- #log( 'registering use of {fnname} in extramethod {classname}::{name_args}')
- register_fn_use( fnname)
- return is_constructor, is_destructor, is_begin_end
- def class_raw_constructor(
- tu,
- register_fn_use,
- classname,
- struct_cursor,
- struct_name,
- base_name,
- extras,
- constructor_fns,
- out_h,
- out_cpp,
- refcheck_if,
- trace_if,
- ):
- '''
- Create a raw constructor - a constructor taking a pointer to underlying
- struct. This raw constructor assumes that it already owns the pointer so it
- does not call fz_keep_*(); the class's destructor will call fz_drop_*().
- '''
- #jlib.log( 'Creating raw constructor {classname=} {struct_name=} {extras.pod=} {extras.constructor_raw=} {fnname=}')
- comment = f'/** Constructor using raw copy of pre-existing `::{struct_name}`. */'
- if extras.pod:
- constructor_decl = f'{classname}(const ::{struct_name}* internal)'
- else:
- constructor_decl = f'{classname}(::{struct_name}* internal)'
- out_h.write( '\n')
- out_h.write( f' {comment}\n')
- explicit = ''
- if parse.has_refs( tu, struct_cursor.type):
- # Don't allow implicit construction from low-level struct, because our
- # destructor will drop it without a prior balancing keep.
- explicit = f'explicit '
- out_h.write(
- f' /* This constructor is marked as `explicit` because wrapper classes do not\n'
- f' call `keep`in constructors, but do call `drop` in destructors. So\n'
- f' automatic construction from a {struct_name}* will generally cause an\n'
- f' unbalanced `drop` resulting in errors such as SEGV. */\n'
- )
- if extras.constructor_raw == 'default':
- out_h.write( f' FZ_FUNCTION {explicit}{classname}(::{struct_name}* internal=NULL);\n')
- else:
- out_h.write( f' FZ_FUNCTION {explicit}{constructor_decl};\n')
- if extras.constructor_raw != 'declaration_only':
- out_cpp.write( f'FZ_FUNCTION {classname}::{constructor_decl}\n')
- if extras.pod == 'inline':
- pass
- elif extras.pod:
- out_cpp.write( ': m_internal(*internal)\n')
- else:
- out_cpp.write( ': m_internal(internal)\n')
- out_cpp.write( '{\n')
- if extras.pod == 'inline':
- assert struct_cursor, f'cannot form raw constructor for inline pod {classname} without cursor for underlying {struct_name}'
- out_cpp.write( f' assert( internal);\n')
- for c in parse.get_members(struct_cursor):
- if c.type.kind == state.clang.cindex.TypeKind.CONSTANTARRAY:
- out_cpp.write( f' memcpy(this->{c.spelling}, internal->{c.spelling}, sizeof(this->{c.spelling}));\n')
- else:
- out_cpp.write( f' this->{c.spelling} = internal->{c.spelling};\n')
- if parse.has_refs( tu, struct_cursor.type):
- out_cpp.write( f' {refcheck_if}\n')
- out_cpp.write( f' if (s_check_refs)\n')
- out_cpp.write( f' {{\n')
- out_cpp.write( f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
- out_cpp.write( f' }}\n')
- out_cpp.write( f' #endif\n')
- out_cpp.write(num_instances(refcheck_if, +1, classname))
- out_cpp.write( '}\n')
- out_cpp.write( '\n')
- if extras.pod == 'inline':
- # Write second constructor that takes underlying struct by value.
- #
- assert not parse.has_refs( tu, struct_cursor.type)
- constructor_decl = f'{classname}(const ::{struct_name} internal)'
- out_h.write( '\n')
- out_h.write( f' {comment}\n')
- out_h.write( f' FZ_FUNCTION {constructor_decl};\n')
- if extras.constructor_raw != 'declaration_only':
- out_cpp.write( f'FZ_FUNCTION {classname}::{constructor_decl}\n')
- out_cpp.write( '{\n')
- for c in parse.get_members(struct_cursor):
- if c.type.kind == state.clang.cindex.TypeKind.CONSTANTARRAY:
- out_cpp.write( f' memcpy(this->{c.spelling}, &internal.{c.spelling}, sizeof(this->{c.spelling}));\n')
- else:
- out_cpp.write( f' this->{c.spelling} = internal.{c.spelling};\n')
- out_cpp.write(num_instances(refcheck_if, +1, classname))
- out_cpp.write( '}\n')
- out_cpp.write( '\n')
- # Write accessor for inline state.state_.
- #
- for const in False, True:
- space_const = ' const' if const else ''
- const_space = 'const ' if const else ''
- out_h.write( '\n')
- out_h.write( f' /** Access as underlying struct. */\n')
- out_h.write( f' FZ_FUNCTION {const_space}::{struct_name}* internal(){space_const};\n')
- out_cpp.write( f'{comment}\n')
- out_cpp.write( f'FZ_FUNCTION {const_space}::{struct_name}* {classname}::internal(){space_const}\n')
- out_cpp.write( '{\n')
- field0 = parse.get_field0(struct_cursor.canonical).spelling
- out_cpp.write( f' auto ret = ({const_space}::{struct_name}*) &this->{field0};\n')
- if parse.has_refs( tu, struct_cursor.type):
- out_cpp.write( f' {refcheck_if}\n')
- out_cpp.write( f' if (s_check_refs)\n')
- out_cpp.write( f' {{\n')
- out_cpp.write( f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n')
- out_cpp.write( f' }}\n')
- out_cpp.write( f' #endif\n')
- out_cpp.write( ' return ret;\n')
- out_cpp.write( '}\n')
- out_cpp.write( '\n')
- def class_accessors(
- tu,
- register_fn_use,
- classname,
- struct_cursor,
- struct_name,
- extras,
- out_h,
- out_cpp,
- ):
- '''
- Writes accessor functions for member data.
- '''
- if not extras.pod:
- jlib.logx( 'creating accessor for non-pod class {classname=} wrapping {struct_name}')
- n = 0
- for cursor in parse.get_members(struct_cursor):
- n += 1
- #jlib.log( 'accessors: {cursor.spelling=} {cursor.type.spelling=}')
- # We set this to fz_keep_<type>() function to call, if we return a
- # wrapper class constructed from raw pointer to underlying fz_* struct.
- keep_function = None
- # Set <decl> to be prototype with %s where the name is, e.g. 'int
- # %s()'; later on we use python's % operator to replace the '%s'
- # with the name we want.
- #
- if cursor.type.kind == state.clang.cindex.TypeKind.POINTER:
- decl = 'const ' + declaration_text( cursor.type, '%s()')
- pointee_type = state.get_name_canonical( cursor.type.get_pointee()).spelling
- pointee_type = util.clip( pointee_type, 'const ')
- pointee_type = util.clip( pointee_type, 'struct ')
- #if 'fz_' in pointee_type:
- # jlib.log( '{pointee_type=}')
- # We don't attempt to make accessors to function pointers.
- if state.get_name_canonical( cursor.type.get_pointee()).kind == state.clang.cindex.TypeKind.FUNCTIONPROTO:
- jlib.logx( 'ignoring {cursor.spelling=} because pointer to FUNCTIONPROTO')
- continue
- elif pointee_type.startswith( ('fz_', 'pdf_')):
- extras2 = parse.get_fz_extras( tu, pointee_type)
- if extras2:
- # Make this accessor return an instance of the wrapping
- # class by value.
- #
- classname2 = rename.class_( pointee_type)
- decl = f'{classname2} %s()'
- # If there's a fz_keep_() function, we must call it on the
- # internal data before returning the wrapper class.
- pointee_type_base = util.clip( pointee_type, ('fz_', 'pdf_'))
- keep_function = f'{parse.prefix(pointee_type)}keep_{pointee_type_base}'
- if state.state_.find_function( tu, keep_function, method=False):
- jlib.logx( 'using {keep_function=}')
- else:
- jlib.log( 'cannot find {keep_function=}')
- keep_function = None
- elif cursor.type.kind == state.clang.cindex.TypeKind.FUNCTIONPROTO:
- jlib.log( 'ignoring {cursor.spelling=} because FUNCTIONPROTO')
- continue
- else:
- if 0 and extras.pod: # lgtm [py/unreachable-statement]
- # Return reference so caller can modify. Unfortunately SWIG
- # converts non-const references to pointers, so generated
- # python isn't useful.
- fn_args = '& %s()'
- else:
- fn_args = '%s()'
- if cursor.type.get_array_size() >= 0:
- if 0: # lgtm [py/unreachable-statement]
- # Return reference to the array; we need to put fn name
- # and args inside (...) to allow the declaration syntax
- # to work - we end up with things like:
- #
- # char (& media_class())[64];
- #
- # Unfortunately SWIG doesn't seem to be able to cope
- # with this.
- decl = declaration_text( cursor.type, '(%s)' % fn_args)
- else:
- # Return pointer to the first element of the array, so
- # that SWIG can cope.
- fn_args = '* %s()'
- type_ = cursor.type.get_array_element_type()
- decl = declaration_text( type_, fn_args)
- else:
- if ( cursor.type.kind == state.clang.cindex.TypeKind.TYPEDEF
- and cursor.type.get_typedef_name() in ('uint8_t', 'int8_t')
- ):
- # Don't let accessor return uint8_t because SWIG thinks it
- # is a char*, leading to memory errors. Instead return int.
- #
- jlib.logx('Changing from {cursor.type.get_typedef_name()=} {cursor.type=} to int')
- decl = f'int {fn_args}'
- else:
- decl = declaration_text( cursor.type, fn_args)
- # todo: if return type is uint8_t or int8_t, maybe return as <int>
- # so SWIG doesn't think it is a string? This would fix errors with
- # fz_image::n and fz_image::bpc.
- out_h.write( f' FZ_FUNCTION {decl % cursor.spelling};\n')
- out_cpp.write( 'FZ_FUNCTION %s\n' % (decl % ( f'{classname}::{cursor.spelling}')))
- out_cpp.write( '{\n')
- if keep_function:
- out_cpp.write( f' {rename.ll_fn(keep_function)}(m_internal->{cursor.spelling});\n')
- out_cpp.write( f' return ({classname2}) m_internal->{cursor.spelling};\n')
- else:
- if extras.pod:
- out_cpp.write( f' return m_internal.{cursor.spelling};\n')
- else:
- out_cpp.write( f' return m_internal->{cursor.spelling};\n')
- out_cpp.write( '}\n')
- out_cpp.write( '\n')
- assert n, f'No fields found for {struct_cursor.spelling}.'
- def class_destructor(
- tu,
- register_fn_use,
- classname,
- extras,
- struct_cursor,
- destructor_fns,
- out_h,
- out_cpp,
- refcheck_if,
- trace_if,
- ):
- if len(destructor_fns) > 1:
- # Use function with shortest name.
- if 0: # lgtm [py/unreachable-statement]
- jlib.log( 'Multiple possible destructor fns for {classname=}')
- for fnname, cursor in destructor_fns:
- jlib.log( ' {fnname=} {cursor.spelling=}')
- shortest = None
- for i in destructor_fns:
- if shortest is None or len(i[0]) < len(shortest[0]):
- shortest = i
- #jlib.log( 'Using: {shortest[0]=}')
- destructor_fns = [shortest]
- if len(destructor_fns):
- fnname, cursor = destructor_fns[0]
- register_fn_use( cursor.spelling)
- out_h.write( f' /** Destructor using {cursor.spelling}(). */\n')
- out_h.write( f' FZ_FUNCTION ~{classname}();\n')
- out_cpp.write( f'FZ_FUNCTION {classname}::~{classname}()\n')
- out_cpp.write( '{\n')
- out_cpp.write( f' {rename.ll_fn(fnname)}(m_internal);\n')
- if parse.has_refs( tu, struct_cursor.type):
- out_cpp.write( f' {refcheck_if}\n')
- out_cpp.write( f' if (s_check_refs)\n')
- out_cpp.write( ' {\n')
- out_cpp.write( f' s_{classname}_refs_check.remove( this, __FILE__, __LINE__, __FUNCTION__);\n')
- out_cpp.write( ' }\n')
- out_cpp.write( f' #endif\n')
- out_cpp.write(num_instances(refcheck_if, -1, classname))
- out_cpp.write( '}\n')
- out_cpp.write( '\n')
- else:
- out_h.write(f' {refcheck_if}\n')
- out_h.write(f' /** Destructor only decrements s_num_instances. */\n')
- out_h.write(f' FZ_FUNCTION ~{classname}();\n')
- out_h.write( ' #else\n')
- out_h.write( ' /** We use default destructor. */\n')
- out_h.write( ' #endif\n')
- out_cpp.write( f'{refcheck_if}\n')
- out_cpp.write( f'FZ_FUNCTION {classname}::~{classname}()\n')
- out_cpp.write( '{\n')
- out_cpp.write(num_instances(refcheck_if, -1, classname))
- out_cpp.write( '}\n')
- out_cpp.write( '#endif\n')
- out_cpp.write( '\n')
- def pod_class_members(
- tu,
- classname,
- struct_cursor,
- struct_name,
- extras,
- out_h,
- out_cpp,
- ):
- '''
- Writes code for wrapper class's to_string() member function.
- '''
- out_h.write( f'\n')
- out_h.write( f' /** Returns string containing our members, labelled and inside (...), using operator<<. */\n')
- out_h.write( f' FZ_FUNCTION std::string to_string();\n')
- out_h.write( f'\n')
- out_h.write( f' /** Comparison method. */\n')
- out_h.write( f' FZ_FUNCTION bool operator==(const {classname}& rhs);\n')
- out_h.write( f'\n')
- out_h.write( f' /** Comparison method. */\n')
- out_h.write( f' FZ_FUNCTION bool operator!=(const {classname}& rhs);\n')
- out_cpp.write( f'FZ_FUNCTION std::string {classname}::to_string()\n')
- out_cpp.write( f'{{\n')
- out_cpp.write( f' std::ostringstream buffer;\n')
- out_cpp.write( f' buffer << *this;\n')
- out_cpp.write( f' return buffer.str();\n')
- out_cpp.write( f'}}\n')
- out_cpp.write( f'\n')
- out_cpp.write( f'FZ_FUNCTION bool {classname}::operator==(const {classname}& rhs)\n')
- out_cpp.write( f'{{\n')
- out_cpp.write( f' return ::operator==( *this, rhs);\n')
- out_cpp.write( f'}}\n')
- out_cpp.write( f'\n')
- out_cpp.write( f'FZ_FUNCTION bool {classname}::operator!=(const {classname}& rhs)\n')
- out_cpp.write( f'{{\n')
- out_cpp.write( f' return ::operator!=( *this, rhs);\n')
- out_cpp.write( f'}}\n')
- out_cpp.write( f'\n')
- def struct_to_string_fns(
- tu,
- struct_cursor,
- struct_name,
- extras,
- out_h,
- out_cpp,
- ):
- '''
- Writes functions for text representation of struct/wrapper-class members.
- '''
- out_h.write( f'\n')
- out_h.write( f'/** Returns string containing a {struct_name}\'s members, labelled and inside (...), using operator<<. */\n')
- out_h.write( f'FZ_FUNCTION std::string to_string_{struct_name}(const ::{struct_name}& s);\n')
- out_h.write( f'\n')
- out_h.write( f'/** Returns string containing a {struct_name}\'s members, labelled and inside (...), using operator<<.\n')
- out_h.write( f'(Convenience overload). */\n')
- out_h.write( f'FZ_FUNCTION std::string to_string(const ::{struct_name}& s);\n')
- out_cpp.write( f'\n')
- out_cpp.write( f'FZ_FUNCTION std::string to_string_{struct_name}(const ::{struct_name}& s)\n')
- out_cpp.write( f'{{\n')
- out_cpp.write( f' std::ostringstream buffer;\n')
- out_cpp.write( f' buffer << s;\n')
- out_cpp.write( f' return buffer.str();\n')
- out_cpp.write( f'}}\n')
- out_cpp.write( f'\n')
- out_cpp.write( f'FZ_FUNCTION std::string to_string(const ::{struct_name}& s)\n')
- out_cpp.write( f'{{\n')
- out_cpp.write( f' return to_string_{struct_name}(s);\n')
- out_cpp.write( f'}}\n')
- def pod_struct_fns(
- tu,
- namespace,
- struct_cursor,
- struct_name,
- extras,
- out_h,
- out_cpp,
- ):
- '''
- Writes extra fns for POD structs - operator<<(), operator==(), operator!=().
- '''
- # Write operator<< functions for streaming text representation of C struct
- # members. We should be at top-level in out_h and out_cpp, i.e. not inside
- # 'namespace mupdf {...}'.
- out_h.write( f'\n')
- out_h.write( f'/** {struct_name}: writes members, labelled and inside (...), to a stream. */\n')
- out_h.write( f'FZ_FUNCTION std::ostream& operator<< (std::ostream& out, const ::{struct_name}& rhs);\n')
- out_cpp.write( f'\n')
- out_cpp.write( f'FZ_FUNCTION std::ostream& operator<< (std::ostream& out, const ::{struct_name}& rhs)\n')
- out_cpp.write( f'{{\n')
- i = 0
- out_cpp.write( f' out\n')
- out_cpp.write( f' << "("\n');
- for cursor in parse.get_members(struct_cursor):
- out_cpp.write( f' << ');
- if i:
- out_cpp.write( f'" {cursor.spelling}="')
- else:
- out_cpp.write( f' "{cursor.spelling}="')
- out_cpp.write( f' << rhs.{cursor.spelling}\n')
- i += 1
- out_cpp.write( f' << ")"\n');
- out_cpp.write( f' ;\n')
- out_cpp.write( f' return out;\n')
- out_cpp.write( f'}}\n')
- # Write comparison fns.
- out_h.write( f'\n')
- out_h.write( f'/** {struct_name}: comparison function. */\n')
- out_h.write( f'FZ_FUNCTION bool operator==( const ::{struct_name}& lhs, const ::{struct_name}& rhs);\n')
- out_h.write( f'\n')
- out_h.write( f'/** {struct_name}: comparison function. */\n')
- out_h.write( f'FZ_FUNCTION bool operator!=( const ::{struct_name}& lhs, const ::{struct_name}& rhs);\n')
- out_cpp.write( f'\n')
- out_cpp.write( f'FZ_FUNCTION bool operator==( const ::{struct_name}& lhs, const ::{struct_name}& rhs)\n')
- out_cpp.write( f'{{\n')
- for cursor in parse.get_members(struct_cursor):
- out_cpp.write( f' if (lhs.{cursor.spelling} != rhs.{cursor.spelling}) return false;\n')
- out_cpp.write( f' return true;\n')
- out_cpp.write( f'}}\n')
- out_cpp.write( f'FZ_FUNCTION bool operator!=( const ::{struct_name}& lhs, const ::{struct_name}& rhs)\n')
- out_cpp.write( f'{{\n')
- out_cpp.write( f' return !(lhs == rhs);\n')
- out_cpp.write( f'}}\n')
- def pod_class_fns(
- tu,
- classname,
- struct_cursor,
- struct_name,
- extras,
- out_h,
- out_cpp,
- ):
- '''
- Writes extra fns for wrappers for POD structs - operator<<(), operator==(),
- operator!=().
- '''
- # Write functions for text representation of wrapper-class members. These
- # functions make use of the corresponding struct functions created by
- # struct_to_string_fns().
- #
- assert extras.pod != 'none'
- classname = f'mupdf::{classname}'
- out_h.write( f'\n')
- out_h.write( f'/** {classname}: writes underlying {struct_name}\'s members, labelled and inside (...), to a stream. */\n')
- out_h.write( f'FZ_FUNCTION std::ostream& operator<< (std::ostream& out, const {classname}& rhs);\n')
- out_cpp.write( f'\n')
- out_cpp.write( f'FZ_FUNCTION std::ostream& operator<< (std::ostream& out, const {classname}& rhs)\n')
- out_cpp.write( f'{{\n')
- if extras.pod == 'inline':
- out_cpp.write( f' return out << *rhs.internal();\n')
- elif extras.pod:
- out_cpp.write( f' return out << rhs.m_internal;\n')
- else:
- out_cpp.write( f' return out << " " << *rhs.m_internal;\n')
- out_cpp.write( f'}}\n')
- # Write comparison fns, using comparison of underlying MuPDF struct.
- out_h.write( f'\n')
- out_h.write( f'/** {classname}: comparison function. */\n')
- out_h.write( f'FZ_FUNCTION bool operator==( const {classname}& lhs, const {classname}& rhs);\n')
- out_h.write( f'\n')
- out_h.write( f'/** {classname}: comparison function. */\n')
- out_h.write( f'FZ_FUNCTION bool operator!=( const {classname}& lhs, const {classname}& rhs);\n')
- out_cpp.write( f'\n')
- out_cpp.write( f'FZ_FUNCTION bool operator==( const {classname}& lhs, const {classname}& rhs)\n')
- out_cpp.write( f'{{\n')
- if extras.pod == 'inline':
- out_cpp.write( f' return *lhs.internal() == *rhs.internal();\n')
- else:
- out_cpp.write( f' return lhs.m_internal == rhs.m_internal;\n')
- out_cpp.write( f'}}\n')
- out_cpp.write( f'\n')
- out_cpp.write( f'FZ_FUNCTION bool operator!=( const {classname}& lhs, const {classname}& rhs)\n')
- out_cpp.write( f'{{\n')
- if extras.pod == 'inline':
- out_cpp.write( f' return *lhs.internal() != *rhs.internal();\n')
- else:
- out_cpp.write( f' return lhs.m_internal != rhs.m_internal;\n')
- out_cpp.write( f'}}\n')
- def get_struct_fnptrs( cursor_struct, shallow_typedef_expansion=False, verbose=False):
- '''
- Yields (cursor, fnptr_type) for function-pointer members of struct defined
- at cusor, where <cursor> is the cursor of the member and <fntr_type> is the
- type.
- cursor_struct:
- Cursor for definition of struct; this can be a typedef.
- shallow_typedef_expansion:
- If true, the returned <fnptr_type> has any top-level typedefs resolved
- so will be a clang.cindex.TypeKind.FUNCTIONPROTO, but typedefs within
- the function args are not resolved, e.g. they can be size_t. This can
- be useful when generating code that will be compiled on different
- platforms with differing definitions of size_t.
- '''
- if verbose:
- jlib.log('Looking for fnptrs in {cursor_struct.spelling=}')
- for cursor in parse.get_members(cursor_struct):
- t = cursor.type
- if verbose:
- jlib.log('{t.kind=} {cursor.spelling=}')
- if t.kind == state.clang.cindex.TypeKind.POINTER:
- t = cursor.type.get_pointee()
- if t.kind in (state.clang.cindex.TypeKind.TYPEDEF, state.clang.cindex.TypeKind.ELABORATED):
- t_cursor = t.get_declaration()
- t = t_cursor.underlying_typedef_type
- if t.kind == state.clang.cindex.TypeKind.FUNCTIONPROTO:
- if shallow_typedef_expansion:
- if verbose:
- jlib.log('Not calling state.get_name_canonical() for {t.spelling=}. {cursor.spelling=}.')
- else:
- tt = state.get_name_canonical( t)
- if verbose:
- jlib.log('{tt.spelling=}')
- if (0
- or 'struct (unnamed at ' in tt.spelling
- or 'unnamed struct at ' in tt.spelling
- ):
- # This is clang giving an unhelpful name to an
- # anonymous struct.
- if verbose:
- jlib.log( 'Avoiding clang anonymous struct placeholder: {tt.spelling=}')
- else:
- t = tt
- if verbose:
- jlib.log('Yielding: {cursor.spelling=} {t.spelling=}')
- yield cursor, t
- def class_wrapper_virtual_fnptrs(
- tu,
- struct_cursor,
- struct_name,
- classname,
- extras,
- out_h,
- out_cpp,
- out_h_end,
- generated,
- refcheck_if,
- trace_if,
- ):
- '''
- Generate extra wrapper class if struct contains function pointers, for
- use as a SWIG Director class so that the function pointers can be made to
- effectively point to Python or C# code.
- '''
- if not extras.virtual_fnptrs:
- return
- verbose = state.state_.show_details( struct_name)
- generated.virtual_fnptrs.append( f'{classname}2')
- self_ = extras.virtual_fnptrs.pop( 'self_')
- self_n = extras.virtual_fnptrs.pop( 'self_n', 1)
- alloc = extras.virtual_fnptrs.pop( 'alloc')
- free = extras.virtual_fnptrs.pop( 'free', None)
- comment = extras.virtual_fnptrs.pop( 'comment', None)
- assert not extras.virtual_fnptrs, f'Unused items in virtual_fnptrs: {extras.virtual_fnptrs}'
- # Class definition beginning.
- #
- out_h.write( '\n')
- out_h.write( f'/** Wrapper class for struct {struct_name} with virtual fns for each fnptr; this is for use as a SWIG Director class. */\n')
- if comment:
- out_h.write(comment)
- out_h.write( f'struct {classname}2 : {classname}\n')
- out_h.write( '{\n')
- out_cpp.write( f'/* Implementation of methods for `{classname}2`, virtual_fnptrs wrapper for `{struct_name}`). */\n')
- out_cpp.write( '\n')
- def get_fnptrs( shallow_typedef_expansion=False):
- for i in get_struct_fnptrs( struct_cursor, shallow_typedef_expansion, verbose=verbose):
- yield i
- # Constructor
- #
- out_h.write( '\n')
- out_h.write( ' /** == Constructor. */\n')
- out_h.write(f' FZ_FUNCTION {classname}2();\n')
- out_cpp.write('\n')
- out_cpp.write(f'FZ_FUNCTION {classname}2::{classname}2()\n')
- out_cpp.write( '{\n')
- alloc = [''] + alloc.split('\n')
- alloc = '\n '.join(alloc)
- out_cpp.write(f'{alloc}\n')
- out_cpp.write(f' {trace_if}\n')
- out_cpp.write(f' if (s_trace_director)\n')
- out_cpp.write( ' {\n')
- out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2::{classname}2(): this=" << this << "\\n";\n')
- if not extras.pod:
- out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2::{classname}2(): m_internal=" << m_internal << "\\n";\n')
- out_cpp.write(f' {classname}2* self = {self_("m_internal")};\n')
- out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2::{classname}2(): self=" << self << "\\n";\n')
- out_cpp.write(' }\n')
- out_cpp.write(' #endif\n')
- out_cpp.write( '}\n')
- # Destructor. This needs to be virtual with an empty implementation,
- # because instances will generally be derived classes.
- out_h.write( '\n')
- out_h.write( ' /** == Destructor. */\n')
- out_h.write(f' FZ_FUNCTION virtual ~{classname}2();\n')
- out_cpp.write('\n')
- out_cpp.write(f'FZ_FUNCTION {classname}2::~{classname}2()\n')
- out_cpp.write( '{\n')
- out_cpp.write(f' {trace_if}\n')
- out_cpp.write(f' if (s_trace_director)\n')
- out_cpp.write( ' {\n')
- out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": ~{classname}2(): this=" << this << "\\n";\n')
- if not extras.pod:
- out_cpp.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": ~{classname}2(): m_internal=" << m_internal << "\\n";\n')
- out_cpp.write( ' }\n')
- out_cpp.write(f' #endif\n')
- if free:
- out_cpp.write(f' {free}\n')
- out_cpp.write( '}\n')
- def write(text):
- out_h.write(text)
- out_cpp.write(text)
- # Define static callback for each fnptr. It's important that these
- # functions do not resolve function parameter typedefs such as size_t to
- # the underlying types such as long int, because:
- #
- # * Our generated code can be compiled on different machines where types
- # such as size_t can be typedef-ed differently.
- #
- # * Elsewhere, code that we generate will assign our static callback
- # functions to MuPDF's function pointers (which use things like size_t).
- #
- # * These assignments will fail if the types don't match exactly.
- #
- # For example fz_output has a member:
- # fz_output_write_fn *write;
- #
- # This typedef is:
- # void (fz_output_write_fn)(fz_context *ctx, void *state, const void *data, size_t n);
- #
- # We generate a static function called Output2_s_write() and we will be
- # setting a fz_output's write member to point to Output2_s_write(), which
- # only works if the types match exactly.
- #
- # So we need to resolve the outer 'typedef fz_output_write_fn', but not
- # the inner 'size_t' typedef for the <n> arg. This is slightly tricky with
- # clang-python - it provide a Type.get_canonical() method that resolves all
- # typedefs, but to resolve just one level of typedefs requires a bit more
- # work. See get_struct_fnptrs() for details.
- #
- # [Usually our generated code deliberately resolves typedefs such as size_t
- # to long int etc, because SWIG-generated code for size_t etc does not
- # always work properly due to SWIG having its own definitions of things
- # like size_t in Python/C#. But in this case the generated static function
- # is not seen by SWIG so it's ok to make it use size_t etc.]
- #
- for cursor, fnptr_type in get_fnptrs( shallow_typedef_expansion=True):
- # Write static callback.
- return_type = _make_top_level(fnptr_type.get_result().spelling)
- out_cpp.write(f'/* Static callback, calls self->{cursor.spelling}(). */\n')
- out_cpp.write(f'static {return_type} {classname}2_s_{cursor.spelling}')
- out_cpp.write('(')
- sep = ''
- for i, arg_type in enumerate( fnptr_type.argument_types()):
- name = f'arg_{i}'
- out_cpp.write(sep)
- out_cpp.write( declaration_text( arg_type, name, expand_typedef=False))
- sep = ', '
- out_cpp.write(')')
- out_cpp.write('\n')
- out_cpp.write('{\n')
- self_expression = self_() if self_n is None else self_( f'arg_{self_n}')
- out_cpp.write(f' {classname}2* self = {self_expression};\n')
- out_cpp.write(f' {trace_if}\n')
- out_cpp.write(f' if (s_trace_director)\n')
- out_cpp.write( ' {\n')
- out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2_s_{cursor.spelling}(): arg_0=" << arg_0 << " arg_1=" << arg_1 << " self=" << self << "\\n";\n')
- out_cpp.write( ' }\n')
- out_cpp.write( ' #endif\n')
- out_cpp.write( ' {\n')
- out_cpp.write( ' char error_message[256] = "";\n')
- out_cpp.write( ' try\n')
- out_cpp.write( ' {\n')
- out_cpp.write(f' return self->{cursor.spelling}(')
- sep = ''
- for i, arg_type in enumerate( fnptr_type.argument_types()):
- if i == self_n:
- # This is the void* from which we found `self` so ignore
- # here. Note that we still pass the fz_context to the virtual
- # fn.
- continue
- name = f'arg_{i}'
- out_cpp.write( f'{sep}{name}')
- sep = ', '
- out_cpp.write(');\n')
- out_cpp.write(' }\n')
- # todo: catch our different exception types and map to FZ_ERROR_*.
- out_cpp.write( ' catch (std::exception& e)\n')
- out_cpp.write( ' {\n')
- out_cpp.write(f' {trace_if}\n')
- out_cpp.write( ' if (s_trace_director)\n')
- out_cpp.write( ' {\n')
- out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2_s_{cursor.spelling}(): converting std::exception to fz_throw(): " << e.what() << "\\n";\n')
- out_cpp.write( ' }\n')
- out_cpp.write( ' #endif\n')
- out_cpp.write( ' fz_strlcpy(error_message, e.what(), sizeof(error_message));\n')
- out_cpp.write( ' }\n')
- out_cpp.write( ' /* We defer fz_throw() to here, to ensure that `std::exception& e` has been destructed. */\n')
- out_cpp.write( ' fz_throw(arg_0, FZ_ERROR_GENERIC, "%s", error_message);\n')
- if return_type != 'void':
- out_cpp.write(f' /* Keep compiler happy. */\n')
- out_cpp.write(f' {return_type} ret;\n')
- out_cpp.write(f' return ret;\n')
- out_cpp.write( ' }\n')
- out_cpp.write('}\n')
- # Define use_virtual_<name>( bool use) method for each fnptr.
- #
- out_h.write(f'\n')
- # Using a Doxygen-style `/**` comment prefix here can break swig with
- # `Error: Syntax error in input(3).` if there are no following method
- # declarations.
- out_h.write(f' /** These methods set the function pointers in *m_internal\n')
- out_h.write(f' to point to internal callbacks that call our virtual methods. */\n')
- for cursor, fnptr_type in get_fnptrs():
- out_h.write(f' FZ_FUNCTION void use_virtual_{cursor.spelling}( bool use=true);\n')
- out_cpp.write(f'FZ_FUNCTION void {classname}2::use_virtual_{cursor.spelling}( bool use)\n')
- out_cpp.write( '{\n')
- out_cpp.write(f' {trace_if}\n')
- out_cpp.write(f' if (s_trace_director)\n')
- out_cpp.write( ' {\n')
- out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2::use_virtual_{cursor.spelling}(): this=" << this << " use=" << use << "\\n";\n')
- out_cpp.write( ' }\n')
- out_cpp.write( ' #endif\n')
- if extras.pod == 'inline':
- # Fnptr (in {classname}2) and virtual function (in {classname})
- # have same name, so we need qualify the fnptr with {classname} to
- # ensure we distinguish between the two.
- out_cpp.write(f' {classname}::{cursor.spelling} = (use) ? {classname}2_s_{cursor.spelling} : nullptr;\n')
- elif extras.pod:
- out_cpp.write(f' m_internal.{cursor.spelling} = (use) ? {classname}2_s_{cursor.spelling} : nullptr;\n')
- else:
- out_cpp.write(f' m_internal->{cursor.spelling} = (use) ? {classname}2_s_{cursor.spelling} : nullptr;\n')
- out_cpp.write( '}\n')
- # Write virtual fn default implementations.
- #
- out_h.write(f'\n')
- # Using a Doxygen-style `/**` comment prefix here can break swig with
- # `Error: Syntax error in input(3).` if there are no following method
- # declarations.
- out_h.write(f' /** Default virtual method implementations; these all throw an exception. */\n')
- for cursor, fnptr_type in get_fnptrs():
- out_h.write(f' FZ_FUNCTION virtual {_make_top_level(fnptr_type.get_result().spelling)} {cursor.spelling}(')
- out_cpp.write(f'/* Default implementation of virtual method. */\n')
- out_cpp.write(f'FZ_FUNCTION {_make_top_level(fnptr_type.get_result().spelling)} {classname}2::{cursor.spelling}(')
- sep = ''
- for i, arg_type in enumerate( fnptr_type.argument_types()):
- if i == self_n:
- # This is the void* from which we found `self` so ignore
- # here. Note that we still pass the fz_context to the virtual
- # fn.
- continue
- name = f'arg_{i}'
- write(f'{sep}')
- decl_text = declaration_text(arg_type, name, verbose=0)
- write(decl_text)
- sep = ', '
- out_h.write( ');\n')
- out_cpp.write( ')\n')
- out_cpp.write( '{\n')
- out_cpp.write(f' std::cerr << "Unexpected call of unimplemented virtual_fnptrs fn {classname}2::{cursor.spelling}(): this=" << this << ".\\n";\n')
- out_cpp.write(f' throw std::runtime_error( "Unexpected call of unimplemented virtual_fnptrs fn {classname}2::{cursor.spelling}()");\n')
- out_cpp.write( '}\n')
- out_h.write( '};\n')
- def class_wrapper(
- tu,
- register_fn_use,
- struct_cursor,
- struct_name,
- classname,
- extras,
- out_h,
- out_cpp,
- out_h_end,
- out_cpp2,
- out_h2,
- generated,
- refcheck_if,
- trace_if,
- ):
- '''
- Creates source for a class called <classname> that wraps <struct_name>,
- with methods that call selected fz_*() functions. Writes to out_h and
- out_cpp.
- Created source is just the per-class code, e.g. it does not contain
- #include's.
- Args:
- tu:
- Clang translation unit.
- struct_cursor:
- Cursor for struct to wrap.
- struct_name:
- Name of struct to wrap.
- classname:
- Name of wrapper class to create.
- out_h:
- Stream to which we write class definition.
- out_cpp:
- Stream to which we write method implementations.
- out_h_end:
- Stream for text that should be put at the end of the generated
- header text.
- generated:
- We write extra python and C# code to generated.out_swig_python and
- generated.out_swig_csharp for use in the swig .i file.
- Returns (is_container, has_to_string). <is_container> is true if generated
- class has custom begin() and end() methods; <has_to_string> is true if we
- have created a to_string() method.
- '''
- assert extras, f'extras is None for {struct_name}'
- if extras.iterator_next:
- class_add_iterator( tu, struct_cursor, struct_name, classname, extras, refcheck_if, trace_if)
- if extras.class_pre:
- out_h.write( textwrap.dedent( extras.class_pre))
- base_name = util.clip( struct_name, ('fz_', 'pdf_'))
- constructor_fns = class_find_constructor_fns( tu, classname, struct_name, base_name, extras)
- for fnname in extras.constructors_wrappers:
- cursor = state.state_.find_function( tu, fnname, method=True)
- assert cursor, f'No cursor for constructor wrapper fnname={fnname}'
- constructor_fns.append( (fnname, cursor, None))
- destructor_fns = class_find_destructor_fns( tu, struct_name, base_name)
- # Class definition beginning.
- #
- out_h.write( '\n')
- if extras.copyable:
- out_h.write( f'/** Wrapper class for struct `{struct_name}`. */\n')
- else:
- out_h.write( f'/** Wrapper class for struct `{struct_name}`. Not copyable or assignable. */\n')
- if struct_cursor.raw_comment:
- raw_comment = struct_cursor.raw_comment.replace('\r', '')
- out_h.write(raw_comment)
- if not raw_comment.endswith( '\n'):
- out_h.write( '\n')
- out_h.write( f'struct {classname}\n{{')
- out_cpp.write( '\n')
- out_cpp.write( f'/* Implementation of methods for {classname} (wrapper for {struct_name}). */\n')
- out_cpp.write( '\n')
- refs = parse.has_refs( tu, struct_cursor.type)
- if refs:
- refs_name, refs_size = refs
- out_cpp.write( f'{refcheck_if}\n')
- if isinstance(refs_name, int):
- # <refs_name> is offset of .refs in the struct.
- allow_int_this = ', true /*allow_int_this*/' if struct_name == 'pdf_obj' else ''
- out_cpp.write( f'static RefsCheck<::{struct_name}, {classname}{allow_int_this}> s_{classname}_refs_check({refs_name}, {refs_size});\n')
- else:
- # <refs_name> is name of .refs in the struct.
- out_cpp.write( f'static RefsCheck<::{struct_name}, {classname}> s_{classname}_refs_check(offsetof(::{struct_name}, {refs_name}), {refs_size});\n')
- out_cpp.write( f'#endif\n')
- out_cpp.write( '\n')
- # Trailing text in header, e.g. typedef for iterator.
- #
- if extras.class_top:
- # Strip leading blank line to avoid slightly odd-looking layout.
- text = util.clip( extras.class_top, '\n')
- text = textwrap.dedent( text)
- text = textwrap.indent( text, ' ')
- out_h.write( '\n')
- out_h.write( text)
- # Constructors
- #
- num_constructors = 0
- have_created_default_constructor = False
- if constructor_fns:
- out_h.write( '\n')
- out_h.write( ' /** == Constructors. */\n')
- num_constructors += len(constructor_fns)
- for fnname, cursor, duplicate_type in constructor_fns:
- # clang-6 appears not to be able to handle fn args that are themselves
- # function pointers, so for now we allow function_wrapper() to fail,
- # so we need to use temporary buffers, otherwise out_functions_h and
- # out_functions_cpp can get partial text written.
- #
- assert cursor, f'No cursor for constructor function. fnname={fnname} duplicate_type={duplicate_type}'
- temp_out_h = io.StringIO()
- temp_out_cpp = io.StringIO()
- if state.state_.show_details(fnname):
- jlib.log('Creating constructor for {=classname fnname}')
- if parse.get_first_arg( tu, cursor) == (None, 0):
- have_created_default_constructor = True
- try:
- function_wrapper_class_aware(
- tu,
- register_fn_use,
- fnname,
- temp_out_h,
- temp_out_cpp,
- struct_name,
- classname,
- cursor,
- refcheck_if,
- trace_if,
- class_static=False,
- class_constructor=True,
- extras=extras,
- struct_cursor=struct_cursor,
- duplicate_type=duplicate_type,
- )
- except Clang6FnArgsBug as e:
- jlib.log( 'Unable to wrap function {fnname} because: {e}')
- else:
- out_h.write( temp_out_h.getvalue())
- out_cpp.write( temp_out_cpp.getvalue())
- # Custom constructors.
- #
- for extra_constructor in extras.constructors_extra:
- if extra_constructor.name_args == '()':
- have_created_default_constructor = True
- class_custom_method(
- tu,
- register_fn_use,
- struct_cursor,
- classname,
- extra_constructor,
- out_h,
- out_cpp,
- refcheck_if,
- trace_if,
- )
- num_constructors += 1
- # Look for function that can be used by copy constructor and operator=.
- #
- if refs:
- assert extras.copyable != 'default', f'Non-POD class for {struct_name=} has refs, so must not use copyable="default"'
- if not extras.pod and extras.copyable and extras.copyable != 'default':
- class_copy_constructor(
- tu,
- register_fn_use,
- struct_name,
- struct_cursor,
- base_name,
- classname,
- constructor_fns,
- out_h,
- out_cpp,
- refcheck_if,
- trace_if,
- )
- elif extras.copyable:
- out_h.write( '\n')
- out_h.write( ' /** We use default copy constructor and operator=. */\n')
- if extras.constructor_default:
- if have_created_default_constructor:
- if 0:
- jlib.log( 'Not creating default constructor because default custom constructor. {struct_name=}')
- elif extras.constructor_raw == 'default':
- if 0:
- jlib.log( 'Not creating default constructor because default raw constructor. {struct_name=}')
- else:
- class_constructor_default(
- tu,
- struct_cursor,
- classname,
- extras,
- out_h,
- out_cpp,
- refcheck_if,
- trace_if,
- )
- num_constructors += 1
- # Auto-add all methods that take <struct_name> as first param, but
- # skip methods that are already wrapped in extras.method_wrappers or
- # extras.methods_extra etc.
- #
- for fnname in parse.find_wrappable_function_with_arg0_type( tu, struct_name):
- if state.state_.show_details(fnname):
- jlib.log('{struct_name=}: looking at potential method wrapping {fnname=}')
- if fnname in extras.method_wrappers:
- #log( 'auto-detected fn already in {struct_name} method_wrappers: {fnname}')
- # Omit this function, because there is an extra method with the
- # same name. (We could probably include both as they will generally
- # have different args so overloading will distinguish them, but
- # extra methods are usually defined to be used in preference.)
- pass
- elif fnname.startswith( 'fz_new_draw_device'):
- # fz_new_draw_device*() functions take first arg fz_matrix, but
- # aren't really appropriate for the fz_matrix wrapper class.
- #
- pass
- elif isinstance( fnname, list):
- assert 0
- else:
- for extramethod in extras.methods_extra:
- if not extramethod.overload:
- if extramethod.name_args.startswith( f'{rename.method( struct_name, fnname)}('):
- jlib.log1( 'Omitting default method because same name as extramethod: {extramethod.name_args}')
- break
- else:
- #log( 'adding to extras.method_wrappers: {fnname}')
- extras.method_wrappers.append( fnname)
- # Extra static methods.
- #
- if extras.method_wrappers_static:
- out_h.write( '\n')
- out_h.write( ' /* == Static methods. */\n')
- for fnname in extras.method_wrappers_static:
- function_wrapper_class_aware(
- tu,
- register_fn_use,
- fnname,
- out_h,
- out_cpp,
- struct_name,
- classname,
- fn_cursor=None,
- refcheck_if=refcheck_if,
- trace_if=trace_if,
- class_static=True,
- struct_cursor=struct_cursor,
- generated=generated,
- )
- # Extra methods that wrap fz_*() fns.
- #
- if extras.method_wrappers or extras.methods_extra:
- out_h.write( '\n')
- out_h.write( ' /* == Methods. */')
- out_h.write( '\n')
- extras.method_wrappers.sort()
- for fnname in extras.method_wrappers:
- function_wrapper_class_aware(
- tu,
- register_fn_use,
- fnname,
- out_h,
- out_cpp,
- struct_name,
- classname,
- None, #fn_cursor
- refcheck_if,
- trace_if,
- struct_cursor=struct_cursor,
- generated=generated,
- debug=state.state_.show_details(fnname),
- )
- # Custom methods.
- #
- is_container = 0
- custom_destructor = False
- for extramethod in extras.methods_extra:
- is_constructor, is_destructor, is_begin_end = class_custom_method(
- tu,
- register_fn_use,
- struct_cursor,
- classname,
- extramethod,
- out_h,
- out_cpp,
- refcheck_if,
- trace_if,
- )
- if is_constructor:
- num_constructors += 1
- if is_destructor:
- custom_destructor = True
- if is_begin_end:
- is_container += 1
- assert is_container==0 or is_container==2, f'struct_name={struct_name} is_container={is_container}' # Should be begin()+end() or neither.
- if is_container:
- pass
- #jlib.log( 'Generated class has begin() and end(): {classname=}')
- if num_constructors == 0 or extras.constructor_raw:
- if state.state_.show_details(struct_name):
- jlib.log('calling class_raw_constructor(). {struct_name=}')
- class_raw_constructor(
- tu,
- register_fn_use,
- classname,
- struct_cursor,
- struct_name,
- base_name,
- extras,
- constructor_fns,
- out_h,
- out_cpp,
- refcheck_if,
- trace_if,
- )
- # Accessor methods to POD data.
- #
- if extras.accessors and extras.pod == 'inline':
- jlib.log( 'ignoring {extras.accessors=} for {struct_name=} because {extras.pod=}.')
- elif extras.accessors:
- out_h.write( f'\n')
- out_h.write( f' /* == Accessors to members of ::{struct_name} m_internal. */\n')
- out_h.write( '\n')
- class_accessors(
- tu,
- register_fn_use,
- classname,
- struct_cursor,
- struct_name,
- extras,
- out_h,
- out_cpp,
- )
- # Destructor.
- #
- if not custom_destructor:
- out_h.write( f'\n')
- class_destructor(
- tu,
- register_fn_use,
- classname,
- extras,
- struct_cursor,
- destructor_fns,
- out_h,
- out_cpp,
- refcheck_if,
- trace_if,
- )
- # If class has '{structname}* m_internal;', provide access to m_iternal as
- # an integer, for use by python etc, and provide `operator bool()`.
- if not extras.pod:
- class_custom_method(
- tu,
- register_fn_use,
- struct_cursor,
- classname,
- classes.ExtraMethod(
- 'long long',
- 'm_internal_value()',
- '''
- {
- return (uintptr_t) m_internal;
- }
- ''',
- '/** Return numerical value of .m_internal; helps with Python debugging. */',
- ),
- out_h,
- out_cpp,
- refcheck_if,
- trace_if,
- )
- class_custom_method(
- tu,
- register_fn_use,
- struct_cursor,
- classname,
- classes.ExtraMethod(
- '',
- 'operator bool()',
- f'''
- {{
- {trace_if}
- if (s_trace)
- {{
- std::cerr << __FILE__ << ":" << __LINE__ << ":"
- << " {classname}::operator bool() called,"
- << " m_internal=" << m_internal << "."
- << "\\n";
- }}
- #endif
- return m_internal ? true : false;
- }}
- ''',
- '/** Return true iff `m_internal` is not null. */',
- ),
- out_h,
- out_cpp,
- refcheck_if,
- trace_if,
- )
- # Class members.
- #
- out_h.write( '\n')
- out_h.write( ' /* == Member data. */\n')
- out_h.write( '\n')
- if extras.pod == 'none':
- pass
- elif extras.pod == 'inline':
- out_h.write( f' /* These members are the same as the members of ::{struct_name}. */\n')
- for c in parse.get_members(struct_cursor):
- out_h.write( f' {declaration_text(c.type, c.spelling)};\n')
- elif extras.pod:
- out_h.write( f' ::{struct_cursor.spelling} m_internal; /** Wrapped data is held by value. */\n')
- else:
- # Putting this double-asterix comment on same line as m_internal breaks
- # swig-4.02 with "Error: Syntax error in input(3).".
- out_h.write( f' /** Pointer to wrapped data. */\n')
- out_h.write( f' ::{struct_name}* m_internal;\n')
- # Declare static `num_instances` variable.
- out_h.write( '\n')
- out_h.write(f' /* Ideally this would be in `{refcheck_if}...#endif`, but Swig will\n')
- out_h.write(f' generate code regardless so we always need to have this available. */\n')
- out_h.write(f' FZ_DATA static int s_num_instances;\n')
- out_cpp.write(f'/* Ideally this would be in `{refcheck_if}...#endif`, but Swig will\n')
- out_cpp.write(f'generate code regardless so we always need to have this available. */\n')
- out_cpp.write(f'int {classname}::s_num_instances = 0;\n')
- out_cpp.write(f'\n')
- # Make operator<< (std::ostream&, ...) for POD classes.
- #
- has_to_string = False
- if extras.pod and extras.pod != 'none':
- has_to_string = True
- pod_class_members(
- tu,
- classname,
- struct_cursor,
- struct_name,
- extras,
- out_h,
- out_cpp,
- )
- # Trailing text in header, e.g. typedef for iterator.
- #
- if extras.class_bottom:
- out_h.write( textwrap.indent( textwrap.dedent( extras.class_bottom), ' '))
- # Private copy constructor if not copyable.
- #
- if not extras.copyable:
- out_h.write( '\n')
- out_h.write( ' private:\n')
- out_h.write( '\n')
- out_h.write( ' /** This class is not copyable or assignable. */\n')
- out_h.write( f' {classname}(const {classname}& rhs);\n')
- out_h.write( f' {classname}& operator=(const {classname}& rhs);\n')
- # Class definition end.
- #
- out_h.write( '};\n')
- if extras.class_post:
- out_h_end.write( textwrap.dedent( extras.class_post))
- if extras.extra_cpp:
- out_cpp.write( f'/* .extra_cpp for {struct_name}. */\n')
- out_cpp.write( textwrap.dedent( extras.extra_cpp))
- class_wrapper_virtual_fnptrs(
- tu,
- struct_cursor,
- struct_name,
- classname,
- extras,
- out_h,
- out_cpp,
- out_h_end,
- generated,
- refcheck_if,
- trace_if,
- )
- return is_container, has_to_string
- def header_guard( name, out):
- '''
- Writes header guard for <name> to stream <out>.
- '''
- m = ''
- for c in name:
- m += c.upper() if c.isalnum() else '_'
- out.write( f'#ifndef {m}\n')
- out.write( f'#define {m}\n')
- out.write( '\n')
- def tabify( filename, text):
- '''
- Returns <text> with leading multiples of 4 spaces converted to tab
- characters.
- '''
- ret = ''
- linenum = 0
- for line in text.split( '\n'):
- linenum += 1
- i = 0
- while 1:
- if i == len(line):
- break
- if line[i] != ' ':
- break
- i += 1
- if i % 4:
- if line[ int(i/4)*4:].startswith( ' *'):
- # This can happen in comments.
- pass
- else:
- jlib.log( '*** {filename}:{linenum}: {i=} {line!r=} indentation is not a multiple of 4')
- num_tabs = int(i / 4)
- ret += num_tabs * '\t' + line[ num_tabs*4:] + '\n'
- # We use [:-1] here because split() always returns extra last item '', so
- # we will have appended an extra '\n'.
- #
- return ret[:-1]
- def refcount_check_code( out, refcheck_if):
- '''
- Writes reference count checking code to <out>.
- '''
- out.write( textwrap.dedent(
- f'''
- /* Support for checking that reference counts of underlying
- MuPDF structs are not smaller than the number of wrapper class
- instances. Enable at runtime by setting environmental variable
- MUPDF_check_refs to "1". */
- static const bool s_check_refs = internal_env_flag("MUPDF_check_refs");
- /* For each MuPDF struct that has an 'int refs' member, we create
- a static instance of this class template with T set to our wrapper
- class, for example:
- static RefsCheck<fz_document, FzDocument> s_FzDocument_refs_check;
- Then if s_check_refs is true, each constructor function calls
- .add(), the destructor calls .remove() and other class functions
- call .check(). This ensures that we check reference counting after
- each class operation.
- If <allow_int_this> is true, we allow _this->m_internal to be
- an invalid pointer less than 4096, in which case we don't try
- to check refs. This is used for pdf_obj because in Python the
- enums PDF_ENUM_NAME_* are converted to mupdf.PdfObj's contain
- .m_internal's which are the enum values cast to (for_pdf_obj*), so
- that they can be used directly.
- If m_size is -1, we don't attempt any checking; this is for fz_xml
- which is reference counted but does not have a simple .refs member.
- */
- {refcheck_if}
- template<typename Struct, typename ClassWrapper, bool allow_int_this=false>
- struct RefsCheck
- {{
- std::mutex m_mutex;
- int m_offset;
- int m_size;
- std::map<Struct*, int> m_this_to_num;
- RefsCheck(int offset, int size)
- : m_offset(offset), m_size(size)
- {{
- assert(offset >= 0 && offset < 1000);
- assert(m_size == 32 || m_size == 16 || m_size == 8 || m_size == -1);
- }}
- void change( const ClassWrapper* this_, const char* file, int line, const char* fn, int delta)
- {{
- assert( s_check_refs);
- if (m_size == -1)
- {{
- /* No well-defined .refs member for us to check, e.g. fz_xml. */
- return;
- }}
- if (!this_->m_internal) return;
- if (allow_int_this)
- {{
- #if 0 // Historic diagnostics, might still be useful.
- std::cerr << __FILE__ << ":" << __LINE__
- << " " << file << ":" << line << ":" << fn << ":"
- << " this_->m_internal=" << this_->m_internal
- << "\\n";
- #endif
- if ((intptr_t) this_->m_internal < 4096)
- {{
- #if 0 // Historic diagnostics, might still be useful.
- std::cerr << __FILE__ << ":" << __LINE__
- << " " << file << ":" << line << ":" << fn << ":"
- << " Ignoring this_->m_internal=" << this_->m_internal
- << "\\n";
- #endif
- return;
- }}
- }}
- std::lock_guard< std::mutex> lock( m_mutex);
- /* Our lock doesn't make our access to
- this_->m_internal->refs thead-safe - other threads
- could be modifying it via fz_keep_<Struct>() or
- fz_drop_<Struct>(). But hopefully our read will be atomic
- in practise anyway? */
- void* refs_ptr = (char*) this_->m_internal + m_offset;
- int refs;
- if (m_size == 32) refs = *(int32_t*) refs_ptr;
- if (m_size == 16) refs = *(int16_t*) refs_ptr;
- if (m_size == 8) refs = *(int8_t* ) refs_ptr;
- int& n = m_this_to_num[ this_->m_internal];
- int n_prev = n;
- assert( n >= 0);
- n += delta;
- #if 0 // Historic diagnostics, might still be useful.
- std::cerr << file << ":" << line << ":" << fn << "():"
- // << " " << typeid(ClassWrapper).name() << ":"
- << " this_=" << this_
- << " this_->m_internal=" << this_->m_internal
- << " refs=" << refs
- << " n: " << n_prev << " => " << n
- << "\\n";
- #endif
- if ( n < 0)
- {{
- #if 0 // Historic diagnostics, might still be useful.
- std::cerr << file << ":" << line << ":" << fn << "():"
- // << " " << typeid(ClassWrapper).name() << ":"
- << " this_=" << this_
- << " this_->m_internal=" << this_->m_internal
- << " bad n: " << n_prev << " => " << n
- << "\\n";
- #endif
- abort();
- }}
- if ( n && refs < n)
- {{
- #if 0 // Historic diagnostics, might still be useful.
- std::cerr << file << ":" << line << ":" << fn << "():"
- // << " " << typeid(ClassWrapper).name() << ":"
- << " this_=" << this_
- << " this_->m_internal=" << this_->m_internal
- << " refs=" << refs
- << " n: " << n_prev << " => " << n
- << " refs mismatch (refs<n):"
- << "\\n";
- #endif
- abort();
- }}
- if (n && ::abs( refs - n) > 1000)
- {{
- /* This traps case where n > 0 but underlying struct is
- freed and .ref is set to bogus value by fz_free() or
- similar. */
- #if 0 // Historic diagnostics, might still be useful.
- std::cerr << file << ":" << line << ":" << fn << "(): " << ": " << typeid(ClassWrapper).name()
- << " bad change to refs."
- << " this_=" << this_
- << " refs=" << refs
- << " n: " << n_prev << " => " << n
- << "\\n";
- #endif
- abort();
- }}
- if (n == 0) m_this_to_num.erase( this_->m_internal);
- }}
- void add( const ClassWrapper* this_, const char* file, int line, const char* fn)
- {{
- change( this_, file, line, fn, +1);
- }}
- void remove( const ClassWrapper* this_, const char* file, int line, const char* fn)
- {{
- change( this_, file, line, fn, -1);
- }}
- void check( const ClassWrapper* this_, const char* file, int line, const char* fn)
- {{
- change( this_, file, line, fn, 0);
- }}
- }};
- #endif
- '''
- ))
- def cpp_source(
- dir_mupdf,
- namespace,
- base,
- header_git,
- generated,
- check_regress,
- clang_info_version,
- refcheck_if,
- trace_if,
- debug,
- ):
- '''
- Generates all .h and .cpp files.
- Args:
- dir_mupdf:
- Location of mupdf checkout.
- namespace:
- C++ namespace to use.
- base:
- Directory in which all generated files are placed.
- header_git:
- If true we include git info in the file comment that is written
- into all generated files.
- generated:
- A Generated instance.
- check_regress:
- If true, we raise exception if generated content differs from what
- is in existing files.
- refcheck_if:
- `#if ... ' text for enabling reference-checking code. For example
- `#if 1` to always enable, `#ifndef NDEBUG` to only enable in debug
- builds, `#if 0` to always disable.
- refcheck_if:
- `#if ... ' text for enabling optional runtime diagnostic, for
- example by setting `MuPDF_trace=1` runtime. For example `#if 1` to
- always enable, `#ifndef NDEBUG` to only enable in debug builds,
- `#if 0` to always disable.
- debug:
- True if debug build.
- Updates <generated> and returns <tu> from clang..
- '''
- assert isinstance(generated, Generated)
- assert not dir_mupdf.endswith( '/')
- assert not base.endswith( '/')
- # Do initial setting up of generated files before parse, because we include extra.h in our parse input.
- doit = True
- if doit:
- class File:
- def __init__( self, filename, tabify=True):
- self.filename = filename
- self.tabify = tabify
- self.file = io.StringIO()
- self.line_begin = True
- self.regressions = True
- self.closed = False
- def write( self, text, fileline=False):
- # Do not allow writes after .close().
- assert not self.closed, f'File.write() called after .close(). {self.filename=}'
- if fileline:
- # Generate #line <line> "<filename>" for our caller's
- # location. This makes any compiler warnings refer to their
- # python code rather than the generated C++ code.
- tb = traceback.extract_stack( None)
- filename, line, function, source = tb[0]
- if self.line_begin:
- self.file.write( f'#line {line} "{filename}"\n')
- self.file.write( text)
- self.line_begin = text.endswith( '\n')
- def close( self):
- if self.closed:
- # Allow multiple calls to .close().
- return
- self.closed = True
- if self.filename:
- # Overwrite if contents differ.
- text = self.get()
- if self.tabify:
- text = tabify( self.filename, text)
- cr = check_regress
- jlib.log('calling util.update_file_regress() check_regress={cr}: {self.filename=}', 1)
- e = util.update_file_regress( text, self.filename, check_regression=cr)
- jlib.log('util.update_file_regress() returned => {e}', 1)
- if e:
- jlib.log('util.update_file_regress() => {e=}', 1)
- self.regressions = True
- jlib.log(f'File updated: {os.path.relpath(self.filename)}')
- else:
- jlib.log(f'File unchanged: {os.path.relpath(self.filename)}')
- def get( self):
- return self.file.getvalue()
- else:
- class File:
- def __init__( self, filename):
- pass
- def write( self, text, fileline=False):
- pass
- def close( self):
- pass
- class Outputs:
- '''
- A set of output files.
- For convenience, after outputs.add( 'foo', 'foo.c'), outputs.foo is a
- python stream that writes to 'foo.c'.
- '''
- def __init__( self):
- self.items = []
- def add( self, name, filename):
- '''
- Sets self.<name> to file opened for writing on <filename>.
- '''
- file = File( filename)
- self.items.append( (name, filename, file))
- setattr( self, name, file)
- def get( self):
- '''
- Returns list of (name, filename, file) tuples.
- '''
- return self.items
- def close( self):
- for name, filename, file in self.items:
- file.close()
- out_cpps = Outputs()
- out_hs = Outputs()
- for name in (
- 'classes',
- 'classes2',
- 'exceptions',
- 'functions',
- 'internal',
- 'extra',
- ):
- out_hs.add( name, f'{base}/include/mupdf/{name}.h')
- out_cpps.add( name, f'{base}/implementation/{name}.cpp')
- # Make text of header comment for all generated file.
- #
- header_text = textwrap.dedent(
- f'''
- /**
- This file was auto-generated by mupdfwrap.py.
- ''')
- if header_git:
- git_id = jlib.get_git_id( dir_mupdf, allow_none=True)
- if git_id:
- git_id = git_id.split('\n', 1)
- header_text += textwrap.dedent(
- f'''
- mupdf checkout:
- {git_id[0]}'
- ''')
- header_text += '*/\n'
- header_text += '\n'
- header_text = header_text[1:] # Strip leading \n.
- for _, _, file in out_cpps.get() + out_hs.get():
- file.write( header_text)
- os.makedirs( f'{base}/include/mupdf', exist_ok=True)
- os.makedirs( f'{base}/implementation', exist_ok=True)
- num_regressions = 0
- # Create extra File that writes to internal buffer rather than an actual
- # file, which we will append to out_h.
- #
- out_h_classes_end = File( None)
- # Write multiple-inclusion guards into headers:
- #
- for name, filename, file in out_hs.get():
- prefix = f'{base}/include/'
- assert filename.startswith( prefix)
- name = filename[ len(prefix):]
- header_guard( name, file)
- # We need to write to out_hs.extra here before we do the parse
- # because out_hs.extra will be part of the input text passed to the
- # clang parser.
- #
- make_extra(out_hs.extra, out_cpps.extra)
- out_hs.extra.write( textwrap.dedent('''
- #endif
- '''))
- out_hs.extra.close()
- out_cpps.extra.close()
- # Now parse.
- #
- try:
- index = state.clang.cindex.Index.create()
- except Exception as e:
- raise Exception(f'libclang does not appear to be installed') from e
- header = f'{dir_mupdf}/include/mupdf/fitz.h'
- assert os.path.isfile( header), f'header={header}'
- # Get clang to parse mupdf/fitz.h and mupdf/pdf.h and mupdf/extra.h.
- #
- # It might be possible to use index.parse()'s <unsaved_files> arg to
- # specify these multiple files, but i couldn't get that to work.
- #
- # So instead we write some #include's to a temporary file and ask clang to
- # parse it.
- #
- temp_h = f'_mupdfwrap_temp.cpp'
- try:
- with open( temp_h, 'w') as f:
- if state.state_.linux or state.state_.macos:
- jlib.log1('Prefixing Fitz headers with `typedef unsigned long size_t;`'
- ' because size_t not available to clang on Linux/MacOS.')
- # On Linux, size_t is defined internally in gcc (e.g. not even
- # in /usr/include/stdint.h) and so not visible to clang.
- #
- # If we don't define it, clang complains about C99 not
- # supporting implicit int and appears to variously expand
- # size_t as different function pointers, e.g. `int (int *)` and
- # `int (*)(int *)`.
- #
- f.write( textwrap.dedent('''
- /*
- Workaround on Linux/MacOS. size_t is defined internally in
- gcc (e.g. not even in /usr/include/stdint.h) and so not visible to clang.
- */
- typedef unsigned long size_t;
- '''))
- if state.state_.macos:
- f.write( textwrap.dedent('''
- /*
- Workaround on MacOS: we need to define fixed-size int types
- and FILE and va_list, similarly as with size_t above.
- */
- typedef signed char int8_t;
- typedef short int16_t;
- typedef int int32_t;
- typedef long long int64_t;
- typedef unsigned char uint8_t;
- typedef unsigned short uint16_t;
- typedef unsigned int uint32_t;
- typedef unsigned long long uint64_t;
- typedef struct FILE FILE;
- typedef struct va_list va_list;
- '''))
- f.write( textwrap.dedent('''
- #include "mupdf/extra.h"
- #include "mupdf/fitz.h"
- #include "mupdf/pdf.h"
- '''))
- # libclang often doesn't have access to system headers so we define
- # MUPDF_WRAP_LIBCLANG so that extra.h can use dummy definition of
- # std::vector.
- #
- args = [
- '-I', f'{dir_mupdf}/include',
- '-I', f'{dir_mupdf}/platform/c++/include',
- '-D', 'MUPDF_WRAP_LIBCLANG',
- '-D', 'FZ_FUNCTION=',
- ]
- tu = index.parse(
- temp_h,
- args = args,
- options = 0
- | state.clang.cindex.TranslationUnit.PARSE_INCOMPLETE
- | state.clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES
- ,
- )
- # Show warnings/errors from the parse. Failure to include stddef.h
- # appears to be harmless on Linux, but other failures seem to cause
- # more problems.
- #
- def show_clang_diagnostic( diagnostic, depth=0):
- for diagnostic2 in diagnostic.children:
- show_clang_diagnostic( diagnostic2, depth + 1)
- jlib.log1( '{" "*4*depth}{diagnostic}')
- if tu.diagnostics:
- jlib.log1( 'tu.diagnostics():')
- for diagnostic in tu.diagnostics:
- show_clang_diagnostic(diagnostic, 1)
- finally:
- if os.path.isfile( temp_h):
- os.remove( temp_h)
- # Write required #includes into .h files:
- #
- out_hs.exceptions.write( textwrap.dedent(
- '''
- #include <stdexcept>
- #include <string>
- #include "mupdf/fitz.h"
- '''))
- out_hs.internal.write( textwrap.dedent(
- '''
- #include <iostream>
- '''))
- out_hs.functions.write( textwrap.dedent(
- '''
- #include "mupdf/extra.h"
- #include "mupdf/fitz.h"
- #include "mupdf/pdf.h"
- #include <iostream>
- #include <string>
- #include <vector>
- '''))
- out_hs.classes.write( textwrap.dedent(
- '''
- #include "mupdf/fitz.h"
- #include "mupdf/functions.h"
- #include "mupdf/pdf.h"
- #include <map>
- #include <string>
- #include <vector>
- '''))
- out_hs.classes2.write( textwrap.dedent(
- '''
- #include "classes.h"
- '''))
- # Write required #includes into .cpp files:
- #
- out_cpps.exceptions.write( textwrap.dedent(
- f'''
- #include "mupdf/exceptions.h"
- #include "mupdf/fitz.h"
- #include "mupdf/internal.h"
- #include <iostream>
- #include <string.h>
- {trace_if}
- static const bool s_trace_exceptions = mupdf::internal_env_flag("MUPDF_trace_exceptions");
- #else
- static const bool s_trace_exceptions_dummy = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_exceptions");
- #endif
- '''))
- out_cpps.functions.write( textwrap.dedent(
- '''
- #include "mupdf/exceptions.h"
- #include "mupdf/functions.h"
- #include "mupdf/internal.h"
- #include "mupdf/extra.h"
- #include <assert.h>
- #include <sstream>
- #include <string.h>
- '''))
- out_cpps.classes.write(
- textwrap.dedent(
- f'''
- #include "mupdf/classes.h"
- #include "mupdf/classes2.h"
- #include "mupdf/exceptions.h"
- #include "mupdf/internal.h"
- #include "mupdf/fitz/geometry.h"
- #include <algorithm>
- #include <map>
- #include <mutex>
- #include <sstream>
- #include <string.h>
- #include <thread>
- #include <string.h>
- {trace_if}
- static const int s_trace = mupdf::internal_env_flag("MUPDF_trace");
- static const bool s_trace_keepdrop = mupdf::internal_env_flag("MUPDF_trace_keepdrop");
- static const bool s_trace_director = mupdf::internal_env_flag("MUPDF_trace_director");
- #else
- static const int s_trace = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace");
- static const bool s_trace_keepdrop = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_keepdrop");
- static const bool s_trace_director = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_director");
- #endif
- '''))
- out_cpps.classes2.write(
- textwrap.dedent(
- f'''
- #include "mupdf/classes2.h"
- #include "mupdf/exceptions.h"
- #include "mupdf/internal.h"
- #include "mupdf/fitz/geometry.h"
- #include <map>
- #include <mutex>
- #include <sstream>
- #include <string.h>
- #include <thread>
- #include <string.h>
- {trace_if}
- static const int s_trace = mupdf::internal_env_flag("MUPDF_trace");
- #else
- static const int s_trace = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace");
- #endif
- '''))
- namespace = 'mupdf'
- for _, _, file in out_cpps.get() + out_hs.get():
- if file in (out_cpps.internal, out_cpps.extra, out_hs.extra):
- continue
- make_namespace_open( namespace, file)
- # Write reference counting check code to out_cpps.classes.
- refcount_check_code( out_cpps.classes, refcheck_if)
- # Write declaration and definition for metadata_keys global.
- #
- out_hs.functions.write(
- textwrap.dedent(
- '''
- /*
- The keys that are defined for fz_lookup_metadata().
- */
- FZ_DATA extern const std::vector<std::string> metadata_keys;
- '''))
- out_cpps.functions.write(
- textwrap.dedent(
- f'''
- FZ_FUNCTION const std::vector<std::string> metadata_keys = {{
- "format",
- "encryption",
- "info:Title",
- "info:Author",
- "info:Subject",
- "info:Keywords",
- "info:Creator",
- "info:Producer",
- "info:CreationDate",
- "info:ModDate",
- }};
- {trace_if}
- static const int s_trace = internal_env_flag("MUPDF_trace");
- static const bool s_trace_keepdrop = internal_env_flag("MUPDF_trace_keepdrop");
- static const bool s_trace_exceptions = internal_env_flag("MUPDF_trace_exceptions");
- static const bool s_check_error_stack = internal_env_flag("MUPDF_check_error_stack");
- #else
- static const int s_trace = internal_env_flag_check_unset("{trace_if}", "MUPDF_trace");
- static const bool s_trace_keepdrop = internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_keepdrop");
- static const bool s_trace_exceptions = internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_exceptions");
- static const bool s_check_error_stack = internal_env_flag_check_unset("{trace_if}", "MUPDF_check_error_stack");
- #endif
- '''))
- # Write source code for exceptions and wrapper functions.
- #
- jlib.log( 'Creating wrapper functions...')
- make_function_wrappers(
- tu,
- namespace,
- out_hs.exceptions,
- out_cpps.exceptions,
- out_hs.functions,
- out_cpps.functions,
- out_hs.internal,
- out_cpps.internal,
- out_hs.classes2,
- out_cpps.classes2,
- generated,
- refcheck_if,
- trace_if,
- )
- fn_usage = dict()
- functions_unrecognised = set()
- for fnname, cursor in state.state_.find_functions_starting_with( tu, '', method=True):
- fn_usage[ fnname] = [0, cursor]
- generated.c_functions.append(fnname)
- for structname, cursor in state.state_.structs[ tu].items():
- generated.c_structs.append( structname)
- # Create windows_mupdf.def, containing explicit exports for all MuPDF
- # global data and functions. We do this instead of explicitly prefixing
- # everything with FZ_FUNCTION or FZ_DATA in the MuPDF header files.
- #
- windows_def_path = os.path.relpath(f'{base}/windows_mupdf.def')
- windows_def = ''
- windows_def += 'EXPORTS\n'
- for name, cursor in state.state_.find_global_data_starting_with( tu, ('fz_', 'pdf_')):
- if state.state_.show_details(name):
- jlib.log('global: {name=}')
- generated.c_globals.append(name)
- windows_def += f' {name} DATA\n'
- for fnname, cursor in state.state_.find_functions_starting_with( tu, ('fz_', 'pdf_', 'FT_'), method=False):
- if cursor.storage_class == state.clang.cindex.StorageClass.STATIC:
- # These fns do not work in windows.def, probably because they are
- # usually inline?
- #
- jlib.log('Not adding to windows_def because static: {fnname}()', 1)
- elif os.path.abspath(cursor.extent.start.file.name) == os.path.abspath(out_hs.extra.filename):
- # Items defined in out_hs.extra are C++ so we would need to use the
- # mangled name if we added them to windows_def. Instead they are
- # explicitly prefixed with `FZ_FUNCTION`.
- #
- # (We use os.path.abspath() to avoid problems with back and forward
- # slashes in cursor.extent.start.file.name on Windows.)
- #
- jlib.log1('Not adding to {windows_def_path} because defined in {os.path.relpath(out_hs.extra.filename)}: {cursor.spelling}')
- else:
- windows_def += f' {fnname}\n'
- # Add some internal fns that PyMuPDF requires.
- for fnname in (
- 'FT_Get_First_Char',
- 'FT_Get_Next_Char',
- ):
- windows_def += f' {fnname}\n'
- if debug:
- # In debug builds these are real fns, not macros, and we need to
- # make them exported.
- windows_def += f' fz_lock_debug_lock\n'
- windows_def += f' fz_lock_debug_unlock\n'
- jlib.fs_update( windows_def, windows_def_path)
- def register_fn_use( name):
- assert name.startswith( ('fz_', 'pdf_'))
- if name in fn_usage:
- fn_usage[ name][0] += 1
- else:
- functions_unrecognised.add( name)
- # Write source code for wrapper classes.
- #
- jlib.log( 'Creating wrapper classes...')
- # Find all classes that we can create.
- #
- classes_ = []
- for cursor in parse.get_children(tu.cursor):
- if not cursor.spelling.startswith( ('fz_', 'pdf_')):
- continue
- if cursor.kind != state.clang.cindex.CursorKind.TYPEDEF_DECL:
- continue;
- type_ = state.get_name_canonical( cursor.underlying_typedef_type)
- if type_.kind not in (state.clang.cindex.TypeKind.RECORD, state.clang.cindex.TypeKind.ELABORATED):
- continue
- if type_.kind == state.clang.cindex.TypeKind.ELABORATED:
- jlib.log( 'state.clang.cindex.TypeKind.ELABORATED: {type_.spelling=}')
- if not cursor.is_definition():
- # Handle abstract type only if we have an ClassExtra for it.
- extras = classes.classextras.get( tu, cursor.spelling)
- if extras and extras.opaque:
- pass
- #log( 'Creating wrapper for opaque struct: {cursor.spelling=}')
- else:
- continue
- #struct_name = type_.spelling
- struct_name = cursor.spelling
- struct_name = util.clip( struct_name, 'struct ')
- if cursor.spelling != struct_name:
- jlib.log('{type_.spelling=} {struct_name=} {cursor.spelling=}')
- classname = rename.class_( struct_name)
- # For some reason after updating mupdf 2020-04-13, clang-python is
- # returning two locations for struct fz_buffer_s, both STRUCT_DECL. One
- # is 'typedef struct fz_buffer_s fz_buffer;', the other is the full
- # struct definition.
- #
- # No idea why this is happening. Using .canonical doesn't seem to
- # affect things.
- #
- for cl, cu, s in classes_:
- if cl == classname:
- jlib.logx( 'ignoring duplicate STRUCT_DECL for {struct_name=}')
- break
- else:
- classes_.append( (classname, cursor, struct_name))
- classes_.sort()
- # Write forward declarations - this is required because some class
- # methods take pointers to other classes.
- #
- out_hs.classes.write( '\n')
- out_hs.classes.write( '/* Forward declarations of all classes that we define. */\n')
- for classname, struct_cursor, struct_name in classes_:
- out_hs.classes.write( f'struct {classname};\n')
- out_hs.classes.write( '\n')
- # Create each class.
- #
- for classname, struct_cursor, struct_name in classes_:
- #jlib.log( 'creating wrapper {classname} for {cursor.spelling}')
- extras = classes.classextras.get( tu, struct_name)
- assert extras, f'struct_name={struct_name}'
- if extras.pod:
- struct_to_string_fns(
- tu,
- struct_cursor,
- struct_name,
- extras,
- out_hs.functions,
- out_cpps.functions,
- )
- with jlib.LogPrefixScope( f'{struct_name}: '):
- is_container, has_to_string = class_wrapper(
- tu,
- register_fn_use,
- struct_cursor,
- struct_name,
- classname,
- extras,
- out_hs.classes,
- out_cpps.classes,
- out_h_classes_end,
- out_cpps.classes2,
- out_hs.classes2,
- generated,
- refcheck_if,
- trace_if,
- )
- if is_container:
- generated.container_classnames.append( classname)
- if has_to_string:
- generated.to_string_structnames.append( struct_name)
- out_hs.functions.write( textwrap.dedent( '''
- /** Reinitializes the MuPDF context for single-threaded use, which
- is slightly faster when calling code is single threaded.
- This should be called before any other use of MuPDF.
- */
- FZ_FUNCTION void reinit_singlethreaded();
- '''))
- # Generate num_instances diagnostic fn.
- out_hs.classes.write('\n')
- out_hs.classes.write('/** Returns map from class name (for example FzDocument) to s_num_instances. */\n')
- out_hs.classes.write('FZ_FUNCTION std::map<std::string, int> num_instances();\n')
- out_cpps.classes.write('FZ_FUNCTION std::map<std::string, int> num_instances()\n')
- out_cpps.classes.write('{\n')
- out_cpps.classes.write(' std::map<std::string, int> ret;\n')
- for classname, struct_cursor, struct_name in classes_:
- out_cpps.classes.write(f' ret["{classname}"] = {classname}::s_num_instances;\n')
- out_cpps.classes.write(' \n')
- out_cpps.classes.write(' return ret;\n')
- out_cpps.classes.write('}\n')
- # Write close of namespace.
- out_hs.classes.write( out_h_classes_end.get())
- for _, _, file in out_cpps.get() + out_hs.get():
- if file in (out_cpps.internal, out_cpps.extra, out_hs.extra):
- continue
- make_namespace_close( namespace, file)
- # Write pod struct fns such as operator<<(), operator==() - these need to
- # be outside the namespace.
- #
- for classname, struct_cursor, struct_name in classes_:
- extras = classes.classextras.get( tu, struct_name)
- if extras.pod:
- # Make operator<<(), operator==(), operator!=() for POD struct.
- #
- pod_struct_fns(
- tu,
- namespace,
- struct_cursor,
- struct_name,
- extras,
- out_hs.functions,
- out_cpps.functions,
- )
- if extras.pod != 'none':
- # Make operator<<(), operator==(), operator!=() for POD class
- # wrappers.
- #
- pod_class_fns(
- tu,
- classname,
- struct_cursor,
- struct_name,
- extras,
- out_hs.classes,
- out_cpps.classes,
- )
- # Terminate multiple-inclusion guards in headers:
- #
- for name, _, file in out_hs.get():
- if name != 'extra':
- file.write( '\n#endif\n')
- out_hs.close()
- out_cpps.close()
- generated.h_files = [filename for _, filename, _ in out_hs.get()]
- generated.cpp_files = [filename for _, filename, _ in out_cpps.get()]
- if 0: # lgtm [py/unreachable-statement]
- jlib.log( 'Have created:')
- for filename in filenames_h + filenames_cpp:
- jlib.log( ' {filename}')
- # Output usage information.
- #
- fn_usage_filename = f'{base}/fn_usage.txt'
- out_fn_usage = File( fn_usage_filename, tabify=False)
- functions_unused = 0
- functions_used = 0
- for fnname in sorted( fn_usage.keys()):
- n, cursor = fn_usage[ fnname]
- exclude_reasons = parse.find_wrappable_function_with_arg0_type_excluded_cache.get( fnname, [])
- if n:
- functions_used += 1
- else:
- functions_unused += 1
- if n and not exclude_reasons:
- continue
- out_fn_usage.write( f'Functions not wrapped by class methods:\n')
- out_fn_usage.write( '\n')
- for fnname in sorted( fn_usage.keys()):
- n, cursor = fn_usage[ fnname]
- exclude_reasons = parse.find_wrappable_function_with_arg0_type_excluded_cache.get( fnname, [])
- if not exclude_reasons:
- continue
- if n:
- continue
- num_interesting_reasons = 0
- for t, description in exclude_reasons:
- if t == parse.MethodExcludeReason_FIRST_ARG_NOT_STRUCT:
- continue
- if t == parse.MethodExcludeReason_VARIADIC:
- continue
- num_interesting_reasons += 1
- if num_interesting_reasons:
- try:
- out_fn_usage.write( f' {declaration_text( cursor.type, cursor.spelling)}\n')
- except Clang6FnArgsBug as e:
- out_fn_usage.write( f' {cursor.spelling} [full prototype not available due to known clang-6 issue]\n')
- for t, description in exclude_reasons:
- if t == parse.MethodExcludeReason_FIRST_ARG_NOT_STRUCT:
- continue
- out_fn_usage.write( f' {description}\n')
- out_fn_usage.write( '\n')
- out_fn_usage.write( f'\n')
- out_fn_usage.write( f'Functions used more than once:\n')
- for fnname in sorted( fn_usage.keys()):
- n, cursor = fn_usage[ fnname]
- if n > 1:
- out_fn_usage.write( f' n={n}: {declaration_text( cursor.type, cursor.spelling)}\n')
- out_fn_usage.write( f'\n')
- out_fn_usage.write( f'Number of wrapped functions: {len(fn_usage)}\n')
- out_fn_usage.write( f'Number of wrapped functions used by wrapper classes: {functions_used}\n')
- out_fn_usage.write( f'Number of wrapped functions not used by wrapper classes: {functions_unused}\n')
- out_fn_usage.close()
- generated.c_enums = state.state_.enums[ tu]
- if num_regressions:
- raise Exception( f'There were {num_regressions} regressions')
- return tu
- def test():
- '''
- Place to experiment with clang-python.
- '''
- text = ''
- if state.state_.linux:
- text += textwrap.dedent('''
- /*
- Workaround on Linux. size_t is defined internally in gcc. It isn't
- even in stdint.h.
- */
- typedef unsigned long size_t;
- ''')
- text += textwrap.dedent('''
- #include "mupdf/fitz.h"
- #include "mupdf/pdf.h"
- ''')
- path = 'wrap-test.c'
- jlib.fs_update( text, path)
- index = state.clang.cindex.Index.create()
- tu = index.parse( path, '-I /usr/include -I include'.split(' '))
- path2 = 'wrap-test.c.c'
- tu.save(path2)
- jlib.log( 'Have saved to: {path2}')
- parse.dump_ast( tu.cursor, 'ast')
- for diagnostic in tu.diagnostics:
- jlib.log('{diagnostic=}')
- for cursor in parse.get_members( tu.cursor):
- if 'cpp_test_' in cursor.spelling:
- parse.dump_ast(cursor, out=jlib.log)
|