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