1# @file ConvertMasmToNasm.py
2# This script assists with conversion of MASM assembly syntax to NASM
3#
4#  Copyright (c) 2007 - 2016, Intel Corporation. All rights reserved.<BR>
5#
6#  SPDX-License-Identifier: BSD-2-Clause-Patent
7#
8
9from __future__ import print_function
10
11#
12# Import Modules
13#
14import argparse
15import io
16import os.path
17import re
18import subprocess
19import sys
20
21
22class UnsupportedConversion(Exception):
23    pass
24
25
26class NoSourceFile(Exception):
27    pass
28
29
30class UnsupportedArch(Exception):
31    unsupported = ('aarch64', 'arm', 'ebc', 'ipf')
32
33
34class CommonUtils:
35
36    # Version and Copyright
37    VersionNumber = "0.01"
38    __version__ = "%prog Version " + VersionNumber
39    __copyright__ = "Copyright (c) 2007 - 2014, Intel Corporation. All rights reserved."
40    __usage__ = "%prog [options] source.asm [destination.nasm]"
41
42    def __init__(self, clone=None):
43        if clone is None:
44            self.args = self.ProcessCommandLine()
45        else:
46            self.args = clone.args
47
48        self.unsupportedSyntaxSeen = False
49        self.src = self.args.source
50        self.keep = self.args.keep
51        assert(os.path.exists(self.src))
52        self.dirmode = os.path.isdir(self.src)
53        srcExt = os.path.splitext(self.src)[1]
54        assert (self.dirmode or srcExt != '.nasm')
55        self.infmode = not self.dirmode and srcExt == '.inf'
56        self.diff = self.args.diff
57        self.git = self.args.git
58        self.force = self.args.force
59
60        if clone is None:
61            self.rootdir = os.getcwd()
62            self.DetectGit()
63        else:
64            self.rootdir = clone.rootdir
65            self.gitdir = clone.gitdir
66            self.gitemail = clone.gitemail
67
68    def ProcessCommandLine(self):
69        parser = argparse.ArgumentParser(description=self.__copyright__)
70        parser.add_argument('--version', action='version',
71                            version='%(prog)s ' + self.VersionNumber)
72        parser.add_argument("-q", "--quiet", action="store_true",
73                            help="Disable all messages except FATAL ERRORS.")
74        parser.add_argument("--git", action="store_true",
75                            help="Use git to create commits for each file converted")
76        parser.add_argument("--keep", action="append", choices=('asm', 's'),
77                            default=[],
78                            help="Don't remove files with this extension")
79        parser.add_argument("--diff", action="store_true",
80                            help="Show diff of conversion")
81        parser.add_argument("-f", "--force", action="store_true",
82                            help="Force conversion even if unsupported")
83        parser.add_argument('source', help='MASM input file')
84        parser.add_argument('dest', nargs='?',
85                            help='NASM output file (default=input.nasm; - for stdout)')
86
87        return parser.parse_args()
88
89    def RootRelative(self, path):
90        result = path
91        if result.startswith(self.rootdir):
92            result = result[len(self.rootdir):]
93            while len(result) > 0 and result[0] in '/\\':
94                result = result[1:]
95        return result
96
97    def MatchAndSetMo(self, regexp, string):
98        self.mo = regexp.match(string)
99        return self.mo is not None
100
101    def SearchAndSetMo(self, regexp, string):
102        self.mo = regexp.search(string)
103        return self.mo is not None
104
105    def ReplacePreserveSpacing(self, string, find, replace):
106        if len(find) >= len(replace):
107            padded = replace + (' ' * (len(find) - len(replace)))
108            return string.replace(find, padded)
109        elif find.find(replace) >= 0:
110            return string.replace(find, replace)
111        else:
112            lenDiff = len(replace) - len(find)
113            result = string
114            for i in range(lenDiff, -1, -1):
115                padded = find + (' ' * i)
116                result = result.replace(padded, replace)
117            return result
118
119    def DetectGit(self):
120        lastpath = os.path.realpath(self.src)
121        self.gitdir = None
122        while True:
123            path = os.path.split(lastpath)[0]
124            if path == lastpath:
125                self.gitemail = None
126                return
127            candidate = os.path.join(path, '.git')
128            if os.path.isdir(candidate):
129                self.gitdir = candidate
130                self.gitemail = self.FormatGitEmailAddress()
131                return
132            lastpath = path
133
134    def FormatGitEmailAddress(self):
135        if not self.git or not self.gitdir:
136            return ''
137
138        cmd = ('git', 'config', 'user.name')
139        name = self.RunAndCaptureOutput(cmd).strip()
140        cmd = ('git', 'config', 'user.email')
141        email = self.RunAndCaptureOutput(cmd).strip()
142        if name.find(',') >= 0:
143            name = '"' + name + '"'
144        return name + ' <' + email + '>'
145
146    def RunAndCaptureOutput(self, cmd, checkExitCode=True, pipeIn=None):
147        if pipeIn:
148            subpStdin = subprocess.PIPE
149        else:
150            subpStdin = None
151        p = subprocess.Popen(args=cmd, stdout=subprocess.PIPE, stdin=subpStdin)
152        (stdout, stderr) = p.communicate(pipeIn)
153        if checkExitCode:
154            if p.returncode != 0:
155                print('command:', ' '.join(cmd))
156                print('stdout:', stdout)
157                print('stderr:', stderr)
158                print('return:', p.returncode)
159            assert p.returncode == 0
160        return stdout.decode('utf-8', 'ignore')
161
162    def FileUpdated(self, path):
163        if not self.git or not self.gitdir:
164            return
165
166        cmd = ('git', 'add', path)
167        self.RunAndCaptureOutput(cmd)
168
169    def FileAdded(self, path):
170        self.FileUpdated(path)
171
172    def RemoveFile(self, path):
173        if not self.git or not self.gitdir:
174            return
175
176        if self.ShouldKeepFile(path):
177            return
178
179        cmd = ('git', 'rm', path)
180        self.RunAndCaptureOutput(cmd)
181
182    def ShouldKeepFile(self, path):
183        ext = os.path.splitext(path)[1].lower()
184        if ext.startswith('.'):
185            ext = ext[1:]
186        return ext in self.keep
187
188    def FileConversionFinished(self, pkg, module, src, dst):
189        if not self.git or not self.gitdir:
190            return
191
192        if not self.args.quiet:
193            print('Committing: Conversion of', dst)
194
195        prefix = ' '.join(filter(lambda a: a, [pkg, module]))
196        message = ''
197        if self.unsupportedSyntaxSeen:
198            message += 'ERROR! '
199        message += '%s: Convert %s to NASM\n' % (prefix, src)
200        message += '\n'
201        message += 'The %s script was used to convert\n' % sys.argv[0]
202        message += '%s to %s\n' % (src, dst)
203        message += '\n'
204        message += 'Contributed-under: TianoCore Contribution Agreement 1.0\n'
205        assert(self.gitemail is not None)
206        message += 'Signed-off-by: %s\n' % self.gitemail
207        message = message.encode('utf-8', 'ignore')
208
209        cmd = ('git', 'commit', '-F', '-')
210        self.RunAndCaptureOutput(cmd, pipeIn=message)
211
212
213class ConvertAsmFile(CommonUtils):
214
215    def __init__(self, src, dst, clone):
216        CommonUtils.__init__(self, clone)
217        self.ConvertAsmFile(src, dst)
218        self.FileAdded(dst)
219        self.RemoveFile(src)
220
221    def ConvertAsmFile(self, inputFile, outputFile=None):
222        self.globals = set()
223        self.unsupportedSyntaxSeen = False
224        self.inputFilename = inputFile
225        if not outputFile:
226            outputFile = os.path.splitext(inputFile)[0] + '.nasm'
227        self.outputFilename = outputFile
228
229        fullSrc = os.path.realpath(inputFile)
230        srcParentDir = os.path.basename(os.path.split(fullSrc)[0])
231        maybeArch = srcParentDir.lower()
232        if maybeArch in UnsupportedArch.unsupported:
233            raise UnsupportedArch
234        self.ia32 = maybeArch == 'ia32'
235        self.x64 = maybeArch == 'x64'
236
237        self.inputFileBase = os.path.basename(self.inputFilename)
238        self.outputFileBase = os.path.basename(self.outputFilename)
239        self.output = io.BytesIO()
240        if not self.args.quiet:
241            dirpath, src = os.path.split(self.inputFilename)
242            dirpath = self.RootRelative(dirpath)
243            dst = os.path.basename(self.outputFilename)
244            print('Converting:', dirpath, src, '->', dst)
245        lines = io.open(self.inputFilename).readlines()
246        self.Convert(lines)
247        if self.outputFilename == '-' and not self.diff:
248            output_data = self.output.getvalue()
249            if sys.version_info >= (3, 0):
250                output_data = output_data.decode('utf-8', 'ignore')
251            sys.stdout.write(output_data)
252            self.output.close()
253        else:
254            f = io.open(self.outputFilename, 'wb')
255            f.write(self.output.getvalue())
256            f.close()
257            self.output.close()
258
259    endOfLineRe = re.compile(r'''
260                                 \s* ( ; .* )? \n $
261                             ''',
262                             re.VERBOSE | re.MULTILINE
263                             )
264    begOfLineRe = re.compile(r'''
265                                 \s*
266                             ''',
267                             re.VERBOSE
268                             )
269
270    def Convert(self, lines):
271        self.proc = None
272        self.anonLabelCount = -1
273        output = self.output
274        self.oldAsmEmptyLineCount = 0
275        self.newAsmEmptyLineCount = 0
276        for line in lines:
277            mo = self.begOfLineRe.search(line)
278            assert mo is not None
279            self.indent = mo.group()
280            lineWithoutBeginning = line[len(self.indent):]
281            mo = self.endOfLineRe.search(lineWithoutBeginning)
282            if mo is None:
283                endOfLine = ''
284            else:
285                endOfLine = mo.group()
286            oldAsm = line[len(self.indent):len(line) - len(endOfLine)]
287            self.originalLine = line.rstrip()
288            if line.strip() == '':
289                self.oldAsmEmptyLineCount += 1
290            self.TranslateAsm(oldAsm, endOfLine)
291            if line.strip() != '':
292                self.oldAsmEmptyLineCount = 0
293
294    procDeclRe = re.compile(r'''
295                                (?: ASM_PFX \s* [(] \s* )?
296                                ([\w@][\w@0-9]*) \s*
297                                [)]? \s+
298                                PROC
299                                (?: \s+ NEAR | FAR )?
300                                (?: \s+ C )?
301                                (?: \s+ (PUBLIC | PRIVATE) )?
302                                (?: \s+ USES ( (?: \s+ \w[\w0-9]* )+ ) )?
303                                \s* $
304                            ''',
305                            re.VERBOSE | re.IGNORECASE
306                            )
307
308    procEndRe = re.compile(r'''
309                               ([\w@][\w@0-9]*) \s+
310                               ENDP
311                               \s* $
312                           ''',
313                           re.VERBOSE | re.IGNORECASE
314                           )
315
316    varAndTypeSubRe = r' (?: [\w@][\w@0-9]* ) (?: \s* : \s* \w+ )? '
317    publicRe = re.compile(r'''
318                              PUBLIC \s+
319                              ( %s (?: \s* , \s* %s )* )
320                              \s* $
321                          ''' % (varAndTypeSubRe, varAndTypeSubRe),
322                          re.VERBOSE | re.IGNORECASE
323                          )
324
325    varAndTypeSubRe = re.compile(varAndTypeSubRe, re.VERBOSE | re.IGNORECASE)
326
327    macroDeclRe = re.compile(r'''
328                                 ([\w@][\w@0-9]*) \s+
329                                 MACRO
330                                 \s* $
331                             ''',
332                             re.VERBOSE | re.IGNORECASE
333                             )
334
335    sectionDeclRe = re.compile(r'''
336                                   ([\w@][\w@0-9]*) \s+
337                                   ( SECTION | ENDS )
338                                   \s* $
339                               ''',
340                               re.VERBOSE | re.IGNORECASE
341                               )
342
343    externRe = re.compile(r'''
344                              EXTE?RN \s+ (?: C \s+ )?
345                              ([\w@][\w@0-9]*) \s* : \s* (\w+)
346                              \s* $
347                           ''',
348                          re.VERBOSE | re.IGNORECASE
349                          )
350
351    externdefRe = re.compile(r'''
352                                 EXTERNDEF \s+ (?: C \s+ )?
353                                 ([\w@][\w@0-9]*) \s* : \s* (\w+)
354                                 \s* $
355                             ''',
356                             re.VERBOSE | re.IGNORECASE
357                             )
358
359    protoRe = re.compile(r'''
360                             ([\w@][\w@0-9]*) \s+
361                             PROTO
362                             (?: \s+ .* )?
363                             \s* $
364                         ''',
365                         re.VERBOSE | re.IGNORECASE
366                         )
367
368    defineDataRe = re.compile(r'''
369                                  ([\w@][\w@0-9]*) \s+
370                                  ( db | dw | dd | dq ) \s+
371                                  ( .*? )
372                                  \s* $
373                              ''',
374                              re.VERBOSE | re.IGNORECASE
375                              )
376
377    equRe = re.compile(r'''
378                           ([\w@][\w@0-9]*) \s+ EQU \s+ (\S.*?)
379                           \s* $
380                       ''',
381                       re.VERBOSE | re.IGNORECASE
382                       )
383
384    ignoreRe = re.compile(r'''
385                              \. (?: const |
386                                     mmx |
387                                     model |
388                                     xmm |
389                                     x?list |
390                                     [3-6]86p?
391                                 ) |
392                              page
393                              (?: \s+ .* )?
394                              \s* $
395                          ''',
396                          re.VERBOSE | re.IGNORECASE
397                          )
398
399    whitespaceRe = re.compile(r'\s+', re.MULTILINE)
400
401    def TranslateAsm(self, oldAsm, endOfLine):
402        assert(oldAsm.strip() == oldAsm)
403
404        endOfLine = endOfLine.replace(self.inputFileBase, self.outputFileBase)
405
406        oldOp = oldAsm.split()
407        if len(oldOp) >= 1:
408            oldOp = oldOp[0]
409        else:
410            oldOp = ''
411
412        if oldAsm == '':
413            newAsm = oldAsm
414            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
415        elif oldOp in ('#include', ):
416            newAsm = oldAsm
417            self.EmitLine(oldAsm + endOfLine)
418        elif oldOp.lower() in ('end', 'title', 'text'):
419            newAsm = ''
420            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
421        elif oldAsm.lower() == '@@:':
422            self.anonLabelCount += 1
423            self.EmitLine(self.anonLabel(self.anonLabelCount) + ':')
424        elif self.MatchAndSetMo(self.ignoreRe, oldAsm):
425            newAsm = ''
426            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
427        elif oldAsm.lower() == 'ret':
428            for i in range(len(self.uses) - 1, -1, -1):
429                register = self.uses[i]
430                self.EmitNewContent('pop     ' + register)
431            newAsm = 'ret'
432            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
433            self.uses = tuple()
434        elif oldOp.lower() == 'lea':
435            newAsm = self.ConvertLea(oldAsm)
436            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
437        elif oldAsm.lower() == 'end':
438            newAsm = ''
439            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
440            self.uses = tuple()
441        elif self.MatchAndSetMo(self.equRe, oldAsm):
442            equ = self.mo.group(1)
443            newAsm = '%%define %s %s' % (equ, self.mo.group(2))
444            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
445        elif self.MatchAndSetMo(self.externRe, oldAsm) or \
446                self.MatchAndSetMo(self.protoRe, oldAsm):
447            extern = self.mo.group(1)
448            self.NewGlobal(extern)
449            newAsm = 'extern ' + extern
450            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
451        elif self.MatchAndSetMo(self.externdefRe, oldAsm):
452            newAsm = ''
453            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
454        elif self.MatchAndSetMo(self.macroDeclRe, oldAsm):
455            newAsm = '%%macro %s 0' % self.mo.group(1)
456            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
457        elif oldOp.lower() == 'endm':
458            newAsm = r'%endmacro'
459            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
460        elif self.MatchAndSetMo(self.sectionDeclRe, oldAsm):
461            name = self.mo.group(1)
462            ty = self.mo.group(2)
463            if ty.lower() == 'section':
464                newAsm = '.' + name
465            else:
466                newAsm = ''
467            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
468        elif self.MatchAndSetMo(self.procDeclRe, oldAsm):
469            proc = self.proc = self.mo.group(1)
470            visibility = self.mo.group(2)
471            if visibility is None:
472                visibility = ''
473            else:
474                visibility = visibility.lower()
475            if visibility != 'private':
476                self.NewGlobal(self.proc)
477                proc = 'ASM_PFX(' + proc + ')'
478                self.EmitNewContent('global ' + proc)
479            newAsm = proc + ':'
480            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
481            uses = self.mo.group(3)
482            if uses is not None:
483                uses = tuple(filter(None, uses.split()))
484            else:
485                uses = tuple()
486            self.uses = uses
487            for register in self.uses:
488                self.EmitNewContent('    push    ' + register)
489        elif self.MatchAndSetMo(self.procEndRe, oldAsm):
490            newAsm = ''
491            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
492        elif self.MatchAndSetMo(self.publicRe, oldAsm):
493            publics = re.findall(self.varAndTypeSubRe, self.mo.group(1))
494            publics = tuple(map(lambda p: p.split(':')[0].strip(), publics))
495            for i in range(len(publics) - 1):
496                name = publics[i]
497                self.EmitNewContent('global ASM_PFX(%s)' % publics[i])
498                self.NewGlobal(name)
499            name = publics[-1]
500            self.NewGlobal(name)
501            newAsm = 'global ASM_PFX(%s)' % name
502            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
503        elif self.MatchAndSetMo(self.defineDataRe, oldAsm):
504            name = self.mo.group(1)
505            ty = self.mo.group(2)
506            value = self.mo.group(3)
507            if value == '?':
508                value = 0
509            newAsm = '%s: %s %s' % (name, ty, value)
510            newAsm = self.CommonConversions(newAsm)
511            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
512        else:
513            newAsm = self.CommonConversions(oldAsm)
514            self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
515
516    def NewGlobal(self, name):
517        regex = re.compile(r'(?<![_\w\d])(?<!ASM_PFX\()(' + re.escape(name) +
518                           r')(?![_\w\d])')
519        self.globals.add(regex)
520
521    def ConvertAnonymousLabels(self, oldAsm):
522        newAsm = oldAsm
523        anonLabel = self.anonLabel(self.anonLabelCount)
524        newAsm = newAsm.replace('@b', anonLabel)
525        newAsm = newAsm.replace('@B', anonLabel)
526        anonLabel = self.anonLabel(self.anonLabelCount + 1)
527        newAsm = newAsm.replace('@f', anonLabel)
528        newAsm = newAsm.replace('@F', anonLabel)
529        return newAsm
530
531    def anonLabel(self, count):
532        return '.%d' % count
533
534    def EmitString(self, string):
535        self.output.write(string.encode('utf-8', 'ignore'))
536
537    def EmitLineWithDiff(self, old, new):
538        newLine = (self.indent + new).rstrip()
539        if self.diff:
540            if old is None:
541                print('+%s' % newLine)
542            elif newLine != old:
543                print('-%s' % old)
544                print('+%s' % newLine)
545            else:
546                print('', newLine)
547        if newLine != '':
548            self.newAsmEmptyLineCount = 0
549        self.EmitString(newLine + '\r\n')
550
551    def EmitLine(self, string):
552        self.EmitLineWithDiff(self.originalLine, string)
553
554    def EmitNewContent(self, string):
555        self.EmitLineWithDiff(None, string)
556
557    def EmitAsmReplaceOp(self, oldAsm, oldOp, newOp, endOfLine):
558        newAsm = oldAsm.replace(oldOp, newOp, 1)
559        self.EmitAsmWithComment(oldAsm, newAsm, endOfLine)
560
561    hexNumRe = re.compile(r'0*((?=[\da-f])\d*(?<=\d)[\da-f]*)h', re.IGNORECASE)
562
563    def EmitAsmWithComment(self, oldAsm, newAsm, endOfLine):
564        for glblRe in self.globals:
565            newAsm = glblRe.sub(r'ASM_PFX(\1)', newAsm)
566
567        newAsm = self.hexNumRe.sub(r'0x\1', newAsm)
568
569        newLine = newAsm + endOfLine
570        emitNewLine = ((newLine.strip() != '') or
571                       ((oldAsm + endOfLine).strip() == ''))
572        if emitNewLine and newLine.strip() == '':
573            self.newAsmEmptyLineCount += 1
574            if self.newAsmEmptyLineCount > 1:
575                emitNewLine = False
576        if emitNewLine:
577            self.EmitLine(newLine.rstrip())
578        elif self.diff:
579            print('-%s' % self.originalLine)
580
581    leaRe = re.compile(r'''
582                           (lea \s+) ([\w@][\w@0-9]*) \s* , \s* (\S (?:.*\S)?)
583                           \s* $
584                       ''',
585                       re.VERBOSE | re.IGNORECASE
586                       )
587
588    def ConvertLea(self, oldAsm):
589        newAsm = oldAsm
590        if self.MatchAndSetMo(self.leaRe, oldAsm):
591            lea = self.mo.group(1)
592            dst = self.mo.group(2)
593            src = self.mo.group(3)
594            if src.find('[') < 0:
595                src = '[' + src + ']'
596            newAsm = lea + dst + ', ' + src
597        newAsm = self.CommonConversions(newAsm)
598        return newAsm
599
600    ptrRe = re.compile(r'''
601                           (?<! \S )
602                           ([dfq]?word|byte) \s+ (?: ptr ) (\s*)
603                           (?= [[\s] )
604                       ''',
605                       re.VERBOSE | re.IGNORECASE
606                       )
607
608    def ConvertPtr(self, oldAsm):
609        newAsm = oldAsm
610        while self.SearchAndSetMo(self.ptrRe, newAsm):
611            ty = self.mo.group(1)
612            if ty.lower() == 'fword':
613                ty = ''
614            else:
615                ty += self.mo.group(2)
616            newAsm = newAsm[:self.mo.start(0)] + ty + newAsm[self.mo.end(0):]
617        return newAsm
618
619    labelByteRe = re.compile(r'''
620                                 (?: \s+ label \s+ (?: [dfq]?word | byte ) )
621                                 (?! \S )
622                             ''',
623                             re.VERBOSE | re.IGNORECASE
624                             )
625
626    def ConvertLabelByte(self, oldAsm):
627        newAsm = oldAsm
628        if self.SearchAndSetMo(self.labelByteRe, newAsm):
629            newAsm = newAsm[:self.mo.start(0)] + ':' + newAsm[self.mo.end(0):]
630        return newAsm
631
632    unaryBitwiseOpRe = re.compile(r'''
633                                      ( NOT )
634                                      (?= \s+ \S )
635                                  ''',
636                                  re.VERBOSE | re.IGNORECASE
637                                  )
638    binaryBitwiseOpRe = re.compile(r'''
639                                       ( \S \s+ )
640                                       ( AND | OR | SHL | SHR )
641                                       (?= \s+ \S )
642                                   ''',
643                                   re.VERBOSE | re.IGNORECASE
644                                   )
645    bitwiseOpReplacements = {
646        'not': '~',
647        'and': '&',
648        'shl': '<<',
649        'shr': '>>',
650        'or': '|',
651    }
652
653    def ConvertBitwiseOp(self, oldAsm):
654        newAsm = oldAsm
655        while self.SearchAndSetMo(self.binaryBitwiseOpRe, newAsm):
656            prefix = self.mo.group(1)
657            op = self.bitwiseOpReplacements[self.mo.group(2).lower()]
658            newAsm = newAsm[:self.mo.start(0)] + prefix + op + \
659                newAsm[self.mo.end(0):]
660        while self.SearchAndSetMo(self.unaryBitwiseOpRe, newAsm):
661            op = self.bitwiseOpReplacements[self.mo.group(1).lower()]
662            newAsm = newAsm[:self.mo.start(0)] + op + newAsm[self.mo.end(0):]
663        return newAsm
664
665    sectionRe = re.compile(r'''
666                               \. ( code |
667                                    data
668                                  )
669                               (?: \s+ .* )?
670                               \s* $
671                           ''',
672                           re.VERBOSE | re.IGNORECASE
673                           )
674
675    segmentRe = re.compile(r'''
676                               ( code |
677                                 data )
678                               (?: \s+ SEGMENT )
679                               (?: \s+ .* )?
680                               \s* $
681                           ''',
682                           re.VERBOSE | re.IGNORECASE
683                           )
684
685    def ConvertSection(self, oldAsm):
686        newAsm = oldAsm
687        if self.MatchAndSetMo(self.sectionRe, newAsm) or \
688           self.MatchAndSetMo(self.segmentRe, newAsm):
689            name = self.mo.group(1).lower()
690            if name == 'code':
691                if self.x64:
692                    self.EmitLine('DEFAULT REL')
693                name = 'text'
694            newAsm = 'SECTION .' + name
695        return newAsm
696
697    fwordRe = re.compile(r'''
698                             (?<! \S )
699                             fword
700                             (?! \S )
701                         ''',
702                         re.VERBOSE | re.IGNORECASE
703                         )
704
705    def FwordUnsupportedCheck(self, oldAsm):
706        newAsm = oldAsm
707        if self.SearchAndSetMo(self.fwordRe, newAsm):
708            newAsm = self.Unsupported(newAsm, 'fword used')
709        return newAsm
710
711    __common_conversion_routines__ = (
712        ConvertAnonymousLabels,
713        ConvertPtr,
714        FwordUnsupportedCheck,
715        ConvertBitwiseOp,
716        ConvertLabelByte,
717        ConvertSection,
718    )
719
720    def CommonConversions(self, oldAsm):
721        newAsm = oldAsm
722        for conv in self.__common_conversion_routines__:
723            newAsm = conv(self, newAsm)
724        return newAsm
725
726    def Unsupported(self, asm, message=None):
727        if not self.force:
728            raise UnsupportedConversion
729
730        self.unsupportedSyntaxSeen = True
731        newAsm = '%error conversion unsupported'
732        if message:
733            newAsm += '; ' + message
734        newAsm += ': ' + asm
735        return newAsm
736
737
738class ConvertInfFile(CommonUtils):
739
740    def __init__(self, inf, clone):
741        CommonUtils.__init__(self, clone)
742        self.inf = inf
743        self.ScanInfAsmFiles()
744        if self.infmode:
745            self.ConvertInfAsmFiles()
746
747    infSrcRe = re.compile(r'''
748                              \s*
749                              ( [\w@][\w@0-9/]* \.(asm|s) )
750                              \s* (?: \| [^#]* )?
751                              \s* (?: \# .* )?
752                              $
753                          ''',
754                          re.VERBOSE | re.IGNORECASE
755                          )
756
757    def GetInfAsmFileMapping(self):
758        srcToDst = {'order': []}
759        for line in self.lines:
760            line = line.rstrip()
761            if self.MatchAndSetMo(self.infSrcRe, line):
762                src = self.mo.group(1)
763                srcExt = self.mo.group(2)
764                dst = os.path.splitext(src)[0] + '.nasm'
765                fullDst = os.path.join(self.dir, dst)
766                if src not in srcToDst and not os.path.exists(fullDst):
767                    srcToDst[src] = dst
768                    srcToDst['order'].append(src)
769        return srcToDst
770
771    def ScanInfAsmFiles(self):
772        src = self.inf
773        assert os.path.isfile(src)
774        f = io.open(src, 'rt')
775        self.lines = f.readlines()
776        f.close()
777
778        path = os.path.realpath(self.inf)
779        (self.dir, inf) = os.path.split(path)
780        parent = os.path.normpath(self.dir)
781        (lastpath, self.moduleName) = os.path.split(parent)
782        self.packageName = None
783        while True:
784            lastpath = os.path.normpath(lastpath)
785            (parent, basename) = os.path.split(lastpath)
786            if parent == lastpath:
787                break
788            if basename.endswith('Pkg'):
789                self.packageName = basename
790                break
791            lastpath = parent
792
793        self.srcToDst = self.GetInfAsmFileMapping()
794
795        self.dstToSrc = {'order': []}
796        for src in self.srcToDst['order']:
797            srcExt = os.path.splitext(src)[1]
798            dst = self.srcToDst[src]
799            if dst not in self.dstToSrc:
800                self.dstToSrc[dst] = [src]
801                self.dstToSrc['order'].append(dst)
802            else:
803                self.dstToSrc[dst].append(src)
804
805    def __len__(self):
806        return len(self.dstToSrc['order'])
807
808    def __iter__(self):
809        return iter(self.dstToSrc['order'])
810
811    def ConvertInfAsmFiles(self):
812        notConverted = []
813        unsupportedArchCount = 0
814        for dst in self:
815            didSomething = False
816            try:
817                self.UpdateInfAsmFile(dst)
818                didSomething = True
819            except UnsupportedConversion:
820                if not self.args.quiet:
821                    print('MASM=>NASM conversion unsupported for', dst)
822                notConverted.append(dst)
823            except NoSourceFile:
824                if not self.args.quiet:
825                    print('Source file missing for', reldst)
826                notConverted.append(dst)
827            except UnsupportedArch:
828                unsupportedArchCount += 1
829            else:
830                if didSomething:
831                    self.ConversionFinished(dst)
832        if len(notConverted) > 0 and not self.args.quiet:
833            for dst in notConverted:
834                reldst = self.RootRelative(dst)
835                print('Unabled to convert', reldst)
836        if unsupportedArchCount > 0 and not self.args.quiet:
837            print('Skipped', unsupportedArchCount, 'files based on architecture')
838
839    def UpdateInfAsmFile(self, dst, IgnoreMissingAsm=False):
840        infPath = os.path.split(os.path.realpath(self.inf))[0]
841        asmSrc = os.path.splitext(dst)[0] + '.asm'
842        fullSrc = os.path.join(infPath, asmSrc)
843        fullDst = os.path.join(infPath, dst)
844        srcParentDir = os.path.basename(os.path.split(fullSrc)[0])
845        if srcParentDir.lower() in UnsupportedArch.unsupported:
846            raise UnsupportedArch
847        elif not os.path.exists(fullSrc):
848            if not IgnoreMissingAsm:
849                raise NoSourceFile
850        else:  # not os.path.exists(fullDst):
851            conv = ConvertAsmFile(fullSrc, fullDst, self)
852            self.unsupportedSyntaxSeen = conv.unsupportedSyntaxSeen
853
854        fileChanged = False
855        recentSources = list()
856        i = 0
857        while i < len(self.lines):
858            line = self.lines[i].rstrip()
859            updatedLine = line
860            lineChanged = False
861            preserveOldSource = False
862            for src in self.dstToSrc[dst]:
863                assert self.srcToDst[src] == dst
864                updatedLine = self.ReplacePreserveSpacing(
865                    updatedLine, src, dst)
866                lineChanged = updatedLine != line
867                if lineChanged:
868                    preserveOldSource = self.ShouldKeepFile(src)
869                    break
870
871            if lineChanged:
872                if preserveOldSource:
873                    if updatedLine.strip() not in recentSources:
874                        self.lines.insert(i, updatedLine + '\n')
875                        recentSources.append(updatedLine.strip())
876                        i += 1
877                        if self.diff:
878                            print('+%s' % updatedLine)
879                    if self.diff:
880                        print('', line)
881                else:
882                    if self.diff:
883                        print('-%s' % line)
884                    if updatedLine.strip() in recentSources:
885                        self.lines[i] = None
886                    else:
887                        self.lines[i] = updatedLine + '\n'
888                        recentSources.append(updatedLine.strip())
889                        if self.diff:
890                            print('+%s' % updatedLine)
891            else:
892                if len(recentSources) > 0:
893                    recentSources = list()
894                if self.diff:
895                    print('', line)
896
897            fileChanged |= lineChanged
898            i += 1
899
900        if fileChanged:
901            self.lines = list(filter(lambda l: l is not None, self.lines))
902
903        for src in self.dstToSrc[dst]:
904            if not src.endswith('.asm'):
905                fullSrc = os.path.join(infPath, src)
906                if os.path.exists(fullSrc):
907                    self.RemoveFile(fullSrc)
908
909        if fileChanged:
910            f = io.open(self.inf, 'w', newline='\r\n')
911            f.writelines(self.lines)
912            f.close()
913            self.FileUpdated(self.inf)
914
915    def ConversionFinished(self, dst):
916        asmSrc = os.path.splitext(dst)[0] + '.asm'
917        self.FileConversionFinished(
918            self.packageName, self.moduleName, asmSrc, dst)
919
920
921class ConvertInfFiles(CommonUtils):
922
923    def __init__(self, infs, clone):
924        CommonUtils.__init__(self, clone)
925        infs = map(lambda i: ConvertInfFile(i, self), infs)
926        infs = filter(lambda i: len(i) > 0, infs)
927        dstToInfs = {'order': []}
928        for inf in infs:
929            for dst in inf:
930                fulldst = os.path.realpath(os.path.join(inf.dir, dst))
931                pair = (inf, dst)
932                if fulldst in dstToInfs:
933                    dstToInfs[fulldst].append(pair)
934                else:
935                    dstToInfs['order'].append(fulldst)
936                    dstToInfs[fulldst] = [pair]
937
938        notConverted = []
939        unsupportedArchCount = 0
940        for dst in dstToInfs['order']:
941            didSomething = False
942            try:
943                for inf, reldst in dstToInfs[dst]:
944                    inf.UpdateInfAsmFile(reldst, IgnoreMissingAsm=didSomething)
945                    didSomething = True
946            except UnsupportedConversion:
947                if not self.args.quiet:
948                    print('MASM=>NASM conversion unsupported for', reldst)
949                notConverted.append(dst)
950            except NoSourceFile:
951                if not self.args.quiet:
952                    print('Source file missing for', reldst)
953                notConverted.append(dst)
954            except UnsupportedArch:
955                unsupportedArchCount += 1
956            else:
957                if didSomething:
958                    inf.ConversionFinished(reldst)
959        if len(notConverted) > 0 and not self.args.quiet:
960            for dst in notConverted:
961                reldst = self.RootRelative(dst)
962                print('Unabled to convert', reldst)
963        if unsupportedArchCount > 0 and not self.args.quiet:
964            print('Skipped', unsupportedArchCount, 'files based on architecture')
965
966
967class ConvertDirectories(CommonUtils):
968
969    def __init__(self, paths, clone):
970        CommonUtils.__init__(self, clone)
971        self.paths = paths
972        self.ConvertInfAndAsmFiles()
973
974    def ConvertInfAndAsmFiles(self):
975        infs = list()
976        for path in self.paths:
977            assert(os.path.exists(path))
978        for path in self.paths:
979            for root, dirs, files in os.walk(path):
980                for d in ('.svn', '.git'):
981                    if d in dirs:
982                        dirs.remove(d)
983                for f in files:
984                    if f.lower().endswith('.inf'):
985                        inf = os.path.realpath(os.path.join(root, f))
986                        infs.append(inf)
987
988        ConvertInfFiles(infs, self)
989
990
991class ConvertAsmApp(CommonUtils):
992
993    def __init__(self):
994        CommonUtils.__init__(self)
995
996        src = self.args.source
997        dst = self.args.dest
998        if self.infmode:
999            ConvertInfFiles((src,), self)
1000        elif self.dirmode:
1001            ConvertDirectories((src,), self)
1002        elif not self.dirmode:
1003            ConvertAsmFile(src, dst, self)
1004
1005ConvertAsmApp()
1006