# Copyright (C) 2011, 2016 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.

require "config"
require "ast"
require "instructions"
require "pathname"
require "registers"
require "self_hash"

class SourceFile
    @@fileNames = []
    
    attr_reader :name, :fileNumber

    def SourceFile.outputDotFileList(outp)
        @@fileNames.each_index {
            | index |
            outp.puts "\".file #{index+1} \\\"#{@@fileNames[index]}\\\"\\n\""
        }
    end

    def initialize(fileName)
        @name = Pathname.new(fileName)
        pathName = "#{@name.realpath}"
        fileNumber = @@fileNames.index(pathName)
        if not fileNumber
            @@fileNames << pathName
            fileNumber = @@fileNames.length
        else
            fileNumber += 1 # File numbers are 1 based
        end
        @fileNumber = fileNumber
    end
end

class CodeOrigin
    attr_reader :lineNumber
    
    def initialize(sourceFile, lineNumber)
        @sourceFile = sourceFile
        @lineNumber = lineNumber
    end

    def fileName
        @sourceFile.name
    end

    def debugDirective
        $emitWinAsm ? nil : "\".loc #{@sourceFile.fileNumber} #{lineNumber}\\n\""
    end

    def to_s
        "#{fileName}:#{lineNumber}"
    end
end

class IncludeFile
    @@includeDirs = []

    attr_reader :fileName

    def initialize(moduleName, defaultDir)
        directory = nil
        @@includeDirs.each {
            | includePath |
            fileName = includePath + (moduleName + ".asm")
            directory = includePath unless not File.file?(fileName)
        }
        if not directory
            directory = defaultDir
        end

        @fileName = directory + (moduleName + ".asm")
    end

    def self.processIncludeOptions()
        while ARGV[0][/^-I/]
            path = ARGV.shift[2..-1]
            if not path
                path = ARGV.shift
            end
            @@includeDirs << (path + "/")
        end
    end
end

class Token
    attr_reader :codeOrigin, :string
    
    def initialize(codeOrigin, string)
        @codeOrigin = codeOrigin
        @string = string
    end
    
    def ==(other)
        if other.is_a? Token
            @string == other.string
        else
            @string == other
        end
    end
    
    def =~(other)
        @string =~ other
    end
    
    def to_s
        "#{@string.inspect} at #{codeOrigin}"
    end
    
    def parseError(*comment)
        if comment.empty?
            raise "Parse error: #{to_s}"
        else
            raise "Parse error: #{to_s}: #{comment[0]}"
        end
    end
end

class Annotation
    attr_reader :codeOrigin, :type, :string
    def initialize(codeOrigin, type, string)
        @codeOrigin = codeOrigin
        @type = type
        @string = string
    end
end

#
# The lexer. Takes a string and returns an array of tokens.
#

def lex(str, file)
    result = []
    lineNumber = 1
    annotation = nil
    whitespaceFound = false
    while not str.empty?
        case str
        when /\A\#([^\n]*)/
            # comment, ignore
        when /\A\/\/\ ?([^\n]*)/
            # annotation
            annotation = $1
            annotationType = whitespaceFound ? :local : :global
        when /\A\n/
            # We've found a '\n'.  Emit the last comment recorded if appropriate:
            # We need to parse annotations regardless of whether the backend does
            # anything with them or not. This is because the C++ backend may make
            # use of this for its cloopDo debugging utility even if
            # enableInstrAnnotations is not enabled.
            if annotation
                result << Annotation.new(CodeOrigin.new(file, lineNumber),
                                         annotationType, annotation)
                annotation = nil
            end
            result << Token.new(CodeOrigin.new(file, lineNumber), $&)
            lineNumber += 1
        when /\A[a-zA-Z%]([a-zA-Z0-9_.%]*)/
            result << Token.new(CodeOrigin.new(file, lineNumber), $&)
        when /\A\.([a-zA-Z0-9_]*)/
            result << Token.new(CodeOrigin.new(file, lineNumber), $&)
        when /\A_([a-zA-Z0-9_%]*)/
            result << Token.new(CodeOrigin.new(file, lineNumber), $&)
        when /\A([ \t]+)/
            # whitespace, ignore
            whitespaceFound = true
            str = $~.post_match
            next
        when /\A0x([0-9a-fA-F]+)/
            result << Token.new(CodeOrigin.new(file, lineNumber), $&.hex.to_s)
        when /\A0([0-7]+)/
            result << Token.new(CodeOrigin.new(file, lineNumber), $&.oct.to_s)
        when /\A([0-9]+)/
            result << Token.new(CodeOrigin.new(file, lineNumber), $&)
        when /\A::/
            result << Token.new(CodeOrigin.new(file, lineNumber), $&)
        when /\A[:,\(\)\[\]=\+\-~\|&^*]/
            result << Token.new(CodeOrigin.new(file, lineNumber), $&)
        when /\A".*"/
            result << Token.new(CodeOrigin.new(file, lineNumber), $&)
        else
            raise "Lexer error at #{CodeOrigin.new(file, lineNumber).to_s}, unexpected sequence #{str[0..20].inspect}"
        end
        whitespaceFound = false
        str = $~.post_match
    end
    result
