1# Copyright (C) 2011 Apple Inc. All rights reserved.
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions
5# are met:
6# 1. Redistributions of source code must retain the above copyright
7#    notice, this list of conditions and the following disclaimer.
8# 2. Redistributions in binary form must reproduce the above copyright
9#    notice, this list of conditions and the following disclaimer in the
10#    documentation and/or other materials provided with the distribution.
11#
12# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
13# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
14# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
15# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
16# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
17# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
18# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
19# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
20# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
21# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
22# THE POSSIBILITY OF SUCH DAMAGE.
23
24require "config"
25require "ast"
26require "instructions"
27require "pathname"
28require "registers"
29require "self_hash"
30
31class CodeOrigin
32    attr_reader :fileName, :lineNumber
33    
34    def initialize(fileName, lineNumber)
35        @fileName = fileName
36        @lineNumber = lineNumber
37    end
38    
39    def to_s
40        "#{fileName}:#{lineNumber}"
41    end
42end
43
44class IncludeFile
45    @@includeDirs = []
46
47    attr_reader :fileName
48
49    def initialize(moduleName, defaultDir)
50        directory = nil
51        @@includeDirs.each {
52            | includePath |
53            fileName = includePath + (moduleName + ".asm")
54            directory = includePath unless not File.file?(fileName)
55        }
56        if not directory
57            directory = defaultDir
58        end
59
60        @fileName = directory + (moduleName + ".asm")
61    end
62
63    def self.processIncludeOptions()
64        while ARGV[0][/-I/]
65            path = ARGV.shift[2..-1]
66            if not path
67                path = ARGV.shift
68            end
69            @@includeDirs << (path + "/")
70        end
71    end
72end
73
74class Token
75    attr_reader :codeOrigin, :string
76    
77    def initialize(codeOrigin, string)
78        @codeOrigin = codeOrigin
79        @string = string
80    end
81    
82    def ==(other)
83        if other.is_a? Token
84            @string == other.string
85        else
86            @string == other
87        end
88    end
89    
90    def =~(other)
91        @string =~ other
92    end
93    
94    def to_s
95        "#{@string.inspect} at #{codeOrigin}"
96    end
97    
98    def parseError(*comment)
99        if comment.empty?
100            raise "Parse error: #{to_s}"
101        else
102            raise "Parse error: #{to_s}: #{comment[0]}"
103        end
104    end
105end
106
107class Annotation
108    attr_reader :codeOrigin, :type, :string
109    def initialize(codeOrigin, type, string)
110        @codeOrigin = codeOrigin
111        @type = type
112        @string = string
113    end
114end
115
116#
117# The lexer. Takes a string and returns an array of tokens.
118#
119
120def lex(str, fileName)
121    fileName = Pathname.new(fileName)
122    result = []
123    lineNumber = 1
124    annotation = nil
125    whitespaceFound = false
126    while not str.empty?
127        case str
128        when /\A\#([^\n]*)/
129            # comment, ignore
130        when /\A\/\/\ ?([^\n]*)/
131            # annotation
132            annotation = $1
133            annotationType = whitespaceFound ? :local : :global
134        when /\A\n/
135            # We've found a '\n'.  Emit the last comment recorded if appropriate:
136            # We need to parse annotations regardless of whether the backend does
137            # anything with them or not. This is because the C++ backend may make
138            # use of this for its cloopDo debugging utility even if
139            # enableInstrAnnotations is not enabled.
140            if annotation
141                result << Annotation.new(CodeOrigin.new(fileName, lineNumber),
142                                         annotationType, annotation)
143                annotation = nil
144            end
145            result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
146            lineNumber += 1
147        when /\A[a-zA-Z]([a-zA-Z0-9_.]*)/
148            result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
149        when /\A\.([a-zA-Z0-9_]*)/
150            result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
151        when /\A_([a-zA-Z0-9_]*)/
152            result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
153        when /\A([ \t]+)/
154            # whitespace, ignore
155            whitespaceFound = true
156            str = $~.post_match
157            next
158        when /\A0x([0-9a-fA-F]+)/
159            result << Token.new(CodeOrigin.new(fileName, lineNumber), $&.hex.to_s)
160        when /\A0([0-7]+)/
161            result << Token.new(CodeOrigin.new(fileName, lineNumber), $&.oct.to_s)
162        when /\A([0-9]+)/
163            result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
164        when /\A::/
165            result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
166        when /\A[:,\(\)\[\]=\+\-~\|&^*]/
167            result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
168        else
169            raise "Lexer error at #{CodeOrigin.new(fileName, lineNumber).to_s}, unexpected sequence #{str[0..20].inspect}"
170        end
171        whitespaceFound = false
172        str = $~.post_match
173    end
174    result
175end
176
177#
178# Token identification.
179#
180
181def isRegister(token)
182    token =~ REGISTER_PATTERN
183end
184
185def isInstruction(token)
186    INSTRUCTION_SET.member? token.string
187end
188
189def isKeyword(token)
190    token =~ /\A((true)|(false)|(if)|(then)|(else)|(elsif)|(end)|(and)|(or)|(not)|(global)|(macro)|(const)|(sizeof)|(error)|(include))\Z/ or
191        token =~ REGISTER_PATTERN or
192        isInstruction(token)
193end
194
195def isIdentifier(token)
196    token =~ /\A[a-zA-Z]([a-zA-Z0-9_.]*)\Z/ and not isKeyword(token)
197end
198
199def isLabel(token)
200    token =~ /\A_([a-zA-Z0-9_]*)\Z/
201end
202
203def isLocalLabel(token)
204    token =~ /\A\.([a-zA-Z0-9_]*)\Z/
205end
206
207def isVariable(token)
208    isIdentifier(token) or isRegister(token)
209end
210
211def isInteger(token)
212    token =~ /\A[0-9]/
213end
214
215#
216# The parser. Takes an array of tokens and returns an AST. Methods
217# other than parse(tokens) are not for public consumption.
218#
219
220class Parser
221    def initialize(data, fileName)
222        @tokens = lex(data, fileName)
223        @idx = 0
224        @annotation = nil
225    end
226    
227    def parseError(*comment)
228        if @tokens[@idx]
229            @tokens[@idx].parseError(*comment)
230        else
231            if comment.empty?
232                raise "Parse error at end of file"
233            else
234                raise "Parse error at end of file: #{comment[0]}"
235            end
236        end
237    end
238    
239    def consume(regexp)
240        if regexp
241            parseError unless @tokens[@idx] =~ regexp
242        else
243            parseError unless @idx == @tokens.length
244        end
245        @idx += 1
246    end
247    
248    def skipNewLine
249        while @tokens[@idx] == "\n"
250            @idx += 1
251        end
252    end
253    
254    def parsePredicateAtom
255        if @tokens[@idx] == "not"
256            codeOrigin = @tokens[@idx].codeOrigin
257            @idx += 1
258            Not.new(codeOrigin, parsePredicateAtom)
259        elsif @tokens[@idx] == "("
260            @idx += 1
261            skipNewLine
262            result = parsePredicate
263            parseError unless @tokens[@idx] == ")"
264            @idx += 1
265            result
266        elsif @tokens[@idx] == "true"
267            result = True.instance
268            @idx += 1
269            result
270        elsif @tokens[@idx] == "false"
271            result = False.instance
272            @idx += 1
273            result
274        elsif isIdentifier @tokens[@idx]
275            result = Setting.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
276            @idx += 1
277            result
278        else
279            parseError
280        end
281    end
282    
283    def parsePredicateAnd
284        result = parsePredicateAtom
285        while @tokens[@idx] == "and"
286            codeOrigin = @tokens[@idx].codeOrigin
287            @idx += 1
288            skipNewLine
289            right = parsePredicateAtom
290            result = And.new(codeOrigin, result, right)
291        end
292        result
293    end
294    
295    def parsePredicate
296        # some examples of precedence:
297        # not a and b -> (not a) and b
298        # a and b or c -> (a and b) or c
299        # a or b and c -> a or (b and c)
300        
301        result = parsePredicateAnd
302        while @tokens[@idx] == "or"
303            codeOrigin = @tokens[@idx].codeOrigin
304            @idx += 1
305            skipNewLine
306            right = parsePredicateAnd
307            result = Or.new(codeOrigin, result, right)
308        end
309        result
310    end
311    
312    def parseVariable
313        if isRegister(@tokens[@idx])
314            if @tokens[@idx] =~ FPR_PATTERN
315                result = FPRegisterID.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
316            else
317                result = RegisterID.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
318            end
319        elsif isIdentifier(@tokens[@idx])
320            result = Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
321        else
322            parseError
323        end
324        @idx += 1
325        result
326    end
327    
328    def parseAddress(offset)
329        parseError unless @tokens[@idx] == "["
330        codeOrigin = @tokens[@idx].codeOrigin
331        
332        # Three possibilities:
333        # []       -> AbsoluteAddress
334        # [a]      -> Address
335        # [a,b]    -> BaseIndex with scale = 1
336        # [a,b,c]  -> BaseIndex
337        
338        @idx += 1
339        if @tokens[@idx] == "]"
340            @idx += 1
341            return AbsoluteAddress.new(codeOrigin, offset)
342        end
343        a = parseVariable
344        if @tokens[@idx] == "]"
345            result = Address.new(codeOrigin, a, offset)
346        else
347            parseError unless @tokens[@idx] == ","
348            @idx += 1
349            b = parseVariable
350            if @tokens[@idx] == "]"
351                result = BaseIndex.new(codeOrigin, a, b, 1, offset)
352            else
353                parseError unless @tokens[@idx] == ","
354                @idx += 1
355                parseError unless ["1", "2", "4", "8"].member? @tokens[@idx].string
356                c = @tokens[@idx].string.to_i
357                @idx += 1
358                parseError unless @tokens[@idx] == "]"
359                result = BaseIndex.new(codeOrigin, a, b, c, offset)
360            end
361        end
362        @idx += 1
363        result
364    end
365    
366    def parseColonColon
367        skipNewLine
368        codeOrigin = @tokens[@idx].codeOrigin
369        parseError unless isIdentifier @tokens[@idx]
370        names = [@tokens[@idx].string]
371        @idx += 1
372        while @tokens[@idx] == "::"
373            @idx += 1
374            parseError unless isIdentifier @tokens[@idx]
375            names << @tokens[@idx].string
376            @idx += 1
377        end
378        raise if names.empty?
379        [codeOrigin, names]
380    end
381    
382    def parseExpressionAtom
383        skipNewLine
384        if @tokens[@idx] == "-"
385            @idx += 1
386            NegImmediate.new(@tokens[@idx - 1].codeOrigin, parseExpressionAtom)
387        elsif @tokens[@idx] == "~"
388            @idx += 1
389            BitnotImmediate.new(@tokens[@idx - 1].codeOrigin, parseExpressionAtom)
390        elsif @tokens[@idx] == "("
391            @idx += 1
392            result = parseExpression
393            parseError unless @tokens[@idx] == ")"
394            @idx += 1
395            result
396        elsif isInteger @tokens[@idx]
397            result = Immediate.new(@tokens[@idx].codeOrigin, @tokens[@idx].string.to_i)
398            @idx += 1
399            result
400        elsif isIdentifier @tokens[@idx]
401            codeOrigin, names = parseColonColon
402            if names.size > 1
403                StructOffset.forField(codeOrigin, names[0..-2].join('::'), names[-1])
404            else
405                Variable.forName(codeOrigin, names[0])
406            end
407        elsif isRegister @tokens[@idx]
408            parseVariable
409        elsif @tokens[@idx] == "sizeof"
410            @idx += 1
411            codeOrigin, names = parseColonColon
412            Sizeof.forName(codeOrigin, names.join('::'))
413        elsif isLabel @tokens[@idx]
414            result = LabelReference.new(@tokens[@idx].codeOrigin, Label.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
415            @idx += 1
416            result
417        elsif isLocalLabel @tokens[@idx]
418            result = LocalLabelReference.new(@tokens[@idx].codeOrigin, LocalLabel.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
419            @idx += 1
420            result
421        else
422            parseError
423        end
424    end
425    
426    def parseExpressionMul
427        skipNewLine
428        result = parseExpressionAtom
429        while @tokens[@idx] == "*"
430            if @tokens[@idx] == "*"
431                @idx += 1
432                result = MulImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAtom)
433            else
434                raise
435            end
436        end
437        result
438    end
439    
440    def couldBeExpression
441        @tokens[@idx] == "-" or @tokens[@idx] == "~" or @tokens[@idx] == "sizeof" or isInteger(@tokens[@idx]) or isVariable(@tokens[@idx]) or @tokens[@idx] == "("
442    end
443    
444    def parseExpressionAdd
445        skipNewLine
446        result = parseExpressionMul
447        while @tokens[@idx] == "+" or @tokens[@idx] == "-"
448            if @tokens[@idx] == "+"
449                @idx += 1
450                result = AddImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionMul)
451            elsif @tokens[@idx] == "-"
452                @idx += 1
453                result = SubImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionMul)
454            else
455                raise
456            end
457        end
458        result
459    end
460    
461    def parseExpressionAnd
462        skipNewLine
463        result = parseExpressionAdd
464        while @tokens[@idx] == "&"
465            @idx += 1
466            result = AndImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAdd)
467        end
468        result
469    end
470    
471    def parseExpression
472        skipNewLine
473        result = parseExpressionAnd
474        while @tokens[@idx] == "|" or @tokens[@idx] == "^"
475            if @tokens[@idx] == "|"
476                @idx += 1
477                result = OrImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAnd)
478            elsif @tokens[@idx] == "^"
479                @idx += 1
480                result = XorImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAnd)
481            else
482                raise
483            end
484        end
485        result
486    end
487    
488    def parseOperand(comment)
489        skipNewLine
490        if couldBeExpression
491            expr = parseExpression
492            if @tokens[@idx] == "["
493                parseAddress(expr)
494            else
495                expr
496            end
497        elsif @tokens[@idx] == "["
498            parseAddress(Immediate.new(@tokens[@idx].codeOrigin, 0))
499        elsif isLabel @tokens[@idx]
500            result = LabelReference.new(@tokens[@idx].codeOrigin, Label.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
501            @idx += 1
502            result
503        elsif isLocalLabel @tokens[@idx]
504            result = LocalLabelReference.new(@tokens[@idx].codeOrigin, LocalLabel.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
505            @idx += 1
506            result
507        else
508            parseError(comment)
509        end
510    end
511    
512    def parseMacroVariables
513        skipNewLine
514        consume(/\A\(\Z/)
515        variables = []
516        loop {
517            skipNewLine
518            if @tokens[@idx] == ")"
519                @idx += 1
520                break
521            elsif isIdentifier(@tokens[@idx])
522                variables << Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
523                @idx += 1
524                skipNewLine
525                if @tokens[@idx] == ")"
526                    @idx += 1
527                    break
528                elsif @tokens[@idx] == ","
529                    @idx += 1
530                else
531                    parseError
532                end
533            else
534                parseError
535            end
536        }
537        variables
538    end
539    
540    def parseSequence(final, comment)
541        firstCodeOrigin = @tokens[@idx].codeOrigin
542        list = []
543        loop {
544            if (@idx == @tokens.length and not final) or (final and @tokens[@idx] =~ final)
545                break
546            elsif @tokens[@idx].is_a? Annotation
547                # This is the only place where we can encounter a global
548                # annotation, and hence need to be able to distinguish between
549                # them.
550                # globalAnnotations are the ones that start from column 0. All
551                # others are considered localAnnotations.  The only reason to
552                # distinguish between them is so that we can format the output
553                # nicely as one would expect.
554
555                codeOrigin = @tokens[@idx].codeOrigin
556                annotationOpcode = (@tokens[@idx].type == :global) ? "globalAnnotation" : "localAnnotation"
557                list << Instruction.new(codeOrigin, annotationOpcode, [], @tokens[@idx].string)
558                @annotation = nil
559                @idx += 2 # Consume the newline as well.
560            elsif @tokens[@idx] == "\n"
561                # ignore
562                @idx += 1
563            elsif @tokens[@idx] == "const"
564                @idx += 1
565                parseError unless isVariable @tokens[@idx]
566                variable = Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
567                @idx += 1
568                parseError unless @tokens[@idx] == "="
569                @idx += 1
570                value = parseOperand("while inside of const #{variable.name}")
571                list << ConstDecl.new(@tokens[@idx].codeOrigin, variable, value)
572            elsif @tokens[@idx] == "error"
573                list << Error.new(@tokens[@idx].codeOrigin)
574                @idx += 1
575            elsif @tokens[@idx] == "if"
576                codeOrigin = @tokens[@idx].codeOrigin
577                @idx += 1
578                skipNewLine
579                predicate = parsePredicate
580                consume(/\A((then)|(\n))\Z/)
581                skipNewLine
582                ifThenElse = IfThenElse.new(codeOrigin, predicate, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\""))
583                list << ifThenElse
584                while @tokens[@idx] == "elsif"
585                    codeOrigin = @tokens[@idx].codeOrigin
586                    @idx += 1
587                    skipNewLine
588                    predicate = parsePredicate
589                    consume(/\A((then)|(\n))\Z/)
590                    skipNewLine
591                    elseCase = IfThenElse.new(codeOrigin, predicate, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\""))
592                    ifThenElse.elseCase = elseCase
593                    ifThenElse = elseCase
594                end
595                if @tokens[@idx] == "else"
596                    @idx += 1
597                    ifThenElse.elseCase = parseSequence(/\Aend\Z/, "while inside of else case for \"if #{predicate.dump}\"")
598                    @idx += 1
599                else
600                    parseError unless @tokens[@idx] == "end"
601                    @idx += 1
602                end
603            elsif @tokens[@idx] == "macro"
604                codeOrigin = @tokens[@idx].codeOrigin
605                @idx += 1
606                skipNewLine
607                parseError unless isIdentifier(@tokens[@idx])
608                name = @tokens[@idx].string
609                @idx += 1
610                variables = parseMacroVariables
611                body = parseSequence(/\Aend\Z/, "while inside of macro #{name}")
612                @idx += 1
613                list << Macro.new(codeOrigin, name, variables, body)
614            elsif @tokens[@idx] == "global"
615                codeOrigin = @tokens[@idx].codeOrigin
616                @idx += 1
617                skipNewLine
618                parseError unless isLabel(@tokens[@idx])
619                name = @tokens[@idx].string
620                @idx += 1
621                Label.setAsGlobal(codeOrigin, name)
622            elsif isInstruction @tokens[@idx]
623                codeOrigin = @tokens[@idx].codeOrigin
624                name = @tokens[@idx].string
625                @idx += 1
626                if (not final and @idx == @tokens.size) or (final and @tokens[@idx] =~ final)
627                    # Zero operand instruction, and it's the last one.
628                    list << Instruction.new(codeOrigin, name, [], @annotation)
629                    @annotation = nil
630                    break
631                elsif @tokens[@idx].is_a? Annotation
632                    list << Instruction.new(codeOrigin, name, [], @tokens[@idx].string)
633                    @annotation = nil
634                    @idx += 2 # Consume the newline as well.
635                elsif @tokens[@idx] == "\n"
636                    # Zero operand instruction.
637                    list << Instruction.new(codeOrigin, name, [], @annotation)
638                    @annotation = nil
639                    @idx += 1
640                else
641                    # It's definitely an instruction, and it has at least one operand.
642                    operands = []
643                    endOfSequence = false
644                    loop {
645                        operands << parseOperand("while inside of instruction #{name}")
646                        if (not final and @idx == @tokens.size) or (final and @tokens[@idx] =~ final)
647                            # The end of the instruction and of the sequence.
648                            endOfSequence = true
649                            break
650                        elsif @tokens[@idx] == ","
651                            # Has another operand.
652                            @idx += 1
653                        elsif @tokens[@idx].is_a? Annotation
654                            @annotation = @tokens[@idx].string
655                            @idx += 2 # Consume the newline as well.
656                            break
657                        elsif @tokens[@idx] == "\n"
658                            # The end of the instruction.
659                            @idx += 1
660                            break
661                        else
662                            parseError("Expected a comma, newline, or #{final} after #{operands.last.dump}")
663                        end
664                    }
665                    list << Instruction.new(codeOrigin, name, operands, @annotation)
666                    @annotation = nil
667                    if endOfSequence
668                        break
669                    end
670                end
671
672            # Check for potential macro invocation:
673            elsif isIdentifier @tokens[@idx]
674                codeOrigin = @tokens[@idx].codeOrigin
675                name = @tokens[@idx].string
676                @idx += 1
677                if @tokens[@idx] == "("
678                    # Macro invocation.
679                    @idx += 1
680                    operands = []
681                    skipNewLine
682                    if @tokens[@idx] == ")"
683                        @idx += 1
684                    else
685                        loop {
686                            skipNewLine
687                            if @tokens[@idx] == "macro"
688                                # It's a macro lambda!
689                                codeOriginInner = @tokens[@idx].codeOrigin
690                                @idx += 1
691                                variables = parseMacroVariables
692                                body = parseSequence(/\Aend\Z/, "while inside of anonymous macro passed as argument to #{name}")
693                                @idx += 1
694                                operands << Macro.new(codeOriginInner, nil, variables, body)
695                            else
696                                operands << parseOperand("while inside of macro call to #{name}")
697                            end
698                            skipNewLine
699                            if @tokens[@idx] == ")"
700                                @idx += 1
701                                break
702                            elsif @tokens[@idx] == ","
703                                @idx += 1
704                            else
705                                parseError "Unexpected #{@tokens[@idx].string.inspect} while parsing invocation of macro #{name}"
706                            end
707                        }
708                    end
709                    # Check if there's a trailing annotation after the macro invoke:
710                    if @tokens[@idx].is_a? Annotation
711                        @annotation = @tokens[@idx].string
712                        @idx += 2 # Consume the newline as well.
713                    end
714                    list << MacroCall.new(codeOrigin, name, operands, @annotation)
715                    @annotation = nil
716                else
717                    parseError "Expected \"(\" after #{name}"
718                end
719            elsif isLabel @tokens[@idx] or isLocalLabel @tokens[@idx]
720                codeOrigin = @tokens[@idx].codeOrigin
721                name = @tokens[@idx].string
722                @idx += 1
723                parseError unless @tokens[@idx] == ":"
724                # It's a label.
725                if isLabel name
726                    list << Label.forName(codeOrigin, name, true)
727                else
728                    list << LocalLabel.forName(codeOrigin, name)
729                end
730                @idx += 1
731            elsif @tokens[@idx] == "include"
732                @idx += 1
733                parseError unless isIdentifier(@tokens[@idx])
734                moduleName = @tokens[@idx].string
735                fileName = IncludeFile.new(moduleName, @tokens[@idx].codeOrigin.fileName.dirname).fileName
736                @idx += 1
737                $stderr.puts "offlineasm: Including file #{fileName}"
738                list << parse(fileName)
739            else
740                parseError "Expecting terminal #{final} #{comment}"
741            end
742        }
743        Sequence.new(firstCodeOrigin, list)
744    end
745
746    def parseIncludes(final, comment)
747        firstCodeOrigin = @tokens[@idx].codeOrigin
748        fileList = []
749        fileList << @tokens[@idx].codeOrigin.fileName
750        loop {
751            if (@idx == @tokens.length and not final) or (final and @tokens[@idx] =~ final)
752                break
753            elsif @tokens[@idx] == "include"
754                @idx += 1
755                parseError unless isIdentifier(@tokens[@idx])
756                moduleName = @tokens[@idx].string
757                fileName = IncludeFile.new(moduleName, @tokens[@idx].codeOrigin.fileName.dirname).fileName
758                @idx += 1
759                
760                fileList << fileName
761            else
762                @idx += 1
763            end
764        }
765
766        return fileList
767    end
768end
769
770def parseData(data, fileName)
771    parser = Parser.new(data, fileName)
772    parser.parseSequence(nil, "")
773end
774
775def parse(fileName)
776    parseData(IO::read(fileName), fileName)
777end
778
779def parseHash(fileName)
780    parser = Parser.new(IO::read(fileName), fileName)
781    fileList = parser.parseIncludes(nil, "")
782    fileListHash(fileList)
783end
784
785