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