# 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"
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
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.
#
fileName = Pathname.new(fileName)
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(fileName, lineNumber),
+ annotationType, annotation)
+ annotation = nil
+ end
result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
lineNumber += 1
- when /\A[a-zA-Z]([a-zA-Z0-9_]*)/
+ when /\A[a-zA-Z]([a-zA-Z0-9_.]*)/
result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
when /\A\.([a-zA-Z0-9_]*)/
result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
when /\A([ \t]+)/
# whitespace, ignore
+ whitespaceFound = true
+ str = $~.post_match
+ next
when /\A0x([0-9a-fA-F]+)/
result << Token.new(CodeOrigin.new(fileName, lineNumber), $&.hex.to_s)
when /\A0([0-7]+)/
result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
when /\A[:,\(\)\[\]=\+\-~\|&^*]/
result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
+ when /\A".*"/
+ result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
else
raise "Lexer error at #{CodeOrigin.new(fileName, lineNumber).to_s}, unexpected sequence #{str[0..20].inspect}"
end
+ whitespaceFound = false
str = $~.post_match
end
result
end
def isInstruction(token)
- token =~ INSTRUCTION_PATTERN
+ INSTRUCTION_SET.member? token.string
end
def isKeyword(token)
- token =~ /\A((true)|(false)|(if)|(then)|(else)|(elsif)|(end)|(and)|(or)|(not)|(macro)|(const)|(sizeof)|(error)|(include))\Z/ or
+ token =~ /\A((true)|(false)|(if)|(then)|(else)|(elsif)|(end)|(and)|(or)|(not)|(global)|(macro)|(const)|(sizeof)|(error)|(include))\Z/ or
token =~ REGISTER_PATTERN or
- token =~ INSTRUCTION_PATTERN
+ isInstruction(token)
end
def isIdentifier(token)
- token =~ /\A[a-zA-Z]([a-zA-Z0-9_]*)\Z/ and not isKeyword(token)
+ token =~ /\A[a-zA-Z]([a-zA-Z0-9_.]*)\Z/ and not isKeyword(token)
end
def isLabel(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.
def initialize(data, fileName)
@tokens = lex(data, fileName)
@idx = 0
+ @annotation = nil
end
def parseError(*comment)
def parsePredicateAtom
if @tokens[@idx] == "not"
+ codeOrigin = @tokens[@idx].codeOrigin
@idx += 1
- parsePredicateAtom
+ Not.new(codeOrigin, parsePredicateAtom)
elsif @tokens[@idx] == "("
@idx += 1
skipNewLine
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
@idx += 1
codeOrigin, names = parseColonColon
Sizeof.forName(codeOrigin, names.join('::'))
+ 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 couldBeExpression
- @tokens[@idx] == "-" or @tokens[@idx] == "~" or @tokens[@idx] == "sizeof" or isInteger(@tokens[@idx]) or isVariable(@tokens[@idx]) or @tokens[@idx] == "("
+ @tokens[@idx] == "-" or @tokens[@idx] == "~" or @tokens[@idx] == "sizeof" or isInteger(@tokens[@idx]) or isString(@tokens[@idx]) or isVariable(@tokens[@idx]) or @tokens[@idx] == "("
end
def parseExpressionAdd
loop {
if (@idx == @tokens.length and not final) or (final and @tokens[@idx] =~ final)
break
+ elsif @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 @tokens[@idx] == "\n"
# ignore
@idx += 1
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, [])
+ 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, [])
+ list << Instruction.new(codeOrigin, name, [], @annotation)
+ @annotation = nil
@idx += 1
else
# It's definitely an instruction, and it has at least one operand.
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
parseError("Expected a comma, newline, or #{final} after #{operands.last.dump}")
end
}
- list << Instruction.new(codeOrigin, name, operands)
+ 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
end
}
end
- list << MacroCall.new(codeOrigin, name, operands)
+ # 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
parseError unless @tokens[@idx] == ":"
# It's a label.
if isLabel name
- list << Label.forName(codeOrigin, name)
+ list << Label.forName(codeOrigin, name, true)
else
list << LocalLabel.forName(codeOrigin, name)
end
@idx += 1
parseError unless isIdentifier(@tokens[@idx])
moduleName = @tokens[@idx].string
- fileName = @tokens[@idx].codeOrigin.fileName.dirname + (moduleName + ".asm")
+ fileName = IncludeFile.new(moduleName, @tokens[@idx].codeOrigin.fileName.dirname).fileName
@idx += 1
$stderr.puts "offlineasm: Including file #{fileName}"
list << parse(fileName)
}
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 parseData(data, fileName)
end
def parseHash(fileName)
- dirHash(Pathname.new(fileName).dirname, /\.asm$/)
+ parser = Parser.new(IO::read(fileName), fileName)
+ fileList = parser.parseIncludes(nil, "")
+ fileListHash(fileList)
end