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