end

#
# Token identification.
#

def isRegister(token)
    token =~ REGISTER_PATTERN
end

def isInstruction(token)
    INSTRUCTION_SET.member? token.string
end

def isKeyword(token)
    token =~ /\A((true)|(false)|(if)|(then)|(else)|(elsif)|(end)|(and)|(or)|(not)|(global)|(macro)|(const)|(constexpr)|(sizeof)|(error)|(include))\Z/ or
        token =~ REGISTER_PATTERN or
        isInstruction(token)
end

def isIdentifier(token)
    token =~ /\A[a-zA-Z%]([a-zA-Z0-9_.%]*)\Z/ and not isKeyword(token)
end

def isLabel(token)
    token =~ /\A_([a-zA-Z0-9_%]*)\Z/
end

def isLocalLabel(token)
    token =~ /\A\.([a-zA-Z0-9_]*)\Z/
end

def isVariable(token)
    isIdentifier(token) or isRegister(token)
end

def isInteger(token)
    token =~ /\A[0-9]/
end

def isString(token)
    token =~ /\A".*"/
end

#
# The parser. Takes an array of tokens and returns an AST. Methods
# other than parse(tokens) are not for public consumption.
#

class Parser
    def initialize(data, fileName)
        @tokens = lex(data, fileName)
        @idx = 0
        @annotation = nil
    end
    
    def parseError(*comment)
        if @tokens[@idx]
            @tokens[@idx].parseError(*comment)
        else
            if comment.empty?
                raise "Parse error at end of file"
            else
                raise "Parse error at end of file: #{comment[0]}"
            end
        end
    end
    
    def consume(regexp)
        if regexp
            parseError unless @tokens[@idx] =~ regexp
        else
            parseError unless @idx == @tokens.length
        end
        @idx += 1
    end
    
    def skipNewLine
        while @tokens[@idx] == "\n"
            @idx += 1
        end
    end
    
    def parsePredicateAtom
        if @tokens[@idx] == "not"
            codeOrigin = @tokens[@idx].codeOrigin
            @idx += 1
            Not.new(codeOrigin, parsePredicateAtom)
        elsif @tokens[@idx] == "("
            @idx += 1
            skipNewLine
            result = parsePredicate
            parseError unless @tokens[@idx] == ")"
            @idx += 1
            result
        elsif @tokens[@idx] == "true"
            result = True.instance
            @idx += 1
            result
        elsif @tokens[@idx] == "false"
            result = False.instance
            @idx += 1
            result
        elsif isIdentifier @tokens[@idx]
            result = Setting.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
            @idx += 1
            result
        else
            parseError
        end
    end
    
    def parsePredicateAnd
        result = parsePredicateAtom
        while @tokens[@idx] == "and"
            codeOrigin = @tokens[@idx].codeOrigin
            @idx += 1
            skipNewLine
            right = parsePredicateAtom
            result = And.new(codeOrigin, result, right)
        end
        result
    end
    
    def parsePredicate
        # some examples of precedence:
        # not a and b -> (not a) and b
        # a and b or c -> (a and b) or c
        # a or b and c -> a or (b and c)
        
        result = parsePredicateAnd
        while @tokens[@idx] == "or"
            codeOrigin = @tokens[@idx].codeOrigin
            @idx += 1
            skipNewLine
            right = parsePredicateAnd
            result = Or.new(codeOrigin, result, right)
        end
        result
    end
    
    def parseVariable
        if isRegister(@tokens[@idx])
            if @tokens[@idx] =~ FPR_PATTERN || @tokens[@idx] =~ WASM_FPR_PATTERN
                result = FPRegisterID.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
            else
                result = RegisterID.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
            end
        elsif isIdentifier(@tokens[@idx])
            result = Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
        else
            parseError
        end
        @idx += 1
        result
    end

    def parseConstExpr
        if @tokens[@idx] == "constexpr"
            @idx += 1
            skipNewLine
            if @tokens[@idx] == "("
                codeOrigin, text = parseTextInParens
                text = text.join
            else
                codeOrigin, text = parseColonColon
                text = text.join("::")
            end
            ConstExpr.forName(codeOrigin, text)
        else
            parseError
        end
    end
    
    def parseAddress(offset)
        parseError unless @tokens[@idx] == "["
        codeOrigin = @tokens[@idx].codeOrigin
        
        # Three possibilities:
        # []       -> AbsoluteAddress
        # [a]      -> Address
        # [a,b]    -> BaseIndex with scale = 1
        # [a,b,c]  -> BaseIndex
        
        @idx += 1
        if @tokens[@idx] == "]"
            @idx += 1
            return AbsoluteAddress.new(codeOrigin, offset)
        end
        a = parseVariable
        if @tokens[@idx] == "]"
            result = Address.new(codeOrigin, a, offset)
        else
            parseError unless @tokens[@idx] == ","
            @idx += 1
            b = parseVariable
            if @tokens[@idx] == "]"
                result = BaseIndex.new(codeOrigin, a, b, Immediate.new(codeOrigin, 1), offset)
            else
                parseError unless @tokens[@idx] == ","
                @idx += 1
                if ["1", "2", "4", "8"].member? @tokens[@idx].string
                    c = Immediate.new(codeOrigin, @tokens[@idx].string.to_i)
                    @idx += 1
                elsif @tokens[@idx] == "constexpr"
                    c = parseConstExpr
                else
                    c = parseVariable
                end
                parseError unless @tokens[@idx] == "]"
                result = BaseIndex.new(codeOrigin, a, b, c, offset)
            end
        end
        @idx += 1
        result
    end
    
    def parseColonColon
        skipNewLine
        codeOrigin = @tokens[@idx].codeOrigin
        parseError unless isIdentifier @tokens[@idx]
        names = [@tokens[@idx].string]
        @idx += 1
        while @tokens[@idx] == "::"
            @idx += 1
            parseError unless isIdentifier @tokens[@idx]
            names << @tokens[@idx].string
            @idx += 1
        end
        raise if names.empty?
        [codeOrigin, names]
    end

    def parseTextInParens
        skipNewLine
        codeOrigin = @tokens[@idx].codeOrigin
        raise unless @tokens[@idx] == "("
        @idx += 1
        # need at least one item
        raise if @tokens[@idx] == ")"
        numEnclosedParens = 0
        text = []
        while @tokens[@idx] != ")" || numEnclosedParens > 0
            if @tokens[@idx] == "("
                numEnclosedParens += 1
            elsif @tokens[@idx] == ")"
                numEnclosedParens -= 1
            end

            text << @tokens[@idx].string
            @idx += 1
        end
        @idx += 1
        return [codeOrigin, text]
    end

    
    def parseExpressionAtom
        skipNewLine
        if @tokens[@idx] == "-"
            @idx += 1
            NegImmediate.new(@tokens[@idx - 1].codeOrigin, parseExpressionAtom)
        elsif @tokens[@idx] == "~"
            @idx += 1
            BitnotImmediate.new(@tokens[@idx - 1].codeOrigin, parseExpressionAtom)
        elsif @tokens[@idx] == "("
            @idx += 1
            result = parseExpression
            parseError unless @tokens[@idx] == ")"
            @idx += 1
            result
        elsif isInteger @tokens[@idx]
            result = Immediate.new(@tokens[@idx].codeOrigin, @tokens[@idx].string.to_i)
            @idx += 1
            result
        elsif isString @tokens[@idx]
            result = StringLiteral.new(@tokens[@idx].codeOrigin, @tokens[@idx].string)
            @idx += 1
            result
        elsif isIdentifier @tokens[@idx]
            codeOrigin, names = parseColonColon
            if names.size > 1
                StructOffset.forField(codeOrigin, names[0..-2].join('::'), names[-1])
            else
                Variable.forName(codeOrigin, names[0])
            end
        elsif isRegister @tokens[@idx]
            parseVariable
        elsif @tokens[@idx] == "sizeof"
            @idx += 1
            codeOrigin, names = parseColonColon
            Sizeof.forName(codeOrigin, names.join('::'))
        elsif @tokens[@idx] == "constexpr"
            parseConstExpr
        elsif isLabel @tokens[@idx]
            result = LabelReference.new(@tokens[@idx].codeOrigin, Label.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
            @idx += 1
            result
        elsif isLocalLabel @tokens[@idx]
            result = LocalLabelReference.new(@tokens[@idx].codeOrigin, LocalLabel.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
            @idx += 1
            result
        else
            parseError
        end
    end
    
    def parseExpressionMul
        skipNewLine
        result = parseExpressionAtom
        while @tokens[@idx] == "*"
            if @tokens[@idx] == "*"
                @idx += 1
                result = MulImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAtom)
            else
                raise
            end
        end
        result
    end
    
    def couldBeExpression
        @tokens[@idx] == "-" or @tokens[@idx] == "~" or @tokens[@idx] == "sizeof" or @tokens[@idx] == "constexpr" or isInteger(@tokens[@idx]) or isString(@tokens[@idx]) or isVariable(@tokens[@idx]) or isLabel(@tokens[@idx]) or @tokens[@idx] == "("
    end
    
    def parseExpressionAdd
        skipNewLine
        result = parseExpressionMul
        while @tokens[@idx] == "+" or @tokens[@idx] == "-"
            if @tokens[@idx] == "+"
                @idx += 1
                result = AddImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionMul)
            elsif @tokens[@idx] == "-"
                @idx += 1
                result = SubImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionMul)
            else
                raise
            end
        end
        result
    end
    
    def parseExpressionAnd
        skipNewLine
        result = parseExpressionAdd
        while @tokens[@idx] == "&"
            @idx += 1
            result = AndImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAdd)
        end
        result
    end
    
    def parseExpression
        skipNewLine
        result = parseExpressionAnd
        while @tokens[@idx] == "|" or @tokens[@idx] == "^"
            if @tokens[@idx] == "|"
                @idx += 1
                result = OrImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAnd)
            elsif @tokens[@idx] == "^"
                @idx += 1
                result = XorImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAnd)
            else
                raise
            end
        end
        result
    end
    
    def parseOperand(comment)
        skipNewLine
        if couldBeExpression
            expr = parseExpression
            if @tokens[@idx] == "["
                parseAddress(expr)
            else
                expr
            end
        elsif @tokens[@idx] == "["
            parseAddress(Immediate.new(@tokens[@idx].codeOrigin, 0))
        elsif isLocalLabel @tokens[@idx]
            result = LocalLabelReference.new(@tokens[@idx].codeOrigin, LocalLabel.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
            @idx += 1
            result
        else
            parseError(comment)
        end
    end
    
    def parseMacroVariables
        skipNewLine
        consume(/\A\(\Z/)
        variables = []
        loop {
            skipNewLine
            if @tokens[@idx] == ")"
                @idx += 1
                break
            elsif isIdentifier(@tokens[@idx])
                variables << Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
                @idx += 1
                skipNewLine
                if @tokens[@idx] == ")"
                    @idx += 1
                    break
                elsif @tokens[@idx] == ","
                    @idx += 1
                else
                    parseError
                end
            else
                parseError
            end
        }
        variables
    end
    
    def parseSequence(final, comment)
        firstCodeOrigin = @tokens[@idx].codeOrigin
        list = []
        loop {
            if @tokens[@idx].is_a? Annotation
                # This is the only place where we can encounter a global
                # annotation, and hence need to be able to distinguish between
                # them.
                # globalAnnotations are the ones that start from column 0. All
                # others are considered localAnnotations.  The only reason to
                # distinguish between them is so that we can format the output
                # nicely as one would expect.

                codeOrigin = @tokens[@idx].codeOrigin
                annotationOpcode = (@tokens[@idx].type == :global) ? "globalAnnotation" : "localAnnotation"
                list << Instruction.new(codeOrigin, annotationOpcode, [], @tokens[@idx].string)
                @annotation = nil
                @idx += 2 # Consume the newline as well.
            elsif (@idx == @tokens.length and not final) or (final and @tokens[@idx] =~ final)
                break
            elsif @tokens[@idx] == "\n"
                # ignore
                @idx += 1
            elsif @tokens[@idx] == "const"
                @idx += 1
                parseError unless isVariable @tokens[@idx]
                variable = Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
                @idx += 1
                parseError unless @tokens[@idx] == "="
                @idx += 1
                value = parseOperand("while inside of const #{variable.name}")
                list << ConstDecl.new(@tokens[@idx].codeOrigin, variable, value)
            elsif @tokens[@idx] == "error"
                list << Error.new(@tokens[@idx].codeOrigin)
                @idx += 1
            elsif @tokens[@idx] == "if"
                codeOrigin = @tokens[@idx].codeOrigin
                @idx += 1
                skipNewLine
                predicate = parsePredicate
                consume(/\A((then)|(\n))\Z/)
                skipNewLine
                ifThenElse = IfThenElse.new(codeOrigin, predicate, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\""))
                list << ifThenElse
                while @tokens[@idx] == "elsif"
                    codeOrigin = @tokens[@idx].codeOrigin
                    @idx += 1
                    skipNewLine
                    predicate = parsePredicate
                    consume(/\A((then)|(\n))\Z/)
                    skipNewLine
                    elseCase = IfThenElse.new(codeOrigin, predicate, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\""))
                    ifThenElse.elseCase = elseCase
                    ifThenElse = elseCase
                end
                if @tokens[@idx] == "else"
                    @idx += 1
                    ifThenElse.elseCase = parseSequence(/\Aend\Z/, "while inside of else case for \"if #{predicate.dump}\"")
                    @idx += 1
                else
                    parseError unless @tokens[@idx] == "end"
                    @idx += 1
                end
            elsif @tokens[@idx] == "macro"
                codeOrigin = @tokens[@idx].codeOrigin
                @idx += 1
                skipNewLine
                parseError unless isIdentifier(@tokens[@idx])
                name = @tokens[@idx].string
                @idx += 1
                variables = parseMacroVariables
                body = parseSequence(/\Aend\Z/, "while inside of macro #{name}")
                @idx += 1
                list << Macro.new(codeOrigin, name, variables, body)
            elsif @tokens[@idx] == "global"
                codeOrigin = @tokens[@idx].codeOrigin
                @idx += 1
                skipNewLine
                parseError unless isLabel(@tokens[@idx])
                name = @tokens[@idx].string
                @idx += 1
                Label.setAsGlobal(codeOrigin, name)
            elsif isInstruction @tokens[@idx]
                codeOrigin = @tokens[@idx].codeOrigin
                name = @tokens[@idx].string
                @idx += 1
                if (not final and @idx == @tokens.size) or (final and @tokens[@idx] =~ final)
                    # Zero operand instruction, and it's the last one.
                    list << Instruction.new(codeOrigin, name, [], @annotation)
                    @annotation = nil
                    break
                elsif @tokens[@idx].is_a? Annotation
                    list << Instruction.new(codeOrigin, name, [], @tokens[@idx].string)
                    @annotation = nil
                    @idx += 2 # Consume the newline as well.
                elsif @tokens[@idx] == "\n"
                    # Zero operand instruction.
                    list << Instruction.new(codeOrigin, name, [], @annotation)
                    @annotation = nil
                    @idx += 1
                else
                    # It's definitely an instruction, and it has at least one operand.
                    operands = []
                    endOfSequence = false
                    loop {
                        operands << parseOperand("while inside of instruction #{name}")
                        if (not final and @idx == @tokens.size) or (final and @tokens[@idx] =~ final)
                            # The end of the instruction and of the sequence.
                            endOfSequence = true
                            break
                        elsif @tokens[@idx] == ","
                            # Has another operand.
                            @idx += 1
                        elsif @tokens[@idx].is_a? Annotation
                            @annotation = @tokens[@idx].string
                            @idx += 2 # Consume the newline as well.
                            break
                        elsif @tokens[@idx] == "\n"
                            # The end of the instruction.
                            @idx += 1
                            break
                        else
                            parseError("Expected a comma, newline, or #{final} after #{operands.last.dump}")
                        end
                    }
                    list << Instruction.new(codeOrigin, name, operands, @annotation)
                    @annotation = nil
                    if endOfSequence
                        break
                    end
                end

            # Check for potential macro invocation:
            elsif isIdentifier @tokens[@idx]
                codeOrigin = @tokens[@idx].codeOrigin
                name = @tokens[@idx].string
                @idx += 1
                if @tokens[@idx] == "("
                    # Macro invocation.
                    @idx += 1
                    operands = []
                    skipNewLine
                    if @tokens[@idx] == ")"
                        @idx += 1
                    else
                        loop {
                            skipNewLine
                            if @tokens[@idx] == "macro"
                                # It's a macro lambda!
                                codeOriginInner = @tokens[@idx].codeOrigin
                                @idx += 1
                                variables = parseMacroVariables
                                body = parseSequence(/\Aend\Z/, "while inside of anonymous macro passed as argument to #{name}")
                                @idx += 1
                                operands << Macro.new(codeOriginInner, nil, variables, body)
                            else
                                operands << parseOperand("while inside of macro call to #{name}")
                            end
                            skipNewLine
                            if @tokens[@idx] == ")"
                                @idx += 1
                                break
                            elsif @tokens[@idx] == ","
                                @idx += 1
                            else
                                parseError "Unexpected #{@tokens[@idx].string.inspect} while parsing invocation of macro #{name}"
                            end
                        }
                    end
                    # Check if there's a trailing annotation after the macro invoke:
                    if @tokens[@idx].is_a? Annotation
                        @annotation = @tokens[@idx].string
                        @idx += 2 # Consume the newline as well.
                    end
                    list << MacroCall.new(codeOrigin, name, operands, @annotation)
                    @annotation = nil
                else
                    parseError "Expected \"(\" after #{name}"
                end
            elsif isLabel @tokens[@idx] or isLocalLabel @tokens[@idx]
                codeOrigin = @tokens[@idx].codeOrigin
                name = @tokens[@idx].string
                @idx += 1
                parseError unless @tokens[@idx] == ":"
                # It's a label.
                if isLabel name
                    list << Label.forName(codeOrigin, name, true)
                else
                    list << LocalLabel.forName(codeOrigin, name)
                end
                @idx += 1
            elsif @tokens[@idx] == "include"
                @idx += 1
                parseError unless isIdentifier(@tokens[@idx])
                moduleName = @tokens[@idx].string
                fileName = IncludeFile.new(moduleName, @tokens[@idx].codeOrigin.fileName.dirname).fileName
                @idx += 1
                list << parse(fileName)
            else
                parseError "Expecting terminal #{final} #{comment}"
            end
        }
        Sequence.new(firstCodeOrigin, list)
    end

    def parseIncludes(final, comment)
        firstCodeOrigin = @tokens[@idx].codeOrigin
        fileList = []
        fileList << @tokens[@idx].codeOrigin.fileName
        loop {
            if (@idx == @tokens.length and not final) or (final and @tokens[@idx] =~ final)
                break
            elsif @tokens[@idx] == "include"
                @idx += 1
                parseError unless isIdentifier(@tokens[@idx])
                moduleName = @tokens[@idx].string
                fileName = IncludeFile.new(moduleName, @tokens[@idx].codeOrigin.fileName.dirname).fileName
                @idx += 1
                
                fileList << fileName
            else
                @idx += 1
            end
        }

        return fileList
    end
end

def readTextFile(fileName)
    data = IO::read(fileName)

    # On Windows, files may contain CRLF line endings (for example, git client might
    # automatically replace \n with \r\n on Windows) which will fail our parsing.
    # Thus, we'll just remove all \r from the data (keeping just the \n characters)
    data.delete!("\r")

    return data
end

def parseData(data, fileName)
    parser = Parser.new(data, SourceFile.new(fileName))
    parser.parseSequence(nil, "")
end

def parse(fileName)
    parseData(readTextFile(fileName), fileName)
end

def parseHash(fileName)
    parser = Parser.new(readTextFile(fileName), SourceFile.new(fileName))
    fileList = parser.parseIncludes(nil, "")
    fileListHash(fileList)
end

