forked from amorilia/nifdocsys
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathnifxml.py
1801 lines (1631 loc) · 72.3 KB
/
nifxml.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# TODO: split in multiple files
"""
This module generates C++ code for Niflib from the NIF file format specification XML.
@author: Amorilia
@author: Shon
@contact: http://niftools.sourceforge.net
@copyright:
Copyright (c) 2005, NIF File Format Library and Tools.
All rights reserved.
@license:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
- Neither the name of the NIF File Format Library and Tools
project nor the names of its contributors may be used to endorse
or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
@var native_types: Maps name of basic or compound type to name of type implemented manually in Niflib.
These are the types tagged by the niflibtype tag in the XML. For example,
if a (basic or compound) type with C{name="ferrari"} has C{niflibtype="car"}
then C{native_types["ferrari"]} equals the string C{"car"}.
@type native_types: C{dictionary}
@var basic_types: Maps name of basic type to L{Basic} instance.
@type basic_types: C{dictionary}
@var compound_types: Maps name of compound type to a L{Compound} instance.
@type compound_types: C{dictionary}
@var block_types: Maps name of the block name to a L{Block} instance.
@type block_types: C{list}
@var basic_names: Sorted keys of L{basic_types}.
@type basic_names: C{list}
@var compound_names: Sorted keys of L{compound_types}.
@type compound_names: C{list}
@var block_names: Sorted keys of L{block_types}.
@type block_names: C{list}
@var ACTION_READ: Constant for use with CFile::stream. Causes it to generate Niflib's Read function.
@type ACTION_READ: C{int}
@var ACTION_WRITE: Constant for use with CFile::stream. Causes it to generate Niflib's Write function.
@type ACTION_WRITE: C{int}
@var ACTION_OUT: Constant for use with CFile::stream. Causes it to generate Niflib's asString function.
@type ACTION_OUT: C{int}
@var ACTION_FIXLINKS: Constant for use with CFile::stream. Causes it to generate Niflib's FixLinks function.
@type ACTION_FIXLINKS: C{int}
@var ACTION_GETREFS: Constant for use with CFile::stream. Causes it to generate Niflib's GetRefs function.
@type ACTION_GETREFS: C{int}
"""
from xml.dom.minidom import *
from textwrap import fill
import sys
import os
import re
import types
#
# global data
#
native_types = {}
native_types['TEMPLATE'] = 'T'
basic_types = {}
enum_types = {}
flag_types = {}
compound_types = {}
block_types = {}
version_types = {}
basic_names = []
compound_names = []
enum_names = []
flag_names = []
block_names = []
version_names = []
ACTION_READ = 0
ACTION_WRITE = 1
ACTION_OUT = 2
ACTION_FIXLINKS = 3
ACTION_GETREFS = 4
#
# HTML Template class
#
class Template:
"""
This class processes template files. These files have tags enclosed
in curly brackets like this: {tag}, which are replaced when a template
is processed.
"""
def __init__(self):
#Initialize variable dictionary
self.vars = {}
def set_var(self, var_name, value):
self.vars[var_name] = value
def parse(self, file_name):
#Open file and read contents to txt variable
f = file(file_name, 'r')
txt = f.read()
f.close()
#Loop through all variables, replacing them in the template text
for i in self.vars:
txt = txt.replace( "{" + i + "}", str(self.vars[i]) )
#return result
return txt
#
# C++ code formatting functions
#
class CFile(file):
"""
This class represents a C++ source file. It is used to open the file for output
and automatically handles indentation by detecting brackets and colons.
It also handles writing the generated Niflib C++ code.
@ivar indent: The current level of indentation.
@type indent: int
@ivar backslash_mode: Determines whether a backslash is appended to each line for creation of multi-line defines
@type backslash_mode: bool
"""
def __init__(self, filename, mode):
"""
This constructor requires the name of the file to open and the IO mode to open it in.
@param filename: The name of the ouput file to open
@type filename: string
@param mode: The IO Mode. Same as fopen? Usually should be 'r', 'w', or 'a'
@type mode: char
"""
file.__init__(self, filename, mode)
self.indent = 0
self.backslash_mode = False
def code(self, txt = None):
r"""
Formats a line of C++ code; the returned result always ends with a newline.
If txt starts with "E{rb}", indent is decreased, if it ends with "E{lb}", indent is increased.
Text ending in "E{:}" de-indents itself. For example "publicE{:}"
Result always ends with a newline
@param txt: None means just a line break. This will also break the backslash, which is kind of handy.
"\n" will create a backslashed newline in backslash mode.
@type txt: string, None
"""
# txt
# this will also break the backslash, which is kind of handy
# call code("\n") if you want a backslashed newline in backslash mode
if txt == None:
self.write("\n")
return
# block end
if txt[:1] == "}": self.indent -= 1
# special, private:, public:, and protected:
if txt[-1:] == ":": self.indent -= 1
# endline string
if self.backslash_mode:
endl = " \\\n"
else:
endl = "\n"
# indent string
prefix = "\t" * self.indent
# strip trailing whitespace, including newlines
txt = txt.rstrip()
# indent, and add newline
result = prefix + txt.replace("\n", endl + prefix) + endl
# block start
if txt[-1:] == "{": self.indent += 1
# special, private:, public:, and protected:
if txt[-1:] == ":": self.indent += 1
self.write(result)
#
def comment(self, txt, doxygen = True):
"""
Wraps text in C++ comments and outputs it to the file. Handles multilined comments as well.
Result always ends with a newline
@param txt: The text to enclose in a Doxygen comment
@type txt: string
"""
# skip comments when we are in backslash mode
if self.backslash_mode: return
lines = txt.split( '\n' )
txt = ""
for l in lines:
txt = txt + fill(l, 80) + "\n"
txt = txt.strip()
num_line_ends = txt.count( '\n' )
if doxygen:
if num_line_ends > 0:
txt = txt.replace("\n", "\n * ")
self.code("/*!\n * " + txt + "\n */")
else:
self.code("/*! " + txt + " */" )
else:
lines = txt.split('\n')
for l in lines:
self.code( "// " + l )
def declare(self, block):
"""
Formats the member variables for a specific class as described by the XML and outputs the result to the file.
@param block: The class or struct to generate member functions for.
@type block: Block, Compound
"""
if isinstance(block, Block):
#self.code('protected:')
prot_mode = True
for y in block.members:
if y.is_declared and not y.is_duplicate:
if isinstance(block, Block):
if y.is_public and prot_mode:
self.code('public:')
prot_mode = False
elif not y.is_public and not prot_mode:
self.code('protected:')
prot_mode = True
self.comment(y.description)
self.code(y.code_declare())
if y.func:
self.comment(y.description)
self.code("%s %s() const;"%(y.ctype,y.func))
def stream(self, block, action, localprefix = "", prefix = "", arg_prefix = "", arg_member = None):
"""
Generates the function code for various functions in Niflib and outputs it to the file.
@param block: The class or struct to generate the function for.
@type block: Block, Compound
@param action: The type of function to generate, valid values are::
ACTION_READ - Read function.
ACTION_WRITE - Write function
ACTION_OUT - asString function
ACTION_FIXLINKS - FixLinks function
ACTION_GETREFS - GetRefs function
@type action: ACTION_X constant
@param localprefix: ?
@type localprefix: string
@param prefix: ?
@type prefix: string
@param arg_prefix: ?
@type arg_prefix: string
@param arg_member: ?
@type arg_member: None, ?
"""
lastver1 = None
lastver2 = None
lastuserver = None
lastcond = None
lastvercond = None
# stream name
if action == ACTION_READ:
stream = "in"
else:
stream = "out"
# preperation
if isinstance(block, Block) or block.name in ["Footer", "Header"]:
if action == ACTION_READ:
if block.has_links or block.has_crossrefs:
self.code("unsigned int block_num;")
if action == ACTION_OUT:
self.code("stringstream out;")
# declare array_output_count, only if it will actually be used
for y in block.members:
if y.arr1.lhs or (y.ctype in ["BoundingVolume", "ByteArray", "KeyGroup"]):
self.code("unsigned int array_output_count = 0;")
break
if action == ACTION_GETREFS:
self.code("list<Ref<NiObject> > refs;")
# stream the ancestor
if isinstance(block, Block):
if block.inherit:
if action == ACTION_READ:
self.code("%s::Read( %s, link_stack, info );"%(block.inherit.cname, stream))
elif action == ACTION_WRITE:
self.code("%s::Write( %s, link_map, info );"%(block.inherit.cname, stream))
elif action == ACTION_OUT:
self.code("%s << %s::asString();"%(stream, block.inherit.cname))
elif action == ACTION_FIXLINKS:
self.code("%s::FixLinks( objects, link_stack, info );"%block.inherit.cname)
elif action == ACTION_GETREFS:
self.code("refs = %s::GetRefs();"%block.inherit.cname)
# declare and calculate local variables (TODO: GET RID OF THIS; PREFERABLY NO LOCAL VARIABLES AT ALL)
if action in [ACTION_READ, ACTION_WRITE, ACTION_OUT]:
block.members.reverse() # calculated data depends on data further down the structure
for y in block.members:
# read + write + out: declare
if not y.is_declared and not y.is_duplicate:
# declare it
self.code(y.code_declare(localprefix))
# write + out: calculate
if action in [ACTION_WRITE, ACTION_OUT]:
if y.cond_ref:
assert(y.is_declared) # bug check
elif y.arr1_ref:
assert(not y.is_declared) # bug check
self.code('%s%s = (%s)(%s%s.size());'%(localprefix, y.cname, y.ctype, prefix, y.carr1_ref[0]))
elif y.arr2_ref:
assert(not y.is_declared) # bug check
if not y.arr1.lhs:
self.code('%s%s = (%s)(%s%s.size());'%(localprefix, y.cname, y.ctype, prefix, y.carr2_ref[0]))
else:
# index of dynamically sized array
self.code('%s%s.resize(%s%s.size());'%(localprefix, y.cname, prefix, y.carr2_ref[0]))
self.code('for (unsigned int i%i = 0; i%i < %s%s.size(); i%i++)'%(self.indent, self.indent, localprefix, y.cname, self.indent))
self.code('\t%s%s[i%i] = (%s)(%s%s[i%i].size());'%(localprefix, y.cname, self.indent, y.ctype, prefix, y.carr2_ref[0], self.indent))
elif y.func:
assert(not y.is_declared) # bug check
self.code('%s%s = %s%s();'%(localprefix, y.cname, prefix, y.func))
else:
assert(y.is_declared) # bug check
elif y.is_declared and not y.is_duplicate and not y.is_manual_update and action in [ACTION_WRITE, ACTION_OUT]:
if y.func:
self.code('%s%s = %s%s();'%(prefix, y.cname, prefix, y.func))
elif y.arr1_ref:
if not y.arr1 or not y.arr1.lhs: # Simple Scalar
cref = block.find_member(y.arr1_ref[0], True)
# if not cref.is_duplicate and not cref.next_dup and (not cref.cond.lhs or cref.cond.lhs == y.name):
# self.code('assert(%s%s == (%s)(%s%s.size()));'%(prefix, y.cname, y.ctype, prefix, cref.cname))
self.code('%s%s = (%s)(%s%s.size());'%(prefix, y.cname, y.ctype, prefix, cref.cname))
elif y.arr2_ref: # 1-dimensional dynamic array
cref = block.find_member(y.arr2_ref[0], True)
if not y.arr1 or not y.arr1.lhs: # Second dimension
# if not cref.is_duplicate and not cref.next_dup (not cref.cond.lhs or cref.cond.lhs == y.name):
# self.code('assert(%s%s == (%s)((%s%s.size() > 0) ? %s%s[0].size() : 0));'%(prefix, y.cname, y.ctype, prefix, cref.cname, prefix, cref.cname))
self.code('%s%s = (%s)((%s%s.size() > 0) ? %s%s[0].size() : 0);'%(prefix, y.cname, y.ctype, prefix, cref.cname, prefix, cref.cname))
else:
# index of dynamically sized array
self.code('for (unsigned int i%i = 0; i%i < %s%s.size(); i%i++)'%(self.indent, self.indent, prefix, cref.cname, self.indent))
self.code('\t%s%s[i%i] = (%s)(%s%s[i%i].size());'%(prefix, y.cname, self.indent, y.ctype, prefix, cref.cname, self.indent))
# else: #has duplicates needs to be selective based on version
# self.code('assert(!"%s");'%(y.name))
block.members.reverse() # undo reverse
# now comes the difficult part: processing all members recursively
for y in block.members:
# get block
if y.type in basic_types:
subblock = basic_types[y.type]
elif y.type in compound_types:
subblock = compound_types[y.type]
elif y.type in enum_types:
subblock = enum_types[y.type]
elif y.type in flag_types:
subblock = flag_types[y.type]
# check for links
if action in [ACTION_FIXLINKS, ACTION_GETREFS]:
if not subblock.has_links and not subblock.has_crossrefs:
continue # contains no links, so skip this member!
if action == ACTION_OUT:
if y.is_duplicate:
continue # don't write variables twice
# resolve array & cond references
y_arr1_lmember = None
y_arr2_lmember = None
y_cond_lmember = None
y_arg = None
y_arr1_prefix = ""
y_arr2_prefix = ""
y_cond_prefix = ""
y_arg_prefix = ""
if y.arr1.lhs or y.arr2.lhs or y.cond.lhs or y.arg:
for z in block.members:
if not y_arr1_lmember and y.arr1.lhs == z.name:
y_arr1_lmember = z
if not y_arr2_lmember and y.arr2.lhs == z.name:
y_arr2_lmember = z
if not y_cond_lmember:
if y.cond.lhs == z.name:
y_cond_lmember = z
elif y.cond.op == '&&' and y.cond.lhs.lhs == z.name:
y_cond_lmember = z
elif y.cond.op == '||' and y.cond.lhs.lhs == z.name:
y_cond_lmember = z
if not y_arg and y.arg == z.name:
y_arg = z
if y_arr1_lmember:
if y_arr1_lmember.is_declared:
y_arr1_prefix = prefix
else:
y_arr1_prefix = localprefix
if y_arr2_lmember:
if y_arr2_lmember.is_declared:
y_arr2_prefix = prefix
else:
y_arr2_prefix = localprefix
if y_cond_lmember:
if y_cond_lmember.is_declared:
y_cond_prefix = prefix
else:
y_cond_prefix = localprefix
if y_arg:
if y_arg.is_declared:
y_arg_prefix = prefix
else:
y_arg_prefix = localprefix
# resolve this prefix
if y.is_declared:
y_prefix = prefix
else:
y_prefix = localprefix
# resolve arguments
if y.arr1 and y.arr1.lhs == 'ARG':
y.arr1.lhs = arg_member.name
y.arr1.clhs = arg_member.cname
y_arr1_prefix = arg_prefix
if y.arr2 and y.arr2.lhs == 'ARG':
y.arr2.lhs = arg_member.name
y.arr2.clhs = arg_member.cname
y_arr2_prefix = arg_prefix
if y.cond and y.cond.lhs == 'ARG':
y.cond.lhs = arg_member.name
y.cond.clhs = arg_member.cname
y_cond_prefix = arg_prefix
# conditioning
y_cond = y.cond.code(y_cond_prefix)
y_vercond = y.vercond.code('info.')
if action in [ACTION_READ, ACTION_WRITE, ACTION_FIXLINKS]:
if lastver1 != y.ver1 or lastver2 != y.ver2 or lastuserver != y.userver or lastvercond != y_vercond:
# we must switch to a new version block
# close old version block
if lastver1 or lastver2 or lastuserver or lastvercond: self.code("};")
# close old condition block as well
if lastcond:
self.code("};")
lastcond = None
# start new version block
concat = ''
verexpr = ''
if y.ver1:
verexpr = "( info.version >= 0x%08X )"%y.ver1
concat = " && "
if y.ver2:
verexpr = "%s%s( info.version <= 0x%08X )"%(verexpr, concat, y.ver2)
concat = " && "
if y.userver != None:
verexpr = "%s%s( info.userVersion == %s )"%(verexpr, concat, y.userver)
concat = " && "
if y_vercond:
verexpr = "%s%s( %s )"%(verexpr, concat, y_vercond)
if verexpr:
# remove outer redundant parenthesis
bleft, bright = scanBrackets(verexpr)
if bleft == 0 and bright == (len(verexpr) - 1):
self.code("if %s {"%verexpr)
else:
self.code("if ( %s ) {"%verexpr)
# start new condition block
if lastcond != y_cond and y_cond:
self.code("if ( %s ) {"%y_cond)
else:
# we remain in the same version block
# check condition block
if lastcond != y_cond:
if lastcond:
self.code("};")
if y_cond:
self.code("if ( %s ) {"%y_cond)
elif action == ACTION_OUT:
# check condition block
if lastcond != y_cond:
if lastcond:
self.code("};")
if y_cond:
self.code("if ( %s ) {"%y_cond)
# loop over arrays
# and resolve variable name
if not y.arr1.lhs:
z = "%s%s"%(y_prefix, y.cname)
else:
if action == ACTION_OUT:
self.code("array_output_count = 0;")
if y.arr1.lhs.isdigit() == False:
if action == ACTION_READ:
# default to local variable, check if variable is in current scope if not then try to use
# definition from resized child
memcode = "%s%s.resize(%s);"%(y_prefix, y.cname, y.arr1.code(y_arr1_prefix))
mem = block.find_member(y.arr1.lhs, True) # find member in self or parents
if mem and not mem.is_declared and not mem.is_duplicate and mem.arr1_ref:
localmem = block.find_member(y.arr1.lhs, False) # find only in self for locals
if not localmem:
ref1 = block.find_first_ref(mem.name)
if ref1 and ref1.name != y.name:
memcode = "%s%s.resize(%s%s.size());"%(y_prefix, y.cname, prefix, member_name(ref1.name))
self.code(memcode)
self.code(\
"for (unsigned int i%i = 0; i%i < %s%s.size(); i%i++) {"%(self.indent, self.indent, y_prefix, y.cname, self.indent))
else:
self.code(\
"for (unsigned int i%i = 0; i%i < %s; i%i++) {"\
%(self.indent, self.indent, y.arr1.code(y_arr1_prefix), self.indent))
if action == ACTION_OUT:
self.code('if ( !verbose && ( array_output_count > MAXARRAYDUMP ) ) {')
self.code('%s << "<Data Truncated. Use verbose mode to see complete listing.>" << endl;'%stream)
self.code('break;')
self.code('};')
if not y.arr2.lhs:
z = "%s%s[i%i]"%(y_prefix, y.cname, self.indent-1)
else:
if not y.arr2_dynamic:
if y.arr2.lhs.isdigit() == False:
if action == ACTION_READ:
self.code("%s%s[i%i].resize(%s);"%(y_prefix, y.cname, self.indent-1, y.arr2.code(y_arr2_prefix)))
self.code(\
"for (unsigned int i%i = 0; i%i < %s%s[i%i].size(); i%i++) {"\
%(self.indent, self.indent, y_prefix, y.cname, self.indent-1, self.indent))
else:
self.code(\
"for (unsigned int i%i = 0; i%i < %s; i%i++) {"\
%(self.indent, self.indent, y.arr2.code(y_arr2_prefix), self.indent))
else:
if action == ACTION_READ:
self.code("%s%s[i%i].resize(%s[i%i]);"%(y_prefix, y.cname, self.indent-1, y.arr2.code(y_arr2_prefix), self.indent-1))
self.code(\
"for (unsigned int i%i = 0; i%i < %s[i%i]; i%i++) {"\
%(self.indent, self.indent, y.arr2.code(y_arr2_prefix), self.indent-1, self.indent))
z = "%s%s[i%i][i%i]"%(y_prefix, y.cname, self.indent-2, self.indent-1)
if native_types.has_key(y.type):
# these actions distinguish between refs and non-refs
if action in [ACTION_READ, ACTION_WRITE, ACTION_FIXLINKS, ACTION_GETREFS]:
if (not subblock.is_link) and (not subblock.is_crossref):
# not a ref
if action in [ACTION_READ, ACTION_WRITE]:
# hack required for vector<bool>
if y.type == "bool" and y.arr1.lhs:
self.code("{");
if action == ACTION_READ:
self.code("bool tmp;")
self.code("NifStream( tmp, %s, info );"%(stream))
self.code("%s = tmp;" % z)
else: # ACTION_WRITE
self.code("bool tmp = %s;" % z)
self.code("NifStream( tmp, %s, info );"%(stream))
self.code("};")
# the usual thing
elif not y.arg:
self.code("NifStream( %s, %s, info );"%(z, stream))
else:
self.code("NifStream( %s, %s, info, %s%s );"%(z, stream, y_prefix, y.carg))
else:
# a ref
if action == ACTION_READ:
self.code("NifStream( block_num, %s, info );"%stream)
if y.is_declared:
self.code("link_stack.push_back( block_num );")
elif action == ACTION_WRITE:
self.code("if ( info.version < VER_3_3_0_13 ) {")
self.code("WritePtr32( &(*%s), %s );"%(z, stream))
self.code("} else {")
self.code("if ( %s != NULL ) {"%z)
self.code("NifStream( link_map.find( StaticCast<NiObject>(%s) )->second, %s, info );"%(z, stream))
self.code("} else {")
self.code("NifStream( 0xFFFFFFFF, %s, info );"%stream)
self.code("}")
self.code("}")
elif action == ACTION_FIXLINKS:
if y.is_declared:
self.code("%s = FixLink<%s>( objects, link_stack, info );"%(z,y.ctemplate))
elif action == ACTION_GETREFS and subblock.is_link:
if y.is_declared and not y.is_duplicate:
self.code('if ( %s != NULL )\n\trefs.push_back(StaticCast<NiObject>(%s));'%(z,z))
# the following actions don't distinguish between refs and non-refs
elif action == ACTION_OUT:
if not y.arr1.lhs:
self.code('%s << "%*s%s: " << %s << endl;'%(stream, 2*self.indent, "", y.name, z))
else:
self.code('if ( !verbose && ( array_output_count > MAXARRAYDUMP ) ) {')
self.code('break;')
self.code('};')
self.code('%s << "%*s%s[" << i%i << "]: " << %s << endl;'%(stream, 2*self.indent, "", y.name, self.indent-1, z))
self.code('array_output_count++;')
else:
subblock = compound_types[y.type]
if not y.arr1.lhs:
self.stream(subblock, action, "%s%s_"%(localprefix, y.cname), "%s."%z, y_arg_prefix, y_arg)
elif not y.arr2.lhs:
self.stream(subblock, action, "%s%s_"%(localprefix, y.cname), "%s."%z, y_arg_prefix, y_arg)
else:
self.stream(subblock, action, "%s%s_"%(localprefix, y.cname), "%s."%z, y_arg_prefix, y_arg)
# close array loops
if y.arr1.lhs:
self.code("};")
if y.arr2.lhs:
self.code("};")
lastver1 = y.ver1
lastver2 = y.ver2
lastuserver = y.userver
lastcond = y_cond
lastvercond = y_vercond
if action in [ACTION_READ, ACTION_WRITE, ACTION_FIXLINKS]:
if lastver1 or lastver2 or not(lastuserver is None) or lastvercond:
self.code("};")
if action in [ACTION_READ, ACTION_WRITE, ACTION_FIXLINKS, ACTION_OUT]:
if lastcond:
self.code("};")
# the end
if isinstance(block, Block) or block.name in ["Header", "Footer"]:
if action == ACTION_OUT:
self.code("return out.str();")
if action == ACTION_GETREFS:
self.code("return refs;")
# declaration
# print "$t Get$n() const; \nvoid Set$n($t value);\n\n";
def getset_declare(self, block, prefix = ""): # prefix is used to tag local variables only
for y in block.members:
if not y.func:
if y.cname.lower().find("unk") == -1:
self.code( y.getter_declare("", ";") )
self.code( y.setter_declare("", ";") )
self.code()
def class_name(n):
"""
Formats a valid C++ class name from the name format used in the XML.
@param n: The class name to format in C++ style.
@type n: string
@return The resulting valid C++ class name
@rtype: string
"""
if n == None: return None
try:
return native_types[n]
except KeyError:
return n.replace(' ', '_')
if n == None: return None
try:
return native_types[n]
except KeyError:
pass
if n == 'TEMPLATE': return 'T'
n2 = ''
for i, c in enumerate(n):
if ('A' <= c) and (c <= 'Z'):
if i > 0: n2 += '_'
n2 += c.lower()
elif (('a' <= c) and (c <= 'z')) or (('0' <= c) and (c <= '9')):
n2 += c
else:
n2 += '_'
return n2
def define_name(n):
"""
Formats an all-uppercase version of the name for use in C++ defines.
@param n: The class name to format in define style.
@type n: string
@return The resulting valid C++ define name
@rtype: string
"""
n2 = ''
for i, c in enumerate(n):
if ('A' <= c) and (c <= 'Z'):
if i > 0:
n2 += '_'
n2 += c
else:
n2 += c
elif (('a' <= c) and (c <= 'z')) or (('0' <= c) and (c <= '9')):
n2 += c.upper()
else:
n2 += '_'
return n2
def member_name(n):
"""
Formats a version of the name for use as a C++ member variable.
@param n: The attribute name to format in variable style.
@type n: string
@return The resulting valid C++ variable name
@rtype: string
"""
if n == None: return None
if n == 'ARG': return 'ARG'
n2 = ''
lower = True
for i, c in enumerate(n):
if c == ' ':
lower = False
elif (('A' <= c) and (c <= 'Z')) or (('a' <= c) and (c <= 'z')) or (('0' <= c) and (c <= '9')):
if lower:
n2 += c.lower()
else:
n2 += c.upper()
lower = True
else:
n2 += '_'
lower = True
return n2
def version2number(s):
"""
Translates a legible NIF version number to the packed-byte numeric representation. For example, "10.0.1.0" is translated to 0x0A000100.
@param s: The version string to translate into numeric form.
@type s: string
@return The resulting numeric version of the given version string.
@rtype: int
"""
if not s: return None
l = s.split('.')
if len(l) > 4:
assert(False)
return int(s)
if len(l) == 2:
version = 0
version += int(l[0]) << (3 * 8)
if len(l[1]) >= 1:
version += int(l[1][0]) << (2 * 8)
if len(l[1]) >= 2:
version += int(l[1][1]) << (1 * 8)
if len(l[1]) >= 3:
version += int(l[1][2:])
return version
else:
version = 0
for i in range( 0, len(l) ):
version += int(l[i]) << ((3-i) * 8)
#return (int(l[0]) << 24) + (int(l[1]) << 16) + (int(l[2]) << 8) + int(l[3])
return version
def userversion2number(s):
"""
Translates a legible NIF user version number to the packed-byte numeric representation.
Currently just converts the string to an int as this may be a raw number.
Probably to be used just in case this understanding changes.
@param s: The version string to translate into numeric form.
@type s: string
@return The resulting numeric version of the given version string.
@rtype: int
"""
if not s: return None
return int(s)
def scanBrackets(expr_str, fromIndex = 0):
"""Looks for matching brackets.
>>> scanBrackets('abcde')
(-1, -1)
>>> scanBrackets('()')
(0, 1)
>>> scanBrackets('(abc(def))g')
(0, 9)
>>> s = ' (abc(dd efy 442))xxg'
>>> startpos, endpos = scanBrackets(s)
>>> print s[startpos+1:endpos]
abc(dd efy 442)
"""
startpos = -1
endpos = -1
scandepth = 0
for scanpos in xrange(fromIndex, len(expr_str)):
scanchar = expr_str[scanpos]
if scanchar == "(":
if startpos == -1:
startpos = scanpos
scandepth += 1
elif scanchar == ")":
scandepth -= 1
if scandepth == 0:
endpos = scanpos
break
else:
if startpos != -1 or endpos != -1:
raise ValueError("expression syntax error (non-matching brackets?)")
return (startpos, endpos)
class Expression(object):
"""This class represents an expression.
>>> class A(object):
... x = False
... y = True
>>> a = A()
>>> e = Expression('x || y')
>>> e.eval(a)
1
>>> Expression('99 & 15').eval(a)
3
>>> bool(Expression('(99&15)&&y').eval(a))
True
>>> a.hello_world = False
>>> def nameFilter(s):
... return 'hello_' + s.lower()
>>> bool(Expression('(99 &15) &&WoRlD', name_filter = nameFilter).eval(a))
False
>>> Expression('c && d').eval(a)
Traceback (most recent call last):
...
AttributeError: 'A' object has no attribute 'c'
>>> bool(Expression('1 == 1').eval())
True
>>> bool(Expression('1 != 1').eval())
False
"""
operators = [ '==', '!=', '>=', '<=', '&&', '||', '&', '|', '-', '+', '>', '<' ]
def __init__(self, expr_str, name_filter = None):
self._code = expr_str
left, self._op, right = self._partition(expr_str)
self._left = self._parse(left, name_filter)
if right:
self._right = self._parse(right, name_filter)
else:
self._right = ''
def eval(self, data = None):
"""Evaluate the expression to an integer."""
if isinstance(self._left, Expression):
left = self._left.eval(data)
elif isinstance(self._left, basestring):
left = getattr(data, self._left) if self._left != '""' else ""
else:
assert(isinstance(self._left, (int, long))) # debug
left = self._left
if not self._op:
return left
if isinstance(self._right, Expression):
right = self._right.eval(data)
elif isinstance(self._right, basestring):
right = getattr(data, self._right) if self._right != '""' else ""
else:
assert(isinstance(self._right, (int, long))) # debug
right = self._right
if self._op == '==':
return int(left == right)
elif self._op == '!=':
return int(left != right)
elif self._op == '>=':
return int(left >= right)
elif self._op == '<=':
return int(left <= right)
elif self._op == '&&':
return int(left and right)
elif self._op == '||':
return int(left or right)
elif self._op == '&':
return left & right
elif self._op == '|':
return left | right
elif self._op == '-':
return left - right
elif self._op == '+':
return left + right
elif self._op == '!':
return not left
else:
raise NotImplementedError("expression syntax error: operator '" + op + "' not implemented")
def __str__(self):
"""Reconstruct the expression to a string."""
left = str(self._left)
if not self._op: return left
right = str(self._right)
return left + ' ' + self._op + ' ' + right
@classmethod
def _parse(cls, expr_str, name_filter = None):
"""Returns an Expression, string, or int, depending on the
contents of <expr_str>."""
# brackets or operators => expression
if ("(" in expr_str) or (")" in expr_str):
return Expression(expr_str, name_filter)
for op in cls.operators:
if expr_str.find(op) != -1:
return Expression(expr_str, name_filter)
mver = re.compile("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+")
iver = re.compile("[0-9]+")
# try to convert it to an integer
try:
if mver.match(expr_str):
return "0x%08X"%(version2number(expr_str))
elif iver.match(expr_str):
return str(int(expr_str))
return name_filter(expr_str) if name_filter else expr_str
# failed, so return the string, passed through the name filter
except ValueError:
return name_filter(expr_str) if name_filter else expr_str
@classmethod
def _partition(cls, expr_str):
"""Partitions expr_str. See examples below.
>>> Expression._partition('abc || efg')
('abc', '||', 'efg')
>>> Expression._partition('abc||efg')
('abc', '||', 'efg')
>>> Expression._partition('abcdefg')
('abcdefg', '', '')
>>> Expression._partition(' abcdefg ')
('abcdefg', '', '')
>>> Expression._partition(' (a | b) & c ')
('a | b', '&', 'c')
>>> Expression._partition('(a | b)!=(b&c)')
('a | b', '!=', 'b&c')
>>> Expression._partition('(a== b) &&(( b!=c)||d )')
('a== b', '&&', '( b!=c)||d')
"""
# check for unary operators
if expr_str.strip().startswith('!'):
return expr_str.lstrip(' !'), '!', None
lenstr = len(expr_str)
# check if the left hand side starts with brackets
# and if so, find the position of the starting bracket and the ending
# bracket
left_startpos, left_endpos = cls._scanBrackets(expr_str)
if left_startpos >= 0:
# yes, it is a bracketted expression
# so remove brackets and whitespace,
# and let that be the left hand side
left_str = expr_str[left_startpos+1:left_endpos].strip()
# the next token should be the operator
# find the position where the operator should start
op_startpos = left_endpos+1
while op_startpos < lenstr and expr_str[op_startpos] == " ":
op_startpos += 1
if op_startpos < lenstr:
# to avoid confusion between && and &, and || and |,
# let's first scan for operators of two characters
# and then for operators of one character
for op_endpos in xrange(op_startpos+1, op_startpos-1, -1):
op_str = expr_str[op_startpos:op_endpos+1]