Mercurial > hgrepos > Python2 > PyMuPDF
comparison mupdf-source/scripts/wrap/cpp.py @ 2:b50eed0cc0ef upstream
ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4.
The directory name has changed: no version number in the expanded directory now.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:43:07 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 1:1d09e1dec1d9 | 2:b50eed0cc0ef |
|---|---|
| 1 ''' | |
| 2 Functions for generating source code for the C++ bindings. | |
| 3 ''' | |
| 4 | |
| 5 import io | |
| 6 import os | |
| 7 import pickle | |
| 8 import re | |
| 9 import textwrap | |
| 10 | |
| 11 import jlib | |
| 12 | |
| 13 from . import classes | |
| 14 from . import csharp | |
| 15 from . import parse | |
| 16 from . import python | |
| 17 from . import rename | |
| 18 from . import state | |
| 19 from . import util | |
| 20 | |
| 21 | |
| 22 def _make_top_level( text, top_level='::'): | |
| 23 if text == 'string': | |
| 24 # This is a hack; for some reason we often end up with `string` when it | |
| 25 # it should be `std::string`. | |
| 26 text = 'std::string' | |
| 27 initial_prefix = [''] | |
| 28 def handle_prefix( text, prefix): | |
| 29 if text.startswith( prefix): | |
| 30 initial_prefix[0] += prefix | |
| 31 return text[ len(prefix):] | |
| 32 return text | |
| 33 text = handle_prefix( text, 'const ') | |
| 34 text = handle_prefix( text, 'struct ') | |
| 35 if text.startswith( ('fz_', 'pdf_')): | |
| 36 text = f'{top_level}{text}' | |
| 37 text = f'{initial_prefix[0]}{text}' | |
| 38 return text | |
| 39 | |
| 40 | |
| 41 def declaration_text( | |
| 42 type_, | |
| 43 name, | |
| 44 nest=0, | |
| 45 name_is_simple=True, | |
| 46 verbose=False, | |
| 47 expand_typedef=True, | |
| 48 top_level='::', | |
| 49 ): | |
| 50 ''' | |
| 51 Returns text for C++ declaration of <type_> called <name>. | |
| 52 | |
| 53 type: | |
| 54 a clang.cindex.Type. | |
| 55 name: | |
| 56 name of type; can be empty. | |
| 57 nest: | |
| 58 for internal diagnostics. | |
| 59 name_is_simple: | |
| 60 true iff <name> is an identifier. | |
| 61 | |
| 62 If name_is_simple is false, we surround <name> with (...) if type is a | |
| 63 function. | |
| 64 ''' | |
| 65 # clang can give unhelpful spelling for anonymous structs. | |
| 66 assert 'struct (unnamed at ' not in type_.spelling, f'type_.spelling={type_.spelling}' | |
| 67 if verbose: | |
| 68 jlib.log( '{nest=} {name=} {type_.spelling=} {type_.get_declaration().get_usr()=}') | |
| 69 jlib.log( '{type_.kind=} {type_.get_array_size()=} {expand_typedef=}') | |
| 70 | |
| 71 array_n = type_.get_array_size() | |
| 72 if verbose: | |
| 73 jlib.log( '{array_n=}') | |
| 74 if array_n >= 0 or type_.kind == state.clang.cindex.TypeKind.INCOMPLETEARRAY: | |
| 75 if verbose: jlib.log( '{array_n=}') | |
| 76 if array_n < 0: | |
| 77 array_n = '' | |
| 78 ret = declaration_text( | |
| 79 type_.get_array_element_type(), | |
| 80 f'{name}[{array_n}]', | |
| 81 nest+1, | |
| 82 name_is_simple, | |
| 83 verbose=verbose, | |
| 84 expand_typedef=expand_typedef, | |
| 85 top_level=top_level, | |
| 86 ) | |
| 87 if verbose: | |
| 88 jlib.log( 'returning {ret=}') | |
| 89 return ret | |
| 90 | |
| 91 pointee = type_.get_pointee() | |
| 92 if pointee and pointee.spelling: | |
| 93 if type_.kind == state.clang.cindex.TypeKind.LVALUEREFERENCE: | |
| 94 pointee_type = '&' | |
| 95 elif type_.kind == state.clang.cindex.TypeKind.POINTER: | |
| 96 pointee_type = '*' | |
| 97 else: | |
| 98 assert 0, f'Unrecognised pointer kind {type_.kind=}.' | |
| 99 if verbose: jlib.log( '{type_=} {type_.kind=} {pointee.spelling=}') | |
| 100 ret = declaration_text( | |
| 101 pointee, | |
| 102 f'{pointee_type}{name}', | |
| 103 nest+1, | |
| 104 name_is_simple=False, | |
| 105 verbose=verbose, | |
| 106 expand_typedef=expand_typedef, | |
| 107 top_level=top_level, | |
| 108 ) | |
| 109 if verbose: | |
| 110 jlib.log( 'returning {ret=}') | |
| 111 return ret | |
| 112 | |
| 113 if expand_typedef and type_.get_typedef_name(): | |
| 114 if verbose: jlib.log( '{type_.get_typedef_name()=}') | |
| 115 const = 'const ' if type_.is_const_qualified() else '' | |
| 116 ret = f'{const}{_make_top_level(type_.get_typedef_name(), top_level)} {name}' | |
| 117 if verbose: | |
| 118 jlib.log( 'returning {ret=}') | |
| 119 return ret | |
| 120 | |
| 121 # On MacOS type `size_t` returns true from get_result() and is | |
| 122 # state.clang.cindex.TypeKind.ELABORATED. | |
| 123 # | |
| 124 if ( type_.get_result().spelling | |
| 125 and type_.kind not in | |
| 126 ( | |
| 127 state.clang.cindex.TypeKind.FUNCTIONNOPROTO, | |
| 128 state.clang.cindex.TypeKind.ELABORATED, | |
| 129 ) | |
| 130 ): | |
| 131 # <type> is a function. We call ourselves with type=type_.get_result() | |
| 132 # and name=<name>(<args>). | |
| 133 # | |
| 134 assert type_.kind == state.clang.cindex.TypeKind.FUNCTIONPROTO, \ | |
| 135 f'{type_.spelling=} {type_.kind=}' | |
| 136 ret = '' | |
| 137 sep = '' | |
| 138 for arg in type_.argument_types(): | |
| 139 ret += sep | |
| 140 ret += declaration_text( | |
| 141 arg, | |
| 142 '', | |
| 143 nest+1, | |
| 144 top_level=top_level, | |
| 145 verbose=verbose, | |
| 146 expand_typedef=expand_typedef, | |
| 147 ) | |
| 148 sep = ', ' | |
| 149 if verbose: jlib.log( '{ret!r=}') | |
| 150 if not name_is_simple: | |
| 151 # If name isn't a simple identifier, put it inside braces, e.g. | |
| 152 # this crudely allows function pointers to work. | |
| 153 name = f'({name})' | |
| 154 ret = f'{name}({ret})' | |
| 155 if verbose: jlib.log( '{type_.get_result()=}') | |
| 156 ret = declaration_text( | |
| 157 type_.get_result(), | |
| 158 ret, | |
| 159 nest+1, | |
| 160 name_is_simple=False, | |
| 161 verbose=verbose, | |
| 162 expand_typedef=expand_typedef, | |
| 163 top_level=top_level, | |
| 164 ) | |
| 165 if verbose: | |
| 166 jlib.log( 'returning {ret=}') | |
| 167 return ret | |
| 168 | |
| 169 ret = f'{_make_top_level(type_.spelling, top_level)} {name}' | |
| 170 assert not 'struct (unnamed at ' in ret, f'Bad clang name for anonymous struct: {ret}' | |
| 171 if verbose: jlib.log( 'returning {ret=}') | |
| 172 return ret | |
| 173 | |
| 174 | |
| 175 def write_call_arg( | |
| 176 tu, | |
| 177 arg, | |
| 178 classname, | |
| 179 have_used_this, | |
| 180 out_cpp, | |
| 181 verbose=False, | |
| 182 python=False, | |
| 183 ): | |
| 184 ''' | |
| 185 Write an arg of a function call, translating between raw and wrapping | |
| 186 classes as appropriate. | |
| 187 | |
| 188 If the required type is a fz_ struct that we wrap, we assume that arg.name | |
| 189 is a reference to an instance of the wrapper class. If the wrapper class | |
| 190 is the same as <classname>, we use 'this->' instead of <name>. We also | |
| 191 generate slightly different code depending on whether the wrapper class is | |
| 192 pod or inline pod. | |
| 193 | |
| 194 arg: | |
| 195 Arg from get_args(). | |
| 196 classname: | |
| 197 Name of wrapper class available as 'this'. | |
| 198 have_used_this: | |
| 199 If true, we never use 'this->...'. | |
| 200 out_cpp: | |
| 201 . | |
| 202 python: | |
| 203 If true, we write python code, not C. | |
| 204 | |
| 205 Returns True if we have used 'this->...', else return <have_used_this>. | |
| 206 ''' | |
| 207 assert isinstance( arg, parse.Arg) | |
| 208 assert isinstance( arg.cursor, state.clang.cindex.Cursor) | |
| 209 if not arg.alt: | |
| 210 # Arg is a normal type; no conversion necessary. | |
| 211 if python: | |
| 212 out_cpp.write( arg.name_python) | |
| 213 else: | |
| 214 out_cpp.write( arg.name) | |
| 215 return have_used_this | |
| 216 | |
| 217 if verbose: | |
| 218 jlib.log( '{=arg.name arg.alt.spelling classname}') | |
| 219 type_ = state.get_name_canonical( arg.cursor.type) | |
| 220 ptr = '*' | |
| 221 #log( '{=arg.name arg.alt.spelling classname type_.spelling}') | |
| 222 if type_.kind == state.clang.cindex.TypeKind.POINTER: | |
| 223 type_ = state.get_name_canonical( type_.get_pointee()) | |
| 224 ptr = '' | |
| 225 #log( '{=arg.name arg.alt.spelling classname type_.spelling}') | |
| 226 extras = parse.get_fz_extras( tu, type_.spelling) | |
| 227 assert extras, f'No extras for type_.spelling={type_.spelling}' | |
| 228 if verbose: | |
| 229 jlib.log( 'param is fz: {type_.spelling=} {extras2.pod=}') | |
| 230 assert extras.pod != 'none' \ | |
| 231 'Cannot pass wrapper for {type_.spelling} as arg because pod is "none" so we cannot recover struct.' | |
| 232 if python: | |
| 233 if extras.pod == 'inline': | |
| 234 out_cpp.write( f'{arg.name_python}.internal()') | |
| 235 elif extras.pod: | |
| 236 out_cpp.write( f'{arg.name_python}.m_internal') | |
| 237 else: | |
| 238 out_cpp.write( f'{arg.name_python}.m_internal') | |
| 239 | |
| 240 elif extras.pod == 'inline': | |
| 241 # We use the address of the first class member, casting it to a pointer | |
| 242 # to the wrapped type. Not sure this is guaranteed safe, but should | |
| 243 # work in practise. | |
| 244 name_ = f'{arg.name}.' | |
| 245 if not have_used_this and rename.class_(arg.alt.type.spelling) == classname: | |
| 246 have_used_this = True | |
| 247 name_ = 'this->' | |
| 248 field0 = parse.get_field0(type_).spelling | |
| 249 out_cpp.write( f'{ptr} {name_}internal()') | |
| 250 else: | |
| 251 if verbose: | |
| 252 jlib.log( '{=arg state.get_name_canonical(arg.cursor.type).kind classname extras}') | |
| 253 if extras.pod and state.get_name_canonical( arg.cursor.type).kind == state.clang.cindex.TypeKind.POINTER: | |
| 254 out_cpp.write( '&') | |
| 255 elif not extras.pod and state.get_name_canonical( arg.cursor.type).kind != state.clang.cindex.TypeKind.POINTER: | |
| 256 out_cpp.write( '*') | |
| 257 elif arg.out_param: | |
| 258 out_cpp.write( '&') | |
| 259 if not have_used_this and rename.class_(arg.alt.type.spelling) == classname: | |
| 260 have_used_this = True | |
| 261 out_cpp.write( 'this->') | |
| 262 else: | |
| 263 out_cpp.write( f'{arg.name}.') | |
| 264 out_cpp.write( 'm_internal') | |
| 265 | |
| 266 return have_used_this | |
| 267 | |
| 268 | |
| 269 def make_fncall( tu, cursor, return_type, fncall, out, refcheck_if, trace_if): | |
| 270 ''' | |
| 271 Writes a low-level function call to <out>, using fz_context_s from | |
| 272 internal_context_get() and with fz_try...fz_catch that converts to C++ | |
| 273 exceptions by calling throw_exception(). | |
| 274 | |
| 275 return_type: | |
| 276 Text return type of function, e.g. 'void' or 'double'. | |
| 277 fncall: | |
| 278 Text containing function call, e.g. 'function(a, b, 34)'. | |
| 279 out: | |
| 280 Stream to which we write generated code. | |
| 281 ''' | |
| 282 uses_fz_context = False; | |
| 283 | |
| 284 # Setting this to False is a hack to elide all fz_try/fz_catch code. This | |
| 285 # has a very small effect on mupdfpy test suite performance - e.g. reduce | |
| 286 # time from 548.1s to 543.2s. | |
| 287 # | |
| 288 use_fz_try = True | |
| 289 | |
| 290 if cursor.spelling in ( | |
| 291 'pdf_specifics', | |
| 292 ): | |
| 293 # This fn takes a fz_context* but never throws, so we can omit | |
| 294 # `fz_try()...fz_catch()`, which might give a small performance | |
| 295 # improvement. | |
| 296 use_fz_try = False | |
| 297 uses_fz_context = True | |
| 298 else: | |
| 299 for arg in parse.get_args( tu, cursor, include_fz_context=True): | |
| 300 if parse.is_pointer_to( arg.cursor.type, 'fz_context'): | |
| 301 uses_fz_context = True | |
| 302 break | |
| 303 if uses_fz_context: | |
| 304 context_get = rename.internal( 'context_get') | |
| 305 throw_exception = rename.internal( 'throw_exception') | |
| 306 out.write( f' fz_context* auto_ctx = {context_get}();\n') | |
| 307 | |
| 308 # Output code that writes diagnostics to std::cerr if $MUPDF_trace is set. | |
| 309 # | |
| 310 def varname_enable(): | |
| 311 for t in 'fz_keep_', 'fz_drop_', 'pdf_keep_', 'pdf_drop_': | |
| 312 if cursor.spelling.startswith( t): | |
| 313 return 's_trace_keepdrop' | |
| 314 return 's_trace > 1' | |
| 315 | |
| 316 out.write( f' {trace_if}\n') | |
| 317 out.write( f' if ({varname_enable()}) {{\n') | |
| 318 out.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): calling {cursor.spelling}():";\n') | |
| 319 for arg in parse.get_args( tu, cursor, include_fz_context=True): | |
| 320 if parse.is_pointer_to( arg.cursor.type, 'fz_context'): | |
| 321 out.write( f' if ({varname_enable()}) std::cerr << " auto_ctx=" << auto_ctx;\n') | |
| 322 elif arg.out_param: | |
| 323 out.write( f' if ({varname_enable()}) std::cerr << " {arg.name}=" << (void*) {arg.name};\n') | |
| 324 elif arg.alt: | |
| 325 # If not a pod, there will not be an operator<<, so just show | |
| 326 # the address of this arg. | |
| 327 # | |
| 328 extras = parse.get_fz_extras( tu, arg.alt.type.spelling) | |
| 329 assert extras.pod != 'none' \ | |
| 330 'Cannot pass wrapper for {type_.spelling} as arg because pod is "none" so we cannot recover struct.' | |
| 331 if extras.pod: | |
| 332 out.write( f' std::cerr << " {arg.name}=" << {arg.name};\n') | |
| 333 elif arg.cursor.type.kind == state.clang.cindex.TypeKind.POINTER: | |
| 334 out.write( f' if ({varname_enable()}) std::cerr << " {arg.name}=" << {arg.name};\n') | |
| 335 elif arg.cursor.type.kind == state.clang.cindex.TypeKind.LVALUEREFERENCE: | |
| 336 out.write( f' if ({varname_enable()}) std::cerr << " &{arg.name}=" << &{arg.name};\n') | |
| 337 else: | |
| 338 out.write( f' std::cerr << " &{arg.name}=" << &{arg.name};\n') | |
| 339 elif parse.is_pointer_to(arg.cursor.type, 'char') and state.get_name_canonical( arg.cursor.type.get_pointee()).is_const_qualified(): | |
| 340 # 'const char*' is assumed to be zero-terminated string. But we | |
| 341 # need to protect against trying to write nullptr because this | |
| 342 # appears to kill std::cerr on Linux. | |
| 343 out.write( f' if ({arg.name}) std::cerr << " {arg.name}=\'" << {arg.name} << "\'";\n') | |
| 344 out.write( f' else std::cerr << " {arg.name}:null";\n') | |
| 345 elif parse.is_( arg.cursor.type, 'va_list'): | |
| 346 out.write( f' std::cerr << " {arg.name}:va_list";\n') | |
| 347 elif (0 | |
| 348 or parse.is_( arg.cursor.type, 'signed char') | |
| 349 or parse.is_( arg.cursor.type, 'unsigned char') | |
| 350 ): | |
| 351 # Typically used for raw data, so not safe to treat as text. | |
| 352 out.write( f' std::cerr << " {arg.name}=" << ((int) {arg.name});\n') | |
| 353 elif (0 | |
| 354 or parse.is_pointer_to(arg.cursor.type, 'signed char') | |
| 355 or parse.is_pointer_to(arg.cursor.type, 'unsigned char') | |
| 356 ): | |
| 357 # Typically used for raw data, so not safe to treat as text. | |
| 358 out.write( f' std::cerr << " {arg.name}=" << ((void*) {arg.name});\n') | |
| 359 elif arg.cursor.type.kind == state.clang.cindex.TypeKind.POINTER: | |
| 360 # Don't assume non-const 'char*' is a zero-terminated string. | |
| 361 out.write( f' if ({varname_enable()}) std::cerr << " {arg.name}=" << (void*) {arg.name};\n') | |
| 362 elif arg.cursor.type.kind == state.clang.cindex.TypeKind.LVALUEREFERENCE: | |
| 363 out.write( f' if ({varname_enable()}) std::cerr << " &{arg.name}=" << &{arg.name};\n') | |
| 364 else: | |
| 365 out.write( f' std::cerr << " {arg.name}=" << {arg.name};\n') | |
| 366 out.write( f' std::cerr << "\\n";\n') | |
| 367 out.write( f' }}\n') | |
| 368 out.write( f' #endif\n') | |
| 369 | |
| 370 if uses_fz_context: | |
| 371 out.write( f' {refcheck_if}\n') | |
| 372 out.write( f' long stack0;\n') | |
| 373 out.write( f' if (s_check_error_stack)\n') | |
| 374 out.write( f' {{\n') | |
| 375 out.write( f' stack0 = auto_ctx->error.top - auto_ctx->error.stack_base;\n') | |
| 376 out.write( f' }}\n') | |
| 377 out.write( f' #endif\n') | |
| 378 | |
| 379 # Now output the function call. | |
| 380 # | |
| 381 if return_type != 'void': | |
| 382 out.write( f' {return_type} ret;\n') | |
| 383 | |
| 384 if cursor.spelling == 'fz_warn': | |
| 385 out.write( ' va_list ap;\n') | |
| 386 out.write( ' fz_var(ap);\n') | |
| 387 | |
| 388 indent = '' | |
| 389 if uses_fz_context and use_fz_try: | |
| 390 out.write( f' fz_try(auto_ctx) {{\n') | |
| 391 indent = ' ' | |
| 392 | |
| 393 if cursor.spelling == 'fz_warn': | |
| 394 out.write( f' {indent}va_start(ap, fmt);\n') | |
| 395 out.write( f' {indent}fz_vwarn(auto_ctx, fmt, ap);\n') | |
| 396 else: | |
| 397 if not uses_fz_context: | |
| 398 out.write( f' /* No fz_context* arg, so no need for fz_try()/fz_catch() to convert MuPDF exceptions into C++ exceptions. */\n') | |
| 399 out.write( f' {indent}') | |
| 400 if return_type != 'void': | |
| 401 out.write( f'ret = ') | |
| 402 out.write( f'{fncall};\n') | |
| 403 | |
| 404 if uses_fz_context and use_fz_try: | |
| 405 out.write( f' }}\n') | |
| 406 | |
| 407 if cursor.spelling == 'fz_warn': | |
| 408 if use_fz_try: | |
| 409 out.write( f' fz_always(auto_ctx) {{\n') | |
| 410 out.write( f' va_end(ap);\n') | |
| 411 out.write( f' }}\n') | |
| 412 else: | |
| 413 out.write( f' va_end(ap);\n') | |
| 414 | |
| 415 if uses_fz_context and use_fz_try: | |
| 416 out.write( f' fz_catch(auto_ctx) {{\n') | |
| 417 out.write( f' {trace_if}\n') | |
| 418 out.write( f' if (s_trace_exceptions) {{\n') | |
| 419 out.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): fz_catch() has caught exception.\\n";\n') | |
| 420 out.write( f' }}\n') | |
| 421 out.write( f' #endif\n') | |
| 422 out.write( f' {throw_exception}(auto_ctx);\n') | |
| 423 out.write( f' }}\n') | |
| 424 | |
| 425 if uses_fz_context: | |
| 426 out.write( f' {refcheck_if}\n') | |
| 427 out.write( f' if (s_check_error_stack)\n') | |
| 428 out.write( f' {{\n') | |
| 429 out.write( f' long stack1 = auto_ctx->error.top - auto_ctx->error.stack_base;\n') | |
| 430 out.write( f' if (stack1 != stack0)\n') | |
| 431 out.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): MuPDF error stack size changed by {cursor.spelling}(): " << stack0 << " -> " << stack1 << "\\n";\n') | |
| 432 out.write( f' }}\n') | |
| 433 out.write( f' #endif\n') | |
| 434 | |
| 435 if return_type != 'void': | |
| 436 out.write( f' return ret;\n') | |
| 437 | |
| 438 | |
| 439 def to_pickle( obj, path): | |
| 440 ''' | |
| 441 Pickles <obj> to file <path>. | |
| 442 ''' | |
| 443 with open( path, 'wb') as f: | |
| 444 pickle.dump( obj, f) | |
| 445 | |
| 446 def from_pickle( path): | |
| 447 ''' | |
| 448 Returns contents of file <path> unpickled. | |
| 449 ''' | |
| 450 with open( path, 'rb') as f: | |
| 451 return pickle.load( f) | |
| 452 | |
| 453 class Generated: | |
| 454 ''' | |
| 455 Stores information generated when we parse headers using clang. | |
| 456 ''' | |
| 457 def __init__( self): | |
| 458 self.h_files = [] | |
| 459 self.cpp_files = [] | |
| 460 self.fn_usage_filename = None | |
| 461 self.container_classnames = [] | |
| 462 self.to_string_structnames = [] | |
| 463 self.fn_usage = dict() | |
| 464 self.output_param_fns = [] | |
| 465 self.c_functions = [] | |
| 466 self.c_globals = [] | |
| 467 self.c_enums = [] | |
| 468 self.c_structs = [] | |
| 469 self.swig_cpp = io.StringIO() | |
| 470 self.swig_cpp_python = io.StringIO() | |
| 471 self.swig_python = io.StringIO() | |
| 472 self.swig_python_exceptions = io.StringIO() | |
| 473 self.swig_python_set_error_classes = io.StringIO() | |
| 474 self.swig_csharp = io.StringIO() | |
| 475 self.virtual_fnptrs = [] # List of extra wrapper class names with virtual fnptrs. | |
| 476 self.cppyy_extra = '' | |
| 477 | |
| 478 def save( self, dirpath): | |
| 479 ''' | |
| 480 Saves state to .pickle file, to be loaded later via pickle.load(). | |
| 481 ''' | |
| 482 to_pickle( self, f'{dirpath}/generated.pickle') | |
| 483 | |
| 484 | |
| 485 def make_outparam_helper( | |
| 486 tu, | |
| 487 cursor, | |
| 488 fnname, | |
| 489 fnname_wrapper, | |
| 490 generated, | |
| 491 ): | |
| 492 ''' | |
| 493 Create extra C++, Python and C# code to make tuple-returning wrapper of | |
| 494 specified function. | |
| 495 | |
| 496 We write Python code to generated.swig_python and C++ code to | |
| 497 generated.swig_cpp. | |
| 498 ''' | |
| 499 verbose = False | |
| 500 main_name = rename.ll_fn(cursor.spelling) | |
| 501 generated.swig_cpp.write( '\n') | |
| 502 | |
| 503 # Write struct. | |
| 504 generated.swig_cpp.write( 'namespace mupdf\n') | |
| 505 generated.swig_cpp.write('{\n') | |
| 506 generated.swig_cpp.write(f' /* Out-params helper class for {cursor.spelling}(). */\n') | |
| 507 generated.swig_cpp.write(f' struct {main_name}_outparams\n') | |
| 508 generated.swig_cpp.write(f' {{\n') | |
| 509 for arg in parse.get_args( tu, cursor): | |
| 510 if not arg.out_param: | |
| 511 continue | |
| 512 decl = declaration_text( arg.cursor.type, arg.name, verbose=verbose) | |
| 513 if verbose: | |
| 514 jlib.log( '{decl=}') | |
| 515 assert arg.cursor.type.kind == state.clang.cindex.TypeKind.POINTER | |
| 516 | |
| 517 # We use state.get_name_canonical() here because, for example, it | |
| 518 # converts int64_t to 'long long', which seems to be handled better by | |
| 519 # swig - swig maps int64_t to mupdf.SWIGTYPE_p_int64_t which can't be | |
| 520 # treated or converted to an integer. | |
| 521 # | |
| 522 # We also value-initialise in case the underlying mupdf function also | |
| 523 # reads the supplied value - i.e. treats it as an in-parm as well as an | |
| 524 # out-param; this is particularly important for pointer out-params. | |
| 525 # | |
| 526 pointee = state.get_name_canonical( arg.cursor.type.get_pointee()) | |
| 527 generated.swig_cpp.write(f' {declaration_text( pointee, arg.name)} = {{}};\n') | |
| 528 generated.swig_cpp.write(f' }};\n') | |
| 529 generated.swig_cpp.write('\n') | |
| 530 | |
| 531 # Write function definition. | |
| 532 name_args = f'{main_name}_outparams_fn(' | |
| 533 sep = '' | |
| 534 for arg in parse.get_args( tu, cursor): | |
| 535 if arg.out_param: | |
| 536 continue | |
| 537 name_args += sep | |
| 538 name_args += declaration_text( arg.cursor.type, arg.name, verbose=verbose) | |
| 539 sep = ', ' | |
| 540 name_args += f'{sep}{main_name}_outparams* outparams' | |
| 541 name_args += ')' | |
| 542 generated.swig_cpp.write(f' /* Out-params function for {cursor.spelling}(). */\n') | |
| 543 generated.swig_cpp.write(f' {declaration_text( cursor.result_type, name_args)}\n') | |
| 544 generated.swig_cpp.write( ' {\n') | |
| 545 return_void = (cursor.result_type.spelling == 'void') | |
| 546 generated.swig_cpp.write(f' ') | |
| 547 if not return_void: | |
| 548 generated.swig_cpp.write(f'{declaration_text(cursor.result_type, "ret")} = ') | |
| 549 generated.swig_cpp.write(f'{rename.ll_fn(cursor.spelling)}(') | |
| 550 sep = '' | |
| 551 for arg in parse.get_args( tu, cursor): | |
| 552 generated.swig_cpp.write(sep) | |
| 553 if arg.out_param: | |
| 554 generated.swig_cpp.write(f'&outparams->{arg.name}') | |
| 555 else: | |
| 556 generated.swig_cpp.write(f'{arg.name}') | |
| 557 sep = ', ' | |
| 558 generated.swig_cpp.write(');\n') | |
| 559 if not return_void: | |
| 560 generated.swig_cpp.write(' return ret;\n') | |
| 561 generated.swig_cpp.write(' }\n') | |
| 562 generated.swig_cpp.write('}\n') | |
| 563 | |
| 564 # Write Python wrapper. | |
| 565 python.make_outparam_helper_python(tu, cursor, fnname, fnname_wrapper, generated, main_name) | |
| 566 | |
| 567 # Write C# wrapper. | |
| 568 csharp.make_outparam_helper_csharp(tu, cursor, fnname, fnname_wrapper, generated, main_name) | |
| 569 | |
| 570 | |
| 571 def make_python_class_method_outparam_override( | |
| 572 tu, | |
| 573 cursor, | |
| 574 fnname, | |
| 575 generated, | |
| 576 structname, | |
| 577 classname, | |
| 578 return_type, | |
| 579 ): | |
| 580 ''' | |
| 581 Writes Python code to `generated.swig_python` that monkey-patches Python | |
| 582 function or method to make it call the underlying MuPDF function's Python | |
| 583 wrapper, which will return out-params in a tuple. | |
| 584 | |
| 585 This is necessary because C++ doesn't support out-params so the C++ API | |
| 586 supports wrapper class out-params by taking references to a dummy wrapper | |
| 587 class instances, whose m_internal is then changed to point to the out-param | |
| 588 struct (with suitable calls to keep/drop to manage the destruction of the | |
| 589 dummy instance). | |
| 590 | |
| 591 In Python, we could create dummy wrapper class instances (e.g. passing | |
| 592 nullptr to constructor) and return them, but instead we make our own call | |
| 593 to the underlying MuPDF function and wrap the out-params into wrapper | |
| 594 classes. | |
| 595 ''' | |
| 596 out = generated.swig_python | |
| 597 # Underlying fn. | |
| 598 main_name = rename.ll_fn(cursor.spelling) | |
| 599 | |
| 600 if structname: | |
| 601 name_new = f'{classname}_{rename.method(structname, cursor.spelling)}_outparams_fn' | |
| 602 else: | |
| 603 name_new = f'{rename.fn(cursor.spelling)}_outparams_fn' | |
| 604 | |
| 605 # Define an internal Python function that will become the class method. | |
| 606 # | |
| 607 out.write( f'def {name_new}(') | |
| 608 if structname: | |
| 609 out.write( ' self') | |
| 610 comma = ', ' | |
| 611 else: | |
| 612 comma = '' | |
| 613 for arg in parse.get_args( tu, cursor): | |
| 614 if arg.out_param: | |
| 615 continue | |
| 616 if structname and parse.is_pointer_to( arg.cursor.type, structname): | |
| 617 continue | |
| 618 out.write(f'{comma}{arg.name_python}') | |
| 619 comma = ', ' | |
| 620 out.write('):\n') | |
| 621 out.write( ' """\n') | |
| 622 if structname: | |
| 623 out.write(f' Helper for out-params of class method {structname}::{main_name}() [{cursor.spelling}()].\n') | |
| 624 else: | |
| 625 out.write(f' Class-aware helper for out-params of {fnname}() [{cursor.spelling}()].\n') | |
| 626 out.write( ' """\n') | |
| 627 | |
| 628 # ret, a, b, ... = foo::bar(self.m_internal, p, q, r, ...) | |
| 629 out.write(f' ') | |
| 630 sep = '' | |
| 631 if cursor.result_type.spelling != 'void': | |
| 632 out.write( 'ret') | |
| 633 sep = ', ' | |
| 634 for arg in parse.get_args( tu, cursor): | |
| 635 if not arg.out_param: | |
| 636 continue | |
| 637 out.write( f'{sep}{arg.name_python}') | |
| 638 sep = ', ' | |
| 639 out.write( f' = {main_name}(') | |
| 640 sep = '' | |
| 641 if structname: | |
| 642 out.write( f' self.m_internal') | |
| 643 sep = ', ' | |
| 644 for arg in parse.get_args( tu, cursor): | |
| 645 if arg.out_param: | |
| 646 continue | |
| 647 if structname and parse.is_pointer_to( arg.cursor.type, structname): | |
| 648 continue | |
| 649 out.write( sep) | |
| 650 write_call_arg( tu, arg, classname, have_used_this=False, out_cpp=out, python=True) | |
| 651 sep = ', ' | |
| 652 out.write( ')\n') | |
| 653 | |
| 654 # return ret, a, b. | |
| 655 # | |
| 656 # We convert returned items to wrapper classes if they are MuPDF types. | |
| 657 # | |
| 658 out.write( ' return ') | |
| 659 sep = '' | |
| 660 if cursor.result_type.spelling != 'void': | |
| 661 if return_type: | |
| 662 #out.write( f'{return_type}(ret)') | |
| 663 # Return type is a class wrapper. | |
| 664 return_ll_type = cursor.result_type | |
| 665 do_keep = False | |
| 666 if cursor.result_type.kind == state.clang.cindex.TypeKind.POINTER: | |
| 667 return_ll_type = return_ll_type.get_pointee() | |
| 668 if parse.has_refs( tu, return_ll_type): | |
| 669 return_ll_type = return_ll_type.spelling | |
| 670 return_ll_type = util.clip( return_ll_type, ('struct ', 'const ')) | |
| 671 assert return_ll_type.startswith( ( 'fz_', 'pdf_')) | |
| 672 for prefix in ( 'fz_', 'pdf_'): | |
| 673 if return_ll_type.startswith( prefix): | |
| 674 break | |
| 675 else: | |
| 676 assert 0, f'Unexpected arg type: {return_ll_type}' | |
| 677 return_extra = classes.classextras.get( tu, return_ll_type) | |
| 678 if not function_name_implies_kept_references( fnname): | |
| 679 do_keep = True | |
| 680 else: | |
| 681 if 'char' in return_ll_type.spelling: | |
| 682 jlib.log('### Function returns {cursor.result_type.spelling=} -> {return_ll_type.spelling=}: {fnname}. {function_name_implies_kept_references(fnname)=}') | |
| 683 if do_keep: | |
| 684 keepfn = f'{prefix}keep_{return_ll_type[ len(prefix):]}' | |
| 685 keepfn = rename.ll_fn( keepfn) | |
| 686 out.write( f'{return_type}( {keepfn}( ret))') | |
| 687 else: | |
| 688 out.write( f'{return_type}(ret)') | |
| 689 else: | |
| 690 out.write( 'ret') | |
| 691 sep = ', ' | |
| 692 for arg in parse.get_args( tu, cursor): | |
| 693 if not arg.out_param: | |
| 694 continue | |
| 695 if arg.alt: | |
| 696 name = util.clip( arg.alt.type.spelling, ('struct ', 'const ')) | |
| 697 for prefix in ( 'fz_', 'pdf_'): | |
| 698 if name.startswith( prefix): | |
| 699 break | |
| 700 else: | |
| 701 assert 0, f'Unexpected arg type: {name}' | |
| 702 if function_name_implies_kept_references( fnname): | |
| 703 out.write( f'{sep}{rename.class_(name)}( {arg.name_python})') | |
| 704 else: | |
| 705 keepfn = f'{prefix}keep_{name[ len(prefix):]}' | |
| 706 keepfn = rename.ll_fn( keepfn) | |
| 707 out.write( f'{sep}{rename.class_(name)}({keepfn}( {arg.name_python}))') | |
| 708 else: | |
| 709 out.write( f'{sep}{arg.name_python}') | |
| 710 sep = ', ' | |
| 711 out.write('\n') | |
| 712 out.write('\n') | |
| 713 | |
| 714 # foo.bar = foo_bar_outparams_fn | |
| 715 if structname: | |
| 716 out.write(f'{classname}.{rename.method(structname, cursor.spelling)} = {name_new}\n') | |
| 717 else: | |
| 718 out.write(f'{rename.fn( cursor.spelling)} = {name_new}\n') | |
| 719 out.write('\n') | |
| 720 out.write('\n') | |
| 721 | |
| 722 | |
| 723 def make_wrapper_comment( | |
| 724 tu, | |
| 725 cursor, | |
| 726 fnname, | |
| 727 fnname_wrapper, | |
| 728 indent, | |
| 729 is_method, | |
| 730 is_low_level, | |
| 731 ): | |
| 732 ret = io.StringIO() | |
| 733 def write(text): | |
| 734 text = text.replace('\n', f'\n{indent}') | |
| 735 ret.write( text) | |
| 736 | |
| 737 num_out_params = 0 | |
| 738 for arg in parse.get_args( | |
| 739 tu, | |
| 740 cursor, | |
| 741 include_fz_context=False, | |
| 742 skip_first_alt=is_method, | |
| 743 ): | |
| 744 if arg.out_param: | |
| 745 num_out_params += 1 | |
| 746 | |
| 747 if is_low_level: | |
| 748 write( f'Low-level wrapper for `{rename.c_fn(cursor.spelling)}()`.') | |
| 749 else: | |
| 750 write( f'Class-aware wrapper for `{rename.c_fn(cursor.spelling)}()`.') | |
| 751 if num_out_params: | |
| 752 tuple_size = num_out_params | |
| 753 if cursor.result_type.spelling != 'void': | |
| 754 tuple_size += 1 | |
| 755 write( f'\n') | |
| 756 write( f'\n') | |
| 757 write( f'This {"method" if is_method else "function"} has out-params. Python/C# wrappers look like:\n') | |
| 758 write( f' `{fnname_wrapper}(') | |
| 759 sep = '' | |
| 760 for arg in parse.get_args( tu, cursor, include_fz_context=False, skip_first_alt=is_method): | |
| 761 if arg.alt or not arg.out_param: | |
| 762 write( f'{sep}{declaration_text( arg.cursor.type, arg.name)}') | |
| 763 sep = ', ' | |
| 764 write(')` => ') | |
| 765 if tuple_size > 1: | |
| 766 write( '`(') | |
| 767 sep = '' | |
| 768 if cursor.result_type.spelling != 'void': | |
| 769 write( f'{cursor.result_type.spelling}') | |
| 770 sep = ', ' | |
| 771 for arg in parse.get_args( tu, cursor, include_fz_context=False, skip_first_alt=is_method): | |
| 772 if not arg.alt and arg.out_param: | |
| 773 write( f'{sep}{declaration_text( arg.cursor.type.get_pointee(), arg.name)}') | |
| 774 sep = ', ' | |
| 775 if tuple_size > 1: | |
| 776 write( ')`') | |
| 777 write( f'\n') | |
| 778 else: | |
| 779 write( ' ') | |
| 780 | |
| 781 return ret.getvalue() | |
| 782 | |
| 783 | |
| 784 def function_wrapper( | |
| 785 tu, | |
| 786 cursor, | |
| 787 fnname, | |
| 788 fnname_wrapper, | |
| 789 out_h, | |
| 790 out_cpp, | |
| 791 generated, | |
| 792 refcheck_if, | |
| 793 trace_if, | |
| 794 ): | |
| 795 ''' | |
| 796 Writes low-level C++ wrapper fn, converting any fz_try..fz_catch exception | |
| 797 into a C++ exception. | |
| 798 | |
| 799 cursor: | |
| 800 Clang cursor for function to wrap. | |
| 801 fnname: | |
| 802 Name of wrapped function. | |
| 803 fnname_wrapper: | |
| 804 Name of function to create. | |
| 805 out_h: | |
| 806 Stream to which we write header output. | |
| 807 out_cpp: | |
| 808 Stream to which we write cpp output. | |
| 809 generated: | |
| 810 A Generated instance. | |
| 811 refcheck_if: | |
| 812 A '#if*' statement that determines whether extra checks are compiled | |
| 813 in. | |
| 814 trace_if: | |
| 815 A '#if*' statement that determines whether runtime diagnostics are | |
| 816 compiled in. | |
| 817 | |
| 818 Example generated function: | |
| 819 | |
| 820 fz_band_writer * mupdf_new_band_writer_of_size(fz_context *ctx, size_t size, fz_output *out) | |
| 821 { | |
| 822 fz_band_writer * ret; | |
| 823 fz_try(ctx) { | |
| 824 ret = fz_new_band_writer_of_size(ctx, size, out); | |
| 825 } | |
| 826 fz_catch(ctx) { | |
| 827 mupdf_throw_exception(ctx); | |
| 828 } | |
| 829 return ret; | |
| 830 } | |
| 831 ''' | |
| 832 assert cursor.kind == state.clang.cindex.CursorKind.FUNCTION_DECL | |
| 833 if cursor.type.is_function_variadic() and fnname != 'fz_warn': | |
| 834 jlib.log( 'Not writing low-level wrapper because variadic: {fnname=}', 1) | |
| 835 return | |
| 836 | |
| 837 verbose = state.state_.show_details( fnname) | |
| 838 if verbose: | |
| 839 jlib.log( 'Wrapping {fnname}') | |
| 840 num_out_params = 0 | |
| 841 for arg in parse.get_args( tu, cursor, include_fz_context=True): | |
| 842 if parse.is_pointer_to(arg.cursor.type, 'fz_context'): | |
| 843 continue | |
| 844 if arg.out_param: | |
| 845 num_out_params += 1 | |
| 846 | |
| 847 # Write first line: <result_type> <fnname_wrapper> (<args>...) | |
| 848 # | |
| 849 comment = make_wrapper_comment( tu, cursor, fnname, fnname_wrapper, indent='', is_method=False, is_low_level=True) | |
| 850 comment = f'/** {comment}*/\n' | |
| 851 for out in out_h, out_cpp: | |
| 852 out.write( comment) | |
| 853 | |
| 854 # Copy any comment into .h file before declaration. | |
| 855 if cursor.raw_comment: | |
| 856 # On Windows, carriage returns can appear in cursor.raw_comment on | |
| 857 # due to line ending inconsistencies in our generated extra.cpp and | |
| 858 # extra.h, and can cause spurious differences in our generated C++ | |
| 859 # code, which in turn causes unnecessary rebuilds. | |
| 860 # | |
| 861 # It would probably better to fix line endings in our generation of | |
| 862 # extra.*. | |
| 863 raw_comment = cursor.raw_comment.replace('\r', '') | |
| 864 out_h.write(raw_comment) | |
| 865 if not raw_comment.endswith( '\n'): | |
| 866 out_h.write( '\n') | |
| 867 | |
| 868 # Write declaration and definition. | |
| 869 name_args_h = f'{fnname_wrapper}(' | |
| 870 name_args_cpp = f'{fnname_wrapper}(' | |
| 871 comma = '' | |
| 872 for arg in parse.get_args( tu, cursor, include_fz_context=True): | |
| 873 if verbose: | |
| 874 jlib.log( '{arg.cursor=} {arg.name=} {arg.separator=} {arg.alt=} {arg.out_param=}') | |
| 875 if parse.is_pointer_to(arg.cursor.type, 'fz_context'): | |
| 876 continue | |
| 877 decl = declaration_text( arg.cursor.type, arg.name, verbose=verbose) | |
| 878 if verbose: | |
| 879 jlib.log( '{decl=}') | |
| 880 name_args_h += f'{comma}{decl}' | |
| 881 decl = declaration_text( arg.cursor.type, arg.name) | |
| 882 name_args_cpp += f'{comma}{decl}' | |
| 883 comma = ', ' | |
| 884 | |
| 885 if cursor.type.is_function_variadic(): | |
| 886 name_args_h += f'{comma}...' | |
| 887 name_args_cpp += f'{comma}...' | |
| 888 | |
| 889 name_args_h += ')' | |
| 890 name_args_cpp += ')' | |
| 891 declaration_h = declaration_text( cursor.result_type, name_args_h, verbose=verbose) | |
| 892 declaration_cpp = declaration_text( cursor.result_type, name_args_cpp, verbose=verbose) | |
| 893 out_h.write( f'FZ_FUNCTION {declaration_h};\n') | |
| 894 out_h.write( '\n') | |
| 895 | |
| 896 # Write function definition. | |
| 897 # | |
| 898 out_cpp.write( f'FZ_FUNCTION {declaration_cpp}\n') | |
| 899 out_cpp.write( '{\n') | |
| 900 return_type = cursor.result_type.spelling | |
| 901 fncall = '' | |
| 902 fncall += f'{rename.c_fn(cursor.spelling)}(' | |
| 903 for arg in parse.get_args( tu, cursor, include_fz_context=True): | |
| 904 if parse.is_pointer_to( arg.cursor.type, 'fz_context'): | |
| 905 fncall += f'{arg.separator}auto_ctx' | |
| 906 else: | |
| 907 fncall += f'{arg.separator}{arg.name}' | |
| 908 fncall += ')' | |
| 909 make_fncall( tu, cursor, return_type, fncall, out_cpp, refcheck_if, trace_if) | |
| 910 out_cpp.write( '}\n') | |
| 911 out_cpp.write( '\n') | |
| 912 | |
| 913 if num_out_params: | |
| 914 make_outparam_helper( | |
| 915 tu, | |
| 916 cursor, | |
| 917 fnname, | |
| 918 fnname_wrapper, | |
| 919 generated, | |
| 920 ) | |
| 921 | |
| 922 | |
| 923 def make_namespace_open( namespace, out): | |
| 924 if namespace: | |
| 925 out.write( '\n') | |
| 926 out.write( f'namespace {namespace}\n') | |
| 927 out.write( '{\n') | |
| 928 | |
| 929 | |
| 930 def make_namespace_close( namespace, out): | |
| 931 if namespace: | |
| 932 out.write( '\n') | |
| 933 out.write( f'}} /* End of namespace {namespace}. */\n') | |
| 934 | |
| 935 | |
| 936 # libclang can't always find headers so we define our own `std::string` | |
| 937 # and `std::vector<>` that work well enough for the generation of the | |
| 938 # C++ API. | |
| 939 # | |
| 940 # We also define extra raw functions to aid SWIG-generated code. These | |
| 941 # are implemented in C++, and should be excluded from the generated | |
| 942 # windows_def file later on, otherwise we get link errors on Windows. | |
| 943 # | |
| 944 g_extra_declarations = textwrap.dedent(f''' | |
| 945 | |
| 946 #ifdef MUPDF_WRAP_LIBCLANG | |
| 947 | |
| 948 namespace std | |
| 949 {{ | |
| 950 template<typename T> | |
| 951 struct vector | |
| 952 {{ | |
| 953 }}; | |
| 954 | |
| 955 struct string | |
| 956 {{ | |
| 957 }}; | |
| 958 }} | |
| 959 | |
| 960 #else | |
| 961 | |
| 962 #include <string> | |
| 963 #include <vector> | |
| 964 | |
| 965 #endif | |
| 966 | |
| 967 #include "mupdf/fitz.h" | |
| 968 #include "mupdf/pdf.h" | |
| 969 | |
| 970 /** | |
| 971 C++ alternative to `fz_lookup_metadata()` that returns a `std::string` | |
| 972 or calls `fz_throw()` if not found. | |
| 973 */ | |
| 974 FZ_FUNCTION std::string fz_lookup_metadata2(fz_context* ctx, fz_document* doc, const char* key); | |
| 975 | |
| 976 /** | |
| 977 C++ alternative to `pdf_lookup_metadata()` that returns a `std::string` | |
| 978 or calls `fz_throw()` if not found. | |
| 979 */ | |
| 980 FZ_FUNCTION std::string pdf_lookup_metadata2(fz_context* ctx, pdf_document* doc, const char* key); | |
| 981 | |
| 982 /** | |
| 983 C++ alternative to `fz_md5_pixmap()` that returns the digest by value. | |
| 984 */ | |
| 985 FZ_FUNCTION std::vector<unsigned char> fz_md5_pixmap2(fz_context* ctx, fz_pixmap* pixmap); | |
| 986 | |
| 987 /** | |
| 988 C++ alternative to fz_md5_final() that returns the digest by value. | |
| 989 */ | |
| 990 FZ_FUNCTION std::vector<unsigned char> fz_md5_final2(fz_md5* md5); | |
| 991 | |
| 992 /** */ | |
| 993 FZ_FUNCTION long long fz_pixmap_samples_int(fz_context* ctx, fz_pixmap* pixmap); | |
| 994 | |
| 995 /** | |
| 996 Provides simple (but slow) access to pixmap data from Python and C#. | |
| 997 */ | |
| 998 FZ_FUNCTION int fz_samples_get(fz_pixmap* pixmap, int offset); | |
| 999 | |
| 1000 /** | |
| 1001 Provides simple (but slow) write access to pixmap data from Python and | |
| 1002 C#. | |
| 1003 */ | |
| 1004 FZ_FUNCTION void fz_samples_set(fz_pixmap* pixmap, int offset, int value); | |
| 1005 | |
| 1006 /** | |
| 1007 C++ alternative to fz_highlight_selection() that returns quads in a | |
| 1008 std::vector. | |
| 1009 */ | |
| 1010 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); | |
| 1011 | |
| 1012 struct fz_search_page2_hit | |
| 1013 {{ | |
| 1014 fz_quad quad; | |
| 1015 int mark; | |
| 1016 }}; | |
| 1017 | |
| 1018 /** | |
| 1019 C++ alternative to fz_search_page() that returns information in a std::vector. | |
| 1020 */ | |
| 1021 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); | |
| 1022 | |
| 1023 /** | |
| 1024 C++ alternative to fz_string_from_text_language() that returns information in a std::string. | |
| 1025 */ | |
| 1026 FZ_FUNCTION std::string fz_string_from_text_language2(fz_text_language lang); | |
| 1027 | |
| 1028 /** | |
| 1029 C++ alternative to fz_get_glyph_name() that returns information in a std::string. | |
| 1030 */ | |
| 1031 FZ_FUNCTION std::string fz_get_glyph_name2(fz_context* ctx, fz_font* font, int glyph); | |
| 1032 | |
| 1033 /** | |
| 1034 Extra struct containing fz_install_load_system_font_funcs()'s args, | |
| 1035 which we wrap with virtual_fnptrs set to allow use from Python/C# via | |
| 1036 Swig Directors. | |
| 1037 */ | |
| 1038 typedef struct fz_install_load_system_font_funcs_args | |
| 1039 {{ | |
| 1040 fz_load_system_font_fn* f; | |
| 1041 fz_load_system_cjk_font_fn* f_cjk; | |
| 1042 fz_load_system_fallback_font_fn* f_fallback; | |
| 1043 }} fz_install_load_system_font_funcs_args; | |
| 1044 | |
| 1045 /** | |
| 1046 Alternative to fz_install_load_system_font_funcs() that takes args in a | |
| 1047 struct, to allow use from Python/C# via Swig Directors. | |
| 1048 */ | |
| 1049 FZ_FUNCTION void fz_install_load_system_font_funcs2(fz_context* ctx, fz_install_load_system_font_funcs_args* args); | |
| 1050 | |
| 1051 /** Internal singleton state to allow Swig Director class to find | |
| 1052 fz_install_load_system_font_funcs_args class wrapper instance. */ | |
| 1053 FZ_DATA extern void* fz_install_load_system_font_funcs2_state; | |
| 1054 | |
| 1055 /** Helper for calling `fz_document_handler::open` function pointer via | |
| 1056 Swig from Python/C#. */ | |
| 1057 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); | |
| 1058 | |
| 1059 /** Helper for calling a `fz_document_handler::recognize` function | |
| 1060 pointer via Swig from Python/C#. */ | |
| 1061 FZ_FUNCTION int fz_document_handler_recognize(fz_context* ctx, const fz_document_handler *handler, const char *magic); | |
| 1062 | |
| 1063 /** Swig-friendly wrapper for pdf_choice_widget_options(), returns the | |
| 1064 options directly in a vector. */ | |
| 1065 FZ_FUNCTION std::vector<std::string> pdf_choice_widget_options2(fz_context* ctx, pdf_annot* tw, int exportval); | |
| 1066 | |
| 1067 /** Swig-friendly wrapper for fz_new_image_from_compressed_buffer(), | |
| 1068 uses specified `decode` and `colorkey` if they are not null (in which | |
| 1069 case we assert that they have size `2*fz_colorspace_n(colorspace)`). */ | |
| 1070 FZ_FUNCTION fz_image* fz_new_image_from_compressed_buffer2( | |
| 1071 fz_context* ctx, | |
| 1072 int w, | |
| 1073 int h, | |
| 1074 int bpc, | |
| 1075 fz_colorspace* colorspace, | |
| 1076 int xres, | |
| 1077 int yres, | |
| 1078 int interpolate, | |
| 1079 int imagemask, | |
| 1080 const std::vector<float>& decode, | |
| 1081 const std::vector<int>& colorkey, | |
| 1082 fz_compressed_buffer* buffer, | |
| 1083 fz_image* mask | |
| 1084 ); | |
| 1085 | |
| 1086 /** Swig-friendly wrapper for pdf_rearrange_pages(). */ | |
| 1087 void pdf_rearrange_pages2( | |
| 1088 fz_context* ctx, | |
| 1089 pdf_document* doc, | |
| 1090 const std::vector<int>& pages, | |
| 1091 pdf_clean_options_structure structure | |
| 1092 ); | |
| 1093 | |
| 1094 /** Swig-friendly wrapper for pdf_subset_fonts(). */ | |
| 1095 void pdf_subset_fonts2(fz_context *ctx, pdf_document *doc, const std::vector<int>& pages); | |
| 1096 | |
| 1097 /** Swig-friendly and typesafe way to do fz_snprintf(fmt, value). `fmt` | |
| 1098 must end with one of 'efg' otherwise we throw an exception. */ | |
| 1099 std::string fz_format_double(fz_context* ctx, const char* fmt, double value); | |
| 1100 | |
| 1101 struct fz_font_ucs_gid | |
| 1102 {{ | |
| 1103 unsigned long ucs; | |
| 1104 unsigned int gid; | |
| 1105 }}; | |
| 1106 | |
| 1107 /** SWIG-friendly wrapper for fz_enumerate_font_cmap(). */ | |
| 1108 std::vector<fz_font_ucs_gid> fz_enumerate_font_cmap2(fz_context* ctx, fz_font* font); | |
| 1109 | |
| 1110 /** SWIG-friendly wrapper for pdf_set_annot_callout_line(). */ | |
| 1111 void pdf_set_annot_callout_line2(fz_context *ctx, pdf_annot *annot, std::vector<fz_point>& callout); | |
| 1112 | |
| 1113 /** SWIG-friendly wrapper for fz_decode_barcode_from_display_list(), | |
| 1114 avoiding leak of the returned string. */ | |
| 1115 std::string fz_decode_barcode_from_display_list2(fz_context *ctx, fz_barcode_type *type, fz_display_list *list, fz_rect subarea, int rotate); | |
| 1116 | |
| 1117 /** SWIG-friendly wrapper for fz_decode_barcode_from_pixmap(), avoiding | |
| 1118 leak of the returned string. */ | |
| 1119 std::string fz_decode_barcode_from_pixmap2(fz_context *ctx, fz_barcode_type *type, fz_pixmap *pix, int rotate); | |
| 1120 | |
| 1121 /** SWIG-friendly wrapper for fz_decode_barcode_from_page(), avoiding | |
| 1122 leak of the returned string. */ | |
| 1123 std::string fz_decode_barcode_from_page2(fz_context *ctx, fz_barcode_type *type, fz_page *page, fz_rect subarea, int rotate); | |
| 1124 ''') | |
| 1125 | |
| 1126 g_extra_definitions = textwrap.dedent(f''' | |
| 1127 | |
| 1128 FZ_FUNCTION std::string fz_lookup_metadata2( fz_context* ctx, fz_document* doc, const char* key) | |
| 1129 {{ | |
| 1130 /* Find length first. */ | |
| 1131 int e = fz_lookup_metadata(ctx, doc, key, NULL /*buf*/, 0 /*size*/); | |
| 1132 if (e < 0) | |
| 1133 {{ | |
| 1134 fz_throw(ctx, FZ_ERROR_GENERIC, "key not found: %s", key); | |
| 1135 }} | |
| 1136 assert(e != 0); | |
| 1137 char* buf = (char*) fz_malloc(ctx, e); | |
| 1138 int e2 = fz_lookup_metadata(ctx, doc, key, buf, e); | |
| 1139 assert(e2 = e); | |
| 1140 std::string ret = buf; | |
| 1141 free(buf); | |
| 1142 return ret; | |
| 1143 }} | |
| 1144 | |
| 1145 FZ_FUNCTION std::string pdf_lookup_metadata2( fz_context* ctx, pdf_document* doc, const char* key) | |
| 1146 {{ | |
| 1147 /* Find length first. */ | |
| 1148 int e = pdf_lookup_metadata(ctx, doc, key, NULL /*buf*/, 0 /*size*/); | |
| 1149 if (e < 0) | |
| 1150 {{ | |
| 1151 fz_throw(ctx, FZ_ERROR_GENERIC, "key not found: %s", key); | |
| 1152 }} | |
| 1153 assert(e != 0); | |
| 1154 char* buf = (char*) fz_malloc(ctx, e); | |
| 1155 int e2 = pdf_lookup_metadata(ctx, doc, key, buf, e); | |
| 1156 assert(e2 = e); | |
| 1157 std::string ret = buf; | |
| 1158 free(buf); | |
| 1159 return ret; | |
| 1160 }} | |
| 1161 | |
| 1162 FZ_FUNCTION std::vector<unsigned char> fz_md5_pixmap2(fz_context* ctx, fz_pixmap* pixmap) | |
| 1163 {{ | |
| 1164 std::vector<unsigned char> ret(16); | |
| 1165 fz_md5_pixmap( ctx, pixmap, &ret[0]); | |
| 1166 return ret; | |
| 1167 }} | |
| 1168 | |
| 1169 FZ_FUNCTION long long fz_pixmap_samples_int(fz_context* ctx, fz_pixmap* pixmap) | |
| 1170 {{ | |
| 1171 long long ret = (intptr_t) pixmap->samples; | |
| 1172 return ret; | |
| 1173 }} | |
| 1174 | |
| 1175 FZ_FUNCTION int fz_samples_get(fz_pixmap* pixmap, int offset) | |
| 1176 {{ | |
| 1177 return pixmap->samples[offset]; | |
| 1178 }} | |
| 1179 | |
| 1180 FZ_FUNCTION void fz_samples_set(fz_pixmap* pixmap, int offset, int value) | |
| 1181 {{ | |
| 1182 pixmap->samples[offset] = value; | |
| 1183 }} | |
| 1184 | |
| 1185 FZ_FUNCTION std::vector<unsigned char> fz_md5_final2(fz_md5* md5) | |
| 1186 {{ | |
| 1187 std::vector<unsigned char> ret(16); | |
| 1188 fz_md5_final( md5, &ret[0]); | |
| 1189 return ret; | |
| 1190 }} | |
| 1191 | |
| 1192 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) | |
| 1193 {{ | |
| 1194 {{ | |
| 1195 std::vector<fz_quad> ret(max_quads); | |
| 1196 int n; | |
| 1197 fz_try(ctx) | |
| 1198 {{ | |
| 1199 n = fz_highlight_selection(ctx, page, a, b, &ret[0], max_quads); | |
| 1200 }} | |
| 1201 fz_catch(ctx) | |
| 1202 {{ | |
| 1203 n = -1; | |
| 1204 }} | |
| 1205 if (n >= 0) | |
| 1206 {{ | |
| 1207 ret.resize(n); | |
| 1208 return ret; | |
| 1209 }} | |
| 1210 }} | |
| 1211 /* We are careful to only call `fz_throw()` after `ret`'s | |
| 1212 destructor has been called. */ | |
| 1213 fz_throw(ctx, FZ_ERROR_GENERIC, "fz_highlight_selection() failed"); | |
| 1214 }} | |
| 1215 | |
| 1216 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) | |
| 1217 {{ | |
| 1218 std::vector<fz_quad> quads(hit_max); | |
| 1219 std::vector<int> marks(hit_max); | |
| 1220 int n = fz_search_page_number(ctx, doc, number, needle, &marks[0], &quads[0], hit_max); | |
| 1221 std::vector<fz_search_page2_hit> ret(n); | |
| 1222 for (int i=0; i<n; ++i) | |
| 1223 {{ | |
| 1224 ret[i].quad = quads[i]; | |
| 1225 ret[i].mark = marks[i]; | |
| 1226 }} | |
| 1227 return ret; | |
| 1228 }} | |
| 1229 | |
| 1230 FZ_FUNCTION std::string fz_string_from_text_language2(fz_text_language lang) | |
| 1231 {{ | |
| 1232 char str[8]; | |
| 1233 fz_string_from_text_language(str, lang); | |
| 1234 return std::string(str); | |
| 1235 }} | |
| 1236 | |
| 1237 FZ_FUNCTION std::string fz_get_glyph_name2(fz_context* ctx, fz_font* font, int glyph) | |
| 1238 {{ | |
| 1239 char name[32]; | |
| 1240 fz_get_glyph_name(ctx, font, glyph, name, sizeof(name)); | |
| 1241 return std::string(name); | |
| 1242 }} | |
| 1243 | |
| 1244 void fz_install_load_system_font_funcs2(fz_context* ctx, fz_install_load_system_font_funcs_args* args) | |
| 1245 {{ | |
| 1246 fz_install_load_system_font_funcs(ctx, args->f, args->f_cjk, args->f_fallback); | |
| 1247 }} | |
| 1248 | |
| 1249 void* fz_install_load_system_font_funcs2_state = nullptr; | |
| 1250 | |
| 1251 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) | |
| 1252 {{ | |
| 1253 return handler->open(ctx, handler, stream, accel, dir, recognize_state); | |
| 1254 }} | |
| 1255 | |
| 1256 FZ_FUNCTION int fz_document_handler_recognize(fz_context* ctx, const fz_document_handler *handler, const char *magic) | |
| 1257 {{ | |
| 1258 return handler->recognize(ctx, handler, magic); | |
| 1259 }} | |
| 1260 | |
| 1261 FZ_FUNCTION std::vector<std::string> pdf_choice_widget_options2(fz_context* ctx, pdf_annot* tw, int exportval) | |
| 1262 {{ | |
| 1263 int n = pdf_choice_widget_options(ctx, tw, exportval, nullptr); | |
| 1264 std::vector<const char*> opts(n); | |
| 1265 int n2 = pdf_choice_widget_options(ctx, tw, exportval, &opts[0]); | |
| 1266 assert(n2 == n); | |
| 1267 std::vector<std::string> ret(n); | |
| 1268 for (int i=0; i<n; ++i) | |
| 1269 {{ | |
| 1270 ret[i] = opts[i]; | |
| 1271 }} | |
| 1272 return ret; | |
| 1273 }} | |
| 1274 | |
| 1275 FZ_FUNCTION fz_image* fz_new_image_from_compressed_buffer2( | |
| 1276 fz_context* ctx, | |
| 1277 int w, | |
| 1278 int h, | |
| 1279 int bpc, | |
| 1280 fz_colorspace* colorspace, | |
| 1281 int xres, | |
| 1282 int yres, | |
| 1283 int interpolate, | |
| 1284 int imagemask, | |
| 1285 const std::vector<float>& decode, | |
| 1286 const std::vector<int>& colorkey, | |
| 1287 fz_compressed_buffer* buffer, | |
| 1288 fz_image* mask | |
| 1289 ) | |
| 1290 {{ | |
| 1291 int n = fz_colorspace_n(ctx, colorspace); | |
| 1292 assert(decode.empty() || decode.size() == 2 * n); | |
| 1293 assert(colorkey.empty() || colorkey.size() == 2 * n); | |
| 1294 const float* decode2 = decode.empty() ? nullptr : &decode[0]; | |
| 1295 const int* colorkey2 = colorkey.empty() ? nullptr : &colorkey[0]; | |
| 1296 fz_image* ret = fz_new_image_from_compressed_buffer( | |
| 1297 ctx, | |
| 1298 w, | |
| 1299 h, | |
| 1300 bpc, | |
| 1301 colorspace, | |
| 1302 xres, | |
| 1303 yres, | |
| 1304 interpolate, | |
| 1305 imagemask, | |
| 1306 decode2, | |
| 1307 colorkey2, | |
| 1308 fz_keep_compressed_buffer(ctx, buffer), | |
| 1309 mask | |
| 1310 ); | |
| 1311 return ret; | |
| 1312 }} | |
| 1313 | |
| 1314 void pdf_rearrange_pages2( | |
| 1315 fz_context* ctx, | |
| 1316 pdf_document* doc, | |
| 1317 const std::vector<int>& pages, | |
| 1318 pdf_clean_options_structure structure | |
| 1319 ) | |
| 1320 {{ | |
| 1321 return pdf_rearrange_pages(ctx, doc, pages.size(), &pages[0], structure); | |
| 1322 }} | |
| 1323 | |
| 1324 void pdf_subset_fonts2(fz_context *ctx, pdf_document *doc, const std::vector<int>& pages) | |
| 1325 {{ | |
| 1326 return pdf_subset_fonts(ctx, doc, pages.size(), &pages[0]); | |
| 1327 }} | |
| 1328 | |
| 1329 static void s_format_check(fz_context* ctx, const char* fmt, const char* specifiers) | |
| 1330 {{ | |
| 1331 int length = strlen(fmt); | |
| 1332 if (!length || !strchr(specifiers, fmt[length-1])) | |
| 1333 {{ | |
| 1334 fz_throw(ctx, FZ_ERROR_ARGUMENT, "Incorrect fmt '%s' should end with one of '%s'.", fmt, specifiers); | |
| 1335 }} | |
| 1336 }} | |
| 1337 | |
| 1338 std::string fz_format_double(fz_context* ctx, const char* fmt, double value) | |
| 1339 {{ | |
| 1340 char buffer[256]; | |
| 1341 s_format_check(ctx, fmt, "efg"); | |
| 1342 fz_snprintf(buffer, sizeof(buffer), fmt, value); | |
| 1343 return buffer; | |
| 1344 }} | |
| 1345 | |
| 1346 static void fz_enumerate_font_cmap2_cb(fz_context* ctx, void* opaque, unsigned long ucs, unsigned int gid) | |
| 1347 {{ | |
| 1348 std::vector<fz_font_ucs_gid>& ret = *(std::vector<fz_font_ucs_gid>*) opaque; | |
| 1349 fz_font_ucs_gid item = {{ucs, gid}}; | |
| 1350 ret.push_back(item); | |
| 1351 }} | |
| 1352 | |
| 1353 std::vector<fz_font_ucs_gid> fz_enumerate_font_cmap2(fz_context* ctx, fz_font* font) | |
| 1354 {{ | |
| 1355 std::vector<fz_font_ucs_gid> ret; | |
| 1356 fz_enumerate_font_cmap(ctx, font, fz_enumerate_font_cmap2_cb, &ret); | |
| 1357 return ret; | |
| 1358 }} | |
| 1359 | |
| 1360 void pdf_set_annot_callout_line2(fz_context *ctx, pdf_annot *annot, std::vector<fz_point>& callout) | |
| 1361 {{ | |
| 1362 pdf_set_annot_callout_line(ctx, annot, &callout[0], callout.size()); | |
| 1363 }} | |
| 1364 | |
| 1365 std::string fz_decode_barcode_from_display_list2(fz_context *ctx, fz_barcode_type *type, fz_display_list *list, fz_rect subarea, int rotate) | |
| 1366 {{ | |
| 1367 char* ret = fz_decode_barcode_from_display_list(ctx, type, list, subarea, rotate); | |
| 1368 std::string ret2 = ret; | |
| 1369 fz_free(ctx, ret); | |
| 1370 return ret2; | |
| 1371 }} | |
| 1372 | |
| 1373 std::string fz_decode_barcode_from_pixmap2(fz_context *ctx, fz_barcode_type *type, fz_pixmap *pix, int rotate) | |
| 1374 {{ | |
| 1375 char* ret = fz_decode_barcode_from_pixmap(ctx, type, pix, rotate); | |
| 1376 std::string ret2 = ret; | |
| 1377 fz_free(ctx, ret); | |
| 1378 return ret2; | |
| 1379 }} | |
| 1380 | |
| 1381 std::string fz_decode_barcode_from_page2(fz_context *ctx, fz_barcode_type *type, fz_page *page, fz_rect subarea, int rotate) | |
| 1382 {{ | |
| 1383 char* ret = fz_decode_barcode_from_page(ctx, type, page, subarea, rotate); | |
| 1384 std::string ret2 = ret; | |
| 1385 fz_free(ctx, ret); | |
| 1386 return ret2; | |
| 1387 }} | |
| 1388 ''') | |
| 1389 | |
| 1390 def make_extra( out_extra_h, out_extra_cpp): | |
| 1391 ''' | |
| 1392 We write extra abstractions here. | |
| 1393 | |
| 1394 These are written in C++ but are at the same level of abstraction as MuPDF | |
| 1395 C functions, for example they take `fz_context` args. This is done so that | |
| 1396 we automatically generate wrappers as class methods as well as global | |
| 1397 functions. | |
| 1398 ''' | |
| 1399 out_extra_h.write( g_extra_declarations) | |
| 1400 | |
| 1401 out_extra_cpp.write( textwrap.dedent(''' | |
| 1402 #include "mupdf/extra.h" | |
| 1403 | |
| 1404 ''')) | |
| 1405 out_extra_cpp.write( g_extra_definitions) | |
| 1406 | |
| 1407 | |
| 1408 def make_internal_functions( namespace, out_h, out_cpp, refcheck_if, trace_if): | |
| 1409 ''' | |
| 1410 Writes internal support functions. | |
| 1411 | |
| 1412 out_h: | |
| 1413 Stream to which we write C++ header text. | |
| 1414 out_cpp: | |
| 1415 Stream to which we write C++ text. | |
| 1416 ''' | |
| 1417 out_h.write( | |
| 1418 textwrap.dedent( | |
| 1419 f''' | |
| 1420 #define internal_assert(expression) (expression) ? (void) 0 : internal_assert_fail(__FILE__, __LINE__, __FUNCTION__, #expression) | |
| 1421 FZ_FUNCTION void internal_assert_fail(const char* file, int line, const char* fn, const char* expression); | |
| 1422 | |
| 1423 /** Internal use only. Looks at environmental variable <name>; returns 0 if unset else int value. */ | |
| 1424 FZ_FUNCTION int {rename.internal('env_flag')}(const char* name); | |
| 1425 | |
| 1426 /** Internal use only. Looks at environmental variable <name>; returns 0 if unset else int value. */ | |
| 1427 FZ_FUNCTION int {rename.internal('env_flag_check_unset')}( const char* if_, const char* name); | |
| 1428 | |
| 1429 /** Internal use only. Returns `fz_context*` for use by current thread. */ | |
| 1430 FZ_FUNCTION fz_context* {rename.internal('context_get')}(); | |
| 1431 ''' | |
| 1432 )) | |
| 1433 | |
| 1434 out_cpp.write( | |
| 1435 textwrap.dedent( | |
| 1436 ''' | |
| 1437 #include "mupdf/exceptions.h" | |
| 1438 #include "mupdf/internal.h" | |
| 1439 | |
| 1440 #include <iostream> | |
| 1441 #include <thread> | |
| 1442 #include <mutex> | |
| 1443 | |
| 1444 #include <string.h> | |
| 1445 | |
| 1446 ''')) | |
| 1447 | |
| 1448 make_namespace_open( namespace, out_cpp) | |
| 1449 | |
| 1450 state_t = rename.internal( 'state') | |
| 1451 thread_state_t = rename.internal( 'thread_state') | |
| 1452 | |
| 1453 cpp_text = textwrap.dedent( | |
| 1454 f''' | |
| 1455 FZ_FUNCTION void internal_assert_fail(const char* file, int line, const char* fn, const char* expression) | |
| 1456 {{ | |
| 1457 std::cerr << file << ":" << line << ":" << fn << "(): " | |
| 1458 << "MuPDF C++ internal assert failure: " << expression | |
| 1459 << "\\n" << std::flush; | |
| 1460 abort(); | |
| 1461 }} | |
| 1462 | |
| 1463 FZ_FUNCTION int {rename.internal('env_flag')}(const char* name) | |
| 1464 {{ | |
| 1465 const char* s = getenv( name); | |
| 1466 if (!s) return 0; | |
| 1467 return atoi( s); | |
| 1468 }} | |
| 1469 | |
| 1470 FZ_FUNCTION int {rename.internal('env_flag_check_unset')}(const char* if_, const char* name) | |
| 1471 {{ | |
| 1472 const char* s = getenv( name); | |
| 1473 if (s) std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():" | |
| 1474 << " Warning: ignoring environmental variable because" | |
| 1475 << " '" << if_ << "' is false: " << name << "\\n"; | |
| 1476 return false; | |
| 1477 }} | |
| 1478 | |
| 1479 {trace_if} | |
| 1480 static const int s_trace = mupdf::internal_env_flag("MUPDF_trace"); | |
| 1481 #else | |
| 1482 static const int s_trace = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace"); | |
| 1483 #endif | |
| 1484 | |
| 1485 static bool s_state_valid = false; | |
| 1486 | |
| 1487 struct {rename.internal("state")} | |
| 1488 {{ | |
| 1489 /* Constructor. */ | |
| 1490 {rename.internal("state")}() | |
| 1491 {{ | |
| 1492 m_locks.user = this; | |
| 1493 m_locks.lock = lock; | |
| 1494 m_locks.unlock = unlock; | |
| 1495 m_ctx = nullptr; | |
| 1496 bool multithreaded = true; | |
| 1497 const char* s = getenv( "MUPDF_mt_ctx"); | |
| 1498 if ( s && !strcmp( s, "0")) multithreaded = false; | |
| 1499 reinit( multithreaded); | |
| 1500 s_state_valid = true; | |
| 1501 }} | |
| 1502 | |
| 1503 void reinit( bool multithreaded) | |
| 1504 {{ | |
| 1505 if (s_trace) | |
| 1506 {{ | |
| 1507 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): " | |
| 1508 << " calling fz_drop_context()\\n"; | |
| 1509 }} | |
| 1510 fz_drop_context( m_ctx); | |
| 1511 m_multithreaded = multithreaded; | |
| 1512 if (s_trace) | |
| 1513 {{ | |
| 1514 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): " | |
| 1515 << " calling fz_new_context()\\n"; | |
| 1516 }} | |
| 1517 m_ctx = fz_new_context(NULL /*alloc*/, (multithreaded) ? &m_locks : nullptr, FZ_STORE_DEFAULT); | |
| 1518 if (s_trace) | |
| 1519 {{ | |
| 1520 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): " | |
| 1521 << "fz_new_context() => " << m_ctx << "\\n"; | |
| 1522 }} | |
| 1523 if (s_trace) | |
| 1524 {{ | |
| 1525 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): " | |
| 1526 << " calling fz_register_document_handlers()\\n"; | |
| 1527 }} | |
| 1528 internal_assert("m_ctx = fz_new_context()" && m_ctx); | |
| 1529 fz_register_document_handlers(m_ctx); | |
| 1530 }} | |
| 1531 static void lock(void *user, int lock) | |
| 1532 {{ | |
| 1533 {rename.internal("state")}* self = ({rename.internal("state")}*) user; | |
| 1534 internal_assert( self->m_multithreaded); | |
| 1535 self->m_mutexes[lock].lock(); | |
| 1536 }} | |
| 1537 static void unlock(void *user, int lock) | |
| 1538 {{ | |
| 1539 {rename.internal("state")}* self = ({rename.internal("state")}*) user; | |
| 1540 internal_assert( self->m_multithreaded); | |
| 1541 self->m_mutexes[lock].unlock(); | |
| 1542 }} | |
| 1543 ~{rename.internal("state")}() | |
| 1544 {{ | |
| 1545 if (s_trace) | |
| 1546 {{ | |
| 1547 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): " | |
| 1548 << " calling fz_drop_context()\\n"; | |
| 1549 }} | |
| 1550 fz_drop_context(m_ctx); | |
| 1551 m_ctx = nullptr; | |
| 1552 s_state_valid = false; | |
| 1553 }} | |
| 1554 | |
| 1555 bool m_multithreaded; | |
| 1556 fz_context* m_ctx; | |
| 1557 std::mutex m_mutex; /* Serialise access to m_ctx. fixme: not actually necessary. */ | |
| 1558 | |
| 1559 /* Provide thread support to mupdf. */ | |
| 1560 std::mutex m_mutexes[FZ_LOCK_MAX]; | |
| 1561 fz_locks_context m_locks; | |
| 1562 }}; | |
| 1563 | |
| 1564 static {rename.internal("state")} s_state; | |
| 1565 | |
| 1566 struct {rename.internal("thread_state")} | |
| 1567 {{ | |
| 1568 {rename.internal("thread_state")}() | |
| 1569 : | |
| 1570 m_ctx( nullptr), | |
| 1571 m_constructed( true) | |
| 1572 {{}} | |
| 1573 fz_context* get_context() | |
| 1574 {{ | |
| 1575 internal_assert( s_state.m_multithreaded); | |
| 1576 | |
| 1577 /* The following code checks that we are not being called after | |
| 1578 we have been destructed. This can happen if global mupdf | |
| 1579 wrapper class instances are defined - thread-local objects | |
| 1580 are destructed /before/ globals. */ | |
| 1581 if (!m_constructed) | |
| 1582 {{ | |
| 1583 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ":\\n" | |
| 1584 << "*** Error - undefined behaviour.\\n" | |
| 1585 << "***\\n" | |
| 1586 << "*** Attempt to get thread-local fz_context after destruction\\n" | |
| 1587 << "*** of thread-local fz_context support instance.\\n" | |
| 1588 << "***\\n" | |
| 1589 << "*** This is undefined behaviour.\\n" | |
| 1590 << "***\\n" | |
| 1591 << "*** This can happen if mupdf wrapper class instances are\\n" | |
| 1592 << "*** created as globals, because in C++ global object\\n" | |
| 1593 << "*** destructors are run after thread_local destructors.\\n" | |
| 1594 << "***\\n" | |
| 1595 ; | |
| 1596 }} | |
| 1597 internal_assert( m_constructed); | |
| 1598 if (!m_ctx) | |
| 1599 {{ | |
| 1600 /* Make a context for this thread by cloning the global | |
| 1601 context. */ | |
| 1602 /* fixme: we don't actually need to take a lock here. */ | |
| 1603 std::lock_guard<std::mutex> lock( s_state.m_mutex); | |
| 1604 if (s_trace) | |
| 1605 {{ | |
| 1606 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): " | |
| 1607 << " calling fz_clone_context()\\n"; | |
| 1608 }} | |
| 1609 internal_assert(s_state_valid); | |
| 1610 m_ctx = fz_clone_context(s_state.m_ctx); | |
| 1611 if (s_trace) | |
| 1612 {{ | |
| 1613 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): " | |
| 1614 << "fz_clone_context(" << s_state.m_ctx << ") => " << m_ctx << "\\n"; | |
| 1615 }} | |
| 1616 internal_assert("m_ctx = fz_clone_context()" && m_ctx); | |
| 1617 }} | |
| 1618 return m_ctx; | |
| 1619 }} | |
| 1620 ~{rename.internal("thread_state")}() | |
| 1621 {{ | |
| 1622 if (m_ctx) | |
| 1623 {{ | |
| 1624 internal_assert( s_state.m_multithreaded); | |
| 1625 if (s_trace) | |
| 1626 {{ | |
| 1627 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): " | |
| 1628 << " calling fz_drop_context()\\n"; | |
| 1629 }} | |
| 1630 fz_drop_context( m_ctx); | |
| 1631 }} | |
| 1632 | |
| 1633 /* These two statements are an attempt to get useful | |
| 1634 diagnostics in cases of undefined behaviour caused by the | |
| 1635 use of global wrapper class instances, whose destructors | |
| 1636 will be called /after/ destruction of this thread-local | |
| 1637 internal_thread_state instance. See check of m_constructed in | |
| 1638 get_context(). | |
| 1639 | |
| 1640 This probably only works in non-optimised builds - | |
| 1641 optimisation will simply elide both these statements. */ | |
| 1642 m_ctx = nullptr; | |
| 1643 m_constructed = false; | |
| 1644 }} | |
| 1645 fz_context* m_ctx; | |
| 1646 bool m_constructed; | |
| 1647 }}; | |
| 1648 | |
| 1649 static thread_local {rename.internal("thread_state")} s_thread_state; | |
| 1650 | |
| 1651 FZ_FUNCTION fz_context* {rename.internal("context_get")}() | |
| 1652 {{ | |
| 1653 if (s_state.m_multithreaded) | |
| 1654 {{ | |
| 1655 return s_thread_state.get_context(); | |
| 1656 }} | |
| 1657 else | |
| 1658 {{ | |
| 1659 /* This gives a small improvement in performance for | |
| 1660 single-threaded use, e.g. from 552.4s to 548.1s. */ | |
| 1661 internal_assert(s_state_valid); | |
| 1662 fz_context* ret = s_state.m_ctx; | |
| 1663 internal_assert(ret); | |
| 1664 return ret; | |
| 1665 }} | |
| 1666 }} | |
| 1667 | |
| 1668 FZ_FUNCTION void reinit_singlethreaded() | |
| 1669 {{ | |
| 1670 if (0) | |
| 1671 {{ | |
| 1672 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): Reinitialising as single-threaded.\\n"; | |
| 1673 }} | |
| 1674 s_state.reinit( false /*multithreaded*/); | |
| 1675 }} | |
| 1676 ''') | |
| 1677 out_cpp.write( cpp_text) | |
| 1678 | |
| 1679 make_namespace_close( namespace, out_cpp) | |
| 1680 | |
| 1681 # Generate code that exposes C++ operator new/delete to Memento. | |
| 1682 # | |
| 1683 # Disabled because our generated code makes very few direct calls | |
| 1684 # to operator new, and Memento ends up catching lots of (presumably | |
| 1685 # false-positive) leaks in the Python interpreter, so isn't very useful. | |
| 1686 # | |
| 1687 if 0: | |
| 1688 out_cpp.write( textwrap.dedent( | |
| 1689 ''' | |
| 1690 #ifdef MEMENTO | |
| 1691 | |
| 1692 void* operator new( size_t size) | |
| 1693 { | |
| 1694 return Memento_cpp_new( size); | |
| 1695 } | |
| 1696 | |
| 1697 void operator delete( void* pointer) | |
| 1698 { | |
| 1699 Memento_cpp_delete( pointer); | |
| 1700 } | |
| 1701 | |
| 1702 void* operator new[]( size_t size) | |
| 1703 { | |
| 1704 return Memento_cpp_new_array( size); | |
| 1705 } | |
| 1706 | |
| 1707 void operator delete[]( void* pointer) | |
| 1708 { | |
| 1709 Memento_cpp_delete_array( pointer); | |
| 1710 } | |
| 1711 | |
| 1712 #endif | |
| 1713 ''' | |
| 1714 )) | |
| 1715 | |
| 1716 | |
| 1717 def make_function_wrappers( | |
| 1718 tu, | |
| 1719 namespace, | |
| 1720 out_exceptions_h, | |
| 1721 out_exceptions_cpp, | |
| 1722 out_functions_h, | |
| 1723 out_functions_cpp, | |
| 1724 out_internal_h, | |
| 1725 out_internal_cpp, | |
| 1726 out_functions_h2, | |
| 1727 out_functions_cpp2, | |
| 1728 generated, | |
| 1729 refcheck_if, | |
| 1730 trace_if, | |
| 1731 ): | |
| 1732 ''' | |
| 1733 Generates C++ source code containing wrappers for all fz_*() functions. | |
| 1734 | |
| 1735 We also create a function throw_exception(fz_context* ctx) that throws a | |
| 1736 C++ exception appropriate for the error in ctx. | |
| 1737 | |
| 1738 If a function has first arg fz_context*, extra code is generated that | |
| 1739 converts fz_try..fz_catch exceptions into C++ exceptions by calling | |
| 1740 throw_exception(). | |
| 1741 | |
| 1742 We remove any fz_context* argument and the implementation calls | |
| 1743 internal_get_context() to get a suitable thread-specific fz_context* to | |
| 1744 use. | |
| 1745 | |
| 1746 We generate a class for each exception type. | |
| 1747 | |
| 1748 Returned source is just the raw functions text, e.g. it does not contain | |
| 1749 required #include's. | |
| 1750 | |
| 1751 Args: | |
| 1752 tu: | |
| 1753 Clang translation unit. | |
| 1754 out_exceptions_h: | |
| 1755 Stream to which we write exception class definitions. | |
| 1756 out_exceptions_cpp: | |
| 1757 Stream to which we write exception class implementation. | |
| 1758 out_functions_h: | |
| 1759 Stream to which we write function declarations. | |
| 1760 out_functions_cpp: | |
| 1761 Stream to which we write function definitions. | |
| 1762 generated: | |
| 1763 A Generated instance. | |
| 1764 ''' | |
| 1765 # Look for FZ_ERROR_* enums. We generate an exception class for each of | |
| 1766 # these. | |
| 1767 # | |
| 1768 error_name_prefix = 'FZ_ERROR_' | |
| 1769 fz_error_names = [] | |
| 1770 fz_error_names_maxlen = 0 # Used for padding so generated code aligns. | |
| 1771 | |
| 1772 for cursor in parse.get_children(tu.cursor): | |
| 1773 if cursor.kind == state.clang.cindex.CursorKind.ENUM_DECL: | |
| 1774 #log( 'enum: {cursor.spelling=}) | |
| 1775 for child in parse.get_members( cursor): | |
| 1776 #log( 'child:{ child.spelling=}) | |
| 1777 if child.spelling.startswith( error_name_prefix): | |
| 1778 name = child.spelling[ len(error_name_prefix):] | |
| 1779 fz_error_names.append( name) | |
| 1780 if len( name) > fz_error_names_maxlen: | |
| 1781 fz_error_names_maxlen = len( name) | |
| 1782 | |
| 1783 def errors(include_error_base=False): | |
| 1784 ''' | |
| 1785 Yields (enum, typename, padding) for each error. | |
| 1786 E.g.: | |
| 1787 enum=FZ_ERROR_SYSTEM | |
| 1788 typename=mupdf_error_memory | |
| 1789 padding=' ' | |
| 1790 ''' | |
| 1791 names = fz_error_names | |
| 1792 if include_error_base: | |
| 1793 names = ['BASE'] + names | |
| 1794 for name in names: | |
| 1795 enum = f'{error_name_prefix}{name}' | |
| 1796 typename = rename.error_class( enum) | |
| 1797 padding = (fz_error_names_maxlen - len(name)) * ' ' | |
| 1798 yield enum, typename, padding | |
| 1799 | |
| 1800 # Declare base exception class and define its methods. | |
| 1801 # | |
| 1802 base_name = rename.error_class('FZ_ERROR_BASE') | |
| 1803 | |
| 1804 out_exceptions_h.write( textwrap.dedent( | |
| 1805 f''' | |
| 1806 /** Base class for exceptions. */ | |
| 1807 struct {base_name} : std::exception | |
| 1808 {{ | |
| 1809 int m_code; | |
| 1810 std::string m_text; | |
| 1811 mutable std::string m_what; | |
| 1812 FZ_FUNCTION const char* what() const throw(); | |
| 1813 FZ_FUNCTION {base_name}(int code, const char* text); | |
| 1814 }}; | |
| 1815 ''')) | |
| 1816 | |
| 1817 out_exceptions_cpp.write( textwrap.dedent( | |
| 1818 f''' | |
| 1819 FZ_FUNCTION {base_name}::{base_name}(int code, const char* text) | |
| 1820 : | |
| 1821 m_code(code), | |
| 1822 m_text(text) | |
| 1823 {{ | |
| 1824 {trace_if} | |
| 1825 if (s_trace_exceptions) | |
| 1826 {{ | |
| 1827 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): {base_name}: " << m_text << "\\n"; | |
| 1828 }} | |
| 1829 #endif | |
| 1830 }}; | |
| 1831 | |
| 1832 FZ_FUNCTION const char* {base_name}::what() const throw() | |
| 1833 {{ | |
| 1834 m_what = "code=" + std::to_string(m_code) + ": " + m_text; | |
| 1835 return m_what.c_str(); | |
| 1836 }}; | |
| 1837 | |
| 1838 ''')) | |
| 1839 | |
| 1840 # Generate SWIG Python code to allow conversion of our error class | |
| 1841 # exceptions into equivalent Python exceptions. | |
| 1842 error_classes_n = 0 | |
| 1843 for enum, typename, padding in errors(): | |
| 1844 error_classes_n += 1 | |
| 1845 | |
| 1846 error_classes_n += 1 # Extra space for FzErrorBase. | |
| 1847 generated.swig_python_exceptions.write( textwrap.dedent( f''' | |
| 1848 | |
| 1849 void internal_set_error_classes(PyObject* classes); | |
| 1850 | |
| 1851 %{{ | |
| 1852 /* A Python list of Error classes, [FzErrorNone, FzErrorMemory, FzErrorGeneric, ...]. */ | |
| 1853 static PyObject* s_error_classes[{error_classes_n}] = {{}}; | |
| 1854 | |
| 1855 /* Called on startup by mupdf.py, with a list of error classes | |
| 1856 to be copied into s_error_classes. This will allow us to create | |
| 1857 instances of these error classes in SWIG's `%exception ...`, so | |
| 1858 Python code will see exceptions as instances of Python error | |
| 1859 classes. */ | |
| 1860 void internal_set_error_classes(PyObject* classes) | |
| 1861 {{ | |
| 1862 assert(PyList_Check(classes)); | |
| 1863 int n = PyList_Size(classes); | |
| 1864 assert(n == {error_classes_n}); | |
| 1865 for (int i=0; i<n; ++i) | |
| 1866 {{ | |
| 1867 PyObject* class_ = PyList_GetItem(classes, i); | |
| 1868 s_error_classes[i] = class_; | |
| 1869 }} | |
| 1870 }} | |
| 1871 | |
| 1872 /* Sets Python exception to a new mupdf.<name> object constructed | |
| 1873 with `text`. */ | |
| 1874 void set_exception(PyObject* class_, int code, const std::string& text) | |
| 1875 {{ | |
| 1876 PyObject* args = Py_BuildValue("(s)", text.c_str()); | |
| 1877 PyObject* instance = PyObject_CallObject(class_, args); | |
| 1878 PyErr_SetObject(class_, instance); | |
| 1879 Py_XDECREF(instance); | |
| 1880 Py_XDECREF(args); | |
| 1881 }} | |
| 1882 | |
| 1883 /* Exception handler for swig-generated code. Uses internal | |
| 1884 `throw;` to recover the current C++ exception then uses | |
| 1885 `set_exception()` to set the current Python exception. Caller | |
| 1886 should do `SWIG_fail;` after we return. */ | |
| 1887 void handle_exception() | |
| 1888 {{ | |
| 1889 try | |
| 1890 {{ | |
| 1891 throw; | |
| 1892 }} | |
| 1893 ''' | |
| 1894 )) | |
| 1895 | |
| 1896 # Declare exception class for each FZ_ERROR_*. Also append catch blocks for | |
| 1897 # each of these exception classes to `handle_exception()`. | |
| 1898 # | |
| 1899 for i, (enum, typename, padding) in enumerate(errors()): | |
| 1900 out_exceptions_h.write( textwrap.dedent( | |
| 1901 f''' | |
| 1902 /** For `{enum}`. */ | |
| 1903 struct {typename} : {base_name} | |
| 1904 {{ | |
| 1905 FZ_FUNCTION {typename}(const char* message); | |
| 1906 }}; | |
| 1907 | |
| 1908 ''')) | |
| 1909 | |
| 1910 generated.swig_python_exceptions.write( textwrap.dedent( f''' | |
| 1911 /**/ | |
| 1912 catch (mupdf::{typename}& e) | |
| 1913 {{ | |
| 1914 if (g_mupdf_trace_exceptions) | |
| 1915 {{ | |
| 1916 std::cerr | |
| 1917 << __FILE__ << ':' << __LINE__ << ':' | |
| 1918 #ifndef _WIN32 | |
| 1919 << __PRETTY_FUNCTION__ << ':' | |
| 1920 #endif | |
| 1921 << " Converting C++ std::exception mupdf::{typename} ({i=}) into Python exception:\\n" | |
| 1922 << " e.m_code: " << e.m_code << "\\n" | |
| 1923 << " e.m_text: " << e.m_text << "\\n" | |
| 1924 << " e.what(): " << e.what() << "\\n" | |
| 1925 << " typeid(e).name(): " << typeid(e).name() << "\\n" | |
| 1926 << "\\n"; | |
| 1927 }} | |
| 1928 set_exception(s_error_classes[{i}], e.m_code, e.m_text); | |
| 1929 | |
| 1930 }}''')) | |
| 1931 | |
| 1932 # Append less specific exception handling. | |
| 1933 generated.swig_python_exceptions.write( textwrap.dedent( f''' | |
| 1934 catch (mupdf::FzErrorBase& e) | |
| 1935 {{ | |
| 1936 if (g_mupdf_trace_exceptions) | |
| 1937 {{ | |
| 1938 std::cerr | |
| 1939 << __FILE__ << ':' << __LINE__ << ':' | |
| 1940 #ifndef _WIN32 | |
| 1941 << __PRETTY_FUNCTION__ << ':' | |
| 1942 #endif | |
| 1943 << " Converting C++ std::exception mupdf::FzErrorBase ({error_classes_n-1=}) into Python exception:\\n" | |
| 1944 << " e.m_code: " << e.m_code << "\\n" | |
| 1945 << " e.m_text: " << e.m_text << "\\n" | |
| 1946 << " e.what(): " << e.what() << "\\n" | |
| 1947 << " typeid(e).name(): " << typeid(e).name() << "\\n" | |
| 1948 << "\\n"; | |
| 1949 }} | |
| 1950 PyObject* class_ = s_error_classes[{error_classes_n-1}]; | |
| 1951 PyObject* args = Py_BuildValue("is", e.m_code, e.m_text.c_str()); | |
| 1952 PyObject* instance = PyObject_CallObject(class_, args); | |
| 1953 PyErr_SetObject(class_, instance); | |
| 1954 Py_XDECREF(instance); | |
| 1955 Py_XDECREF(args); | |
| 1956 }} | |
| 1957 catch (std::exception& e) | |
| 1958 {{ | |
| 1959 if (g_mupdf_trace_exceptions) | |
| 1960 {{ | |
| 1961 std::cerr | |
| 1962 << __FILE__ << ':' << __LINE__ << ':' | |
| 1963 #ifndef _WIN32 | |
| 1964 << __PRETTY_FUNCTION__ << ':' | |
| 1965 #endif | |
| 1966 << " Converting C++ std::exception into Python exception: " | |
| 1967 << e.what() | |
| 1968 << " typeid(e).name(): " << typeid(e).name() << "\\n" | |
| 1969 << "\\n"; | |
| 1970 }} | |
| 1971 SWIG_Error(SWIG_RuntimeError, e.what()); | |
| 1972 | |
| 1973 }} | |
| 1974 catch (...) | |
| 1975 {{ | |
| 1976 if (g_mupdf_trace_exceptions) | |
| 1977 {{ | |
| 1978 std::cerr | |
| 1979 << __FILE__ << ':' << __LINE__ << ':' | |
| 1980 #ifndef _WIN32 | |
| 1981 << __PRETTY_FUNCTION__ << ':' | |
| 1982 #endif | |
| 1983 << " Converting unknown C++ exception into Python exception." | |
| 1984 << "\\n"; | |
| 1985 }} | |
| 1986 SWIG_Error(SWIG_RuntimeError, "Unknown exception"); | |
| 1987 }} | |
| 1988 }} | |
| 1989 | |
| 1990 %}} | |
| 1991 | |
| 1992 %exception | |
| 1993 {{ | |
| 1994 try | |
| 1995 {{ | |
| 1996 $action | |
| 1997 }} | |
| 1998 catch (...) | |
| 1999 {{ | |
| 2000 handle_exception(); | |
| 2001 SWIG_fail; | |
| 2002 }} | |
| 2003 }} | |
| 2004 ''')) | |
| 2005 | |
| 2006 generated.swig_python_set_error_classes.write( f'# Define __str()__ for each error/exception class, to use self.what().\n') | |
| 2007 for enum, typename, padding in errors(include_error_base=1): | |
| 2008 generated.swig_python_set_error_classes.write( f'{typename}.__str__ = lambda self: self.what()\n') | |
| 2009 | |
| 2010 generated.swig_python_set_error_classes.write( textwrap.dedent( f''' | |
| 2011 # This must be after the declaration of mupdf::FzError* | |
| 2012 # classes in mupdf/exceptions.h and declaration of | |
| 2013 # `internal_set_error_classes()`, otherwise generated code is | |
| 2014 # before the declaration of the Python class or similar. */ | |
| 2015 internal_set_error_classes([ | |
| 2016 ''')) | |
| 2017 for enum, typename, padding in errors(): | |
| 2018 generated.swig_python_set_error_classes.write(f' {typename},\n') | |
| 2019 generated.swig_python_set_error_classes.write( textwrap.dedent( f''' | |
| 2020 FzErrorBase, | |
| 2021 ]) | |
| 2022 ''')) | |
| 2023 | |
| 2024 # Define constructor for each exception class. | |
| 2025 # | |
| 2026 for enum, typename, padding in errors(): | |
| 2027 out_exceptions_cpp.write( textwrap.dedent( | |
| 2028 f''' | |
| 2029 FZ_FUNCTION {typename}::{typename}(const char* text) | |
| 2030 : {base_name}({enum}, text) | |
| 2031 {{ | |
| 2032 {trace_if} | |
| 2033 if (s_trace_exceptions) | |
| 2034 {{ | |
| 2035 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): {typename} constructor, text: " << m_text << "\\n"; | |
| 2036 }} | |
| 2037 #endif | |
| 2038 }} | |
| 2039 | |
| 2040 ''')) | |
| 2041 | |
| 2042 # Generate function that throws an appropriate exception from a fz_context. | |
| 2043 # | |
| 2044 throw_exception = rename.internal( 'throw_exception') | |
| 2045 out_exceptions_h.write( textwrap.dedent( | |
| 2046 f''' | |
| 2047 /** Throw exception appropriate for error in `ctx`. */ | |
| 2048 FZ_FUNCTION void {throw_exception}(fz_context* ctx); | |
| 2049 | |
| 2050 ''')) | |
| 2051 out_exceptions_cpp.write( textwrap.dedent( | |
| 2052 f''' | |
| 2053 FZ_FUNCTION void {throw_exception}(fz_context* ctx) | |
| 2054 {{ | |
| 2055 int code; | |
| 2056 const char* text = fz_convert_error(ctx, &code); | |
| 2057 {trace_if} | |
| 2058 if (s_trace_exceptions) | |
| 2059 {{ | |
| 2060 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): code=" << code << "\\n"; | |
| 2061 std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "(): text=" << text << "\\n"; | |
| 2062 }} | |
| 2063 #endif | |
| 2064 ''')) | |
| 2065 for enum, typename, padding in errors(): | |
| 2066 out_exceptions_cpp.write( f' if (code == {enum}) {padding}throw {typename}{padding}(text);\n') | |
| 2067 out_exceptions_cpp.write( f' throw {base_name}(code, text);\n') | |
| 2068 out_exceptions_cpp.write( f'}}\n') | |
| 2069 out_exceptions_cpp.write( '\n') | |
| 2070 | |
| 2071 make_internal_functions( namespace, out_internal_h, out_internal_cpp, refcheck_if, trace_if) | |
| 2072 | |
| 2073 # Generate wrappers for each function that we find. | |
| 2074 # | |
| 2075 functions = [] | |
| 2076 for fnname, cursor in state.state_.find_functions_starting_with( tu, ('fz_', 'pdf_'), method=False): | |
| 2077 assert fnname not in state.omit_fns | |
| 2078 #jlib.log( '{fnname=} {cursor.spelling=} {cursor.type.spelling=}') | |
| 2079 if ( cursor.type == state.clang.cindex.TypeKind.FUNCTIONPROTO | |
| 2080 and cursor.type.is_function_variadic() | |
| 2081 ): | |
| 2082 # We don't attempt to wrap variadic functions - would need to find | |
| 2083 # the equivalent function that takes a va_list. | |
| 2084 if 0: | |
| 2085 jlib.log( 'Variadic fn: {cursor.type.spelling=}') | |
| 2086 if fnname != 'fz_warn': | |
| 2087 continue | |
| 2088 if fnname == 'fz_push_try': | |
| 2089 # This is partof implementation of fz_try/catch so doesn't make | |
| 2090 # sense to provide a wrapper. Also it is OS-dependent so including | |
| 2091 # it makes our generated code OS-specific. | |
| 2092 continue | |
| 2093 | |
| 2094 functions.append( (fnname, cursor)) | |
| 2095 | |
| 2096 jlib.log1( '{len(functions)=}') | |
| 2097 | |
| 2098 # Sort by function-name to make output easier to read. | |
| 2099 functions.sort() | |
| 2100 for fnname, cursor in functions: | |
| 2101 if state.state_.show_details( fnname): | |
| 2102 jlib.log( 'Looking at {fnname}') | |
| 2103 fnname_wrapper = rename.ll_fn( fnname) | |
| 2104 function_wrapper( | |
| 2105 tu, | |
| 2106 cursor, | |
| 2107 fnname, | |
| 2108 fnname_wrapper, | |
| 2109 out_functions_h, | |
| 2110 out_functions_cpp, | |
| 2111 generated, | |
| 2112 refcheck_if, | |
| 2113 trace_if, | |
| 2114 ) | |
| 2115 if not fnname.startswith( ( 'fz_keep_', 'fz_drop_', 'pdf_keep_', 'pdf_drop_')): | |
| 2116 function_wrapper_class_aware( | |
| 2117 tu, | |
| 2118 register_fn_use=None, | |
| 2119 struct_name=None, | |
| 2120 class_name=None, | |
| 2121 fn_cursor=cursor, | |
| 2122 refcheck_if=refcheck_if, | |
| 2123 trace_if=trace_if, | |
| 2124 fnname=fnname, | |
| 2125 out_h=out_functions_h2, | |
| 2126 out_cpp=out_functions_cpp2, | |
| 2127 generated=generated, | |
| 2128 ) | |
| 2129 | |
| 2130 python.cppyy_add_outparams_wrapper( tu, fnname, cursor, state.state_, generated) | |
| 2131 | |
| 2132 if fnname == "pdf_load_field_name": #(fz_context *ctx, pdf_obj *field); | |
| 2133 # Output wrapper that returns std::string instead of buffer that | |
| 2134 # caller needs to free. | |
| 2135 out_functions_h.write( | |
| 2136 textwrap.dedent( | |
| 2137 f''' | |
| 2138 /** Alternative to `{rename.ll_fn('pdf_load_field_name')}()` that returns a std::string. */ | |
| 2139 FZ_FUNCTION std::string {rename.ll_fn('pdf_load_field_name2')}(pdf_obj* field); | |
| 2140 | |
| 2141 ''')) | |
| 2142 out_functions_cpp.write( | |
| 2143 textwrap.dedent( | |
| 2144 f''' | |
| 2145 FZ_FUNCTION std::string {rename.ll_fn('pdf_load_field_name2')}(pdf_obj* field) | |
| 2146 {{ | |
| 2147 char* buffer = {rename.ll_fn('pdf_load_field_name')}( field); | |
| 2148 std::string ret( buffer); | |
| 2149 {rename.ll_fn('fz_free')}( buffer); | |
| 2150 return ret; | |
| 2151 }} | |
| 2152 ''')) | |
| 2153 out_functions_h2.write( | |
| 2154 textwrap.indent( | |
| 2155 textwrap.dedent( | |
| 2156 f''' | |
| 2157 /** Alternative to `{rename.fn('pdf_load_field_name')}()` that returns a std::string. */ | |
| 2158 FZ_FUNCTION std::string {rename.fn('pdf_load_field_name2')}({rename.class_('pdf_obj')}& field); | |
| 2159 '''), | |
| 2160 ' ', | |
| 2161 ) | |
| 2162 ) | |
| 2163 out_functions_cpp2.write( | |
| 2164 textwrap.dedent( | |
| 2165 f''' | |
| 2166 FZ_FUNCTION std::string {rename.fn('pdf_load_field_name2')}({rename.class_('pdf_obj')}& field) | |
| 2167 {{ | |
| 2168 return {rename.ll_fn('pdf_load_field_name2')}( field.m_internal); | |
| 2169 }} | |
| 2170 ''')) | |
| 2171 | |
| 2172 # Output custom wrappers for variadic pdf_dict_getl(). | |
| 2173 # | |
| 2174 | |
| 2175 decl = f'''FZ_FUNCTION pdf_obj* {rename.ll_fn('pdf_dict_getlv')}( pdf_obj* dict, va_list keys)''' | |
| 2176 out_functions_h.write( textwrap.dedent( f''' | |
| 2177 /* Low-level wrapper for `pdf_dict_getl()`. `keys` must be null-terminated list of `pdf_obj*`'s. */ | |
| 2178 {decl}; | |
| 2179 ''')) | |
| 2180 out_functions_cpp.write( textwrap.dedent( f''' | |
| 2181 {decl} | |
| 2182 {{ | |
| 2183 pdf_obj *key; | |
| 2184 while (dict != NULL && (key = va_arg(keys, pdf_obj *)) != NULL) | |
| 2185 {{ | |
| 2186 dict = {rename.ll_fn('pdf_dict_get')}( dict, key); | |
| 2187 }} | |
| 2188 return dict; | |
| 2189 }} | |
| 2190 ''')) | |
| 2191 | |
| 2192 decl = f'''FZ_FUNCTION pdf_obj* {rename.ll_fn('pdf_dict_getl')}( pdf_obj* dict, ...)''' | |
| 2193 out_functions_h.write( textwrap.dedent( f''' | |
| 2194 /* Low-level wrapper for `pdf_dict_getl()`. `...` must be null-terminated list of `pdf_obj*`'s. */ | |
| 2195 {decl}; | |
| 2196 ''')) | |
| 2197 out_functions_cpp.write( textwrap.dedent( f''' | |
| 2198 {decl} | |
| 2199 {{ | |
| 2200 va_list keys; | |
| 2201 va_start(keys, dict); | |
| 2202 try | |
| 2203 {{ | |
| 2204 dict = {rename.ll_fn('pdf_dict_getlv')}( dict, keys); | |
| 2205 }} | |
| 2206 catch( std::exception&) | |
| 2207 {{ | |
| 2208 va_end(keys); | |
| 2209 throw; | |
| 2210 }} | |
| 2211 va_end(keys); | |
| 2212 return dict; | |
| 2213 }} | |
| 2214 ''')) | |
| 2215 | |
| 2216 decl = f'''FZ_FUNCTION {rename.class_('pdf_obj')} {rename.fn('pdf_dict_getlv')}( {rename.class_('pdf_obj')}& dict, va_list keys)''' | |
| 2217 out_functions_h2.write( | |
| 2218 textwrap.indent( | |
| 2219 textwrap.dedent( f''' | |
| 2220 /* Class-aware wrapper for `pdf_dict_getl()`. `keys` must be null-terminated list of | |
| 2221 `pdf_obj*`'s, not `{rename.class_('pdf_obj')}*`'s, so that conventional | |
| 2222 use with `PDF_NAME()` works. */ | |
| 2223 {decl}; | |
| 2224 '''), | |
| 2225 ' ', | |
| 2226 ) | |
| 2227 ) | |
| 2228 out_functions_cpp2.write( textwrap.dedent( f''' | |
| 2229 {decl} | |
| 2230 {{ | |
| 2231 pdf_obj* ret = {rename.ll_fn('pdf_dict_getlv')}( dict.m_internal, keys); | |
| 2232 return {rename.class_('pdf_obj')}( {rename.ll_fn('pdf_keep_obj')}( ret)); | |
| 2233 }} | |
| 2234 ''')) | |
| 2235 | |
| 2236 decl = f'''FZ_FUNCTION {rename.class_('pdf_obj')} {rename.fn('pdf_dict_getl')}( {rename.class_('pdf_obj')}* dict, ...)''' | |
| 2237 out_functions_h2.write( | |
| 2238 textwrap.indent( | |
| 2239 textwrap.dedent( f''' | |
| 2240 /* Class-aware wrapper for `pdf_dict_getl()`. `...` must be null-terminated list of | |
| 2241 `pdf_obj*`'s, not `{rename.class_('pdf_obj')}*`'s, so that conventional | |
| 2242 use with `PDF_NAME()` works. [We use pointer `dict` arg because variadic | |
| 2243 args do not with with reference args.] */ | |
| 2244 {decl}; | |
| 2245 '''), | |
| 2246 ' ', | |
| 2247 ), | |
| 2248 ) | |
| 2249 out_functions_cpp2.write( textwrap.dedent( f''' | |
| 2250 {decl} | |
| 2251 {{ | |
| 2252 va_list keys; | |
| 2253 va_start(keys, dict); | |
| 2254 try | |
| 2255 {{ | |
| 2256 {rename.class_('pdf_obj')} ret = {rename.fn('pdf_dict_getlv')}( *dict, keys); | |
| 2257 va_end( keys); | |
| 2258 return ret; | |
| 2259 }} | |
| 2260 catch (std::exception&) | |
| 2261 {{ | |
| 2262 va_end( keys); | |
| 2263 throw; | |
| 2264 }} | |
| 2265 }} | |
| 2266 ''')) | |
| 2267 | |
| 2268 | |
| 2269 def class_add_iterator( tu, struct_cursor, struct_name, classname, extras, refcheck_if, trace_if): | |
| 2270 ''' | |
| 2271 Add begin() and end() methods so that this generated class is iterable | |
| 2272 from C++ with: | |
| 2273 | |
| 2274 for (auto i: foo) {...} | |
| 2275 | |
| 2276 We modify <extras> to create an iterator class and add begin() and end() | |
| 2277 methods that each return an instance of the iterator class. | |
| 2278 ''' | |
| 2279 it_begin, it_end = extras.iterator_next | |
| 2280 | |
| 2281 # Figure out type of what the iterator returns by looking at type of | |
| 2282 # <it_begin>. | |
| 2283 if it_begin: | |
| 2284 c = parse.find_name( struct_cursor, it_begin) | |
| 2285 assert c.type.kind == state.clang.cindex.TypeKind.POINTER | |
| 2286 it_internal_type = state.get_name_canonical( c.type.get_pointee()).spelling | |
| 2287 it_internal_type = util.clip( it_internal_type, 'struct ') | |
| 2288 it_type = rename.class_( it_internal_type) | |
| 2289 else: | |
| 2290 # The container is also the first item in the linked list. | |
| 2291 it_internal_type = struct_name | |
| 2292 it_type = classname | |
| 2293 | |
| 2294 # We add to extras.methods_extra(). | |
| 2295 # | |
| 2296 check_refs = 1 if parse.has_refs( tu, struct_cursor.type) else 0 | |
| 2297 extras.methods_extra.append( | |
| 2298 classes.ExtraMethod( f'{classname}Iterator', 'begin()', | |
| 2299 f''' | |
| 2300 {{ | |
| 2301 auto ret = {classname}Iterator({'m_internal->'+it_begin if it_begin else '*this'}); | |
| 2302 {refcheck_if} | |
| 2303 #if {check_refs} | |
| 2304 if (s_check_refs) | |
| 2305 {{ | |
| 2306 s_{classname}_refs_check.check( this, __FILE__, __LINE__, __FUNCTION__); | |
| 2307 }} | |
| 2308 #endif | |
| 2309 #endif | |
| 2310 return ret; | |
| 2311 }} | |
| 2312 ''', | |
| 2313 f'/* Used for iteration over linked list of {it_type} items starting at {it_internal_type}::{it_begin}. */', | |
| 2314 ), | |
| 2315 ) | |
| 2316 extras.methods_extra.append( | |
| 2317 classes.ExtraMethod( f'{classname}Iterator', 'end()', | |
| 2318 f''' | |
| 2319 {{ | |
| 2320 auto ret = {classname}Iterator({it_type}()); | |
| 2321 {refcheck_if} | |
| 2322 #if {check_refs} | |
| 2323 if (s_check_refs) | |
| 2324 {{ | |
| 2325 s_{classname}_refs_check.check( this, __FILE__, __LINE__, __FUNCTION__); | |
| 2326 }} | |
| 2327 #endif | |
| 2328 #endif | |
| 2329 return ret; | |
| 2330 }} | |
| 2331 ''', | |
| 2332 f'/* Used for iteration over linked list of {it_type} items starting at {it_internal_type}::{it_begin}. */', | |
| 2333 ), | |
| 2334 ) | |
| 2335 | |
| 2336 extras.class_bottom += f'\n typedef {classname}Iterator iterator;\n' | |
| 2337 | |
| 2338 extras.class_pre += f'\nstruct {classname}Iterator;\n' | |
| 2339 | |
| 2340 extras.class_post += f''' | |
| 2341 struct {classname}Iterator | |
| 2342 {{ | |
| 2343 FZ_FUNCTION {classname}Iterator(const {it_type}& item); | |
| 2344 FZ_FUNCTION {classname}Iterator& operator++(); | |
| 2345 FZ_FUNCTION bool operator==( const {classname}Iterator& rhs); | |
| 2346 FZ_FUNCTION bool operator!=( const {classname}Iterator& rhs); | |
| 2347 FZ_FUNCTION {it_type} operator*(); | |
| 2348 FZ_FUNCTION {it_type}* operator->(); | |
| 2349 private: | |
| 2350 {it_type} m_item; | |
| 2351 }}; | |
| 2352 ''' | |
| 2353 keep_text = '' | |
| 2354 if extras.copyable and extras.copyable != 'default': | |
| 2355 # Our operator++ needs to create it_type from m_item.m_internal->next, | |
| 2356 # so we need to call fz_keep_<it_type>(). | |
| 2357 # | |
| 2358 # [Perhaps life would be simpler if our generated constructors always | |
| 2359 # called fz_keep_*() as necessary? In some circumstances this would | |
| 2360 # require us to call fz_drop_*() when constructing an instance, but | |
| 2361 # that might be simpler?] | |
| 2362 # | |
| 2363 base_name = util.clip( struct_name, ('fz_', 'pdf_')) | |
| 2364 if struct_name.startswith( 'fz_'): | |
| 2365 keep_name = f'fz_keep_{base_name}' | |
| 2366 elif struct_name.startswith( 'pdf_'): | |
| 2367 keep_name = f'pdf_keep_{base_name}' | |
| 2368 keep_name = rename.ll_fn(keep_name) | |
| 2369 keep_text = f'{keep_name}(m_item.m_internal->next);' | |
| 2370 | |
| 2371 extras.extra_cpp += f''' | |
| 2372 FZ_FUNCTION {classname}Iterator::{classname}Iterator(const {it_type}& item) | |
| 2373 : m_item( item) | |
| 2374 {{ | |
| 2375 }} | |
| 2376 FZ_FUNCTION {classname}Iterator& {classname}Iterator::operator++() | |
| 2377 {{ | |
| 2378 {keep_text} | |
| 2379 m_item = {it_type}(m_item.m_internal->next); | |
| 2380 return *this; | |
| 2381 }} | |
| 2382 FZ_FUNCTION bool {classname}Iterator::operator==( const {classname}Iterator& rhs) | |
| 2383 {{ | |
| 2384 return m_item.m_internal == rhs.m_item.m_internal; | |
| 2385 }} | |
| 2386 FZ_FUNCTION bool {classname}Iterator::operator!=( const {classname}Iterator& rhs) | |
| 2387 {{ | |
| 2388 return m_item.m_internal != rhs.m_item.m_internal; | |
| 2389 }} | |
| 2390 FZ_FUNCTION {it_type} {classname}Iterator::operator*() | |
| 2391 {{ | |
| 2392 return m_item; | |
| 2393 }} | |
| 2394 FZ_FUNCTION {it_type}* {classname}Iterator::operator->() | |
| 2395 {{ | |
| 2396 return &m_item; | |
| 2397 }} | |
| 2398 | |
| 2399 ''' | |
| 2400 | |
| 2401 | |
| 2402 def class_find_constructor_fns( tu, classname, struct_name, base_name, extras): | |
| 2403 ''' | |
| 2404 Returns list of functions that could be used as constructors of the | |
| 2405 specified wrapper class. | |
| 2406 | |
| 2407 For example we look for functions that return a pointer to <struct_name> or | |
| 2408 return a POD <struct_name> by value. | |
| 2409 | |
| 2410 tu: | |
| 2411 . | |
| 2412 classname: | |
| 2413 Name of our wrapper class. | |
| 2414 struct_name: | |
| 2415 Name of underlying mupdf struct. | |
| 2416 base_name: | |
| 2417 Name of struct without 'fz_' prefix. | |
| 2418 extras: | |
| 2419 . | |
| 2420 ''' | |
| 2421 assert struct_name == f'fz_{base_name}' or struct_name == f'pdf_{base_name}' | |
| 2422 verbose = state.state_.show_details( struct_name) | |
| 2423 constructor_fns = [] | |
| 2424 if '-' not in extras.constructor_prefixes: | |
| 2425 # Add default constructor fn prefix. | |
| 2426 if struct_name.startswith( 'fz_'): | |
| 2427 extras.constructor_prefixes.insert( 0, f'fz_new_') | |
| 2428 extras.constructor_prefixes.insert( 0, f'pdf_new_') | |
| 2429 elif struct_name.startswith( 'pdf_'): | |
| 2430 extras.constructor_prefixes.insert( 0, f'pdf_new_') | |
| 2431 for fnprefix in extras.constructor_prefixes: | |
| 2432 if verbose: | |
| 2433 jlib.log('{struct_name=} {fnprefix=}') | |
| 2434 for fnname, cursor in state.state_.find_functions_starting_with( tu, fnprefix, method=True): | |
| 2435 # Check whether this has identical signature to any fn we've | |
| 2436 # already found. | |
| 2437 if verbose: | |
| 2438 jlib.log( '{struct_name=} {fnname=}') | |
| 2439 duplicate_type = None | |
| 2440 duplicate_name = False | |
| 2441 for f, c, is_duplicate in constructor_fns: | |
| 2442 if verbose: | |
| 2443 jlib.log( '{struct_name=} {cursor.spelling=} {c.type.spelling=}') | |
| 2444 if f == fnname: | |
| 2445 if verbose: | |
| 2446 jlib.log('setting duplicate_name to true') | |
| 2447 duplicate_name = True | |
| 2448 break | |
| 2449 if c.type == cursor.type: | |
| 2450 if verbose: | |
| 2451 jlib.log( '{struct_name} wrapper: ignoring candidate constructor {fnname}() because prototype is indistinguishable from {f=}()') | |
| 2452 duplicate_type = f | |
| 2453 break | |
| 2454 if duplicate_name: | |
| 2455 continue | |
| 2456 ok = False | |
| 2457 | |
| 2458 arg, n = parse.get_first_arg( tu, cursor) | |
| 2459 if arg and n == 1 and parse.is_pointer_to( arg.cursor.type, struct_name): | |
| 2460 # This avoids generation of bogus copy constructor wrapping | |
| 2461 # function fz_new_pixmap_from_alpha_channel() introduced | |
| 2462 # 2021-05-07. | |
| 2463 # | |
| 2464 if verbose: | |
| 2465 jlib.log('ignoring possible constructor because looks like copy constructor: {fnname}') | |
| 2466 elif fnname in extras.constructor_excludes: | |
| 2467 if verbose: | |
| 2468 jlib.log('{fnname=} is in {extras.constructor_excludes=}') | |
| 2469 elif extras.pod and extras.pod != 'none' and state.get_name_canonical( cursor.result_type).spelling == f'{struct_name}': | |
| 2470 # Returns POD struct by value. | |
| 2471 ok = True | |
| 2472 elif not extras.pod and parse.is_pointer_to( cursor.result_type, f'{struct_name}'): | |
| 2473 # Returns pointer to struct. | |
| 2474 ok = True | |
| 2475 | |
| 2476 if ok: | |
| 2477 if duplicate_type and extras.copyable: | |
| 2478 if verbose: | |
| 2479 jlib.log1( 'adding static method wrapper for {fnname}') | |
| 2480 extras.method_wrappers_static.append( fnname) | |
| 2481 else: | |
| 2482 if duplicate_type: | |
| 2483 if verbose: | |
| 2484 jlib.log( 'not able to provide static factory fn {struct_name}::{fnname} because wrapper class is not copyable.') | |
| 2485 if verbose: | |
| 2486 jlib.log( 'adding constructor wrapper for {fnname}') | |
| 2487 constructor_fns.append( (fnname, cursor, duplicate_type)) | |
| 2488 else: | |
| 2489 if verbose: | |
| 2490 jlib.log( 'ignoring possible constructor for {classname=} because does not return required type: {fnname=} -> {cursor.result_type.spelling=}') | |
| 2491 | |
| 2492 constructor_fns.sort() | |
| 2493 return constructor_fns | |
| 2494 | |
| 2495 | |
| 2496 def class_find_destructor_fns( tu, struct_name, base_name): | |
| 2497 ''' | |
| 2498 Returns list of functions that could be used by destructor - must be called | |
| 2499 'fz_drop_<typename>', must take a <struct>* arg, may take a fz_context* | |
| 2500 arg. | |
| 2501 ''' | |
| 2502 if struct_name.startswith( 'fz_'): | |
| 2503 destructor_prefix = f'fz_drop_{base_name}' | |
| 2504 elif struct_name.startswith( 'pdf_'): | |
| 2505 destructor_prefix = f'pdf_drop_{base_name}' | |
| 2506 destructor_fns = [] | |
| 2507 for fnname, cursor in state.state_.find_functions_starting_with( tu, destructor_prefix, method=True): | |
| 2508 arg_struct = False | |
| 2509 arg_context = False | |
| 2510 args_num = 0 | |
| 2511 for arg in parse.get_args( tu, cursor): | |
| 2512 if not arg_struct and parse.is_pointer_to( arg.cursor.type, struct_name): | |
| 2513 arg_struct = True | |
| 2514 elif not arg_context and parse.is_pointer_to( arg.cursor.type, 'fz_context'): | |
| 2515 arg_context = True | |
| 2516 args_num += 1 | |
| 2517 if arg_struct: | |
| 2518 if args_num == 1 or (args_num == 2 and arg_context): | |
| 2519 # No params other than <struct>* and fz_context* so this is | |
| 2520 # candidate destructor. | |
| 2521 #log( 'adding candidate destructor: {fnname}') | |
| 2522 destructor_fns.append( (fnname, cursor)) | |
| 2523 | |
| 2524 destructor_fns.sort() | |
| 2525 return destructor_fns | |
| 2526 | |
| 2527 | |
| 2528 def num_instances(refcheck_if, delta, name): | |
| 2529 ''' | |
| 2530 Returns C++ code to embed in a wrapper class constructor/destructor function | |
| 2531 to update the class static `s_num_instances` variable. | |
| 2532 ''' | |
| 2533 ret = '' | |
| 2534 ret += f' {refcheck_if}\n' | |
| 2535 if delta == +1: | |
| 2536 ret += ' ++s_num_instances;\n' | |
| 2537 elif delta == -1: | |
| 2538 ret += ' --s_num_instances;\n' | |
| 2539 else: | |
| 2540 assert 0 | |
| 2541 ret += ' #endif\n' | |
| 2542 return ret | |
| 2543 | |
| 2544 | |
| 2545 def class_constructor_default( | |
| 2546 tu, | |
| 2547 struct_cursor, | |
| 2548 classname, | |
| 2549 extras, | |
| 2550 out_h, | |
| 2551 out_cpp, | |
| 2552 refcheck_if, | |
| 2553 trace_if, | |
| 2554 ): | |
| 2555 ''' | |
| 2556 Generates constructor that sets each member to default value. | |
| 2557 ''' | |
| 2558 if extras.pod: | |
| 2559 comment = f'Default constructor, sets each member to default value.' | |
| 2560 else: | |
| 2561 comment = f'Default constructor, sets `m_internal` to null.' | |
| 2562 out_h.write( '\n') | |
| 2563 out_h.write( f' /** {comment} */\n') | |
| 2564 out_h.write( f' FZ_FUNCTION {classname}();\n') | |
| 2565 | |
| 2566 out_cpp.write( f'/** {comment} */\n') | |
| 2567 out_cpp.write( f'FZ_FUNCTION {classname}::{classname}()\n') | |
| 2568 if not extras.pod: | |
| 2569 out_cpp.write( f': m_internal(nullptr)\n') | |
| 2570 out_cpp.write( f'{{\n') | |
| 2571 if extras.pod == 'none': | |
| 2572 pass | |
| 2573 elif extras.pod: | |
| 2574 for c in parse.get_members(struct_cursor): | |
| 2575 if extras.pod == 'inline': | |
| 2576 c_name = f'this->{c.spelling}' | |
| 2577 else: | |
| 2578 c_name = f'this->m_internal.{c.spelling}' | |
| 2579 if c.type.kind == state.clang.cindex.TypeKind.CONSTANTARRAY: | |
| 2580 out_cpp.write( f' memset(&{c_name}, 0, sizeof({c_name}));\n') | |
| 2581 else: | |
| 2582 out_cpp.write( f' {c_name} = {{}};\n') | |
| 2583 else: | |
| 2584 if parse.has_refs( tu, struct_cursor.type): | |
| 2585 out_cpp.write(f' {refcheck_if}\n') | |
| 2586 out_cpp.write( ' if (s_check_refs)\n') | |
| 2587 out_cpp.write( ' {\n') | |
| 2588 out_cpp.write(f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n') | |
| 2589 out_cpp.write( ' }\n') | |
| 2590 out_cpp.write( ' #endif\n') | |
| 2591 | |
| 2592 out_cpp.write(num_instances(refcheck_if, +1, classname)) | |
| 2593 | |
| 2594 out_cpp.write( f'}};\n') | |
| 2595 | |
| 2596 | |
| 2597 def class_copy_constructor( | |
| 2598 tu, | |
| 2599 functions, | |
| 2600 struct_name, | |
| 2601 struct_cursor, | |
| 2602 base_name, | |
| 2603 classname, | |
| 2604 constructor_fns, | |
| 2605 out_h, | |
| 2606 out_cpp, | |
| 2607 refcheck_if, | |
| 2608 trace_if, | |
| 2609 ): | |
| 2610 ''' | |
| 2611 Generate a copy constructor and operator= by finding a suitable fz_keep_*() | |
| 2612 function. | |
| 2613 | |
| 2614 We raise an exception if we can't find one. | |
| 2615 ''' | |
| 2616 if struct_name.startswith( 'fz_'): | |
| 2617 keep_name = f'fz_keep_{base_name}' | |
| 2618 drop_name = f'fz_drop_{base_name}' | |
| 2619 elif struct_name.startswith( 'pdf_'): | |
| 2620 keep_name = f'pdf_keep_{base_name}' | |
| 2621 drop_name = f'pdf_drop_{base_name}' | |
| 2622 for name in keep_name, drop_name: | |
| 2623 cursor = state.state_.find_function( tu, name, method=True) | |
| 2624 if not cursor: | |
| 2625 classextra = classes.classextras.get( tu, struct_name) | |
| 2626 if classextra.copyable: | |
| 2627 if 1 or state.state_.show_details( struct_name): | |
| 2628 jlib.log( 'changing to non-copyable because no function {name}(): {struct_name}') | |
| 2629 classextra.copyable = False | |
| 2630 return | |
| 2631 if name == keep_name: | |
| 2632 pvoid = parse.is_pointer_to( cursor.result_type, 'void') | |
| 2633 assert ( pvoid | |
| 2634 or parse.is_pointer_to( cursor.result_type, struct_name) | |
| 2635 ), ( | |
| 2636 f'Function {name}(): result_type not void* or pointer to {struct_name}: {cursor.result_type.spelling}' | |
| 2637 ) | |
| 2638 arg, n = parse.get_first_arg( tu, cursor) | |
| 2639 assert n == 1, f'should take exactly one arg: {cursor.spelling}()' | |
| 2640 assert parse.is_pointer_to( arg.cursor.type, struct_name), ( | |
| 2641 f'arg0 is not pointer to {struct_name}: {cursor.spelling}(): {arg.cursor.spelling} {arg.name}') | |
| 2642 | |
| 2643 for fnname, cursor, duplicate_type in constructor_fns: | |
| 2644 fnname2 = rename.ll_fn(fnname) | |
| 2645 if fnname2 == keep_name: | |
| 2646 jlib.log( 'not generating copy constructor with {keep_name=} because already used by a constructor.') | |
| 2647 break | |
| 2648 else: | |
| 2649 functions( keep_name) | |
| 2650 comment = f'Copy constructor using `{keep_name}()`.' | |
| 2651 out_h.write( '\n') | |
| 2652 out_h.write( f' /** {comment} */\n') | |
| 2653 out_h.write( f' FZ_FUNCTION {classname}(const {classname}& rhs);\n') | |
| 2654 out_h.write( '\n') | |
| 2655 | |
| 2656 cast = '' | |
| 2657 if pvoid: | |
| 2658 # Need to cast the void* to the correct type. | |
| 2659 cast = f'(::{struct_name}*) ' | |
| 2660 | |
| 2661 out_cpp.write( f'/** {comment} */\n') | |
| 2662 out_cpp.write( f'FZ_FUNCTION {classname}::{classname}(const {classname}& rhs)\n') | |
| 2663 out_cpp.write( f': m_internal({cast}{rename.ll_fn(keep_name)}(rhs.m_internal))\n') | |
| 2664 out_cpp.write( '{\n') | |
| 2665 | |
| 2666 # Write trace code. | |
| 2667 out_cpp.write( f' {trace_if}\n') | |
| 2668 out_cpp.write( f' if (s_trace_keepdrop) {{\n') | |
| 2669 out_cpp.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():"\n') | |
| 2670 out_cpp.write( f' << " have called {rename.ll_fn(keep_name)}(rhs.m_internal)\\n"\n') | |
| 2671 out_cpp.write( f' ;\n') | |
| 2672 out_cpp.write( f' }}\n') | |
| 2673 out_cpp.write( f' #endif\n') | |
| 2674 | |
| 2675 if parse.has_refs( tu, struct_cursor.type): | |
| 2676 out_cpp.write(f' {refcheck_if}\n') | |
| 2677 out_cpp.write( ' if (s_check_refs)\n') | |
| 2678 out_cpp.write( ' {\n') | |
| 2679 out_cpp.write(f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n') | |
| 2680 out_cpp.write( ' }\n') | |
| 2681 out_cpp.write( ' #endif\n') | |
| 2682 out_cpp.write(num_instances(refcheck_if, +1, classname)) | |
| 2683 out_cpp.write( '}\n') | |
| 2684 out_cpp.write( '\n') | |
| 2685 | |
| 2686 # Make operator=(). | |
| 2687 # | |
| 2688 comment = f'operator= using `{keep_name}()` and `{drop_name}()`.' | |
| 2689 out_h.write( f' /** {comment} */\n') | |
| 2690 out_h.write( f' FZ_FUNCTION {classname}& operator=(const {classname}& rhs);\n') | |
| 2691 | |
| 2692 out_cpp.write( f'/* {comment} */\n') | |
| 2693 out_cpp.write( f'FZ_FUNCTION {classname}& {classname}::operator=(const {classname}& rhs)\n') | |
| 2694 out_cpp.write( '{\n') | |
| 2695 out_cpp.write( f' {trace_if}\n') | |
| 2696 out_cpp.write( f' if (s_trace_keepdrop) {{\n') | |
| 2697 out_cpp.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():"\n') | |
| 2698 out_cpp.write( f' << " calling {rename.ll_fn(drop_name)}(this->m_internal)"\n') | |
| 2699 out_cpp.write( f' << " and {rename.ll_fn(keep_name)}(rhs.m_internal)\\n"\n') | |
| 2700 out_cpp.write( f' ;\n') | |
| 2701 out_cpp.write( f' }}\n') | |
| 2702 out_cpp.write( f' #endif\n') | |
| 2703 out_cpp.write( f' {rename.ll_fn(drop_name)}(this->m_internal);\n') | |
| 2704 out_cpp.write( f' {rename.ll_fn(keep_name)}(rhs.m_internal);\n') | |
| 2705 if parse.has_refs( tu, struct_cursor.type): | |
| 2706 out_cpp.write(f' {refcheck_if}\n') | |
| 2707 out_cpp.write( ' if (s_check_refs)\n') | |
| 2708 out_cpp.write( ' {\n') | |
| 2709 out_cpp.write(f' s_{classname}_refs_check.remove( this, __FILE__, __LINE__, __FUNCTION__);\n') | |
| 2710 out_cpp.write( ' }\n') | |
| 2711 out_cpp.write( ' #endif\n') | |
| 2712 out_cpp.write( f' this->m_internal = {cast}rhs.m_internal;\n') | |
| 2713 if parse.has_refs( tu, struct_cursor.type): | |
| 2714 out_cpp.write(f' {refcheck_if}\n') | |
| 2715 out_cpp.write( ' if (s_check_refs)\n') | |
| 2716 out_cpp.write( ' {\n') | |
| 2717 out_cpp.write(f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n') | |
| 2718 out_cpp.write( ' }\n') | |
| 2719 out_cpp.write( ' #endif\n') | |
| 2720 out_cpp.write( f' return *this;\n') | |
| 2721 out_cpp.write( '}\n') | |
| 2722 out_cpp.write( '\n') | |
| 2723 | |
| 2724 def function_name_implies_kept_references( fnname): | |
| 2725 ''' | |
| 2726 Returns true if <fnname> implies the function would return kept | |
| 2727 reference(s). | |
| 2728 ''' | |
| 2729 if fnname in ( | |
| 2730 'pdf_page_write', | |
| 2731 'fz_decomp_image_from_stream', | |
| 2732 'fz_get_pixmap_from_image', | |
| 2733 ): | |
| 2734 return True | |
| 2735 for i in ( | |
| 2736 'add', | |
| 2737 'convert', | |
| 2738 'copy', | |
| 2739 'create', | |
| 2740 'deep_copy', | |
| 2741 'find', | |
| 2742 'graft', | |
| 2743 'keep', | |
| 2744 'load', | |
| 2745 'new', | |
| 2746 'open', | |
| 2747 'parse', | |
| 2748 'read', | |
| 2749 ): | |
| 2750 if fnname.startswith(f'fz_{i}_') or fnname.startswith(f'pdf_{i}_'): | |
| 2751 if state.state_.show_details(fnname): | |
| 2752 jlib.log('Assuming that {fnname=} returns a kept reference.') | |
| 2753 return True | |
| 2754 return False | |
| 2755 | |
| 2756 | |
| 2757 def function_wrapper_class_aware_body( | |
| 2758 tu, | |
| 2759 fnname, | |
| 2760 out_cpp, | |
| 2761 struct_name, | |
| 2762 class_name, | |
| 2763 class_static, | |
| 2764 class_constructor, | |
| 2765 extras, | |
| 2766 struct_cursor, | |
| 2767 fn_cursor, | |
| 2768 return_cursor, | |
| 2769 wrap_return, | |
| 2770 refcheck_if, | |
| 2771 trace_if, | |
| 2772 ): | |
| 2773 ''' | |
| 2774 Writes function or method body to <out_cpp> that calls a generated C++ wrapper | |
| 2775 function. | |
| 2776 | |
| 2777 fnname: | |
| 2778 . | |
| 2779 out_cpp: | |
| 2780 . | |
| 2781 struct_name: | |
| 2782 If false, we write a class-aware wrapping function body. Otherwise name | |
| 2783 of struct such as 'fz_rect' and we write method body for the struct's | |
| 2784 wrapper class. | |
| 2785 class_name: | |
| 2786 class_static: | |
| 2787 If true, this is a static class method. | |
| 2788 class_constructor: | |
| 2789 If true, this is a constructor. | |
| 2790 extras: | |
| 2791 . | |
| 2792 struct_cursor: | |
| 2793 . | |
| 2794 fn_cursor: | |
| 2795 Cursor for the underlying MuPDF function. | |
| 2796 return_cursor: | |
| 2797 If not None, the cursor for definition of returned type. | |
| 2798 wrap_return: | |
| 2799 If 'pointer', the underlying function returns a pointer to a struct | |
| 2800 that we wrap. | |
| 2801 | |
| 2802 If 'value' the underlying function returns, by value, a | |
| 2803 struct that we wrap, so we need to construct our wrapper from the | |
| 2804 address of this value. | |
| 2805 | |
| 2806 Otherwise we don't wrap the returned value. | |
| 2807 ''' | |
| 2808 verbose = state.state_.show_details( fnname) | |
| 2809 out_cpp.write( f'{{\n') | |
| 2810 return_void = (fn_cursor.result_type.spelling == 'void') | |
| 2811 | |
| 2812 # Write trace code. | |
| 2813 out_cpp.write( f' {trace_if}\n') | |
| 2814 out_cpp.write( f' if (s_trace) {{\n') | |
| 2815 out_cpp.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():"\n') | |
| 2816 out_cpp.write( f' << " calling mupdf::{rename.ll_fn(fnname)}()\\n";\n') | |
| 2817 out_cpp.write( f' }}\n') | |
| 2818 out_cpp.write( f' #endif\n') | |
| 2819 | |
| 2820 if fn_cursor.type.is_function_variadic(): | |
| 2821 assert fnname == 'fz_warn', f'{fnname=}' | |
| 2822 out_cpp.write( f' va_list ap;\n') | |
| 2823 out_cpp.write( f' va_start( ap, fmt);\n') | |
| 2824 out_cpp.write( f' {rename.ll_fn("fz_vwarn")}( fmt, ap);\n') | |
| 2825 out_cpp.write( f' va_end( ap);\n') | |
| 2826 | |
| 2827 elif class_constructor or not struct_name: | |
| 2828 # This code can generate a class method, but we choose to not use this, | |
| 2829 # instead method body simply calls the class-aware function (see below). | |
| 2830 def get_keep_drop(arg): | |
| 2831 name = util.clip( arg.alt.type.spelling, 'struct ') | |
| 2832 if name.startswith('fz_'): | |
| 2833 prefix = 'fz' | |
| 2834 name = name[3:] | |
| 2835 elif name.startswith('pdf_'): | |
| 2836 prefix = 'pdf' | |
| 2837 name = name[4:] | |
| 2838 else: | |
| 2839 assert 0 | |
| 2840 return rename.ll_fn(f'{prefix}_keep_{name}'), rename.ll_fn(f'{prefix}_drop_{name}') | |
| 2841 | |
| 2842 # Handle wrapper-class out-params - need to drop .m_internal and set to | |
| 2843 # null. | |
| 2844 # | |
| 2845 # fixme: maybe instead simply call <arg.name>'s destructor directly? | |
| 2846 # | |
| 2847 for arg in parse.get_args( tu, fn_cursor): | |
| 2848 if arg.alt and arg.out_param: | |
| 2849 if parse.has_refs(tu, arg.alt.type): | |
| 2850 keep_fn, drop_fn = get_keep_drop(arg) | |
| 2851 out_cpp.write( f' /* Out-param {arg.name}.m_internal will be overwritten. */\n') | |
| 2852 out_cpp.write( f' {drop_fn}({arg.name}.m_internal);\n') | |
| 2853 out_cpp.write( f' {arg.name}.m_internal = nullptr;\n') | |
| 2854 | |
| 2855 # Write function call. | |
| 2856 if class_constructor: | |
| 2857 if extras.pod: | |
| 2858 if extras.pod == 'inline': | |
| 2859 out_cpp.write( f' *(::{struct_name}*) &this->{parse.get_field0(struct_cursor.type).spelling} = ') | |
| 2860 else: | |
| 2861 out_cpp.write( f' this->m_internal = ') | |
| 2862 if fn_cursor.result_type.kind == state.clang.cindex.TypeKind.POINTER: | |
| 2863 out_cpp.write( f'*') | |
| 2864 else: | |
| 2865 out_cpp.write( f' this->m_internal = ') | |
| 2866 if fn_cursor.result_type.kind == state.clang.cindex.TypeKind.POINTER: | |
| 2867 pass | |
| 2868 else: | |
| 2869 assert 0, 'cannot handle underlying fn returning by value when not pod.' | |
| 2870 out_cpp.write( f'{rename.ll_fn(fnname)}(') | |
| 2871 elif wrap_return == 'value': | |
| 2872 out_cpp.write( f' {_make_top_level(return_cursor.spelling)} temp = mupdf::{rename.ll_fn(fnname)}(') | |
| 2873 elif wrap_return == 'pointer': | |
| 2874 out_cpp.write( f' {_make_top_level(return_cursor.spelling)}* temp = mupdf::{rename.ll_fn(fnname)}(') | |
| 2875 elif wrap_return == 'const pointer': | |
| 2876 out_cpp.write( f' const {_make_top_level(return_cursor.spelling)}* temp = mupdf::{rename.ll_fn(fnname)}(') | |
| 2877 elif return_void: | |
| 2878 out_cpp.write( f' mupdf::{rename.ll_fn(fnname)}(') | |
| 2879 else: | |
| 2880 out_cpp.write( f' auto ret = mupdf::{rename.ll_fn(fnname)}(') | |
| 2881 | |
| 2882 have_used_this = False | |
| 2883 sep = '' | |
| 2884 for arg in parse.get_args( tu, fn_cursor): | |
| 2885 arg_classname = class_name | |
| 2886 if class_static or class_constructor: | |
| 2887 arg_classname = None | |
| 2888 out_cpp.write( sep) | |
| 2889 have_used_this = write_call_arg( | |
| 2890 tu, | |
| 2891 arg, | |
| 2892 arg_classname, | |
| 2893 have_used_this, | |
| 2894 out_cpp, | |
| 2895 state.state_.show_details(fnname), | |
| 2896 ) | |
| 2897 sep = ', ' | |
| 2898 out_cpp.write( f');\n') | |
| 2899 | |
| 2900 if state.state_.show_details(fnname): | |
| 2901 jlib.log('{=wrap_return}') | |
| 2902 refcounted_return = False | |
| 2903 if wrap_return in ('pointer', 'const pointer') and parse.has_refs( tu, return_cursor.type): | |
| 2904 refcounted_return = True | |
| 2905 refcounted_return_struct_cursor = return_cursor | |
| 2906 elif class_constructor and parse.has_refs( tu, struct_cursor.type): | |
| 2907 refcounted_return = True | |
| 2908 refcounted_return_struct_cursor = struct_cursor | |
| 2909 | |
| 2910 if refcounted_return: | |
| 2911 # This MuPDF function returns pointer to a struct which uses reference | |
| 2912 # counting. If the function returns a borrowed reference, we need | |
| 2913 # to increment its reference count before passing it to our wrapper | |
| 2914 # class's constructor. | |
| 2915 # | |
| 2916 #jlib.log('Function returns pointer to {return_cursor=}') | |
| 2917 return_struct_name = util.clip( refcounted_return_struct_cursor.spelling, 'struct ') | |
| 2918 if return_struct_name.startswith('fz_'): | |
| 2919 prefix = 'fz_' | |
| 2920 elif return_struct_name.startswith('pdf_'): | |
| 2921 prefix = 'pdf_' | |
| 2922 else: | |
| 2923 prefix = None | |
| 2924 if state.state_.show_details(fnname): | |
| 2925 jlib.log('{=prefix}') | |
| 2926 if prefix: | |
| 2927 if function_name_implies_kept_references( fnname): | |
| 2928 pass | |
| 2929 #out_cpp.write( f' /* We assume that {fnname} returns a kept reference. */\n') | |
| 2930 else: | |
| 2931 if state.state_.show_details(fnname): | |
| 2932 jlib.log('{=classname fnname constructor} Assuming that {fnname=} returns a borrowed reference.') | |
| 2933 # This function returns a borrowed reference. | |
| 2934 suffix = return_struct_name[ len(prefix):] | |
| 2935 keep_fn = f'{prefix}keep_{suffix}' | |
| 2936 #jlib.log('Function assumed to return borrowed reference: {fnname=} => {return_struct_name=} {keep_fn=}') | |
| 2937 #out_cpp.write( f' /* We assume that {fnname} returns a borrowed reference. */\n') | |
| 2938 if class_constructor: | |
| 2939 out_cpp.write( f' {rename.ll_fn(keep_fn)}(this->m_internal);\n') | |
| 2940 else: | |
| 2941 out_cpp.write( f' {rename.ll_fn(keep_fn)}(temp);\n') | |
| 2942 | |
| 2943 if wrap_return == 'value': | |
| 2944 out_cpp.write( f' auto ret = {rename.class_(return_cursor.spelling)}(&temp);\n') | |
| 2945 elif wrap_return in ('pointer', 'const pointer'): | |
| 2946 out_cpp.write( f' auto ret = {rename.class_(return_cursor.spelling)}(temp);\n') | |
| 2947 | |
| 2948 # Handle wrapper-class out-params - need to keep arg.m_internal if | |
| 2949 # fnname implies it will be a borrowed reference. | |
| 2950 for arg in parse.get_args( tu, fn_cursor): | |
| 2951 if arg.alt and arg.out_param: | |
| 2952 if parse.has_refs(tu, arg.alt.type): | |
| 2953 if function_name_implies_kept_references( fnname): | |
| 2954 out_cpp.write( f' /* We assume that out-param {arg.name}.m_internal is a kept reference. */\n') | |
| 2955 else: | |
| 2956 keep_fn, drop_fn = get_keep_drop(arg) | |
| 2957 out_cpp.write( f' /* We assume that out-param {arg.name}.m_internal is a borrowed reference. */\n') | |
| 2958 out_cpp.write( f' {keep_fn}({arg.name}.m_internal);\n') | |
| 2959 else: | |
| 2960 # Class method simply calls the class-aware function, which will have | |
| 2961 # been generated elsewhere. | |
| 2962 out_cpp.write( ' ') | |
| 2963 if not return_void: | |
| 2964 out_cpp.write( 'auto ret = ') | |
| 2965 | |
| 2966 out_cpp.write( f'mupdf::{rename.fn(fnname)}(') | |
| 2967 sep = '' | |
| 2968 for i, arg in enumerate( parse.get_args( tu, fn_cursor)): | |
| 2969 out_cpp.write( sep) | |
| 2970 if i==0 and not class_static: | |
| 2971 out_cpp.write( '*this') | |
| 2972 else: | |
| 2973 out_cpp.write( f'{arg.name}') | |
| 2974 sep = ', ' | |
| 2975 | |
| 2976 out_cpp.write( ');\n') | |
| 2977 | |
| 2978 if struct_name and not class_static: | |
| 2979 if parse.has_refs( tu, struct_cursor.type): | |
| 2980 # Write code that does runtime checking of reference counts. | |
| 2981 out_cpp.write( f' {refcheck_if}\n') | |
| 2982 out_cpp.write( f' if (s_check_refs)\n') | |
| 2983 out_cpp.write( f' {{\n') | |
| 2984 if class_constructor: | |
| 2985 out_cpp.write( f' s_{class_name}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n') | |
| 2986 else: | |
| 2987 out_cpp.write( f' s_{class_name}_refs_check.check( this, __FILE__, __LINE__, __FUNCTION__);\n') | |
| 2988 out_cpp.write( f' }}\n') | |
| 2989 out_cpp.write( f' #endif\n') | |
| 2990 | |
| 2991 if class_constructor: | |
| 2992 out_cpp.write(num_instances(refcheck_if, +1, class_name)) | |
| 2993 | |
| 2994 if not return_void and not class_constructor: | |
| 2995 out_cpp.write( f' return ret;\n') | |
| 2996 | |
| 2997 out_cpp.write( f'}}\n') | |
| 2998 out_cpp.write( f'\n') | |
| 2999 | |
| 3000 | |
| 3001 def function_wrapper_class_aware( | |
| 3002 tu, | |
| 3003 register_fn_use, | |
| 3004 fnname, | |
| 3005 out_h, | |
| 3006 out_cpp, | |
| 3007 struct_name, | |
| 3008 class_name, | |
| 3009 fn_cursor, | |
| 3010 refcheck_if, | |
| 3011 trace_if, | |
| 3012 class_static=False, | |
| 3013 class_constructor=False, | |
| 3014 extras=None, | |
| 3015 struct_cursor=None, | |
| 3016 duplicate_type=None, | |
| 3017 generated=None, | |
| 3018 debug=None, | |
| 3019 ): | |
| 3020 ''' | |
| 3021 Writes a function or class method that calls <fnname>. | |
| 3022 | |
| 3023 Also appends python and C# code to generated.swig_python and | |
| 3024 generated.swig_csharp if <generated> is not None. | |
| 3025 | |
| 3026 tu | |
| 3027 . | |
| 3028 register_fn_use | |
| 3029 Callback to keep track of what fz_*() fns have been used. | |
| 3030 fnname | |
| 3031 Name of fz_*() fn to wrap, e.g. fz_concat. | |
| 3032 out_h | |
| 3033 out_cpp | |
| 3034 Where to write generated code. | |
| 3035 struct_name | |
| 3036 If false, we generate class-aware wrapping function. Otherwise name | |
| 3037 of struct such as 'fz_rect' and we create a method in the struct's | |
| 3038 wrapper class. | |
| 3039 class_name | |
| 3040 Ignored if struct_name is false. | |
| 3041 | |
| 3042 Name of wrapper class, e.g. 'Rect'. | |
| 3043 class_static | |
| 3044 Ignored if struct_name is false. | |
| 3045 | |
| 3046 If true, we generate a static method. | |
| 3047 | |
| 3048 Otherwise we generate a normal class method, where first arg that | |
| 3049 is type <struct_name> is omitted from the generated method's | |
| 3050 prototype; in the implementation we use <this>. | |
| 3051 class_constructor | |
| 3052 If true, we write a constructor. | |
| 3053 extras | |
| 3054 None or ClassExtras instance. | |
| 3055 Only used if <constructor> is true. | |
| 3056 struct_cursor | |
| 3057 None or cursor for the struct definition. | |
| 3058 Only used if <constructor> is true. | |
| 3059 duplicate_type: | |
| 3060 If true, we have already generated a method with the same args, so | |
| 3061 this generated method will be commented-out. | |
| 3062 generated: | |
| 3063 If not None and there are one or more out-params, we write | |
| 3064 python code to generated.swig_python that overrides the default | |
| 3065 SWIG-generated method to call our *_outparams_fn() alternative. | |
| 3066 debug | |
| 3067 Show extra diagnostics. | |
| 3068 ''' | |
| 3069 verbose = state.state_.show_details( fnname) | |
| 3070 if fn_cursor and fn_cursor.type.is_function_variadic() and fnname != 'fz_warn': | |
| 3071 jlib.log( 'Not writing class-aware wrapper because variadic: {fnname=}', 1) | |
| 3072 return | |
| 3073 if verbose: | |
| 3074 jlib.log( 'Writing class-aware wrapper for {fnname=}') | |
| 3075 if struct_name: | |
| 3076 assert fnname not in state.omit_methods, jlib.log_text( '{=fnname}') | |
| 3077 if debug: | |
| 3078 jlib.log( '{class_name=} {fnname=}') | |
| 3079 assert fnname.startswith( ('fz_', 'pdf_')) | |
| 3080 if not fn_cursor: | |
| 3081 fn_cursor = state.state_.find_function( tu, fnname, method=True) | |
| 3082 if not fn_cursor: | |
| 3083 jlib.log( '*** ignoring {fnname=}') | |
| 3084 return | |
| 3085 | |
| 3086 if fnname.endswith('_drop'): | |
| 3087 # E.g. fz_concat_push_drop() is not safe (or necessary) for us because | |
| 3088 # we need to manage reference counts ourselves. | |
| 3089 #jlib.log('Ignoring because ends with "_drop": {fnname}') | |
| 3090 return | |
| 3091 | |
| 3092 if struct_name: | |
| 3093 methodname = rename.method( struct_name, fnname) | |
| 3094 else: | |
| 3095 methodname = rename.fn( fnname) | |
| 3096 | |
| 3097 if verbose: | |
| 3098 jlib.log( 'Writing class-aware wrapper for {fnname=}') | |
| 3099 # Construct prototype fnname(args). | |
| 3100 # | |
| 3101 if class_constructor: | |
| 3102 assert struct_name | |
| 3103 decl_h = f'{class_name}(' | |
| 3104 decl_cpp = f'{class_name}(' | |
| 3105 else: | |
| 3106 decl_h = f'{methodname}(' | |
| 3107 decl_cpp = f'{methodname}(' | |
| 3108 have_used_this = False | |
| 3109 num_out_params = 0 | |
| 3110 num_class_wrapper_params = 0 | |
| 3111 comma = '' | |
| 3112 this_is_const = False | |
| 3113 debug = state.state_.show_details( fnname) | |
| 3114 | |
| 3115 for arg in parse.get_args( tu, fn_cursor): | |
| 3116 if debug: | |
| 3117 jlib.log( 'Looking at {struct_name=} {fnname=} {fnname_wrapper} {arg=}', 1) | |
| 3118 decl_h += comma | |
| 3119 decl_cpp += comma | |
| 3120 if arg.out_param: | |
| 3121 num_out_params += 1 | |
| 3122 if arg.alt: | |
| 3123 # This parameter is a pointer to a struct that we wrap. | |
| 3124 num_class_wrapper_params += 1 | |
| 3125 arg_extras = classes.classextras.get( tu, arg.alt.type.spelling) | |
| 3126 assert arg_extras, jlib.log_text( '{=structname fnname arg.alt.type.spelling}') | |
| 3127 const = '' | |
| 3128 if not arg.out_param and (not arg_extras.pod or arg.cursor.type.kind != state.clang.cindex.TypeKind.POINTER): | |
| 3129 const = 'const ' | |
| 3130 | |
| 3131 if (1 | |
| 3132 and struct_name | |
| 3133 and not class_static | |
| 3134 and not class_constructor | |
| 3135 and rename.class_(util.clip( arg.alt.type.spelling, 'struct ')) == class_name | |
| 3136 and not have_used_this | |
| 3137 ): | |
| 3138 assert not arg.out_param | |
| 3139 # Omit this arg from the method's prototype - we'll use <this> | |
| 3140 # when calling the underlying fz_ function. | |
| 3141 have_used_this = True | |
| 3142 if not arg_extras.pod: | |
| 3143 this_is_const = const | |
| 3144 continue | |
| 3145 | |
| 3146 if arg_extras.pod == 'none': | |
| 3147 jlib.log( 'Not wrapping because {arg=} wrapper has {extras.pod=}', 1) | |
| 3148 return | |
| 3149 text = f'{const}{rename.class_(arg.alt.type.spelling)}& {arg.name}' | |
| 3150 decl_h += text | |
| 3151 decl_cpp += text | |
| 3152 else: | |
| 3153 jlib.logx( '{arg.spelling=}') | |
| 3154 decl_text = declaration_text( arg.cursor.type, arg.name) | |
| 3155 decl_h += decl_text | |
| 3156 decl_cpp += decl_text | |
| 3157 comma = ', ' | |
| 3158 | |
| 3159 if fn_cursor.type.is_function_variadic(): | |
| 3160 decl_h += f'{comma}...' | |
| 3161 decl_cpp += f'{comma}...' | |
| 3162 | |
| 3163 decl_h += ')' | |
| 3164 decl_cpp += ')' | |
| 3165 if this_is_const: | |
| 3166 decl_h += ' const' | |
| 3167 decl_cpp += ' const' | |
| 3168 | |
| 3169 if verbose: | |
| 3170 jlib.log( '{=struct_name class_constructor}') | |
| 3171 if class_constructor: | |
| 3172 comment = f'Constructor using `{fnname}()`.' | |
| 3173 else: | |
| 3174 comment = make_wrapper_comment( | |
| 3175 tu, | |
| 3176 fn_cursor, | |
| 3177 fnname, | |
| 3178 methodname, | |
| 3179 indent=' ', | |
| 3180 is_method=bool(struct_name), | |
| 3181 is_low_level=False, | |
| 3182 ) | |
| 3183 | |
| 3184 if struct_name and not class_static and not class_constructor: | |
| 3185 assert have_used_this, f'error: wrapper for {struct_name}: {fnname}() is not useful - does not have a {struct_name} arg.' | |
| 3186 | |
| 3187 if struct_name and not duplicate_type: | |
| 3188 register_fn_use( fnname) | |
| 3189 | |
| 3190 # If this is true, we explicitly construct a temporary from what the | |
| 3191 # wrapped function returns. | |
| 3192 # | |
| 3193 wrap_return = None | |
| 3194 | |
| 3195 warning_not_copyable = False | |
| 3196 warning_no_raw_constructor = False | |
| 3197 | |
| 3198 # Figure out return type for our generated function/method. | |
| 3199 # | |
| 3200 if verbose: | |
| 3201 jlib.log( 'Looking at return type...') | |
| 3202 return_cursor = None | |
| 3203 return_type = None | |
| 3204 return_extras = None | |
| 3205 if class_constructor: | |
| 3206 assert struct_name | |
| 3207 fn_h = f'{decl_h}' | |
| 3208 fn_cpp = f'{class_name}::{decl_cpp}' | |
| 3209 else: | |
| 3210 fn_h = declaration_text( fn_cursor.result_type, decl_h) | |
| 3211 if verbose: | |
| 3212 jlib.log( '{fn_cursor.result_type=}') | |
| 3213 if struct_name: | |
| 3214 fn_cpp = declaration_text( fn_cursor.result_type, f'{class_name}::{decl_cpp}') | |
| 3215 else: | |
| 3216 fn_cpp = declaration_text( fn_cursor.result_type, f'{decl_cpp}') | |
| 3217 | |
| 3218 # See whether we can convert return type to an instance of a wrapper | |
| 3219 # class. | |
| 3220 # | |
| 3221 if verbose: | |
| 3222 jlib.log( '{fn_cursor.result_type.kind=}') | |
| 3223 if fn_cursor.result_type.kind == state.clang.cindex.TypeKind.POINTER: | |
| 3224 # Function returns a pointer. | |
| 3225 t = state.get_name_canonical( fn_cursor.result_type.get_pointee()) | |
| 3226 if verbose: | |
| 3227 jlib.log( '{t.spelling=}') | |
| 3228 return_cursor = parse.find_struct( tu, t.spelling, require_definition=False) | |
| 3229 if verbose: | |
| 3230 jlib.log( '{=t.spelling return_cursor}') | |
| 3231 if return_cursor: | |
| 3232 # Function returns a pointer to a struct. | |
| 3233 return_extras = classes.classextras.get( tu, return_cursor.spelling) | |
| 3234 if return_extras: | |
| 3235 # Function returns a pointer to a struct for which we | |
| 3236 # generate a class wrapper, so change return type to be an | |
| 3237 # instance of the class wrapper. | |
| 3238 return_type = rename.class_(return_cursor.spelling) | |
| 3239 if verbose: | |
| 3240 jlib.log( '{=return_type}') | |
| 3241 if 0 and (state.state_.show_details(return_cursor.type.spelling) or state.state_.show_details(struct_name)): | |
| 3242 jlib.log('{return_cursor.type.spelling=}' | |
| 3243 ' {return_cursor.spelling=}' | |
| 3244 ' {struct_name=} {return_extras.copyable=}' | |
| 3245 ' {return_extras.constructor_raw=}' | |
| 3246 ) | |
| 3247 fn_h = f'{return_type} {decl_h}' | |
| 3248 if struct_name: | |
| 3249 fn_cpp = f'{return_type} {class_name}::{decl_cpp}' | |
| 3250 else: | |
| 3251 fn_cpp = f'{return_type} {decl_cpp}' | |
| 3252 if t.is_const_qualified(): | |
| 3253 wrap_return = 'const pointer' | |
| 3254 else: | |
| 3255 wrap_return = 'pointer' | |
| 3256 else: | |
| 3257 return_pointee = fn_cursor.result_type.get_pointee() | |
| 3258 if 'char' in return_pointee.spelling: | |
| 3259 if function_name_implies_kept_references(fnname): | |
| 3260 # For now we just output a diagnostic, but eventually | |
| 3261 # we might make C++ wrappers return a std::string here, | |
| 3262 # free()-ing the char* before returning. | |
| 3263 jlib.log1( 'Function name implies kept reference and returns char*:' | |
| 3264 ' {fnname}(): {fn_cursor.result_type.spelling=}' | |
| 3265 ' -> {return_pointee.spelling=}.' | |
| 3266 ) | |
| 3267 | |
| 3268 if verbose: | |
| 3269 jlib.log( '{=warning_not_copyable warning_no_raw_constructor}') | |
| 3270 else: | |
| 3271 # The fz_*() function returns by value. See whether we can convert | |
| 3272 # its return type to an instance of a wrapper class. | |
| 3273 # | |
| 3274 # If so, we will use constructor that takes pointer to the fz_ | |
| 3275 # struct. C++ doesn't allow us to use address of temporary, so we | |
| 3276 # generate code like this: | |
| 3277 # | |
| 3278 # fz_quad_s ret = mupdf_snap_selection(...); | |
| 3279 # return Quad(&ret); | |
| 3280 # | |
| 3281 t = state.get_name_canonical( fn_cursor.result_type) | |
| 3282 | |
| 3283 # 2023-02-09: parse.find_struct() will actually find any definition, | |
| 3284 # and we now prefix Fitz headers with a typedef of size_t on Linux, | |
| 3285 # so we need to avoid calling parse.find_struct() unless `t` is for | |
| 3286 # a MuPDF type. | |
| 3287 # | |
| 3288 if t.spelling.startswith( ('fz_', 'pdf_')): | |
| 3289 return_cursor = parse.find_struct( tu, t.spelling) | |
| 3290 if return_cursor: | |
| 3291 tt = state.get_name_canonical( return_cursor.type) | |
| 3292 if tt.kind == state.clang.cindex.TypeKind.ENUM: | |
| 3293 # For now, we return this type directly with no wrapping. | |
| 3294 pass | |
| 3295 else: | |
| 3296 return_extras = classes.classextras.get( tu, return_cursor.spelling) | |
| 3297 return_type = rename.class_(return_cursor.type.spelling) | |
| 3298 fn_h = f'{return_type} {decl_h}' | |
| 3299 if struct_name: | |
| 3300 fn_cpp = f'{return_type} {class_name}::{decl_cpp}' | |
| 3301 else: | |
| 3302 fn_cpp = f'{return_type} {decl_cpp}' | |
| 3303 wrap_return = 'value' | |
| 3304 | |
| 3305 if return_extras: | |
| 3306 if not return_extras.copyable: | |
| 3307 out_h.write( | |
| 3308 textwrap.indent( | |
| 3309 textwrap.dedent( f''' | |
| 3310 /* Class-aware wrapper for `{fnname}()` | |
| 3311 is not available because returned wrapper class for `{return_cursor.spelling}` | |
| 3312 is non-copyable. */ | |
| 3313 ''' | |
| 3314 ), | |
| 3315 ' ', | |
| 3316 ) | |
| 3317 ) | |
| 3318 if verbose: | |
| 3319 jlib.log( 'Not creating class-aware wrapper because returned wrapper class is non-copyable: {fnname=}.') | |
| 3320 return | |
| 3321 if not return_extras.constructor_raw: | |
| 3322 out_h.write( | |
| 3323 textwrap.indent( | |
| 3324 textwrap.dedent( f''' | |
| 3325 /* Class-aware wrapper for `{fnname}()` | |
| 3326 is not available because returned wrapper class for `{return_cursor.spelling}` | |
| 3327 does not have raw constructor. */ | |
| 3328 ''' | |
| 3329 ), | |
| 3330 ' ', | |
| 3331 ) | |
| 3332 ) | |
| 3333 if verbose: | |
| 3334 jlib.log( 'Not creating class-aware wrapper because returned wrapper class does not have raw constructor: {fnname=}.') | |
| 3335 return | |
| 3336 | |
| 3337 out_h.write( '\n') | |
| 3338 out_h.write( f' /** {comment} */\n') | |
| 3339 | |
| 3340 # Copy any comment (indented) into class definition above method | |
| 3341 # declaration. | |
| 3342 if fn_cursor.raw_comment: | |
| 3343 raw_comment = fn_cursor.raw_comment.replace('\r', '') | |
| 3344 for line in raw_comment.split( '\n'): | |
| 3345 out_h.write( f' {line}\n') | |
| 3346 | |
| 3347 if duplicate_type: | |
| 3348 out_h.write( f' /* Disabled because same args as {duplicate_type}.\n') | |
| 3349 | |
| 3350 out_h.write( f' FZ_FUNCTION {"static " if class_static else ""}{fn_h};\n') | |
| 3351 | |
| 3352 if duplicate_type: | |
| 3353 out_h.write( f' */\n') | |
| 3354 | |
| 3355 if not struct_name: | |
| 3356 # Use extra spacing between non-class functions. Class methods are | |
| 3357 # grouped together. | |
| 3358 out_cpp.write( f'\n') | |
| 3359 | |
| 3360 out_cpp.write( f'/* {comment} */\n') | |
| 3361 if duplicate_type: | |
| 3362 out_cpp.write( f'/* Disabled because same args as {duplicate_type}.\n') | |
| 3363 | |
| 3364 out_cpp.write( f'FZ_FUNCTION {fn_cpp}\n') | |
| 3365 | |
| 3366 function_wrapper_class_aware_body( | |
| 3367 tu, | |
| 3368 fnname, | |
| 3369 out_cpp, | |
| 3370 struct_name, | |
| 3371 class_name, | |
| 3372 class_static, | |
| 3373 class_constructor, | |
| 3374 extras, | |
| 3375 struct_cursor, | |
| 3376 fn_cursor, | |
| 3377 return_cursor, | |
| 3378 wrap_return, | |
| 3379 refcheck_if, | |
| 3380 trace_if, | |
| 3381 ) | |
| 3382 | |
| 3383 if struct_name: | |
| 3384 if duplicate_type: | |
| 3385 out_cpp.write( f'*/\n') | |
| 3386 | |
| 3387 # fixme: the test of `struct_name` means that we don't generate outparam override for | |
| 3388 # class-aware fns which don't have any struct/class args, e.g. fz_lookup_cjk_font(). | |
| 3389 # | |
| 3390 | |
| 3391 if generated and num_out_params: | |
| 3392 make_python_class_method_outparam_override( | |
| 3393 tu, | |
| 3394 fn_cursor, | |
| 3395 fnname, | |
| 3396 generated, | |
| 3397 struct_name, | |
| 3398 class_name, | |
| 3399 return_type, | |
| 3400 ) | |
| 3401 | |
| 3402 | |
| 3403 def class_custom_method( | |
| 3404 tu, | |
| 3405 register_fn_use, | |
| 3406 struct_cursor, | |
| 3407 classname, | |
| 3408 extramethod, | |
| 3409 out_h, | |
| 3410 out_cpp, | |
| 3411 refcheck_if, | |
| 3412 trace_if, | |
| 3413 ): | |
| 3414 ''' | |
| 3415 Writes custom method as specified by <extramethod>. | |
| 3416 | |
| 3417 tu | |
| 3418 . | |
| 3419 register_fn_use | |
| 3420 Callable taking single <fnname> arg. | |
| 3421 struct_cursor | |
| 3422 Cursor for definition of MuPDF struct. | |
| 3423 classname | |
| 3424 Name of wrapper class for <struct_cursor>. | |
| 3425 extramethod | |
| 3426 An ExtraMethod or ExtraConstructor instance. | |
| 3427 out_h | |
| 3428 out_cpp | |
| 3429 Where to write generated code. | |
| 3430 ''' | |
| 3431 assert isinstance( extramethod, ( classes.ExtraMethod, classes.ExtraConstructor)), f'{type(extramethod)}' | |
| 3432 is_constructor = False | |
| 3433 is_destructor = False | |
| 3434 is_begin_end = False | |
| 3435 | |
| 3436 if extramethod.return_: | |
| 3437 name_args = extramethod.name_args | |
| 3438 return_space = f'{extramethod.return_} ' | |
| 3439 comment = 'Custom method.' | |
| 3440 if name_args.startswith( 'begin(') or name_args.startswith( 'end('): | |
| 3441 is_begin_end = True | |
| 3442 elif extramethod.name_args == '~()': | |
| 3443 # Destructor. | |
| 3444 name_args = f'~{classname}{extramethod.name_args[1:]}' | |
| 3445 return_space = '' | |
| 3446 comment = 'Custom destructor.' | |
| 3447 is_destructor = True | |
| 3448 elif extramethod.name_args.startswith('operator '): | |
| 3449 name_args = extramethod.name_args | |
| 3450 comment = 'Custom operator.' | |
| 3451 return_space = '' | |
| 3452 else: | |
| 3453 # Constructor. | |
| 3454 assert extramethod.name_args.startswith( '('), f'bad constructor/destructor in {classname=}: {extramethod.name_args=}' | |
| 3455 name_args = f'{classname}{extramethod.name_args}' | |
| 3456 return_space = '' | |
| 3457 comment = 'Custom constructor.' | |
| 3458 is_constructor = True | |
| 3459 | |
| 3460 out_h.write( f'\n') | |
| 3461 if extramethod.comment: | |
| 3462 for i, line in enumerate( extramethod.comment.strip().split('\n')): | |
| 3463 line = line.replace( '/* ', '/** ') | |
| 3464 out_h.write( f' {line}\n') | |
| 3465 else: | |
| 3466 out_h.write( f' /** {comment} */\n') | |
| 3467 out_h.write( f' FZ_FUNCTION {return_space}{name_args};\n') | |
| 3468 | |
| 3469 out_cpp.write( f'/** {comment} */\n') | |
| 3470 # Remove any default arg values from <name_args>. | |
| 3471 name_args_no_defaults = re.sub('= *[^(][^),]*', '', name_args) | |
| 3472 if name_args_no_defaults != name_args: | |
| 3473 jlib.log('have changed {name_args=} to {name_args_no_defaults=}', 1) | |
| 3474 out_cpp.write( f'FZ_FUNCTION {return_space}{classname}::{name_args_no_defaults}') | |
| 3475 | |
| 3476 body = textwrap.dedent(extramethod.body) | |
| 3477 | |
| 3478 end = body.rfind('}') | |
| 3479 assert end >= 0 | |
| 3480 out_cpp.write( body[:end]) | |
| 3481 | |
| 3482 if is_constructor and parse.has_refs( tu, struct_cursor.type): | |
| 3483 # Insert ref checking code into end of custom constructor body. | |
| 3484 out_cpp.write( f' {refcheck_if}\n') | |
| 3485 out_cpp.write( f' if (s_check_refs)\n') | |
| 3486 out_cpp.write( f' {{\n') | |
| 3487 out_cpp.write( f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n') | |
| 3488 out_cpp.write( f' }}\n') | |
| 3489 out_cpp.write( f' #endif\n') | |
| 3490 if is_constructor: | |
| 3491 out_cpp.write( num_instances(refcheck_if, +1, classname)) | |
| 3492 if is_destructor: | |
| 3493 out_cpp.write( num_instances(refcheck_if, -1, classname)) | |
| 3494 | |
| 3495 out_cpp.write( body[end:]) | |
| 3496 | |
| 3497 out_cpp.write( f'\n') | |
| 3498 | |
| 3499 if 1: # lgtm [py/constant-conditional-expression] | |
| 3500 # Register calls of all fz_* functions. Not necessarily helpful - we | |
| 3501 # might only be interested in calls of fz_* functions that are directly | |
| 3502 # available to uses of class. | |
| 3503 # | |
| 3504 for fnname in re.findall( '(mupdf::[a-zA-Z0-9_]+) *[(]', extramethod.body): | |
| 3505 fnname = util.clip( fnname, 'mupdf::') | |
| 3506 if not fnname.startswith( 'pdf_'): | |
| 3507 fnname = 'fz_' + fnname | |
| 3508 #log( 'registering use of {fnname} in extramethod {classname}::{name_args}') | |
| 3509 register_fn_use( fnname) | |
| 3510 | |
| 3511 return is_constructor, is_destructor, is_begin_end | |
| 3512 | |
| 3513 | |
| 3514 def class_raw_constructor( | |
| 3515 tu, | |
| 3516 register_fn_use, | |
| 3517 classname, | |
| 3518 struct_cursor, | |
| 3519 struct_name, | |
| 3520 base_name, | |
| 3521 extras, | |
| 3522 constructor_fns, | |
| 3523 out_h, | |
| 3524 out_cpp, | |
| 3525 refcheck_if, | |
| 3526 trace_if, | |
| 3527 ): | |
| 3528 ''' | |
| 3529 Create a raw constructor - a constructor taking a pointer to underlying | |
| 3530 struct. This raw constructor assumes that it already owns the pointer so it | |
| 3531 does not call fz_keep_*(); the class's destructor will call fz_drop_*(). | |
| 3532 ''' | |
| 3533 #jlib.log( 'Creating raw constructor {classname=} {struct_name=} {extras.pod=} {extras.constructor_raw=} {fnname=}') | |
| 3534 comment = f'/** Constructor using raw copy of pre-existing `::{struct_name}`. */' | |
| 3535 if extras.pod: | |
| 3536 constructor_decl = f'{classname}(const ::{struct_name}* internal)' | |
| 3537 else: | |
| 3538 constructor_decl = f'{classname}(::{struct_name}* internal)' | |
| 3539 out_h.write( '\n') | |
| 3540 out_h.write( f' {comment}\n') | |
| 3541 explicit = '' | |
| 3542 if parse.has_refs( tu, struct_cursor.type): | |
| 3543 # Don't allow implicit construction from low-level struct, because our | |
| 3544 # destructor will drop it without a prior balancing keep. | |
| 3545 explicit = f'explicit ' | |
| 3546 out_h.write( | |
| 3547 f' /* This constructor is marked as `explicit` because wrapper classes do not\n' | |
| 3548 f' call `keep`in constructors, but do call `drop` in destructors. So\n' | |
| 3549 f' automatic construction from a {struct_name}* will generally cause an\n' | |
| 3550 f' unbalanced `drop` resulting in errors such as SEGV. */\n' | |
| 3551 ) | |
| 3552 if extras.constructor_raw == 'default': | |
| 3553 out_h.write( f' FZ_FUNCTION {explicit}{classname}(::{struct_name}* internal=NULL);\n') | |
| 3554 else: | |
| 3555 out_h.write( f' FZ_FUNCTION {explicit}{constructor_decl};\n') | |
| 3556 | |
| 3557 if extras.constructor_raw != 'declaration_only': | |
| 3558 out_cpp.write( f'FZ_FUNCTION {classname}::{constructor_decl}\n') | |
| 3559 if extras.pod == 'inline': | |
| 3560 pass | |
| 3561 elif extras.pod: | |
| 3562 out_cpp.write( ': m_internal(*internal)\n') | |
| 3563 else: | |
| 3564 out_cpp.write( ': m_internal(internal)\n') | |
| 3565 out_cpp.write( '{\n') | |
| 3566 if extras.pod == 'inline': | |
| 3567 assert struct_cursor, f'cannot form raw constructor for inline pod {classname} without cursor for underlying {struct_name}' | |
| 3568 out_cpp.write( f' assert( internal);\n') | |
| 3569 for c in parse.get_members(struct_cursor): | |
| 3570 if c.type.kind == state.clang.cindex.TypeKind.CONSTANTARRAY: | |
| 3571 out_cpp.write( f' memcpy(this->{c.spelling}, internal->{c.spelling}, sizeof(this->{c.spelling}));\n') | |
| 3572 else: | |
| 3573 out_cpp.write( f' this->{c.spelling} = internal->{c.spelling};\n') | |
| 3574 if parse.has_refs( tu, struct_cursor.type): | |
| 3575 out_cpp.write( f' {refcheck_if}\n') | |
| 3576 out_cpp.write( f' if (s_check_refs)\n') | |
| 3577 out_cpp.write( f' {{\n') | |
| 3578 out_cpp.write( f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n') | |
| 3579 out_cpp.write( f' }}\n') | |
| 3580 out_cpp.write( f' #endif\n') | |
| 3581 | |
| 3582 out_cpp.write(num_instances(refcheck_if, +1, classname)) | |
| 3583 | |
| 3584 out_cpp.write( '}\n') | |
| 3585 out_cpp.write( '\n') | |
| 3586 | |
| 3587 if extras.pod == 'inline': | |
| 3588 # Write second constructor that takes underlying struct by value. | |
| 3589 # | |
| 3590 assert not parse.has_refs( tu, struct_cursor.type) | |
| 3591 constructor_decl = f'{classname}(const ::{struct_name} internal)' | |
| 3592 out_h.write( '\n') | |
| 3593 out_h.write( f' {comment}\n') | |
| 3594 out_h.write( f' FZ_FUNCTION {constructor_decl};\n') | |
| 3595 | |
| 3596 if extras.constructor_raw != 'declaration_only': | |
| 3597 out_cpp.write( f'FZ_FUNCTION {classname}::{constructor_decl}\n') | |
| 3598 out_cpp.write( '{\n') | |
| 3599 for c in parse.get_members(struct_cursor): | |
| 3600 if c.type.kind == state.clang.cindex.TypeKind.CONSTANTARRAY: | |
| 3601 out_cpp.write( f' memcpy(this->{c.spelling}, &internal.{c.spelling}, sizeof(this->{c.spelling}));\n') | |
| 3602 else: | |
| 3603 out_cpp.write( f' this->{c.spelling} = internal.{c.spelling};\n') | |
| 3604 | |
| 3605 out_cpp.write(num_instances(refcheck_if, +1, classname)) | |
| 3606 out_cpp.write( '}\n') | |
| 3607 out_cpp.write( '\n') | |
| 3608 | |
| 3609 # Write accessor for inline state.state_. | |
| 3610 # | |
| 3611 for const in False, True: | |
| 3612 space_const = ' const' if const else '' | |
| 3613 const_space = 'const ' if const else '' | |
| 3614 out_h.write( '\n') | |
| 3615 out_h.write( f' /** Access as underlying struct. */\n') | |
| 3616 out_h.write( f' FZ_FUNCTION {const_space}::{struct_name}* internal(){space_const};\n') | |
| 3617 out_cpp.write( f'{comment}\n') | |
| 3618 out_cpp.write( f'FZ_FUNCTION {const_space}::{struct_name}* {classname}::internal(){space_const}\n') | |
| 3619 out_cpp.write( '{\n') | |
| 3620 field0 = parse.get_field0(struct_cursor.canonical).spelling | |
| 3621 out_cpp.write( f' auto ret = ({const_space}::{struct_name}*) &this->{field0};\n') | |
| 3622 if parse.has_refs( tu, struct_cursor.type): | |
| 3623 out_cpp.write( f' {refcheck_if}\n') | |
| 3624 out_cpp.write( f' if (s_check_refs)\n') | |
| 3625 out_cpp.write( f' {{\n') | |
| 3626 out_cpp.write( f' s_{classname}_refs_check.add( this, __FILE__, __LINE__, __FUNCTION__);\n') | |
| 3627 out_cpp.write( f' }}\n') | |
| 3628 out_cpp.write( f' #endif\n') | |
| 3629 | |
| 3630 out_cpp.write( ' return ret;\n') | |
| 3631 out_cpp.write( '}\n') | |
| 3632 out_cpp.write( '\n') | |
| 3633 | |
| 3634 | |
| 3635 | |
| 3636 def class_accessors( | |
| 3637 tu, | |
| 3638 register_fn_use, | |
| 3639 classname, | |
| 3640 struct_cursor, | |
| 3641 struct_name, | |
| 3642 extras, | |
| 3643 out_h, | |
| 3644 out_cpp, | |
| 3645 ): | |
| 3646 ''' | |
| 3647 Writes accessor functions for member data. | |
| 3648 ''' | |
| 3649 if not extras.pod: | |
| 3650 jlib.logx( 'creating accessor for non-pod class {classname=} wrapping {struct_name}') | |
| 3651 | |
| 3652 n = 0 | |
| 3653 | |
| 3654 for cursor in parse.get_members(struct_cursor): | |
| 3655 n += 1 | |
| 3656 #jlib.log( 'accessors: {cursor.spelling=} {cursor.type.spelling=}') | |
| 3657 | |
| 3658 # We set this to fz_keep_<type>() function to call, if we return a | |
| 3659 # wrapper class constructed from raw pointer to underlying fz_* struct. | |
| 3660 keep_function = None | |
| 3661 | |
| 3662 # Set <decl> to be prototype with %s where the name is, e.g. 'int | |
| 3663 # %s()'; later on we use python's % operator to replace the '%s' | |
| 3664 # with the name we want. | |
| 3665 # | |
| 3666 if cursor.type.kind == state.clang.cindex.TypeKind.POINTER: | |
| 3667 decl = 'const ' + declaration_text( cursor.type, '%s()') | |
| 3668 pointee_type = state.get_name_canonical( cursor.type.get_pointee()).spelling | |
| 3669 pointee_type = util.clip( pointee_type, 'const ') | |
| 3670 pointee_type = util.clip( pointee_type, 'struct ') | |
| 3671 #if 'fz_' in pointee_type: | |
| 3672 # jlib.log( '{pointee_type=}') | |
| 3673 # We don't attempt to make accessors to function pointers. | |
| 3674 if state.get_name_canonical( cursor.type.get_pointee()).kind == state.clang.cindex.TypeKind.FUNCTIONPROTO: | |
| 3675 jlib.logx( 'ignoring {cursor.spelling=} because pointer to FUNCTIONPROTO') | |
| 3676 continue | |
| 3677 elif pointee_type.startswith( ('fz_', 'pdf_')): | |
| 3678 extras2 = parse.get_fz_extras( tu, pointee_type) | |
| 3679 if extras2: | |
| 3680 # Make this accessor return an instance of the wrapping | |
| 3681 # class by value. | |
| 3682 # | |
| 3683 classname2 = rename.class_( pointee_type) | |
| 3684 decl = f'{classname2} %s()' | |
| 3685 | |
| 3686 # If there's a fz_keep_() function, we must call it on the | |
| 3687 # internal data before returning the wrapper class. | |
| 3688 pointee_type_base = util.clip( pointee_type, ('fz_', 'pdf_')) | |
| 3689 keep_function = f'{parse.prefix(pointee_type)}keep_{pointee_type_base}' | |
| 3690 if state.state_.find_function( tu, keep_function, method=False): | |
| 3691 jlib.logx( 'using {keep_function=}') | |
| 3692 else: | |
| 3693 jlib.log( 'cannot find {keep_function=}') | |
| 3694 keep_function = None | |
| 3695 elif cursor.type.kind == state.clang.cindex.TypeKind.FUNCTIONPROTO: | |
| 3696 jlib.log( 'ignoring {cursor.spelling=} because FUNCTIONPROTO') | |
| 3697 continue | |
| 3698 else: | |
| 3699 if 0 and extras.pod: # lgtm [py/unreachable-statement] | |
| 3700 # Return reference so caller can modify. Unfortunately SWIG | |
| 3701 # converts non-const references to pointers, so generated | |
| 3702 # python isn't useful. | |
| 3703 fn_args = '& %s()' | |
| 3704 else: | |
| 3705 fn_args = '%s()' | |
| 3706 if cursor.type.get_array_size() >= 0: | |
| 3707 if 0: # lgtm [py/unreachable-statement] | |
| 3708 # Return reference to the array; we need to put fn name | |
| 3709 # and args inside (...) to allow the declaration syntax | |
| 3710 # to work - we end up with things like: | |
| 3711 # | |
| 3712 # char (& media_class())[64]; | |
| 3713 # | |
| 3714 # Unfortunately SWIG doesn't seem to be able to cope | |
| 3715 # with this. | |
| 3716 decl = declaration_text( cursor.type, '(%s)' % fn_args) | |
| 3717 else: | |
| 3718 # Return pointer to the first element of the array, so | |
| 3719 # that SWIG can cope. | |
| 3720 fn_args = '* %s()' | |
| 3721 type_ = cursor.type.get_array_element_type() | |
| 3722 decl = declaration_text( type_, fn_args) | |
| 3723 else: | |
| 3724 if ( cursor.type.kind == state.clang.cindex.TypeKind.TYPEDEF | |
| 3725 and cursor.type.get_typedef_name() in ('uint8_t', 'int8_t') | |
| 3726 ): | |
| 3727 # Don't let accessor return uint8_t because SWIG thinks it | |
| 3728 # is a char*, leading to memory errors. Instead return int. | |
| 3729 # | |
| 3730 jlib.logx('Changing from {cursor.type.get_typedef_name()=} {cursor.type=} to int') | |
| 3731 decl = f'int {fn_args}' | |
| 3732 else: | |
| 3733 decl = declaration_text( cursor.type, fn_args) | |
| 3734 | |
| 3735 # todo: if return type is uint8_t or int8_t, maybe return as <int> | |
| 3736 # so SWIG doesn't think it is a string? This would fix errors with | |
| 3737 # fz_image::n and fz_image::bpc. | |
| 3738 out_h.write( f' FZ_FUNCTION {decl % cursor.spelling};\n') | |
| 3739 out_cpp.write( 'FZ_FUNCTION %s\n' % (decl % ( f'{classname}::{cursor.spelling}'))) | |
| 3740 out_cpp.write( '{\n') | |
| 3741 if keep_function: | |
| 3742 out_cpp.write( f' {rename.ll_fn(keep_function)}(m_internal->{cursor.spelling});\n') | |
| 3743 out_cpp.write( f' return ({classname2}) m_internal->{cursor.spelling};\n') | |
| 3744 else: | |
| 3745 if extras.pod: | |
| 3746 out_cpp.write( f' return m_internal.{cursor.spelling};\n') | |
| 3747 else: | |
| 3748 out_cpp.write( f' return m_internal->{cursor.spelling};\n') | |
| 3749 out_cpp.write( '}\n') | |
| 3750 out_cpp.write( '\n') | |
| 3751 assert n, f'No fields found for {struct_cursor.spelling}.' | |
| 3752 | |
| 3753 | |
| 3754 | |
| 3755 def class_destructor( | |
| 3756 tu, | |
| 3757 register_fn_use, | |
| 3758 classname, | |
| 3759 extras, | |
| 3760 struct_cursor, | |
| 3761 destructor_fns, | |
| 3762 out_h, | |
| 3763 out_cpp, | |
| 3764 refcheck_if, | |
| 3765 trace_if, | |
| 3766 ): | |
| 3767 if len(destructor_fns) > 1: | |
| 3768 # Use function with shortest name. | |
| 3769 if 0: # lgtm [py/unreachable-statement] | |
| 3770 jlib.log( 'Multiple possible destructor fns for {classname=}') | |
| 3771 for fnname, cursor in destructor_fns: | |
| 3772 jlib.log( ' {fnname=} {cursor.spelling=}') | |
| 3773 shortest = None | |
| 3774 for i in destructor_fns: | |
| 3775 if shortest is None or len(i[0]) < len(shortest[0]): | |
| 3776 shortest = i | |
| 3777 #jlib.log( 'Using: {shortest[0]=}') | |
| 3778 destructor_fns = [shortest] | |
| 3779 if len(destructor_fns): | |
| 3780 fnname, cursor = destructor_fns[0] | |
| 3781 register_fn_use( cursor.spelling) | |
| 3782 out_h.write( f' /** Destructor using {cursor.spelling}(). */\n') | |
| 3783 out_h.write( f' FZ_FUNCTION ~{classname}();\n') | |
| 3784 | |
| 3785 out_cpp.write( f'FZ_FUNCTION {classname}::~{classname}()\n') | |
| 3786 out_cpp.write( '{\n') | |
| 3787 out_cpp.write( f' {rename.ll_fn(fnname)}(m_internal);\n') | |
| 3788 if parse.has_refs( tu, struct_cursor.type): | |
| 3789 out_cpp.write( f' {refcheck_if}\n') | |
| 3790 out_cpp.write( f' if (s_check_refs)\n') | |
| 3791 out_cpp.write( ' {\n') | |
| 3792 out_cpp.write( f' s_{classname}_refs_check.remove( this, __FILE__, __LINE__, __FUNCTION__);\n') | |
| 3793 out_cpp.write( ' }\n') | |
| 3794 out_cpp.write( f' #endif\n') | |
| 3795 | |
| 3796 out_cpp.write(num_instances(refcheck_if, -1, classname)) | |
| 3797 | |
| 3798 out_cpp.write( '}\n') | |
| 3799 out_cpp.write( '\n') | |
| 3800 else: | |
| 3801 out_h.write(f' {refcheck_if}\n') | |
| 3802 out_h.write(f' /** Destructor only decrements s_num_instances. */\n') | |
| 3803 out_h.write(f' FZ_FUNCTION ~{classname}();\n') | |
| 3804 out_h.write( ' #else\n') | |
| 3805 out_h.write( ' /** We use default destructor. */\n') | |
| 3806 out_h.write( ' #endif\n') | |
| 3807 | |
| 3808 out_cpp.write( f'{refcheck_if}\n') | |
| 3809 out_cpp.write( f'FZ_FUNCTION {classname}::~{classname}()\n') | |
| 3810 out_cpp.write( '{\n') | |
| 3811 out_cpp.write(num_instances(refcheck_if, -1, classname)) | |
| 3812 out_cpp.write( '}\n') | |
| 3813 out_cpp.write( '#endif\n') | |
| 3814 out_cpp.write( '\n') | |
| 3815 | |
| 3816 | |
| 3817 | |
| 3818 def pod_class_members( | |
| 3819 tu, | |
| 3820 classname, | |
| 3821 struct_cursor, | |
| 3822 struct_name, | |
| 3823 extras, | |
| 3824 out_h, | |
| 3825 out_cpp, | |
| 3826 ): | |
| 3827 ''' | |
| 3828 Writes code for wrapper class's to_string() member function. | |
| 3829 ''' | |
| 3830 out_h.write( f'\n') | |
| 3831 out_h.write( f' /** Returns string containing our members, labelled and inside (...), using operator<<. */\n') | |
| 3832 out_h.write( f' FZ_FUNCTION std::string to_string();\n') | |
| 3833 | |
| 3834 out_h.write( f'\n') | |
| 3835 out_h.write( f' /** Comparison method. */\n') | |
| 3836 out_h.write( f' FZ_FUNCTION bool operator==(const {classname}& rhs);\n') | |
| 3837 | |
| 3838 out_h.write( f'\n') | |
| 3839 out_h.write( f' /** Comparison method. */\n') | |
| 3840 out_h.write( f' FZ_FUNCTION bool operator!=(const {classname}& rhs);\n') | |
| 3841 | |
| 3842 out_cpp.write( f'FZ_FUNCTION std::string {classname}::to_string()\n') | |
| 3843 out_cpp.write( f'{{\n') | |
| 3844 out_cpp.write( f' std::ostringstream buffer;\n') | |
| 3845 out_cpp.write( f' buffer << *this;\n') | |
| 3846 out_cpp.write( f' return buffer.str();\n') | |
| 3847 out_cpp.write( f'}}\n') | |
| 3848 out_cpp.write( f'\n') | |
| 3849 | |
| 3850 out_cpp.write( f'FZ_FUNCTION bool {classname}::operator==(const {classname}& rhs)\n') | |
| 3851 out_cpp.write( f'{{\n') | |
| 3852 out_cpp.write( f' return ::operator==( *this, rhs);\n') | |
| 3853 out_cpp.write( f'}}\n') | |
| 3854 out_cpp.write( f'\n') | |
| 3855 | |
| 3856 out_cpp.write( f'FZ_FUNCTION bool {classname}::operator!=(const {classname}& rhs)\n') | |
| 3857 out_cpp.write( f'{{\n') | |
| 3858 out_cpp.write( f' return ::operator!=( *this, rhs);\n') | |
| 3859 out_cpp.write( f'}}\n') | |
| 3860 out_cpp.write( f'\n') | |
| 3861 | |
| 3862 | |
| 3863 def struct_to_string_fns( | |
| 3864 tu, | |
| 3865 struct_cursor, | |
| 3866 struct_name, | |
| 3867 extras, | |
| 3868 out_h, | |
| 3869 out_cpp, | |
| 3870 ): | |
| 3871 ''' | |
| 3872 Writes functions for text representation of struct/wrapper-class members. | |
| 3873 ''' | |
| 3874 out_h.write( f'\n') | |
| 3875 out_h.write( f'/** Returns string containing a {struct_name}\'s members, labelled and inside (...), using operator<<. */\n') | |
| 3876 out_h.write( f'FZ_FUNCTION std::string to_string_{struct_name}(const ::{struct_name}& s);\n') | |
| 3877 | |
| 3878 out_h.write( f'\n') | |
| 3879 out_h.write( f'/** Returns string containing a {struct_name}\'s members, labelled and inside (...), using operator<<.\n') | |
| 3880 out_h.write( f'(Convenience overload). */\n') | |
| 3881 out_h.write( f'FZ_FUNCTION std::string to_string(const ::{struct_name}& s);\n') | |
| 3882 | |
| 3883 out_cpp.write( f'\n') | |
| 3884 out_cpp.write( f'FZ_FUNCTION std::string to_string_{struct_name}(const ::{struct_name}& s)\n') | |
| 3885 out_cpp.write( f'{{\n') | |
| 3886 out_cpp.write( f' std::ostringstream buffer;\n') | |
| 3887 out_cpp.write( f' buffer << s;\n') | |
| 3888 out_cpp.write( f' return buffer.str();\n') | |
| 3889 out_cpp.write( f'}}\n') | |
| 3890 | |
| 3891 out_cpp.write( f'\n') | |
| 3892 out_cpp.write( f'FZ_FUNCTION std::string to_string(const ::{struct_name}& s)\n') | |
| 3893 out_cpp.write( f'{{\n') | |
| 3894 out_cpp.write( f' return to_string_{struct_name}(s);\n') | |
| 3895 out_cpp.write( f'}}\n') | |
| 3896 | |
| 3897 | |
| 3898 def pod_struct_fns( | |
| 3899 tu, | |
| 3900 namespace, | |
| 3901 struct_cursor, | |
| 3902 struct_name, | |
| 3903 extras, | |
| 3904 out_h, | |
| 3905 out_cpp, | |
| 3906 ): | |
| 3907 ''' | |
| 3908 Writes extra fns for POD structs - operator<<(), operator==(), operator!=(). | |
| 3909 ''' | |
| 3910 # Write operator<< functions for streaming text representation of C struct | |
| 3911 # members. We should be at top-level in out_h and out_cpp, i.e. not inside | |
| 3912 # 'namespace mupdf {...}'. | |
| 3913 out_h.write( f'\n') | |
| 3914 out_h.write( f'/** {struct_name}: writes members, labelled and inside (...), to a stream. */\n') | |
| 3915 out_h.write( f'FZ_FUNCTION std::ostream& operator<< (std::ostream& out, const ::{struct_name}& rhs);\n') | |
| 3916 | |
| 3917 out_cpp.write( f'\n') | |
| 3918 out_cpp.write( f'FZ_FUNCTION std::ostream& operator<< (std::ostream& out, const ::{struct_name}& rhs)\n') | |
| 3919 out_cpp.write( f'{{\n') | |
| 3920 i = 0 | |
| 3921 out_cpp.write( f' out\n') | |
| 3922 out_cpp.write( f' << "("\n'); | |
| 3923 for cursor in parse.get_members(struct_cursor): | |
| 3924 out_cpp.write( f' << '); | |
| 3925 if i: | |
| 3926 out_cpp.write( f'" {cursor.spelling}="') | |
| 3927 else: | |
| 3928 out_cpp.write( f' "{cursor.spelling}="') | |
| 3929 out_cpp.write( f' << rhs.{cursor.spelling}\n') | |
| 3930 i += 1 | |
| 3931 out_cpp.write( f' << ")"\n'); | |
| 3932 out_cpp.write( f' ;\n') | |
| 3933 out_cpp.write( f' return out;\n') | |
| 3934 out_cpp.write( f'}}\n') | |
| 3935 | |
| 3936 # Write comparison fns. | |
| 3937 out_h.write( f'\n') | |
| 3938 out_h.write( f'/** {struct_name}: comparison function. */\n') | |
| 3939 out_h.write( f'FZ_FUNCTION bool operator==( const ::{struct_name}& lhs, const ::{struct_name}& rhs);\n') | |
| 3940 out_h.write( f'\n') | |
| 3941 out_h.write( f'/** {struct_name}: comparison function. */\n') | |
| 3942 out_h.write( f'FZ_FUNCTION bool operator!=( const ::{struct_name}& lhs, const ::{struct_name}& rhs);\n') | |
| 3943 | |
| 3944 out_cpp.write( f'\n') | |
| 3945 out_cpp.write( f'FZ_FUNCTION bool operator==( const ::{struct_name}& lhs, const ::{struct_name}& rhs)\n') | |
| 3946 out_cpp.write( f'{{\n') | |
| 3947 for cursor in parse.get_members(struct_cursor): | |
| 3948 out_cpp.write( f' if (lhs.{cursor.spelling} != rhs.{cursor.spelling}) return false;\n') | |
| 3949 out_cpp.write( f' return true;\n') | |
| 3950 out_cpp.write( f'}}\n') | |
| 3951 out_cpp.write( f'FZ_FUNCTION bool operator!=( const ::{struct_name}& lhs, const ::{struct_name}& rhs)\n') | |
| 3952 out_cpp.write( f'{{\n') | |
| 3953 out_cpp.write( f' return !(lhs == rhs);\n') | |
| 3954 out_cpp.write( f'}}\n') | |
| 3955 | |
| 3956 | |
| 3957 def pod_class_fns( | |
| 3958 tu, | |
| 3959 classname, | |
| 3960 struct_cursor, | |
| 3961 struct_name, | |
| 3962 extras, | |
| 3963 out_h, | |
| 3964 out_cpp, | |
| 3965 ): | |
| 3966 ''' | |
| 3967 Writes extra fns for wrappers for POD structs - operator<<(), operator==(), | |
| 3968 operator!=(). | |
| 3969 ''' | |
| 3970 # Write functions for text representation of wrapper-class members. These | |
| 3971 # functions make use of the corresponding struct functions created by | |
| 3972 # struct_to_string_fns(). | |
| 3973 # | |
| 3974 assert extras.pod != 'none' | |
| 3975 classname = f'mupdf::{classname}' | |
| 3976 out_h.write( f'\n') | |
| 3977 out_h.write( f'/** {classname}: writes underlying {struct_name}\'s members, labelled and inside (...), to a stream. */\n') | |
| 3978 out_h.write( f'FZ_FUNCTION std::ostream& operator<< (std::ostream& out, const {classname}& rhs);\n') | |
| 3979 | |
| 3980 out_cpp.write( f'\n') | |
| 3981 out_cpp.write( f'FZ_FUNCTION std::ostream& operator<< (std::ostream& out, const {classname}& rhs)\n') | |
| 3982 out_cpp.write( f'{{\n') | |
| 3983 if extras.pod == 'inline': | |
| 3984 out_cpp.write( f' return out << *rhs.internal();\n') | |
| 3985 elif extras.pod: | |
| 3986 out_cpp.write( f' return out << rhs.m_internal;\n') | |
| 3987 else: | |
| 3988 out_cpp.write( f' return out << " " << *rhs.m_internal;\n') | |
| 3989 out_cpp.write( f'}}\n') | |
| 3990 | |
| 3991 # Write comparison fns, using comparison of underlying MuPDF struct. | |
| 3992 out_h.write( f'\n') | |
| 3993 out_h.write( f'/** {classname}: comparison function. */\n') | |
| 3994 out_h.write( f'FZ_FUNCTION bool operator==( const {classname}& lhs, const {classname}& rhs);\n') | |
| 3995 out_h.write( f'\n') | |
| 3996 out_h.write( f'/** {classname}: comparison function. */\n') | |
| 3997 out_h.write( f'FZ_FUNCTION bool operator!=( const {classname}& lhs, const {classname}& rhs);\n') | |
| 3998 | |
| 3999 out_cpp.write( f'\n') | |
| 4000 out_cpp.write( f'FZ_FUNCTION bool operator==( const {classname}& lhs, const {classname}& rhs)\n') | |
| 4001 out_cpp.write( f'{{\n') | |
| 4002 if extras.pod == 'inline': | |
| 4003 out_cpp.write( f' return *lhs.internal() == *rhs.internal();\n') | |
| 4004 else: | |
| 4005 out_cpp.write( f' return lhs.m_internal == rhs.m_internal;\n') | |
| 4006 out_cpp.write( f'}}\n') | |
| 4007 | |
| 4008 out_cpp.write( f'\n') | |
| 4009 out_cpp.write( f'FZ_FUNCTION bool operator!=( const {classname}& lhs, const {classname}& rhs)\n') | |
| 4010 out_cpp.write( f'{{\n') | |
| 4011 if extras.pod == 'inline': | |
| 4012 out_cpp.write( f' return *lhs.internal() != *rhs.internal();\n') | |
| 4013 else: | |
| 4014 out_cpp.write( f' return lhs.m_internal != rhs.m_internal;\n') | |
| 4015 out_cpp.write( f'}}\n') | |
| 4016 | |
| 4017 | |
| 4018 def get_struct_fnptrs( cursor_struct, shallow_typedef_expansion=False, verbose=False): | |
| 4019 ''' | |
| 4020 Yields (cursor, fnptr_type) for function-pointer members of struct defined | |
| 4021 at cusor, where <cursor> is the cursor of the member and <fntr_type> is the | |
| 4022 type. | |
| 4023 | |
| 4024 cursor_struct: | |
| 4025 Cursor for definition of struct; this can be a typedef. | |
| 4026 shallow_typedef_expansion: | |
| 4027 If true, the returned <fnptr_type> has any top-level typedefs resolved | |
| 4028 so will be a clang.cindex.TypeKind.FUNCTIONPROTO, but typedefs within | |
| 4029 the function args are not resolved, e.g. they can be size_t. This can | |
| 4030 be useful when generating code that will be compiled on different | |
| 4031 platforms with differing definitions of size_t. | |
| 4032 ''' | |
| 4033 if verbose: | |
| 4034 jlib.log('Looking for fnptrs in {cursor_struct.spelling=}') | |
| 4035 for cursor in parse.get_members(cursor_struct): | |
| 4036 t = cursor.type | |
| 4037 if verbose: | |
| 4038 jlib.log('{t.kind=} {cursor.spelling=}') | |
| 4039 if t.kind == state.clang.cindex.TypeKind.POINTER: | |
| 4040 t = cursor.type.get_pointee() | |
| 4041 if t.kind in (state.clang.cindex.TypeKind.TYPEDEF, state.clang.cindex.TypeKind.ELABORATED): | |
| 4042 t_cursor = t.get_declaration() | |
| 4043 t = t_cursor.underlying_typedef_type | |
| 4044 if t.kind == state.clang.cindex.TypeKind.FUNCTIONPROTO: | |
| 4045 if shallow_typedef_expansion: | |
| 4046 if verbose: | |
| 4047 jlib.log('Not calling state.get_name_canonical() for {t.spelling=}. {cursor.spelling=}.') | |
| 4048 else: | |
| 4049 tt = state.get_name_canonical( t) | |
| 4050 if verbose: | |
| 4051 jlib.log('{tt.spelling=}') | |
| 4052 if (0 | |
| 4053 or 'struct (unnamed at ' in tt.spelling | |
| 4054 or 'unnamed struct at ' in tt.spelling | |
| 4055 ): | |
| 4056 | |
| 4057 # This is clang giving an unhelpful name to an | |
| 4058 # anonymous struct. | |
| 4059 if verbose: | |
| 4060 jlib.log( 'Avoiding clang anonymous struct placeholder: {tt.spelling=}') | |
| 4061 else: | |
| 4062 t = tt | |
| 4063 if verbose: | |
| 4064 jlib.log('Yielding: {cursor.spelling=} {t.spelling=}') | |
| 4065 yield cursor, t | |
| 4066 | |
| 4067 | |
| 4068 def class_wrapper_virtual_fnptrs( | |
| 4069 tu, | |
| 4070 struct_cursor, | |
| 4071 struct_name, | |
| 4072 classname, | |
| 4073 extras, | |
| 4074 out_h, | |
| 4075 out_cpp, | |
| 4076 out_h_end, | |
| 4077 generated, | |
| 4078 refcheck_if, | |
| 4079 trace_if, | |
| 4080 ): | |
| 4081 ''' | |
| 4082 Generate extra wrapper class if struct contains function pointers, for | |
| 4083 use as a SWIG Director class so that the function pointers can be made to | |
| 4084 effectively point to Python or C# code. | |
| 4085 ''' | |
| 4086 if not extras.virtual_fnptrs: | |
| 4087 return | |
| 4088 verbose = state.state_.show_details( struct_name) | |
| 4089 generated.virtual_fnptrs.append( f'{classname}2') | |
| 4090 | |
| 4091 self_ = extras.virtual_fnptrs.pop( 'self_') | |
| 4092 self_n = extras.virtual_fnptrs.pop( 'self_n', 1) | |
| 4093 alloc = extras.virtual_fnptrs.pop( 'alloc') | |
| 4094 free = extras.virtual_fnptrs.pop( 'free', None) | |
| 4095 comment = extras.virtual_fnptrs.pop( 'comment', None) | |
| 4096 assert not extras.virtual_fnptrs, f'Unused items in virtual_fnptrs: {extras.virtual_fnptrs}' | |
| 4097 | |
| 4098 # Class definition beginning. | |
| 4099 # | |
| 4100 out_h.write( '\n') | |
| 4101 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') | |
| 4102 if comment: | |
| 4103 out_h.write(comment) | |
| 4104 out_h.write( f'struct {classname}2 : {classname}\n') | |
| 4105 out_h.write( '{\n') | |
| 4106 | |
| 4107 out_cpp.write( f'/* Implementation of methods for `{classname}2`, virtual_fnptrs wrapper for `{struct_name}`). */\n') | |
| 4108 out_cpp.write( '\n') | |
| 4109 | |
| 4110 def get_fnptrs( shallow_typedef_expansion=False): | |
| 4111 for i in get_struct_fnptrs( struct_cursor, shallow_typedef_expansion, verbose=verbose): | |
| 4112 yield i | |
| 4113 | |
| 4114 # Constructor | |
| 4115 # | |
| 4116 out_h.write( '\n') | |
| 4117 out_h.write( ' /** == Constructor. */\n') | |
| 4118 out_h.write(f' FZ_FUNCTION {classname}2();\n') | |
| 4119 out_cpp.write('\n') | |
| 4120 out_cpp.write(f'FZ_FUNCTION {classname}2::{classname}2()\n') | |
| 4121 out_cpp.write( '{\n') | |
| 4122 alloc = [''] + alloc.split('\n') | |
| 4123 alloc = '\n '.join(alloc) | |
| 4124 out_cpp.write(f'{alloc}\n') | |
| 4125 out_cpp.write(f' {trace_if}\n') | |
| 4126 out_cpp.write(f' if (s_trace_director)\n') | |
| 4127 out_cpp.write( ' {\n') | |
| 4128 out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2::{classname}2(): this=" << this << "\\n";\n') | |
| 4129 if not extras.pod: | |
| 4130 out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2::{classname}2(): m_internal=" << m_internal << "\\n";\n') | |
| 4131 out_cpp.write(f' {classname}2* self = {self_("m_internal")};\n') | |
| 4132 out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2::{classname}2(): self=" << self << "\\n";\n') | |
| 4133 out_cpp.write(' }\n') | |
| 4134 out_cpp.write(' #endif\n') | |
| 4135 out_cpp.write( '}\n') | |
| 4136 | |
| 4137 # Destructor. This needs to be virtual with an empty implementation, | |
| 4138 # because instances will generally be derived classes. | |
| 4139 out_h.write( '\n') | |
| 4140 out_h.write( ' /** == Destructor. */\n') | |
| 4141 out_h.write(f' FZ_FUNCTION virtual ~{classname}2();\n') | |
| 4142 out_cpp.write('\n') | |
| 4143 out_cpp.write(f'FZ_FUNCTION {classname}2::~{classname}2()\n') | |
| 4144 out_cpp.write( '{\n') | |
| 4145 out_cpp.write(f' {trace_if}\n') | |
| 4146 out_cpp.write(f' if (s_trace_director)\n') | |
| 4147 out_cpp.write( ' {\n') | |
| 4148 out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": ~{classname}2(): this=" << this << "\\n";\n') | |
| 4149 if not extras.pod: | |
| 4150 out_cpp.write( f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": ~{classname}2(): m_internal=" << m_internal << "\\n";\n') | |
| 4151 out_cpp.write( ' }\n') | |
| 4152 out_cpp.write(f' #endif\n') | |
| 4153 if free: | |
| 4154 out_cpp.write(f' {free}\n') | |
| 4155 out_cpp.write( '}\n') | |
| 4156 | |
| 4157 def write(text): | |
| 4158 out_h.write(text) | |
| 4159 out_cpp.write(text) | |
| 4160 | |
| 4161 # Define static callback for each fnptr. It's important that these | |
| 4162 # functions do not resolve function parameter typedefs such as size_t to | |
| 4163 # the underlying types such as long int, because: | |
| 4164 # | |
| 4165 # * Our generated code can be compiled on different machines where types | |
| 4166 # such as size_t can be typedef-ed differently. | |
| 4167 # | |
| 4168 # * Elsewhere, code that we generate will assign our static callback | |
| 4169 # functions to MuPDF's function pointers (which use things like size_t). | |
| 4170 # | |
| 4171 # * These assignments will fail if the types don't match exactly. | |
| 4172 # | |
| 4173 # For example fz_output has a member: | |
| 4174 # fz_output_write_fn *write; | |
| 4175 # | |
| 4176 # This typedef is: | |
| 4177 # void (fz_output_write_fn)(fz_context *ctx, void *state, const void *data, size_t n); | |
| 4178 # | |
| 4179 # We generate a static function called Output2_s_write() and we will be | |
| 4180 # setting a fz_output's write member to point to Output2_s_write(), which | |
| 4181 # only works if the types match exactly. | |
| 4182 # | |
| 4183 # So we need to resolve the outer 'typedef fz_output_write_fn', but not | |
| 4184 # the inner 'size_t' typedef for the <n> arg. This is slightly tricky with | |
| 4185 # clang-python - it provide a Type.get_canonical() method that resolves all | |
| 4186 # typedefs, but to resolve just one level of typedefs requires a bit more | |
| 4187 # work. See get_struct_fnptrs() for details. | |
| 4188 # | |
| 4189 # [Usually our generated code deliberately resolves typedefs such as size_t | |
| 4190 # to long int etc, because SWIG-generated code for size_t etc does not | |
| 4191 # always work properly due to SWIG having its own definitions of things | |
| 4192 # like size_t in Python/C#. But in this case the generated static function | |
| 4193 # is not seen by SWIG so it's ok to make it use size_t etc.] | |
| 4194 # | |
| 4195 for cursor, fnptr_type in get_fnptrs( shallow_typedef_expansion=True): | |
| 4196 | |
| 4197 # Write static callback. | |
| 4198 return_type = _make_top_level(fnptr_type.get_result().spelling) | |
| 4199 out_cpp.write(f'/* Static callback, calls self->{cursor.spelling}(). */\n') | |
| 4200 out_cpp.write(f'static {return_type} {classname}2_s_{cursor.spelling}') | |
| 4201 out_cpp.write('(') | |
| 4202 sep = '' | |
| 4203 for i, arg_type in enumerate( fnptr_type.argument_types()): | |
| 4204 name = f'arg_{i}' | |
| 4205 out_cpp.write(sep) | |
| 4206 out_cpp.write( declaration_text( arg_type, name, expand_typedef=False)) | |
| 4207 sep = ', ' | |
| 4208 out_cpp.write(')') | |
| 4209 out_cpp.write('\n') | |
| 4210 out_cpp.write('{\n') | |
| 4211 self_expression = self_() if self_n is None else self_( f'arg_{self_n}') | |
| 4212 out_cpp.write(f' {classname}2* self = {self_expression};\n') | |
| 4213 out_cpp.write(f' {trace_if}\n') | |
| 4214 out_cpp.write(f' if (s_trace_director)\n') | |
| 4215 out_cpp.write( ' {\n') | |
| 4216 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') | |
| 4217 out_cpp.write( ' }\n') | |
| 4218 out_cpp.write( ' #endif\n') | |
| 4219 out_cpp.write( ' {\n') | |
| 4220 out_cpp.write( ' char error_message[256] = "";\n') | |
| 4221 out_cpp.write( ' try\n') | |
| 4222 out_cpp.write( ' {\n') | |
| 4223 out_cpp.write(f' return self->{cursor.spelling}(') | |
| 4224 sep = '' | |
| 4225 for i, arg_type in enumerate( fnptr_type.argument_types()): | |
| 4226 if i == self_n: | |
| 4227 # This is the void* from which we found `self` so ignore | |
| 4228 # here. Note that we still pass the fz_context to the virtual | |
| 4229 # fn. | |
| 4230 continue | |
| 4231 name = f'arg_{i}' | |
| 4232 out_cpp.write( f'{sep}{name}') | |
| 4233 sep = ', ' | |
| 4234 out_cpp.write(');\n') | |
| 4235 out_cpp.write(' }\n') | |
| 4236 | |
| 4237 # todo: catch our different exception types and map to FZ_ERROR_*. | |
| 4238 out_cpp.write( ' catch (std::exception& e)\n') | |
| 4239 out_cpp.write( ' {\n') | |
| 4240 out_cpp.write(f' {trace_if}\n') | |
| 4241 out_cpp.write( ' if (s_trace_director)\n') | |
| 4242 out_cpp.write( ' {\n') | |
| 4243 out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2_s_{cursor.spelling}(): converting std::exception to fz_throw(): " << e.what() << "\\n";\n') | |
| 4244 out_cpp.write( ' }\n') | |
| 4245 out_cpp.write( ' #endif\n') | |
| 4246 out_cpp.write( ' fz_strlcpy(error_message, e.what(), sizeof(error_message));\n') | |
| 4247 out_cpp.write( ' }\n') | |
| 4248 out_cpp.write( ' /* We defer fz_throw() to here, to ensure that `std::exception& e` has been destructed. */\n') | |
| 4249 out_cpp.write( ' fz_throw(arg_0, FZ_ERROR_GENERIC, "%s", error_message);\n') | |
| 4250 if return_type != 'void': | |
| 4251 out_cpp.write(f' /* Keep compiler happy. */\n') | |
| 4252 out_cpp.write(f' {return_type} ret;\n') | |
| 4253 out_cpp.write(f' return ret;\n') | |
| 4254 out_cpp.write( ' }\n') | |
| 4255 out_cpp.write('}\n') | |
| 4256 | |
| 4257 # Define use_virtual_<name>( bool use) method for each fnptr. | |
| 4258 # | |
| 4259 out_h.write(f'\n') | |
| 4260 # Using a Doxygen-style `/**` comment prefix here can break swig with | |
| 4261 # `Error: Syntax error in input(3).` if there are no following method | |
| 4262 # declarations. | |
| 4263 out_h.write(f' /** These methods set the function pointers in *m_internal\n') | |
| 4264 out_h.write(f' to point to internal callbacks that call our virtual methods. */\n') | |
| 4265 for cursor, fnptr_type in get_fnptrs(): | |
| 4266 out_h.write(f' FZ_FUNCTION void use_virtual_{cursor.spelling}( bool use=true);\n') | |
| 4267 out_cpp.write(f'FZ_FUNCTION void {classname}2::use_virtual_{cursor.spelling}( bool use)\n') | |
| 4268 out_cpp.write( '{\n') | |
| 4269 | |
| 4270 out_cpp.write(f' {trace_if}\n') | |
| 4271 out_cpp.write(f' if (s_trace_director)\n') | |
| 4272 out_cpp.write( ' {\n') | |
| 4273 out_cpp.write(f' std::cerr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << ": {classname}2::use_virtual_{cursor.spelling}(): this=" << this << " use=" << use << "\\n";\n') | |
| 4274 out_cpp.write( ' }\n') | |
| 4275 out_cpp.write( ' #endif\n') | |
| 4276 | |
| 4277 if extras.pod == 'inline': | |
| 4278 # Fnptr (in {classname}2) and virtual function (in {classname}) | |
| 4279 # have same name, so we need qualify the fnptr with {classname} to | |
| 4280 # ensure we distinguish between the two. | |
| 4281 out_cpp.write(f' {classname}::{cursor.spelling} = (use) ? {classname}2_s_{cursor.spelling} : nullptr;\n') | |
| 4282 elif extras.pod: | |
| 4283 out_cpp.write(f' m_internal.{cursor.spelling} = (use) ? {classname}2_s_{cursor.spelling} : nullptr;\n') | |
| 4284 else: | |
| 4285 out_cpp.write(f' m_internal->{cursor.spelling} = (use) ? {classname}2_s_{cursor.spelling} : nullptr;\n') | |
| 4286 out_cpp.write( '}\n') | |
| 4287 | |
| 4288 # Write virtual fn default implementations. | |
| 4289 # | |
| 4290 out_h.write(f'\n') | |
| 4291 | |
| 4292 # Using a Doxygen-style `/**` comment prefix here can break swig with | |
| 4293 # `Error: Syntax error in input(3).` if there are no following method | |
| 4294 # declarations. | |
| 4295 out_h.write(f' /** Default virtual method implementations; these all throw an exception. */\n') | |
| 4296 for cursor, fnptr_type in get_fnptrs(): | |
| 4297 | |
| 4298 out_h.write(f' FZ_FUNCTION virtual {_make_top_level(fnptr_type.get_result().spelling)} {cursor.spelling}(') | |
| 4299 out_cpp.write(f'/* Default implementation of virtual method. */\n') | |
| 4300 out_cpp.write(f'FZ_FUNCTION {_make_top_level(fnptr_type.get_result().spelling)} {classname}2::{cursor.spelling}(') | |
| 4301 sep = '' | |
| 4302 for i, arg_type in enumerate( fnptr_type.argument_types()): | |
| 4303 if i == self_n: | |
| 4304 # This is the void* from which we found `self` so ignore | |
| 4305 # here. Note that we still pass the fz_context to the virtual | |
| 4306 # fn. | |
| 4307 continue | |
| 4308 name = f'arg_{i}' | |
| 4309 write(f'{sep}') | |
| 4310 decl_text = declaration_text(arg_type, name, verbose=0) | |
| 4311 write(decl_text) | |
| 4312 sep = ', ' | |
| 4313 out_h.write( ');\n') | |
| 4314 out_cpp.write( ')\n') | |
| 4315 out_cpp.write( '{\n') | |
| 4316 out_cpp.write(f' std::cerr << "Unexpected call of unimplemented virtual_fnptrs fn {classname}2::{cursor.spelling}(): this=" << this << ".\\n";\n') | |
| 4317 out_cpp.write(f' throw std::runtime_error( "Unexpected call of unimplemented virtual_fnptrs fn {classname}2::{cursor.spelling}()");\n') | |
| 4318 out_cpp.write( '}\n') | |
| 4319 | |
| 4320 out_h.write( '};\n') | |
| 4321 | |
| 4322 | |
| 4323 def class_wrapper( | |
| 4324 tu, | |
| 4325 register_fn_use, | |
| 4326 struct_cursor, | |
| 4327 struct_name, | |
| 4328 classname, | |
| 4329 extras, | |
| 4330 out_h, | |
| 4331 out_cpp, | |
| 4332 out_h_end, | |
| 4333 out_cpp2, | |
| 4334 out_h2, | |
| 4335 generated, | |
| 4336 refcheck_if, | |
| 4337 trace_if, | |
| 4338 ): | |
| 4339 ''' | |
| 4340 Creates source for a class called <classname> that wraps <struct_name>, | |
| 4341 with methods that call selected fz_*() functions. Writes to out_h and | |
| 4342 out_cpp. | |
| 4343 | |
| 4344 Created source is just the per-class code, e.g. it does not contain | |
| 4345 #include's. | |
| 4346 | |
| 4347 Args: | |
| 4348 tu: | |
| 4349 Clang translation unit. | |
| 4350 struct_cursor: | |
| 4351 Cursor for struct to wrap. | |
| 4352 struct_name: | |
| 4353 Name of struct to wrap. | |
| 4354 classname: | |
| 4355 Name of wrapper class to create. | |
| 4356 out_h: | |
| 4357 Stream to which we write class definition. | |
| 4358 out_cpp: | |
| 4359 Stream to which we write method implementations. | |
| 4360 out_h_end: | |
| 4361 Stream for text that should be put at the end of the generated | |
| 4362 header text. | |
| 4363 generated: | |
| 4364 We write extra python and C# code to generated.out_swig_python and | |
| 4365 generated.out_swig_csharp for use in the swig .i file. | |
| 4366 | |
| 4367 Returns (is_container, has_to_string). <is_container> is true if generated | |
| 4368 class has custom begin() and end() methods; <has_to_string> is true if we | |
| 4369 have created a to_string() method. | |
| 4370 ''' | |
| 4371 assert extras, f'extras is None for {struct_name}' | |
| 4372 if extras.iterator_next: | |
| 4373 class_add_iterator( tu, struct_cursor, struct_name, classname, extras, refcheck_if, trace_if) | |
| 4374 | |
| 4375 if extras.class_pre: | |
| 4376 out_h.write( textwrap.dedent( extras.class_pre)) | |
| 4377 | |
| 4378 base_name = util.clip( struct_name, ('fz_', 'pdf_')) | |
| 4379 | |
| 4380 constructor_fns = class_find_constructor_fns( tu, classname, struct_name, base_name, extras) | |
| 4381 for fnname in extras.constructors_wrappers: | |
| 4382 cursor = state.state_.find_function( tu, fnname, method=True) | |
| 4383 assert cursor, f'No cursor for constructor wrapper fnname={fnname}' | |
| 4384 constructor_fns.append( (fnname, cursor, None)) | |
| 4385 | |
| 4386 destructor_fns = class_find_destructor_fns( tu, struct_name, base_name) | |
| 4387 | |
| 4388 # Class definition beginning. | |
| 4389 # | |
| 4390 out_h.write( '\n') | |
| 4391 if extras.copyable: | |
| 4392 out_h.write( f'/** Wrapper class for struct `{struct_name}`. */\n') | |
| 4393 else: | |
| 4394 out_h.write( f'/** Wrapper class for struct `{struct_name}`. Not copyable or assignable. */\n') | |
| 4395 if struct_cursor.raw_comment: | |
| 4396 raw_comment = struct_cursor.raw_comment.replace('\r', '') | |
| 4397 out_h.write(raw_comment) | |
| 4398 if not raw_comment.endswith( '\n'): | |
| 4399 out_h.write( '\n') | |
| 4400 out_h.write( f'struct {classname}\n{{') | |
| 4401 | |
| 4402 out_cpp.write( '\n') | |
| 4403 out_cpp.write( f'/* Implementation of methods for {classname} (wrapper for {struct_name}). */\n') | |
| 4404 out_cpp.write( '\n') | |
| 4405 refs = parse.has_refs( tu, struct_cursor.type) | |
| 4406 if refs: | |
| 4407 refs_name, refs_size = refs | |
| 4408 out_cpp.write( f'{refcheck_if}\n') | |
| 4409 if isinstance(refs_name, int): | |
| 4410 # <refs_name> is offset of .refs in the struct. | |
| 4411 allow_int_this = ', true /*allow_int_this*/' if struct_name == 'pdf_obj' else '' | |
| 4412 out_cpp.write( f'static RefsCheck<::{struct_name}, {classname}{allow_int_this}> s_{classname}_refs_check({refs_name}, {refs_size});\n') | |
| 4413 else: | |
| 4414 # <refs_name> is name of .refs in the struct. | |
| 4415 out_cpp.write( f'static RefsCheck<::{struct_name}, {classname}> s_{classname}_refs_check(offsetof(::{struct_name}, {refs_name}), {refs_size});\n') | |
| 4416 out_cpp.write( f'#endif\n') | |
| 4417 out_cpp.write( '\n') | |
| 4418 | |
| 4419 # Trailing text in header, e.g. typedef for iterator. | |
| 4420 # | |
| 4421 if extras.class_top: | |
| 4422 # Strip leading blank line to avoid slightly odd-looking layout. | |
| 4423 text = util.clip( extras.class_top, '\n') | |
| 4424 text = textwrap.dedent( text) | |
| 4425 text = textwrap.indent( text, ' ') | |
| 4426 out_h.write( '\n') | |
| 4427 out_h.write( text) | |
| 4428 | |
| 4429 # Constructors | |
| 4430 # | |
| 4431 num_constructors = 0 | |
| 4432 have_created_default_constructor = False | |
| 4433 | |
| 4434 if constructor_fns: | |
| 4435 out_h.write( '\n') | |
| 4436 out_h.write( ' /** == Constructors. */\n') | |
| 4437 num_constructors += len(constructor_fns) | |
| 4438 for fnname, cursor, duplicate_type in constructor_fns: | |
| 4439 # clang-6 appears not to be able to handle fn args that are themselves | |
| 4440 # function pointers, so for now we allow function_wrapper() to fail, | |
| 4441 # so we need to use temporary buffers, otherwise out_functions_h and | |
| 4442 # out_functions_cpp can get partial text written. | |
| 4443 # | |
| 4444 assert cursor, f'No cursor for constructor function. fnname={fnname} duplicate_type={duplicate_type}' | |
| 4445 temp_out_h = io.StringIO() | |
| 4446 temp_out_cpp = io.StringIO() | |
| 4447 if state.state_.show_details(fnname): | |
| 4448 jlib.log('Creating constructor for {=classname fnname}') | |
| 4449 if parse.get_first_arg( tu, cursor) == (None, 0): | |
| 4450 have_created_default_constructor = True | |
| 4451 try: | |
| 4452 function_wrapper_class_aware( | |
| 4453 tu, | |
| 4454 register_fn_use, | |
| 4455 fnname, | |
| 4456 temp_out_h, | |
| 4457 temp_out_cpp, | |
| 4458 struct_name, | |
| 4459 classname, | |
| 4460 cursor, | |
| 4461 refcheck_if, | |
| 4462 trace_if, | |
| 4463 class_static=False, | |
| 4464 class_constructor=True, | |
| 4465 extras=extras, | |
| 4466 struct_cursor=struct_cursor, | |
| 4467 duplicate_type=duplicate_type, | |
| 4468 ) | |
| 4469 except Clang6FnArgsBug as e: | |
| 4470 jlib.log( 'Unable to wrap function {fnname} because: {e}') | |
| 4471 else: | |
| 4472 out_h.write( temp_out_h.getvalue()) | |
| 4473 out_cpp.write( temp_out_cpp.getvalue()) | |
| 4474 | |
| 4475 # Custom constructors. | |
| 4476 # | |
| 4477 for extra_constructor in extras.constructors_extra: | |
| 4478 if extra_constructor.name_args == '()': | |
| 4479 have_created_default_constructor = True | |
| 4480 class_custom_method( | |
| 4481 tu, | |
| 4482 register_fn_use, | |
| 4483 struct_cursor, | |
| 4484 classname, | |
| 4485 extra_constructor, | |
| 4486 out_h, | |
| 4487 out_cpp, | |
| 4488 refcheck_if, | |
| 4489 trace_if, | |
| 4490 ) | |
| 4491 num_constructors += 1 | |
| 4492 | |
| 4493 # Look for function that can be used by copy constructor and operator=. | |
| 4494 # | |
| 4495 if refs: | |
| 4496 assert extras.copyable != 'default', f'Non-POD class for {struct_name=} has refs, so must not use copyable="default"' | |
| 4497 | |
| 4498 if not extras.pod and extras.copyable and extras.copyable != 'default': | |
| 4499 class_copy_constructor( | |
| 4500 tu, | |
| 4501 register_fn_use, | |
| 4502 struct_name, | |
| 4503 struct_cursor, | |
| 4504 base_name, | |
| 4505 classname, | |
| 4506 constructor_fns, | |
| 4507 out_h, | |
| 4508 out_cpp, | |
| 4509 refcheck_if, | |
| 4510 trace_if, | |
| 4511 ) | |
| 4512 elif extras.copyable: | |
| 4513 out_h.write( '\n') | |
| 4514 out_h.write( ' /** We use default copy constructor and operator=. */\n') | |
| 4515 | |
| 4516 if extras.constructor_default: | |
| 4517 if have_created_default_constructor: | |
| 4518 if 0: | |
| 4519 jlib.log( 'Not creating default constructor because default custom constructor. {struct_name=}') | |
| 4520 elif extras.constructor_raw == 'default': | |
| 4521 if 0: | |
| 4522 jlib.log( 'Not creating default constructor because default raw constructor. {struct_name=}') | |
| 4523 else: | |
| 4524 class_constructor_default( | |
| 4525 tu, | |
| 4526 struct_cursor, | |
| 4527 classname, | |
| 4528 extras, | |
| 4529 out_h, | |
| 4530 out_cpp, | |
| 4531 refcheck_if, | |
| 4532 trace_if, | |
| 4533 ) | |
| 4534 num_constructors += 1 | |
| 4535 | |
| 4536 # Auto-add all methods that take <struct_name> as first param, but | |
| 4537 # skip methods that are already wrapped in extras.method_wrappers or | |
| 4538 # extras.methods_extra etc. | |
| 4539 # | |
| 4540 for fnname in parse.find_wrappable_function_with_arg0_type( tu, struct_name): | |
| 4541 if state.state_.show_details(fnname): | |
| 4542 jlib.log('{struct_name=}: looking at potential method wrapping {fnname=}') | |
| 4543 if fnname in extras.method_wrappers: | |
| 4544 #log( 'auto-detected fn already in {struct_name} method_wrappers: {fnname}') | |
| 4545 # Omit this function, because there is an extra method with the | |
| 4546 # same name. (We could probably include both as they will generally | |
| 4547 # have different args so overloading will distinguish them, but | |
| 4548 # extra methods are usually defined to be used in preference.) | |
| 4549 pass | |
| 4550 elif fnname.startswith( 'fz_new_draw_device'): | |
| 4551 # fz_new_draw_device*() functions take first arg fz_matrix, but | |
| 4552 # aren't really appropriate for the fz_matrix wrapper class. | |
| 4553 # | |
| 4554 pass | |
| 4555 elif isinstance( fnname, list): | |
| 4556 assert 0 | |
| 4557 else: | |
| 4558 for extramethod in extras.methods_extra: | |
| 4559 if not extramethod.overload: | |
| 4560 if extramethod.name_args.startswith( f'{rename.method( struct_name, fnname)}('): | |
| 4561 jlib.log1( 'Omitting default method because same name as extramethod: {extramethod.name_args}') | |
| 4562 break | |
| 4563 else: | |
| 4564 #log( 'adding to extras.method_wrappers: {fnname}') | |
| 4565 extras.method_wrappers.append( fnname) | |
| 4566 | |
| 4567 # Extra static methods. | |
| 4568 # | |
| 4569 if extras.method_wrappers_static: | |
| 4570 out_h.write( '\n') | |
| 4571 out_h.write( ' /* == Static methods. */\n') | |
| 4572 for fnname in extras.method_wrappers_static: | |
| 4573 function_wrapper_class_aware( | |
| 4574 tu, | |
| 4575 register_fn_use, | |
| 4576 fnname, | |
| 4577 out_h, | |
| 4578 out_cpp, | |
| 4579 struct_name, | |
| 4580 classname, | |
| 4581 fn_cursor=None, | |
| 4582 refcheck_if=refcheck_if, | |
| 4583 trace_if=trace_if, | |
| 4584 class_static=True, | |
| 4585 struct_cursor=struct_cursor, | |
| 4586 generated=generated, | |
| 4587 ) | |
| 4588 | |
| 4589 # Extra methods that wrap fz_*() fns. | |
| 4590 # | |
| 4591 if extras.method_wrappers or extras.methods_extra: | |
| 4592 out_h.write( '\n') | |
| 4593 out_h.write( ' /* == Methods. */') | |
| 4594 out_h.write( '\n') | |
| 4595 extras.method_wrappers.sort() | |
| 4596 for fnname in extras.method_wrappers: | |
| 4597 function_wrapper_class_aware( | |
| 4598 tu, | |
| 4599 register_fn_use, | |
| 4600 fnname, | |
| 4601 out_h, | |
| 4602 out_cpp, | |
| 4603 struct_name, | |
| 4604 classname, | |
| 4605 None, #fn_cursor | |
| 4606 refcheck_if, | |
| 4607 trace_if, | |
| 4608 struct_cursor=struct_cursor, | |
| 4609 generated=generated, | |
| 4610 debug=state.state_.show_details(fnname), | |
| 4611 ) | |
| 4612 | |
| 4613 # Custom methods. | |
| 4614 # | |
| 4615 is_container = 0 | |
| 4616 custom_destructor = False | |
| 4617 for extramethod in extras.methods_extra: | |
| 4618 is_constructor, is_destructor, is_begin_end = class_custom_method( | |
| 4619 tu, | |
| 4620 register_fn_use, | |
| 4621 struct_cursor, | |
| 4622 classname, | |
| 4623 extramethod, | |
| 4624 out_h, | |
| 4625 out_cpp, | |
| 4626 refcheck_if, | |
| 4627 trace_if, | |
| 4628 ) | |
| 4629 if is_constructor: | |
| 4630 num_constructors += 1 | |
| 4631 if is_destructor: | |
| 4632 custom_destructor = True | |
| 4633 if is_begin_end: | |
| 4634 is_container += 1 | |
| 4635 | |
| 4636 assert is_container==0 or is_container==2, f'struct_name={struct_name} is_container={is_container}' # Should be begin()+end() or neither. | |
| 4637 if is_container: | |
| 4638 pass | |
| 4639 #jlib.log( 'Generated class has begin() and end(): {classname=}') | |
| 4640 | |
| 4641 if num_constructors == 0 or extras.constructor_raw: | |
| 4642 if state.state_.show_details(struct_name): | |
| 4643 jlib.log('calling class_raw_constructor(). {struct_name=}') | |
| 4644 class_raw_constructor( | |
| 4645 tu, | |
| 4646 register_fn_use, | |
| 4647 classname, | |
| 4648 struct_cursor, | |
| 4649 struct_name, | |
| 4650 base_name, | |
| 4651 extras, | |
| 4652 constructor_fns, | |
| 4653 out_h, | |
| 4654 out_cpp, | |
| 4655 refcheck_if, | |
| 4656 trace_if, | |
| 4657 ) | |
| 4658 | |
| 4659 # Accessor methods to POD data. | |
| 4660 # | |
| 4661 if extras.accessors and extras.pod == 'inline': | |
| 4662 jlib.log( 'ignoring {extras.accessors=} for {struct_name=} because {extras.pod=}.') | |
| 4663 elif extras.accessors: | |
| 4664 out_h.write( f'\n') | |
| 4665 out_h.write( f' /* == Accessors to members of ::{struct_name} m_internal. */\n') | |
| 4666 out_h.write( '\n') | |
| 4667 class_accessors( | |
| 4668 tu, | |
| 4669 register_fn_use, | |
| 4670 classname, | |
| 4671 struct_cursor, | |
| 4672 struct_name, | |
| 4673 extras, | |
| 4674 out_h, | |
| 4675 out_cpp, | |
| 4676 ) | |
| 4677 | |
| 4678 # Destructor. | |
| 4679 # | |
| 4680 if not custom_destructor: | |
| 4681 out_h.write( f'\n') | |
| 4682 class_destructor( | |
| 4683 tu, | |
| 4684 register_fn_use, | |
| 4685 classname, | |
| 4686 extras, | |
| 4687 struct_cursor, | |
| 4688 destructor_fns, | |
| 4689 out_h, | |
| 4690 out_cpp, | |
| 4691 refcheck_if, | |
| 4692 trace_if, | |
| 4693 ) | |
| 4694 | |
| 4695 # If class has '{structname}* m_internal;', provide access to m_iternal as | |
| 4696 # an integer, for use by python etc, and provide `operator bool()`. | |
| 4697 if not extras.pod: | |
| 4698 class_custom_method( | |
| 4699 tu, | |
| 4700 register_fn_use, | |
| 4701 struct_cursor, | |
| 4702 classname, | |
| 4703 classes.ExtraMethod( | |
| 4704 'long long', | |
| 4705 'm_internal_value()', | |
| 4706 ''' | |
| 4707 { | |
| 4708 return (uintptr_t) m_internal; | |
| 4709 } | |
| 4710 ''', | |
| 4711 '/** Return numerical value of .m_internal; helps with Python debugging. */', | |
| 4712 ), | |
| 4713 out_h, | |
| 4714 out_cpp, | |
| 4715 refcheck_if, | |
| 4716 trace_if, | |
| 4717 ) | |
| 4718 class_custom_method( | |
| 4719 tu, | |
| 4720 register_fn_use, | |
| 4721 struct_cursor, | |
| 4722 classname, | |
| 4723 classes.ExtraMethod( | |
| 4724 '', | |
| 4725 'operator bool()', | |
| 4726 f''' | |
| 4727 {{ | |
| 4728 {trace_if} | |
| 4729 if (s_trace) | |
| 4730 {{ | |
| 4731 std::cerr << __FILE__ << ":" << __LINE__ << ":" | |
| 4732 << " {classname}::operator bool() called," | |
| 4733 << " m_internal=" << m_internal << "." | |
| 4734 << "\\n"; | |
| 4735 }} | |
| 4736 #endif | |
| 4737 return m_internal ? true : false; | |
| 4738 }} | |
| 4739 ''', | |
| 4740 '/** Return true iff `m_internal` is not null. */', | |
| 4741 ), | |
| 4742 out_h, | |
| 4743 out_cpp, | |
| 4744 refcheck_if, | |
| 4745 trace_if, | |
| 4746 ) | |
| 4747 | |
| 4748 # Class members. | |
| 4749 # | |
| 4750 out_h.write( '\n') | |
| 4751 out_h.write( ' /* == Member data. */\n') | |
| 4752 out_h.write( '\n') | |
| 4753 if extras.pod == 'none': | |
| 4754 pass | |
| 4755 elif extras.pod == 'inline': | |
| 4756 out_h.write( f' /* These members are the same as the members of ::{struct_name}. */\n') | |
| 4757 for c in parse.get_members(struct_cursor): | |
| 4758 out_h.write( f' {declaration_text(c.type, c.spelling)};\n') | |
| 4759 elif extras.pod: | |
| 4760 out_h.write( f' ::{struct_cursor.spelling} m_internal; /** Wrapped data is held by value. */\n') | |
| 4761 else: | |
| 4762 # Putting this double-asterix comment on same line as m_internal breaks | |
| 4763 # swig-4.02 with "Error: Syntax error in input(3).". | |
| 4764 out_h.write( f' /** Pointer to wrapped data. */\n') | |
| 4765 out_h.write( f' ::{struct_name}* m_internal;\n') | |
| 4766 | |
| 4767 # Declare static `num_instances` variable. | |
| 4768 out_h.write( '\n') | |
| 4769 out_h.write(f' /* Ideally this would be in `{refcheck_if}...#endif`, but Swig will\n') | |
| 4770 out_h.write(f' generate code regardless so we always need to have this available. */\n') | |
| 4771 out_h.write(f' FZ_DATA static int s_num_instances;\n') | |
| 4772 | |
| 4773 out_cpp.write(f'/* Ideally this would be in `{refcheck_if}...#endif`, but Swig will\n') | |
| 4774 out_cpp.write(f'generate code regardless so we always need to have this available. */\n') | |
| 4775 out_cpp.write(f'int {classname}::s_num_instances = 0;\n') | |
| 4776 out_cpp.write(f'\n') | |
| 4777 | |
| 4778 # Make operator<< (std::ostream&, ...) for POD classes. | |
| 4779 # | |
| 4780 has_to_string = False | |
| 4781 if extras.pod and extras.pod != 'none': | |
| 4782 has_to_string = True | |
| 4783 pod_class_members( | |
| 4784 tu, | |
| 4785 classname, | |
| 4786 struct_cursor, | |
| 4787 struct_name, | |
| 4788 extras, | |
| 4789 out_h, | |
| 4790 out_cpp, | |
| 4791 ) | |
| 4792 | |
| 4793 # Trailing text in header, e.g. typedef for iterator. | |
| 4794 # | |
| 4795 if extras.class_bottom: | |
| 4796 out_h.write( textwrap.indent( textwrap.dedent( extras.class_bottom), ' ')) | |
| 4797 | |
| 4798 # Private copy constructor if not copyable. | |
| 4799 # | |
| 4800 if not extras.copyable: | |
| 4801 out_h.write( '\n') | |
| 4802 out_h.write( ' private:\n') | |
| 4803 out_h.write( '\n') | |
| 4804 out_h.write( ' /** This class is not copyable or assignable. */\n') | |
| 4805 out_h.write( f' {classname}(const {classname}& rhs);\n') | |
| 4806 out_h.write( f' {classname}& operator=(const {classname}& rhs);\n') | |
| 4807 | |
| 4808 # Class definition end. | |
| 4809 # | |
| 4810 out_h.write( '};\n') | |
| 4811 | |
| 4812 if extras.class_post: | |
| 4813 out_h_end.write( textwrap.dedent( extras.class_post)) | |
| 4814 | |
| 4815 if extras.extra_cpp: | |
| 4816 out_cpp.write( f'/* .extra_cpp for {struct_name}. */\n') | |
| 4817 out_cpp.write( textwrap.dedent( extras.extra_cpp)) | |
| 4818 | |
| 4819 class_wrapper_virtual_fnptrs( | |
| 4820 tu, | |
| 4821 struct_cursor, | |
| 4822 struct_name, | |
| 4823 classname, | |
| 4824 extras, | |
| 4825 out_h, | |
| 4826 out_cpp, | |
| 4827 out_h_end, | |
| 4828 generated, | |
| 4829 refcheck_if, | |
| 4830 trace_if, | |
| 4831 ) | |
| 4832 | |
| 4833 return is_container, has_to_string | |
| 4834 | |
| 4835 | |
| 4836 def header_guard( name, out): | |
| 4837 ''' | |
| 4838 Writes header guard for <name> to stream <out>. | |
| 4839 ''' | |
| 4840 m = '' | |
| 4841 for c in name: | |
| 4842 m += c.upper() if c.isalnum() else '_' | |
| 4843 out.write( f'#ifndef {m}\n') | |
| 4844 out.write( f'#define {m}\n') | |
| 4845 out.write( '\n') | |
| 4846 | |
| 4847 | |
| 4848 def tabify( filename, text): | |
| 4849 ''' | |
| 4850 Returns <text> with leading multiples of 4 spaces converted to tab | |
| 4851 characters. | |
| 4852 ''' | |
| 4853 ret = '' | |
| 4854 linenum = 0 | |
| 4855 for line in text.split( '\n'): | |
| 4856 linenum += 1 | |
| 4857 i = 0 | |
| 4858 while 1: | |
| 4859 if i == len(line): | |
| 4860 break | |
| 4861 if line[i] != ' ': | |
| 4862 break | |
| 4863 i += 1 | |
| 4864 if i % 4: | |
| 4865 if line[ int(i/4)*4:].startswith( ' *'): | |
| 4866 # This can happen in comments. | |
| 4867 pass | |
| 4868 else: | |
| 4869 jlib.log( '*** {filename}:{linenum}: {i=} {line!r=} indentation is not a multiple of 4') | |
| 4870 num_tabs = int(i / 4) | |
| 4871 ret += num_tabs * '\t' + line[ num_tabs*4:] + '\n' | |
| 4872 | |
| 4873 # We use [:-1] here because split() always returns extra last item '', so | |
| 4874 # we will have appended an extra '\n'. | |
| 4875 # | |
| 4876 return ret[:-1] | |
| 4877 | |
| 4878 | |
| 4879 def refcount_check_code( out, refcheck_if): | |
| 4880 ''' | |
| 4881 Writes reference count checking code to <out>. | |
| 4882 ''' | |
| 4883 out.write( textwrap.dedent( | |
| 4884 f''' | |
| 4885 /* Support for checking that reference counts of underlying | |
| 4886 MuPDF structs are not smaller than the number of wrapper class | |
| 4887 instances. Enable at runtime by setting environmental variable | |
| 4888 MUPDF_check_refs to "1". */ | |
| 4889 | |
| 4890 static const bool s_check_refs = internal_env_flag("MUPDF_check_refs"); | |
| 4891 | |
| 4892 /* For each MuPDF struct that has an 'int refs' member, we create | |
| 4893 a static instance of this class template with T set to our wrapper | |
| 4894 class, for example: | |
| 4895 | |
| 4896 static RefsCheck<fz_document, FzDocument> s_FzDocument_refs_check; | |
| 4897 | |
| 4898 Then if s_check_refs is true, each constructor function calls | |
| 4899 .add(), the destructor calls .remove() and other class functions | |
| 4900 call .check(). This ensures that we check reference counting after | |
| 4901 each class operation. | |
| 4902 | |
| 4903 If <allow_int_this> is true, we allow _this->m_internal to be | |
| 4904 an invalid pointer less than 4096, in which case we don't try | |
| 4905 to check refs. This is used for pdf_obj because in Python the | |
| 4906 enums PDF_ENUM_NAME_* are converted to mupdf.PdfObj's contain | |
| 4907 .m_internal's which are the enum values cast to (for_pdf_obj*), so | |
| 4908 that they can be used directly. | |
| 4909 | |
| 4910 If m_size is -1, we don't attempt any checking; this is for fz_xml | |
| 4911 which is reference counted but does not have a simple .refs member. | |
| 4912 */ | |
| 4913 {refcheck_if} | |
| 4914 template<typename Struct, typename ClassWrapper, bool allow_int_this=false> | |
| 4915 struct RefsCheck | |
| 4916 {{ | |
| 4917 std::mutex m_mutex; | |
| 4918 int m_offset; | |
| 4919 int m_size; | |
| 4920 std::map<Struct*, int> m_this_to_num; | |
| 4921 | |
| 4922 RefsCheck(int offset, int size) | |
| 4923 : m_offset(offset), m_size(size) | |
| 4924 {{ | |
| 4925 assert(offset >= 0 && offset < 1000); | |
| 4926 assert(m_size == 32 || m_size == 16 || m_size == 8 || m_size == -1); | |
| 4927 }} | |
| 4928 | |
| 4929 void change( const ClassWrapper* this_, const char* file, int line, const char* fn, int delta) | |
| 4930 {{ | |
| 4931 assert( s_check_refs); | |
| 4932 if (m_size == -1) | |
| 4933 {{ | |
| 4934 /* No well-defined .refs member for us to check, e.g. fz_xml. */ | |
| 4935 return; | |
| 4936 }} | |
| 4937 if (!this_->m_internal) return; | |
| 4938 if (allow_int_this) | |
| 4939 {{ | |
| 4940 #if 0 // Historic diagnostics, might still be useful. | |
| 4941 std::cerr << __FILE__ << ":" << __LINE__ | |
| 4942 << " " << file << ":" << line << ":" << fn << ":" | |
| 4943 << " this_->m_internal=" << this_->m_internal | |
| 4944 << "\\n"; | |
| 4945 #endif | |
| 4946 if ((intptr_t) this_->m_internal < 4096) | |
| 4947 {{ | |
| 4948 #if 0 // Historic diagnostics, might still be useful. | |
| 4949 std::cerr << __FILE__ << ":" << __LINE__ | |
| 4950 << " " << file << ":" << line << ":" << fn << ":" | |
| 4951 << " Ignoring this_->m_internal=" << this_->m_internal | |
| 4952 << "\\n"; | |
| 4953 #endif | |
| 4954 return; | |
| 4955 }} | |
| 4956 }} | |
| 4957 std::lock_guard< std::mutex> lock( m_mutex); | |
| 4958 /* Our lock doesn't make our access to | |
| 4959 this_->m_internal->refs thead-safe - other threads | |
| 4960 could be modifying it via fz_keep_<Struct>() or | |
| 4961 fz_drop_<Struct>(). But hopefully our read will be atomic | |
| 4962 in practise anyway? */ | |
| 4963 void* refs_ptr = (char*) this_->m_internal + m_offset; | |
| 4964 int refs; | |
| 4965 if (m_size == 32) refs = *(int32_t*) refs_ptr; | |
| 4966 if (m_size == 16) refs = *(int16_t*) refs_ptr; | |
| 4967 if (m_size == 8) refs = *(int8_t* ) refs_ptr; | |
| 4968 | |
| 4969 int& n = m_this_to_num[ this_->m_internal]; | |
| 4970 int n_prev = n; | |
| 4971 assert( n >= 0); | |
| 4972 n += delta; | |
| 4973 #if 0 // Historic diagnostics, might still be useful. | |
| 4974 std::cerr << file << ":" << line << ":" << fn << "():" | |
| 4975 // << " " << typeid(ClassWrapper).name() << ":" | |
| 4976 << " this_=" << this_ | |
| 4977 << " this_->m_internal=" << this_->m_internal | |
| 4978 << " refs=" << refs | |
| 4979 << " n: " << n_prev << " => " << n | |
| 4980 << "\\n"; | |
| 4981 #endif | |
| 4982 if ( n < 0) | |
| 4983 {{ | |
| 4984 #if 0 // Historic diagnostics, might still be useful. | |
| 4985 std::cerr << file << ":" << line << ":" << fn << "():" | |
| 4986 // << " " << typeid(ClassWrapper).name() << ":" | |
| 4987 << " this_=" << this_ | |
| 4988 << " this_->m_internal=" << this_->m_internal | |
| 4989 << " bad n: " << n_prev << " => " << n | |
| 4990 << "\\n"; | |
| 4991 #endif | |
| 4992 abort(); | |
| 4993 }} | |
| 4994 if ( n && refs < n) | |
| 4995 {{ | |
| 4996 #if 0 // Historic diagnostics, might still be useful. | |
| 4997 std::cerr << file << ":" << line << ":" << fn << "():" | |
| 4998 // << " " << typeid(ClassWrapper).name() << ":" | |
| 4999 << " this_=" << this_ | |
| 5000 << " this_->m_internal=" << this_->m_internal | |
| 5001 << " refs=" << refs | |
| 5002 << " n: " << n_prev << " => " << n | |
| 5003 << " refs mismatch (refs<n):" | |
| 5004 << "\\n"; | |
| 5005 #endif | |
| 5006 abort(); | |
| 5007 }} | |
| 5008 if (n && ::abs( refs - n) > 1000) | |
| 5009 {{ | |
| 5010 /* This traps case where n > 0 but underlying struct is | |
| 5011 freed and .ref is set to bogus value by fz_free() or | |
| 5012 similar. */ | |
| 5013 #if 0 // Historic diagnostics, might still be useful. | |
| 5014 std::cerr << file << ":" << line << ":" << fn << "(): " << ": " << typeid(ClassWrapper).name() | |
| 5015 << " bad change to refs." | |
| 5016 << " this_=" << this_ | |
| 5017 << " refs=" << refs | |
| 5018 << " n: " << n_prev << " => " << n | |
| 5019 << "\\n"; | |
| 5020 #endif | |
| 5021 abort(); | |
| 5022 }} | |
| 5023 if (n == 0) m_this_to_num.erase( this_->m_internal); | |
| 5024 }} | |
| 5025 void add( const ClassWrapper* this_, const char* file, int line, const char* fn) | |
| 5026 {{ | |
| 5027 change( this_, file, line, fn, +1); | |
| 5028 }} | |
| 5029 void remove( const ClassWrapper* this_, const char* file, int line, const char* fn) | |
| 5030 {{ | |
| 5031 change( this_, file, line, fn, -1); | |
| 5032 }} | |
| 5033 void check( const ClassWrapper* this_, const char* file, int line, const char* fn) | |
| 5034 {{ | |
| 5035 change( this_, file, line, fn, 0); | |
| 5036 }} | |
| 5037 }}; | |
| 5038 #endif | |
| 5039 | |
| 5040 ''' | |
| 5041 )) | |
| 5042 | |
| 5043 def cpp_source( | |
| 5044 dir_mupdf, | |
| 5045 namespace, | |
| 5046 base, | |
| 5047 header_git, | |
| 5048 generated, | |
| 5049 check_regress, | |
| 5050 clang_info_version, | |
| 5051 refcheck_if, | |
| 5052 trace_if, | |
| 5053 debug, | |
| 5054 ): | |
| 5055 ''' | |
| 5056 Generates all .h and .cpp files. | |
| 5057 | |
| 5058 Args: | |
| 5059 | |
| 5060 dir_mupdf: | |
| 5061 Location of mupdf checkout. | |
| 5062 namespace: | |
| 5063 C++ namespace to use. | |
| 5064 base: | |
| 5065 Directory in which all generated files are placed. | |
| 5066 header_git: | |
| 5067 If true we include git info in the file comment that is written | |
| 5068 into all generated files. | |
| 5069 generated: | |
| 5070 A Generated instance. | |
| 5071 check_regress: | |
| 5072 If true, we raise exception if generated content differs from what | |
| 5073 is in existing files. | |
| 5074 refcheck_if: | |
| 5075 `#if ... ' text for enabling reference-checking code. For example | |
| 5076 `#if 1` to always enable, `#ifndef NDEBUG` to only enable in debug | |
| 5077 builds, `#if 0` to always disable. | |
| 5078 refcheck_if: | |
| 5079 `#if ... ' text for enabling optional runtime diagnostic, for | |
| 5080 example by setting `MuPDF_trace=1` runtime. For example `#if 1` to | |
| 5081 always enable, `#ifndef NDEBUG` to only enable in debug builds, | |
| 5082 `#if 0` to always disable. | |
| 5083 debug: | |
| 5084 True if debug build. | |
| 5085 | |
| 5086 Updates <generated> and returns <tu> from clang.. | |
| 5087 ''' | |
| 5088 assert isinstance(generated, Generated) | |
| 5089 assert not dir_mupdf.endswith( '/') | |
| 5090 assert not base.endswith( '/') | |
| 5091 | |
| 5092 # Do initial setting up of generated files before parse, because we include extra.h in our parse input. | |
| 5093 | |
| 5094 doit = True | |
| 5095 if doit: | |
| 5096 class File: | |
| 5097 def __init__( self, filename, tabify=True): | |
| 5098 self.filename = filename | |
| 5099 self.tabify = tabify | |
| 5100 self.file = io.StringIO() | |
| 5101 self.line_begin = True | |
| 5102 self.regressions = True | |
| 5103 self.closed = False | |
| 5104 def write( self, text, fileline=False): | |
| 5105 # Do not allow writes after .close(). | |
| 5106 assert not self.closed, f'File.write() called after .close(). {self.filename=}' | |
| 5107 if fileline: | |
| 5108 # Generate #line <line> "<filename>" for our caller's | |
| 5109 # location. This makes any compiler warnings refer to their | |
| 5110 # python code rather than the generated C++ code. | |
| 5111 tb = traceback.extract_stack( None) | |
| 5112 filename, line, function, source = tb[0] | |
| 5113 if self.line_begin: | |
| 5114 self.file.write( f'#line {line} "{filename}"\n') | |
| 5115 self.file.write( text) | |
| 5116 self.line_begin = text.endswith( '\n') | |
| 5117 def close( self): | |
| 5118 if self.closed: | |
| 5119 # Allow multiple calls to .close(). | |
| 5120 return | |
| 5121 self.closed = True | |
| 5122 if self.filename: | |
| 5123 # Overwrite if contents differ. | |
| 5124 text = self.get() | |
| 5125 if self.tabify: | |
| 5126 text = tabify( self.filename, text) | |
| 5127 cr = check_regress | |
| 5128 jlib.log('calling util.update_file_regress() check_regress={cr}: {self.filename=}', 1) | |
| 5129 e = util.update_file_regress( text, self.filename, check_regression=cr) | |
| 5130 jlib.log('util.update_file_regress() returned => {e}', 1) | |
| 5131 if e: | |
| 5132 jlib.log('util.update_file_regress() => {e=}', 1) | |
| 5133 self.regressions = True | |
| 5134 jlib.log(f'File updated: {os.path.relpath(self.filename)}') | |
| 5135 else: | |
| 5136 jlib.log(f'File unchanged: {os.path.relpath(self.filename)}') | |
| 5137 def get( self): | |
| 5138 return self.file.getvalue() | |
| 5139 else: | |
| 5140 class File: | |
| 5141 def __init__( self, filename): | |
| 5142 pass | |
| 5143 def write( self, text, fileline=False): | |
| 5144 pass | |
| 5145 def close( self): | |
| 5146 pass | |
| 5147 | |
| 5148 class Outputs: | |
| 5149 ''' | |
| 5150 A set of output files. | |
| 5151 | |
| 5152 For convenience, after outputs.add( 'foo', 'foo.c'), outputs.foo is a | |
| 5153 python stream that writes to 'foo.c'. | |
| 5154 ''' | |
| 5155 def __init__( self): | |
| 5156 self.items = [] | |
| 5157 | |
| 5158 def add( self, name, filename): | |
| 5159 ''' | |
| 5160 Sets self.<name> to file opened for writing on <filename>. | |
| 5161 ''' | |
| 5162 file = File( filename) | |
| 5163 self.items.append( (name, filename, file)) | |
| 5164 setattr( self, name, file) | |
| 5165 | |
| 5166 def get( self): | |
| 5167 ''' | |
| 5168 Returns list of (name, filename, file) tuples. | |
| 5169 ''' | |
| 5170 return self.items | |
| 5171 | |
| 5172 def close( self): | |
| 5173 for name, filename, file in self.items: | |
| 5174 file.close() | |
| 5175 | |
| 5176 out_cpps = Outputs() | |
| 5177 out_hs = Outputs() | |
| 5178 for name in ( | |
| 5179 'classes', | |
| 5180 'classes2', | |
| 5181 'exceptions', | |
| 5182 'functions', | |
| 5183 'internal', | |
| 5184 'extra', | |
| 5185 ): | |
| 5186 out_hs.add( name, f'{base}/include/mupdf/{name}.h') | |
| 5187 out_cpps.add( name, f'{base}/implementation/{name}.cpp') | |
| 5188 | |
| 5189 # Make text of header comment for all generated file. | |
| 5190 # | |
| 5191 header_text = textwrap.dedent( | |
| 5192 f''' | |
| 5193 /** | |
| 5194 This file was auto-generated by mupdfwrap.py. | |
| 5195 ''') | |
| 5196 | |
| 5197 if header_git: | |
| 5198 git_id = jlib.get_git_id( dir_mupdf, allow_none=True) | |
| 5199 if git_id: | |
| 5200 git_id = git_id.split('\n', 1) | |
| 5201 header_text += textwrap.dedent( | |
| 5202 f''' | |
| 5203 mupdf checkout: | |
| 5204 {git_id[0]}' | |
| 5205 ''') | |
| 5206 | |
| 5207 header_text += '*/\n' | |
| 5208 header_text += '\n' | |
| 5209 header_text = header_text[1:] # Strip leading \n. | |
| 5210 for _, _, file in out_cpps.get() + out_hs.get(): | |
| 5211 file.write( header_text) | |
| 5212 | |
| 5213 os.makedirs( f'{base}/include/mupdf', exist_ok=True) | |
| 5214 os.makedirs( f'{base}/implementation', exist_ok=True) | |
| 5215 | |
| 5216 num_regressions = 0 | |
| 5217 # Create extra File that writes to internal buffer rather than an actual | |
| 5218 # file, which we will append to out_h. | |
| 5219 # | |
| 5220 out_h_classes_end = File( None) | |
| 5221 | |
| 5222 # Write multiple-inclusion guards into headers: | |
| 5223 # | |
| 5224 for name, filename, file in out_hs.get(): | |
| 5225 prefix = f'{base}/include/' | |
| 5226 assert filename.startswith( prefix) | |
| 5227 name = filename[ len(prefix):] | |
| 5228 header_guard( name, file) | |
| 5229 | |
| 5230 # We need to write to out_hs.extra here before we do the parse | |
| 5231 # because out_hs.extra will be part of the input text passed to the | |
| 5232 # clang parser. | |
| 5233 # | |
| 5234 make_extra(out_hs.extra, out_cpps.extra) | |
| 5235 out_hs.extra.write( textwrap.dedent(''' | |
| 5236 #endif | |
| 5237 ''')) | |
| 5238 out_hs.extra.close() | |
| 5239 out_cpps.extra.close() | |
| 5240 | |
| 5241 # Now parse. | |
| 5242 # | |
| 5243 try: | |
| 5244 index = state.clang.cindex.Index.create() | |
| 5245 except Exception as e: | |
| 5246 raise Exception(f'libclang does not appear to be installed') from e | |
| 5247 | |
| 5248 header = f'{dir_mupdf}/include/mupdf/fitz.h' | |
| 5249 assert os.path.isfile( header), f'header={header}' | |
| 5250 | |
| 5251 # Get clang to parse mupdf/fitz.h and mupdf/pdf.h and mupdf/extra.h. | |
| 5252 # | |
| 5253 # It might be possible to use index.parse()'s <unsaved_files> arg to | |
| 5254 # specify these multiple files, but i couldn't get that to work. | |
| 5255 # | |
| 5256 # So instead we write some #include's to a temporary file and ask clang to | |
| 5257 # parse it. | |
| 5258 # | |
| 5259 temp_h = f'_mupdfwrap_temp.cpp' | |
| 5260 try: | |
| 5261 with open( temp_h, 'w') as f: | |
| 5262 if state.state_.linux or state.state_.macos: | |
| 5263 jlib.log1('Prefixing Fitz headers with `typedef unsigned long size_t;`' | |
| 5264 ' because size_t not available to clang on Linux/MacOS.') | |
| 5265 # On Linux, size_t is defined internally in gcc (e.g. not even | |
| 5266 # in /usr/include/stdint.h) and so not visible to clang. | |
| 5267 # | |
| 5268 # If we don't define it, clang complains about C99 not | |
| 5269 # supporting implicit int and appears to variously expand | |
| 5270 # size_t as different function pointers, e.g. `int (int *)` and | |
| 5271 # `int (*)(int *)`. | |
| 5272 # | |
| 5273 f.write( textwrap.dedent(''' | |
| 5274 /* | |
| 5275 Workaround on Linux/MacOS. size_t is defined internally in | |
| 5276 gcc (e.g. not even in /usr/include/stdint.h) and so not visible to clang. | |
| 5277 */ | |
| 5278 typedef unsigned long size_t; | |
| 5279 ''')) | |
| 5280 if state.state_.macos: | |
| 5281 f.write( textwrap.dedent(''' | |
| 5282 /* | |
| 5283 Workaround on MacOS: we need to define fixed-size int types | |
| 5284 and FILE and va_list, similarly as with size_t above. | |
| 5285 */ | |
| 5286 typedef signed char int8_t; | |
| 5287 typedef short int16_t; | |
| 5288 typedef int int32_t; | |
| 5289 typedef long long int64_t; | |
| 5290 typedef unsigned char uint8_t; | |
| 5291 typedef unsigned short uint16_t; | |
| 5292 typedef unsigned int uint32_t; | |
| 5293 typedef unsigned long long uint64_t; | |
| 5294 typedef struct FILE FILE; | |
| 5295 typedef struct va_list va_list; | |
| 5296 ''')) | |
| 5297 f.write( textwrap.dedent(''' | |
| 5298 #include "mupdf/extra.h" | |
| 5299 | |
| 5300 #include "mupdf/fitz.h" | |
| 5301 #include "mupdf/pdf.h" | |
| 5302 ''')) | |
| 5303 | |
| 5304 # libclang often doesn't have access to system headers so we define | |
| 5305 # MUPDF_WRAP_LIBCLANG so that extra.h can use dummy definition of | |
| 5306 # std::vector. | |
| 5307 # | |
| 5308 args = [ | |
| 5309 '-I', f'{dir_mupdf}/include', | |
| 5310 '-I', f'{dir_mupdf}/platform/c++/include', | |
| 5311 '-D', 'MUPDF_WRAP_LIBCLANG', | |
| 5312 '-D', 'FZ_FUNCTION=', | |
| 5313 ] | |
| 5314 tu = index.parse( | |
| 5315 temp_h, | |
| 5316 args = args, | |
| 5317 options = 0 | |
| 5318 | state.clang.cindex.TranslationUnit.PARSE_INCOMPLETE | |
| 5319 | state.clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES | |
| 5320 , | |
| 5321 ) | |
| 5322 | |
| 5323 # Show warnings/errors from the parse. Failure to include stddef.h | |
| 5324 # appears to be harmless on Linux, but other failures seem to cause | |
| 5325 # more problems. | |
| 5326 # | |
| 5327 def show_clang_diagnostic( diagnostic, depth=0): | |
| 5328 for diagnostic2 in diagnostic.children: | |
| 5329 show_clang_diagnostic( diagnostic2, depth + 1) | |
| 5330 jlib.log1( '{" "*4*depth}{diagnostic}') | |
| 5331 if tu.diagnostics: | |
| 5332 jlib.log1( 'tu.diagnostics():') | |
| 5333 for diagnostic in tu.diagnostics: | |
| 5334 show_clang_diagnostic(diagnostic, 1) | |
| 5335 | |
| 5336 finally: | |
| 5337 if os.path.isfile( temp_h): | |
| 5338 os.remove( temp_h) | |
| 5339 | |
| 5340 # Write required #includes into .h files: | |
| 5341 # | |
| 5342 out_hs.exceptions.write( textwrap.dedent( | |
| 5343 ''' | |
| 5344 #include <stdexcept> | |
| 5345 #include <string> | |
| 5346 | |
| 5347 #include "mupdf/fitz.h" | |
| 5348 | |
| 5349 ''')) | |
| 5350 | |
| 5351 out_hs.internal.write( textwrap.dedent( | |
| 5352 ''' | |
| 5353 #include <iostream> | |
| 5354 | |
| 5355 ''')) | |
| 5356 | |
| 5357 out_hs.functions.write( textwrap.dedent( | |
| 5358 ''' | |
| 5359 #include "mupdf/extra.h" | |
| 5360 | |
| 5361 #include "mupdf/fitz.h" | |
| 5362 #include "mupdf/pdf.h" | |
| 5363 | |
| 5364 #include <iostream> | |
| 5365 #include <string> | |
| 5366 #include <vector> | |
| 5367 | |
| 5368 ''')) | |
| 5369 | |
| 5370 out_hs.classes.write( textwrap.dedent( | |
| 5371 ''' | |
| 5372 #include "mupdf/fitz.h" | |
| 5373 #include "mupdf/functions.h" | |
| 5374 #include "mupdf/pdf.h" | |
| 5375 | |
| 5376 #include <map> | |
| 5377 #include <string> | |
| 5378 #include <vector> | |
| 5379 | |
| 5380 ''')) | |
| 5381 | |
| 5382 out_hs.classes2.write( textwrap.dedent( | |
| 5383 ''' | |
| 5384 #include "classes.h" | |
| 5385 | |
| 5386 ''')) | |
| 5387 | |
| 5388 # Write required #includes into .cpp files: | |
| 5389 # | |
| 5390 out_cpps.exceptions.write( textwrap.dedent( | |
| 5391 f''' | |
| 5392 #include "mupdf/exceptions.h" | |
| 5393 #include "mupdf/fitz.h" | |
| 5394 #include "mupdf/internal.h" | |
| 5395 | |
| 5396 #include <iostream> | |
| 5397 | |
| 5398 #include <string.h> | |
| 5399 | |
| 5400 {trace_if} | |
| 5401 static const bool s_trace_exceptions = mupdf::internal_env_flag("MUPDF_trace_exceptions"); | |
| 5402 #else | |
| 5403 static const bool s_trace_exceptions_dummy = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_exceptions"); | |
| 5404 #endif | |
| 5405 ''')) | |
| 5406 | |
| 5407 out_cpps.functions.write( textwrap.dedent( | |
| 5408 ''' | |
| 5409 #include "mupdf/exceptions.h" | |
| 5410 #include "mupdf/functions.h" | |
| 5411 #include "mupdf/internal.h" | |
| 5412 #include "mupdf/extra.h" | |
| 5413 | |
| 5414 #include <assert.h> | |
| 5415 #include <sstream> | |
| 5416 | |
| 5417 #include <string.h> | |
| 5418 | |
| 5419 ''')) | |
| 5420 | |
| 5421 out_cpps.classes.write( | |
| 5422 textwrap.dedent( | |
| 5423 f''' | |
| 5424 #include "mupdf/classes.h" | |
| 5425 #include "mupdf/classes2.h" | |
| 5426 #include "mupdf/exceptions.h" | |
| 5427 #include "mupdf/internal.h" | |
| 5428 | |
| 5429 #include "mupdf/fitz/geometry.h" | |
| 5430 | |
| 5431 #include <algorithm> | |
| 5432 #include <map> | |
| 5433 #include <mutex> | |
| 5434 #include <sstream> | |
| 5435 #include <string.h> | |
| 5436 #include <thread> | |
| 5437 | |
| 5438 #include <string.h> | |
| 5439 | |
| 5440 {trace_if} | |
| 5441 static const int s_trace = mupdf::internal_env_flag("MUPDF_trace"); | |
| 5442 static const bool s_trace_keepdrop = mupdf::internal_env_flag("MUPDF_trace_keepdrop"); | |
| 5443 static const bool s_trace_director = mupdf::internal_env_flag("MUPDF_trace_director"); | |
| 5444 #else | |
| 5445 static const int s_trace = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace"); | |
| 5446 static const bool s_trace_keepdrop = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_keepdrop"); | |
| 5447 static const bool s_trace_director = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_director"); | |
| 5448 #endif | |
| 5449 ''')) | |
| 5450 | |
| 5451 out_cpps.classes2.write( | |
| 5452 textwrap.dedent( | |
| 5453 f''' | |
| 5454 #include "mupdf/classes2.h" | |
| 5455 #include "mupdf/exceptions.h" | |
| 5456 #include "mupdf/internal.h" | |
| 5457 | |
| 5458 #include "mupdf/fitz/geometry.h" | |
| 5459 | |
| 5460 #include <map> | |
| 5461 #include <mutex> | |
| 5462 #include <sstream> | |
| 5463 #include <string.h> | |
| 5464 #include <thread> | |
| 5465 | |
| 5466 #include <string.h> | |
| 5467 | |
| 5468 {trace_if} | |
| 5469 static const int s_trace = mupdf::internal_env_flag("MUPDF_trace"); | |
| 5470 #else | |
| 5471 static const int s_trace = mupdf::internal_env_flag_check_unset("{trace_if}", "MUPDF_trace"); | |
| 5472 #endif | |
| 5473 ''')) | |
| 5474 | |
| 5475 namespace = 'mupdf' | |
| 5476 for _, _, file in out_cpps.get() + out_hs.get(): | |
| 5477 if file in (out_cpps.internal, out_cpps.extra, out_hs.extra): | |
| 5478 continue | |
| 5479 make_namespace_open( namespace, file) | |
| 5480 | |
| 5481 # Write reference counting check code to out_cpps.classes. | |
| 5482 refcount_check_code( out_cpps.classes, refcheck_if) | |
| 5483 | |
| 5484 # Write declaration and definition for metadata_keys global. | |
| 5485 # | |
| 5486 out_hs.functions.write( | |
| 5487 textwrap.dedent( | |
| 5488 ''' | |
| 5489 /* | |
| 5490 The keys that are defined for fz_lookup_metadata(). | |
| 5491 */ | |
| 5492 FZ_DATA extern const std::vector<std::string> metadata_keys; | |
| 5493 | |
| 5494 ''')) | |
| 5495 out_cpps.functions.write( | |
| 5496 textwrap.dedent( | |
| 5497 f''' | |
| 5498 FZ_FUNCTION const std::vector<std::string> metadata_keys = {{ | |
| 5499 "format", | |
| 5500 "encryption", | |
| 5501 "info:Title", | |
| 5502 "info:Author", | |
| 5503 "info:Subject", | |
| 5504 "info:Keywords", | |
| 5505 "info:Creator", | |
| 5506 "info:Producer", | |
| 5507 "info:CreationDate", | |
| 5508 "info:ModDate", | |
| 5509 }}; | |
| 5510 | |
| 5511 {trace_if} | |
| 5512 static const int s_trace = internal_env_flag("MUPDF_trace"); | |
| 5513 static const bool s_trace_keepdrop = internal_env_flag("MUPDF_trace_keepdrop"); | |
| 5514 static const bool s_trace_exceptions = internal_env_flag("MUPDF_trace_exceptions"); | |
| 5515 static const bool s_check_error_stack = internal_env_flag("MUPDF_check_error_stack"); | |
| 5516 #else | |
| 5517 static const int s_trace = internal_env_flag_check_unset("{trace_if}", "MUPDF_trace"); | |
| 5518 static const bool s_trace_keepdrop = internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_keepdrop"); | |
| 5519 static const bool s_trace_exceptions = internal_env_flag_check_unset("{trace_if}", "MUPDF_trace_exceptions"); | |
| 5520 static const bool s_check_error_stack = internal_env_flag_check_unset("{trace_if}", "MUPDF_check_error_stack"); | |
| 5521 #endif | |
| 5522 | |
| 5523 ''')) | |
| 5524 | |
| 5525 # Write source code for exceptions and wrapper functions. | |
| 5526 # | |
| 5527 jlib.log( 'Creating wrapper functions...') | |
| 5528 make_function_wrappers( | |
| 5529 tu, | |
| 5530 namespace, | |
| 5531 out_hs.exceptions, | |
| 5532 out_cpps.exceptions, | |
| 5533 out_hs.functions, | |
| 5534 out_cpps.functions, | |
| 5535 out_hs.internal, | |
| 5536 out_cpps.internal, | |
| 5537 out_hs.classes2, | |
| 5538 out_cpps.classes2, | |
| 5539 generated, | |
| 5540 refcheck_if, | |
| 5541 trace_if, | |
| 5542 ) | |
| 5543 | |
| 5544 fn_usage = dict() | |
| 5545 functions_unrecognised = set() | |
| 5546 | |
| 5547 for fnname, cursor in state.state_.find_functions_starting_with( tu, '', method=True): | |
| 5548 fn_usage[ fnname] = [0, cursor] | |
| 5549 generated.c_functions.append(fnname) | |
| 5550 | |
| 5551 for structname, cursor in state.state_.structs[ tu].items(): | |
| 5552 generated.c_structs.append( structname) | |
| 5553 | |
| 5554 # Create windows_mupdf.def, containing explicit exports for all MuPDF | |
| 5555 # global data and functions. We do this instead of explicitly prefixing | |
| 5556 # everything with FZ_FUNCTION or FZ_DATA in the MuPDF header files. | |
| 5557 # | |
| 5558 windows_def_path = os.path.relpath(f'{base}/windows_mupdf.def') | |
| 5559 windows_def = '' | |
| 5560 windows_def += 'EXPORTS\n' | |
| 5561 | |
| 5562 for name, cursor in state.state_.find_global_data_starting_with( tu, ('fz_', 'pdf_')): | |
| 5563 if state.state_.show_details(name): | |
| 5564 jlib.log('global: {name=}') | |
| 5565 generated.c_globals.append(name) | |
| 5566 windows_def += f' {name} DATA\n' | |
| 5567 for fnname, cursor in state.state_.find_functions_starting_with( tu, ('fz_', 'pdf_', 'FT_'), method=False): | |
| 5568 if cursor.storage_class == state.clang.cindex.StorageClass.STATIC: | |
| 5569 # These fns do not work in windows.def, probably because they are | |
| 5570 # usually inline? | |
| 5571 # | |
| 5572 jlib.log('Not adding to windows_def because static: {fnname}()', 1) | |
| 5573 elif os.path.abspath(cursor.extent.start.file.name) == os.path.abspath(out_hs.extra.filename): | |
| 5574 # Items defined in out_hs.extra are C++ so we would need to use the | |
| 5575 # mangled name if we added them to windows_def. Instead they are | |
| 5576 # explicitly prefixed with `FZ_FUNCTION`. | |
| 5577 # | |
| 5578 # (We use os.path.abspath() to avoid problems with back and forward | |
| 5579 # slashes in cursor.extent.start.file.name on Windows.) | |
| 5580 # | |
| 5581 jlib.log1('Not adding to {windows_def_path} because defined in {os.path.relpath(out_hs.extra.filename)}: {cursor.spelling}') | |
| 5582 else: | |
| 5583 windows_def += f' {fnname}\n' | |
| 5584 # Add some internal fns that PyMuPDF requires. | |
| 5585 for fnname in ( | |
| 5586 'FT_Get_First_Char', | |
| 5587 'FT_Get_Next_Char', | |
| 5588 ): | |
| 5589 windows_def += f' {fnname}\n' | |
| 5590 | |
| 5591 if debug: | |
| 5592 # In debug builds these are real fns, not macros, and we need to | |
| 5593 # make them exported. | |
| 5594 windows_def += f' fz_lock_debug_lock\n' | |
| 5595 windows_def += f' fz_lock_debug_unlock\n' | |
| 5596 | |
| 5597 jlib.fs_update( windows_def, windows_def_path) | |
| 5598 | |
| 5599 def register_fn_use( name): | |
| 5600 assert name.startswith( ('fz_', 'pdf_')) | |
| 5601 if name in fn_usage: | |
| 5602 fn_usage[ name][0] += 1 | |
| 5603 else: | |
| 5604 functions_unrecognised.add( name) | |
| 5605 | |
| 5606 # Write source code for wrapper classes. | |
| 5607 # | |
| 5608 jlib.log( 'Creating wrapper classes...') | |
| 5609 | |
| 5610 # Find all classes that we can create. | |
| 5611 # | |
| 5612 classes_ = [] | |
| 5613 for cursor in parse.get_children(tu.cursor): | |
| 5614 if not cursor.spelling.startswith( ('fz_', 'pdf_')): | |
| 5615 continue | |
| 5616 if cursor.kind != state.clang.cindex.CursorKind.TYPEDEF_DECL: | |
| 5617 continue; | |
| 5618 type_ = state.get_name_canonical( cursor.underlying_typedef_type) | |
| 5619 if type_.kind not in (state.clang.cindex.TypeKind.RECORD, state.clang.cindex.TypeKind.ELABORATED): | |
| 5620 continue | |
| 5621 if type_.kind == state.clang.cindex.TypeKind.ELABORATED: | |
| 5622 jlib.log( 'state.clang.cindex.TypeKind.ELABORATED: {type_.spelling=}') | |
| 5623 | |
| 5624 if not cursor.is_definition(): | |
| 5625 # Handle abstract type only if we have an ClassExtra for it. | |
| 5626 extras = classes.classextras.get( tu, cursor.spelling) | |
| 5627 if extras and extras.opaque: | |
| 5628 pass | |
| 5629 #log( 'Creating wrapper for opaque struct: {cursor.spelling=}') | |
| 5630 else: | |
| 5631 continue | |
| 5632 | |
| 5633 #struct_name = type_.spelling | |
| 5634 struct_name = cursor.spelling | |
| 5635 struct_name = util.clip( struct_name, 'struct ') | |
| 5636 if cursor.spelling != struct_name: | |
| 5637 jlib.log('{type_.spelling=} {struct_name=} {cursor.spelling=}') | |
| 5638 classname = rename.class_( struct_name) | |
| 5639 | |
| 5640 # For some reason after updating mupdf 2020-04-13, clang-python is | |
| 5641 # returning two locations for struct fz_buffer_s, both STRUCT_DECL. One | |
| 5642 # is 'typedef struct fz_buffer_s fz_buffer;', the other is the full | |
| 5643 # struct definition. | |
| 5644 # | |
| 5645 # No idea why this is happening. Using .canonical doesn't seem to | |
| 5646 # affect things. | |
| 5647 # | |
| 5648 for cl, cu, s in classes_: | |
| 5649 if cl == classname: | |
| 5650 jlib.logx( 'ignoring duplicate STRUCT_DECL for {struct_name=}') | |
| 5651 break | |
| 5652 else: | |
| 5653 classes_.append( (classname, cursor, struct_name)) | |
| 5654 | |
| 5655 classes_.sort() | |
| 5656 | |
| 5657 # Write forward declarations - this is required because some class | |
| 5658 # methods take pointers to other classes. | |
| 5659 # | |
| 5660 out_hs.classes.write( '\n') | |
| 5661 out_hs.classes.write( '/* Forward declarations of all classes that we define. */\n') | |
| 5662 for classname, struct_cursor, struct_name in classes_: | |
| 5663 out_hs.classes.write( f'struct {classname};\n') | |
| 5664 out_hs.classes.write( '\n') | |
| 5665 | |
| 5666 # Create each class. | |
| 5667 # | |
| 5668 for classname, struct_cursor, struct_name in classes_: | |
| 5669 #jlib.log( 'creating wrapper {classname} for {cursor.spelling}') | |
| 5670 extras = classes.classextras.get( tu, struct_name) | |
| 5671 assert extras, f'struct_name={struct_name}' | |
| 5672 if extras.pod: | |
| 5673 struct_to_string_fns( | |
| 5674 tu, | |
| 5675 struct_cursor, | |
| 5676 struct_name, | |
| 5677 extras, | |
| 5678 out_hs.functions, | |
| 5679 out_cpps.functions, | |
| 5680 ) | |
| 5681 | |
| 5682 with jlib.LogPrefixScope( f'{struct_name}: '): | |
| 5683 is_container, has_to_string = class_wrapper( | |
| 5684 tu, | |
| 5685 register_fn_use, | |
| 5686 struct_cursor, | |
| 5687 struct_name, | |
| 5688 classname, | |
| 5689 extras, | |
| 5690 out_hs.classes, | |
| 5691 out_cpps.classes, | |
| 5692 out_h_classes_end, | |
| 5693 out_cpps.classes2, | |
| 5694 out_hs.classes2, | |
| 5695 generated, | |
| 5696 refcheck_if, | |
| 5697 trace_if, | |
| 5698 ) | |
| 5699 if is_container: | |
| 5700 generated.container_classnames.append( classname) | |
| 5701 if has_to_string: | |
| 5702 generated.to_string_structnames.append( struct_name) | |
| 5703 | |
| 5704 out_hs.functions.write( textwrap.dedent( ''' | |
| 5705 /** Reinitializes the MuPDF context for single-threaded use, which | |
| 5706 is slightly faster when calling code is single threaded. | |
| 5707 | |
| 5708 This should be called before any other use of MuPDF. | |
| 5709 */ | |
| 5710 FZ_FUNCTION void reinit_singlethreaded(); | |
| 5711 | |
| 5712 ''')) | |
| 5713 | |
| 5714 # Generate num_instances diagnostic fn. | |
| 5715 out_hs.classes.write('\n') | |
| 5716 out_hs.classes.write('/** Returns map from class name (for example FzDocument) to s_num_instances. */\n') | |
| 5717 out_hs.classes.write('FZ_FUNCTION std::map<std::string, int> num_instances();\n') | |
| 5718 out_cpps.classes.write('FZ_FUNCTION std::map<std::string, int> num_instances()\n') | |
| 5719 out_cpps.classes.write('{\n') | |
| 5720 out_cpps.classes.write(' std::map<std::string, int> ret;\n') | |
| 5721 for classname, struct_cursor, struct_name in classes_: | |
| 5722 out_cpps.classes.write(f' ret["{classname}"] = {classname}::s_num_instances;\n') | |
| 5723 out_cpps.classes.write(' \n') | |
| 5724 out_cpps.classes.write(' return ret;\n') | |
| 5725 out_cpps.classes.write('}\n') | |
| 5726 | |
| 5727 # Write close of namespace. | |
| 5728 out_hs.classes.write( out_h_classes_end.get()) | |
| 5729 for _, _, file in out_cpps.get() + out_hs.get(): | |
| 5730 if file in (out_cpps.internal, out_cpps.extra, out_hs.extra): | |
| 5731 continue | |
| 5732 make_namespace_close( namespace, file) | |
| 5733 | |
| 5734 # Write pod struct fns such as operator<<(), operator==() - these need to | |
| 5735 # be outside the namespace. | |
| 5736 # | |
| 5737 for classname, struct_cursor, struct_name in classes_: | |
| 5738 extras = classes.classextras.get( tu, struct_name) | |
| 5739 if extras.pod: | |
| 5740 # Make operator<<(), operator==(), operator!=() for POD struct. | |
| 5741 # | |
| 5742 pod_struct_fns( | |
| 5743 tu, | |
| 5744 namespace, | |
| 5745 struct_cursor, | |
| 5746 struct_name, | |
| 5747 extras, | |
| 5748 out_hs.functions, | |
| 5749 out_cpps.functions, | |
| 5750 ) | |
| 5751 if extras.pod != 'none': | |
| 5752 # Make operator<<(), operator==(), operator!=() for POD class | |
| 5753 # wrappers. | |
| 5754 # | |
| 5755 pod_class_fns( | |
| 5756 tu, | |
| 5757 classname, | |
| 5758 struct_cursor, | |
| 5759 struct_name, | |
| 5760 extras, | |
| 5761 out_hs.classes, | |
| 5762 out_cpps.classes, | |
| 5763 ) | |
| 5764 | |
| 5765 | |
| 5766 # Terminate multiple-inclusion guards in headers: | |
| 5767 # | |
| 5768 for name, _, file in out_hs.get(): | |
| 5769 if name != 'extra': | |
| 5770 file.write( '\n#endif\n') | |
| 5771 | |
| 5772 out_hs.close() | |
| 5773 out_cpps.close() | |
| 5774 | |
| 5775 generated.h_files = [filename for _, filename, _ in out_hs.get()] | |
| 5776 generated.cpp_files = [filename for _, filename, _ in out_cpps.get()] | |
| 5777 if 0: # lgtm [py/unreachable-statement] | |
| 5778 jlib.log( 'Have created:') | |
| 5779 for filename in filenames_h + filenames_cpp: | |
| 5780 jlib.log( ' {filename}') | |
| 5781 | |
| 5782 | |
| 5783 # Output usage information. | |
| 5784 # | |
| 5785 | |
| 5786 fn_usage_filename = f'{base}/fn_usage.txt' | |
| 5787 out_fn_usage = File( fn_usage_filename, tabify=False) | |
| 5788 functions_unused = 0 | |
| 5789 functions_used = 0 | |
| 5790 | |
| 5791 for fnname in sorted( fn_usage.keys()): | |
| 5792 n, cursor = fn_usage[ fnname] | |
| 5793 exclude_reasons = parse.find_wrappable_function_with_arg0_type_excluded_cache.get( fnname, []) | |
| 5794 if n: | |
| 5795 functions_used += 1 | |
| 5796 else: | |
| 5797 functions_unused += 1 | |
| 5798 if n and not exclude_reasons: | |
| 5799 continue | |
| 5800 | |
| 5801 out_fn_usage.write( f'Functions not wrapped by class methods:\n') | |
| 5802 out_fn_usage.write( '\n') | |
| 5803 | |
| 5804 for fnname in sorted( fn_usage.keys()): | |
| 5805 n, cursor = fn_usage[ fnname] | |
| 5806 exclude_reasons = parse.find_wrappable_function_with_arg0_type_excluded_cache.get( fnname, []) | |
| 5807 if not exclude_reasons: | |
| 5808 continue | |
| 5809 if n: | |
| 5810 continue | |
| 5811 num_interesting_reasons = 0 | |
| 5812 for t, description in exclude_reasons: | |
| 5813 if t == parse.MethodExcludeReason_FIRST_ARG_NOT_STRUCT: | |
| 5814 continue | |
| 5815 if t == parse.MethodExcludeReason_VARIADIC: | |
| 5816 continue | |
| 5817 num_interesting_reasons += 1 | |
| 5818 if num_interesting_reasons: | |
| 5819 try: | |
| 5820 out_fn_usage.write( f' {declaration_text( cursor.type, cursor.spelling)}\n') | |
| 5821 except Clang6FnArgsBug as e: | |
| 5822 out_fn_usage.write( f' {cursor.spelling} [full prototype not available due to known clang-6 issue]\n') | |
| 5823 for t, description in exclude_reasons: | |
| 5824 if t == parse.MethodExcludeReason_FIRST_ARG_NOT_STRUCT: | |
| 5825 continue | |
| 5826 out_fn_usage.write( f' {description}\n') | |
| 5827 out_fn_usage.write( '\n') | |
| 5828 | |
| 5829 out_fn_usage.write( f'\n') | |
| 5830 out_fn_usage.write( f'Functions used more than once:\n') | |
| 5831 for fnname in sorted( fn_usage.keys()): | |
| 5832 n, cursor = fn_usage[ fnname] | |
| 5833 if n > 1: | |
| 5834 out_fn_usage.write( f' n={n}: {declaration_text( cursor.type, cursor.spelling)}\n') | |
| 5835 | |
| 5836 out_fn_usage.write( f'\n') | |
| 5837 out_fn_usage.write( f'Number of wrapped functions: {len(fn_usage)}\n') | |
| 5838 out_fn_usage.write( f'Number of wrapped functions used by wrapper classes: {functions_used}\n') | |
| 5839 out_fn_usage.write( f'Number of wrapped functions not used by wrapper classes: {functions_unused}\n') | |
| 5840 | |
| 5841 out_fn_usage.close() | |
| 5842 | |
| 5843 generated.c_enums = state.state_.enums[ tu] | |
| 5844 | |
| 5845 if num_regressions: | |
| 5846 raise Exception( f'There were {num_regressions} regressions') | |
| 5847 return tu | |
| 5848 | |
| 5849 | |
| 5850 def test(): | |
| 5851 ''' | |
| 5852 Place to experiment with clang-python. | |
| 5853 ''' | |
| 5854 text = '' | |
| 5855 if state.state_.linux: | |
| 5856 text += textwrap.dedent(''' | |
| 5857 /* | |
| 5858 Workaround on Linux. size_t is defined internally in gcc. It isn't | |
| 5859 even in stdint.h. | |
| 5860 */ | |
| 5861 typedef unsigned long size_t; | |
| 5862 ''') | |
| 5863 | |
| 5864 text += textwrap.dedent(''' | |
| 5865 #include "mupdf/fitz.h" | |
| 5866 #include "mupdf/pdf.h" | |
| 5867 ''') | |
| 5868 path = 'wrap-test.c' | |
| 5869 jlib.fs_update( text, path) | |
| 5870 index = state.clang.cindex.Index.create() | |
| 5871 tu = index.parse( path, '-I /usr/include -I include'.split(' ')) | |
| 5872 path2 = 'wrap-test.c.c' | |
| 5873 tu.save(path2) | |
| 5874 jlib.log( 'Have saved to: {path2}') | |
| 5875 parse.dump_ast( tu.cursor, 'ast') | |
| 5876 for diagnostic in tu.diagnostics: | |
| 5877 jlib.log('{diagnostic=}') | |
| 5878 for cursor in parse.get_members( tu.cursor): | |
| 5879 if 'cpp_test_' in cursor.spelling: | |
| 5880 parse.dump_ast(cursor, out=jlib.log) |
