mirror of
https://github.com/darlinghq/darling-JavaScriptCore.git
synced 2025-04-11 03:10:57 +00:00
882 lines
30 KiB
Ruby
882 lines
30 KiB
Ruby
# 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
|
|
|