parser.rb 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  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. require "config"
  24. require "ast"
  25. require "instructions"
  26. require "pathname"
  27. require "registers"
  28. require "self_hash"
  29. class CodeOrigin
  30. attr_reader :fileName, :lineNumber
  31. def initialize(fileName, lineNumber)
  32. @fileName = fileName
  33. @lineNumber = lineNumber
  34. end
  35. def to_s
  36. "#{fileName}:#{lineNumber}"
  37. end
  38. end
  39. class Token
  40. attr_reader :codeOrigin, :string
  41. def initialize(codeOrigin, string)
  42. @codeOrigin = codeOrigin
  43. @string = string
  44. end
  45. def ==(other)
  46. if other.is_a? Token
  47. @string == other.string
  48. else
  49. @string == other
  50. end
  51. end
  52. def =~(other)
  53. @string =~ other
  54. end
  55. def to_s
  56. "#{@string.inspect} at #{codeOrigin}"
  57. end
  58. def parseError(*comment)
  59. if comment.empty?
  60. raise "Parse error: #{to_s}"
  61. else
  62. raise "Parse error: #{to_s}: #{comment[0]}"
  63. end
  64. end
  65. end
  66. class Annotation
  67. attr_reader :codeOrigin, :type, :string
  68. def initialize(codeOrigin, type, string)
  69. @codeOrigin = codeOrigin
  70. @type = type
  71. @string = string
  72. end
  73. end
  74. #
  75. # The lexer. Takes a string and returns an array of tokens.
  76. #
  77. def lex(str, fileName)
  78. fileName = Pathname.new(fileName)
  79. result = []
  80. lineNumber = 1
  81. annotation = nil
  82. whitespaceFound = false
  83. while not str.empty?
  84. case str
  85. when /\A\#([^\n]*)/
  86. # comment, ignore
  87. when /\A\/\/\ ?([^\n]*)/
  88. # annotation
  89. annotation = $1
  90. annotationType = whitespaceFound ? :local : :global
  91. when /\A\n/
  92. # We've found a '\n'. Emit the last comment recorded if appropriate:
  93. # We need to parse annotations regardless of whether the backend does
  94. # anything with them or not. This is because the C++ backend may make
  95. # use of this for its cloopDo debugging utility even if
  96. # enableInstrAnnotations is not enabled.
  97. if annotation
  98. result << Annotation.new(CodeOrigin.new(fileName, lineNumber),
  99. annotationType, annotation)
  100. annotation = nil
  101. end
  102. result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
  103. lineNumber += 1
  104. when /\A[a-zA-Z]([a-zA-Z0-9_]*)/
  105. result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
  106. when /\A\.([a-zA-Z0-9_]*)/
  107. result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
  108. when /\A_([a-zA-Z0-9_]*)/
  109. result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
  110. when /\A([ \t]+)/
  111. # whitespace, ignore
  112. whitespaceFound = true
  113. str = $~.post_match
  114. next
  115. when /\A0x([0-9a-fA-F]+)/
  116. result << Token.new(CodeOrigin.new(fileName, lineNumber), $&.hex.to_s)
  117. when /\A0([0-7]+)/
  118. result << Token.new(CodeOrigin.new(fileName, lineNumber), $&.oct.to_s)
  119. when /\A([0-9]+)/
  120. result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
  121. when /\A::/
  122. result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
  123. when /\A[:,\(\)\[\]=\+\-~\|&^*]/
  124. result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
  125. else
  126. raise "Lexer error at #{CodeOrigin.new(fileName, lineNumber).to_s}, unexpected sequence #{str[0..20].inspect}"
  127. end
  128. whitespaceFound = false
  129. str = $~.post_match
  130. end
  131. result
  132. end
  133. #
  134. # Token identification.
  135. #
  136. def isRegister(token)
  137. token =~ REGISTER_PATTERN
  138. end
  139. def isInstruction(token)
  140. token =~ INSTRUCTION_PATTERN
  141. end
  142. def isKeyword(token)
  143. token =~ /\A((true)|(false)|(if)|(then)|(else)|(elsif)|(end)|(and)|(or)|(not)|(macro)|(const)|(sizeof)|(error)|(include))\Z/ or
  144. token =~ REGISTER_PATTERN or
  145. token =~ INSTRUCTION_PATTERN
  146. end
  147. def isIdentifier(token)
  148. token =~ /\A[a-zA-Z]([a-zA-Z0-9_]*)\Z/ and not isKeyword(token)
  149. end
  150. def isLabel(token)
  151. token =~ /\A_([a-zA-Z0-9_]*)\Z/
  152. end
  153. def isLocalLabel(token)
  154. token =~ /\A\.([a-zA-Z0-9_]*)\Z/
  155. end
  156. def isVariable(token)
  157. isIdentifier(token) or isRegister(token)
  158. end
  159. def isInteger(token)
  160. token =~ /\A[0-9]/
  161. end
  162. #
  163. # The parser. Takes an array of tokens and returns an AST. Methods
  164. # other than parse(tokens) are not for public consumption.
  165. #
  166. class Parser
  167. def initialize(data, fileName)
  168. @tokens = lex(data, fileName)
  169. @idx = 0
  170. @annotation = nil
  171. end
  172. def parseError(*comment)
  173. if @tokens[@idx]
  174. @tokens[@idx].parseError(*comment)
  175. else
  176. if comment.empty?
  177. raise "Parse error at end of file"
  178. else
  179. raise "Parse error at end of file: #{comment[0]}"
  180. end
  181. end
  182. end
  183. def consume(regexp)
  184. if regexp
  185. parseError unless @tokens[@idx] =~ regexp
  186. else
  187. parseError unless @idx == @tokens.length
  188. end
  189. @idx += 1
  190. end
  191. def skipNewLine
  192. while @tokens[@idx] == "\n"
  193. @idx += 1
  194. end
  195. end
  196. def parsePredicateAtom
  197. if @tokens[@idx] == "not"
  198. codeOrigin = @tokens[@idx].codeOrigin
  199. @idx += 1
  200. Not.new(codeOrigin, parsePredicateAtom)
  201. elsif @tokens[@idx] == "("
  202. @idx += 1
  203. skipNewLine
  204. result = parsePredicate
  205. parseError unless @tokens[@idx] == ")"
  206. @idx += 1
  207. result
  208. elsif @tokens[@idx] == "true"
  209. result = True.instance
  210. @idx += 1
  211. result
  212. elsif @tokens[@idx] == "false"
  213. result = False.instance
  214. @idx += 1
  215. result
  216. elsif isIdentifier @tokens[@idx]
  217. result = Setting.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
  218. @idx += 1
  219. result
  220. else
  221. parseError
  222. end
  223. end
  224. def parsePredicateAnd
  225. result = parsePredicateAtom
  226. while @tokens[@idx] == "and"
  227. codeOrigin = @tokens[@idx].codeOrigin
  228. @idx += 1
  229. skipNewLine
  230. right = parsePredicateAtom
  231. result = And.new(codeOrigin, result, right)
  232. end
  233. result
  234. end
  235. def parsePredicate
  236. # some examples of precedence:
  237. # not a and b -> (not a) and b
  238. # a and b or c -> (a and b) or c
  239. # a or b and c -> a or (b and c)
  240. result = parsePredicateAnd
  241. while @tokens[@idx] == "or"
  242. codeOrigin = @tokens[@idx].codeOrigin
  243. @idx += 1
  244. skipNewLine
  245. right = parsePredicateAnd
  246. result = Or.new(codeOrigin, result, right)
  247. end
  248. result
  249. end
  250. def parseVariable
  251. if isRegister(@tokens[@idx])
  252. if @tokens[@idx] =~ FPR_PATTERN
  253. result = FPRegisterID.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
  254. else
  255. result = RegisterID.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
  256. end
  257. elsif isIdentifier(@tokens[@idx])
  258. result = Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
  259. else
  260. parseError
  261. end
  262. @idx += 1
  263. result
  264. end
  265. def parseAddress(offset)
  266. parseError unless @tokens[@idx] == "["
  267. codeOrigin = @tokens[@idx].codeOrigin
  268. # Three possibilities:
  269. # [] -> AbsoluteAddress
  270. # [a] -> Address
  271. # [a,b] -> BaseIndex with scale = 1
  272. # [a,b,c] -> BaseIndex
  273. @idx += 1
  274. if @tokens[@idx] == "]"
  275. @idx += 1
  276. return AbsoluteAddress.new(codeOrigin, offset)
  277. end
  278. a = parseVariable
  279. if @tokens[@idx] == "]"
  280. result = Address.new(codeOrigin, a, offset)
  281. else
  282. parseError unless @tokens[@idx] == ","
  283. @idx += 1
  284. b = parseVariable
  285. if @tokens[@idx] == "]"
  286. result = BaseIndex.new(codeOrigin, a, b, 1, offset)
  287. else
  288. parseError unless @tokens[@idx] == ","
  289. @idx += 1
  290. parseError unless ["1", "2", "4", "8"].member? @tokens[@idx].string
  291. c = @tokens[@idx].string.to_i
  292. @idx += 1
  293. parseError unless @tokens[@idx] == "]"
  294. result = BaseIndex.new(codeOrigin, a, b, c, offset)
  295. end
  296. end
  297. @idx += 1
  298. result
  299. end
  300. def parseColonColon
  301. skipNewLine
  302. codeOrigin = @tokens[@idx].codeOrigin
  303. parseError unless isIdentifier @tokens[@idx]
  304. names = [@tokens[@idx].string]
  305. @idx += 1
  306. while @tokens[@idx] == "::"
  307. @idx += 1
  308. parseError unless isIdentifier @tokens[@idx]
  309. names << @tokens[@idx].string
  310. @idx += 1
  311. end
  312. raise if names.empty?
  313. [codeOrigin, names]
  314. end
  315. def parseExpressionAtom
  316. skipNewLine
  317. if @tokens[@idx] == "-"
  318. @idx += 1
  319. NegImmediate.new(@tokens[@idx - 1].codeOrigin, parseExpressionAtom)
  320. elsif @tokens[@idx] == "~"
  321. @idx += 1
  322. BitnotImmediate.new(@tokens[@idx - 1].codeOrigin, parseExpressionAtom)
  323. elsif @tokens[@idx] == "("
  324. @idx += 1
  325. result = parseExpression
  326. parseError unless @tokens[@idx] == ")"
  327. @idx += 1
  328. result
  329. elsif isInteger @tokens[@idx]
  330. result = Immediate.new(@tokens[@idx].codeOrigin, @tokens[@idx].string.to_i)
  331. @idx += 1
  332. result
  333. elsif isIdentifier @tokens[@idx]
  334. codeOrigin, names = parseColonColon
  335. if names.size > 1
  336. StructOffset.forField(codeOrigin, names[0..-2].join('::'), names[-1])
  337. else
  338. Variable.forName(codeOrigin, names[0])
  339. end
  340. elsif isRegister @tokens[@idx]
  341. parseVariable
  342. elsif @tokens[@idx] == "sizeof"
  343. @idx += 1
  344. codeOrigin, names = parseColonColon
  345. Sizeof.forName(codeOrigin, names.join('::'))
  346. else
  347. parseError
  348. end
  349. end
  350. def parseExpressionMul
  351. skipNewLine
  352. result = parseExpressionAtom
  353. while @tokens[@idx] == "*"
  354. if @tokens[@idx] == "*"
  355. @idx += 1
  356. result = MulImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAtom)
  357. else
  358. raise
  359. end
  360. end
  361. result
  362. end
  363. def couldBeExpression
  364. @tokens[@idx] == "-" or @tokens[@idx] == "~" or @tokens[@idx] == "sizeof" or isInteger(@tokens[@idx]) or isVariable(@tokens[@idx]) or @tokens[@idx] == "("
  365. end
  366. def parseExpressionAdd
  367. skipNewLine
  368. result = parseExpressionMul
  369. while @tokens[@idx] == "+" or @tokens[@idx] == "-"
  370. if @tokens[@idx] == "+"
  371. @idx += 1
  372. result = AddImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionMul)
  373. elsif @tokens[@idx] == "-"
  374. @idx += 1
  375. result = SubImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionMul)
  376. else
  377. raise
  378. end
  379. end
  380. result
  381. end
  382. def parseExpressionAnd
  383. skipNewLine
  384. result = parseExpressionAdd
  385. while @tokens[@idx] == "&"
  386. @idx += 1
  387. result = AndImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAdd)
  388. end
  389. result
  390. end
  391. def parseExpression
  392. skipNewLine
  393. result = parseExpressionAnd
  394. while @tokens[@idx] == "|" or @tokens[@idx] == "^"
  395. if @tokens[@idx] == "|"
  396. @idx += 1
  397. result = OrImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAnd)
  398. elsif @tokens[@idx] == "^"
  399. @idx += 1
  400. result = XorImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAnd)
  401. else
  402. raise
  403. end
  404. end
  405. result
  406. end
  407. def parseOperand(comment)
  408. skipNewLine
  409. if couldBeExpression
  410. expr = parseExpression
  411. if @tokens[@idx] == "["
  412. parseAddress(expr)
  413. else
  414. expr
  415. end
  416. elsif @tokens[@idx] == "["
  417. parseAddress(Immediate.new(@tokens[@idx].codeOrigin, 0))
  418. elsif isLabel @tokens[@idx]
  419. result = LabelReference.new(@tokens[@idx].codeOrigin, Label.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
  420. @idx += 1
  421. result
  422. elsif isLocalLabel @tokens[@idx]
  423. result = LocalLabelReference.new(@tokens[@idx].codeOrigin, LocalLabel.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
  424. @idx += 1
  425. result
  426. else
  427. parseError(comment)
  428. end
  429. end
  430. def parseMacroVariables
  431. skipNewLine
  432. consume(/\A\(\Z/)
  433. variables = []
  434. loop {
  435. skipNewLine
  436. if @tokens[@idx] == ")"
  437. @idx += 1
  438. break
  439. elsif isIdentifier(@tokens[@idx])
  440. variables << Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
  441. @idx += 1
  442. skipNewLine
  443. if @tokens[@idx] == ")"
  444. @idx += 1
  445. break
  446. elsif @tokens[@idx] == ","
  447. @idx += 1
  448. else
  449. parseError
  450. end
  451. else
  452. parseError
  453. end
  454. }
  455. variables
  456. end
  457. def parseSequence(final, comment)
  458. firstCodeOrigin = @tokens[@idx].codeOrigin
  459. list = []
  460. loop {
  461. if (@idx == @tokens.length and not final) or (final and @tokens[@idx] =~ final)
  462. break
  463. elsif @tokens[@idx].is_a? Annotation
  464. # This is the only place where we can encounter a global
  465. # annotation, and hence need to be able to distinguish between
  466. # them.
  467. # globalAnnotations are the ones that start from column 0. All
  468. # others are considered localAnnotations. The only reason to
  469. # distinguish between them is so that we can format the output
  470. # nicely as one would expect.
  471. codeOrigin = @tokens[@idx].codeOrigin
  472. annotationOpcode = (@tokens[@idx].type == :global) ? "globalAnnotation" : "localAnnotation"
  473. list << Instruction.new(codeOrigin, annotationOpcode, [], @tokens[@idx].string)
  474. @annotation = nil
  475. @idx += 2 # Consume the newline as well.
  476. elsif @tokens[@idx] == "\n"
  477. # ignore
  478. @idx += 1
  479. elsif @tokens[@idx] == "const"
  480. @idx += 1
  481. parseError unless isVariable @tokens[@idx]
  482. variable = Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
  483. @idx += 1
  484. parseError unless @tokens[@idx] == "="
  485. @idx += 1
  486. value = parseOperand("while inside of const #{variable.name}")
  487. list << ConstDecl.new(@tokens[@idx].codeOrigin, variable, value)
  488. elsif @tokens[@idx] == "error"
  489. list << Error.new(@tokens[@idx].codeOrigin)
  490. @idx += 1
  491. elsif @tokens[@idx] == "if"
  492. codeOrigin = @tokens[@idx].codeOrigin
  493. @idx += 1
  494. skipNewLine
  495. predicate = parsePredicate
  496. consume(/\A((then)|(\n))\Z/)
  497. skipNewLine
  498. ifThenElse = IfThenElse.new(codeOrigin, predicate, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\""))
  499. list << ifThenElse
  500. while @tokens[@idx] == "elsif"
  501. codeOrigin = @tokens[@idx].codeOrigin
  502. @idx += 1
  503. skipNewLine
  504. predicate = parsePredicate
  505. consume(/\A((then)|(\n))\Z/)
  506. skipNewLine
  507. elseCase = IfThenElse.new(codeOrigin, predicate, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\""))
  508. ifThenElse.elseCase = elseCase
  509. ifThenElse = elseCase
  510. end
  511. if @tokens[@idx] == "else"
  512. @idx += 1
  513. ifThenElse.elseCase = parseSequence(/\Aend\Z/, "while inside of else case for \"if #{predicate.dump}\"")
  514. @idx += 1
  515. else
  516. parseError unless @tokens[@idx] == "end"
  517. @idx += 1
  518. end
  519. elsif @tokens[@idx] == "macro"
  520. codeOrigin = @tokens[@idx].codeOrigin
  521. @idx += 1
  522. skipNewLine
  523. parseError unless isIdentifier(@tokens[@idx])
  524. name = @tokens[@idx].string
  525. @idx += 1
  526. variables = parseMacroVariables
  527. body = parseSequence(/\Aend\Z/, "while inside of macro #{name}")
  528. @idx += 1
  529. list << Macro.new(codeOrigin, name, variables, body)
  530. elsif isInstruction @tokens[@idx]
  531. codeOrigin = @tokens[@idx].codeOrigin
  532. name = @tokens[@idx].string
  533. @idx += 1
  534. if (not final and @idx == @tokens.size) or (final and @tokens[@idx] =~ final)
  535. # Zero operand instruction, and it's the last one.
  536. list << Instruction.new(codeOrigin, name, [], @annotation)
  537. @annotation = nil
  538. break
  539. elsif @tokens[@idx].is_a? Annotation
  540. list << Instruction.new(codeOrigin, name, [], @tokens[@idx].string)
  541. @annotation = nil
  542. @idx += 2 # Consume the newline as well.
  543. elsif @tokens[@idx] == "\n"
  544. # Zero operand instruction.
  545. list << Instruction.new(codeOrigin, name, [], @annotation)
  546. @annotation = nil
  547. @idx += 1
  548. else
  549. # It's definitely an instruction, and it has at least one operand.
  550. operands = []
  551. endOfSequence = false
  552. loop {
  553. operands << parseOperand("while inside of instruction #{name}")
  554. if (not final and @idx == @tokens.size) or (final and @tokens[@idx] =~ final)
  555. # The end of the instruction and of the sequence.
  556. endOfSequence = true
  557. break
  558. elsif @tokens[@idx] == ","
  559. # Has another operand.
  560. @idx += 1
  561. elsif @tokens[@idx].is_a? Annotation
  562. @annotation = @tokens[@idx].string
  563. @idx += 2 # Consume the newline as well.
  564. break
  565. elsif @tokens[@idx] == "\n"
  566. # The end of the instruction.
  567. @idx += 1
  568. break
  569. else
  570. parseError("Expected a comma, newline, or #{final} after #{operands.last.dump}")
  571. end
  572. }
  573. list << Instruction.new(codeOrigin, name, operands, @annotation)
  574. @annotation = nil
  575. if endOfSequence
  576. break
  577. end
  578. end
  579. # Check for potential macro invocation:
  580. elsif isIdentifier @tokens[@idx]
  581. codeOrigin = @tokens[@idx].codeOrigin
  582. name = @tokens[@idx].string
  583. @idx += 1
  584. if @tokens[@idx] == "("
  585. # Macro invocation.
  586. @idx += 1
  587. operands = []
  588. skipNewLine
  589. if @tokens[@idx] == ")"
  590. @idx += 1
  591. else
  592. loop {
  593. skipNewLine
  594. if @tokens[@idx] == "macro"
  595. # It's a macro lambda!
  596. codeOriginInner = @tokens[@idx].codeOrigin
  597. @idx += 1
  598. variables = parseMacroVariables
  599. body = parseSequence(/\Aend\Z/, "while inside of anonymous macro passed as argument to #{name}")
  600. @idx += 1
  601. operands << Macro.new(codeOriginInner, nil, variables, body)
  602. else
  603. operands << parseOperand("while inside of macro call to #{name}")
  604. end
  605. skipNewLine
  606. if @tokens[@idx] == ")"
  607. @idx += 1
  608. break
  609. elsif @tokens[@idx] == ","
  610. @idx += 1
  611. else
  612. parseError "Unexpected #{@tokens[@idx].string.inspect} while parsing invocation of macro #{name}"
  613. end
  614. }
  615. end
  616. # Check if there's a trailing annotation after the macro invoke:
  617. if @tokens[@idx].is_a? Annotation
  618. @annotation = @tokens[@idx].string
  619. @idx += 2 # Consume the newline as well.
  620. end
  621. list << MacroCall.new(codeOrigin, name, operands, @annotation)
  622. @annotation = nil
  623. else
  624. parseError "Expected \"(\" after #{name}"
  625. end
  626. elsif isLabel @tokens[@idx] or isLocalLabel @tokens[@idx]
  627. codeOrigin = @tokens[@idx].codeOrigin
  628. name = @tokens[@idx].string
  629. @idx += 1
  630. parseError unless @tokens[@idx] == ":"
  631. # It's a label.
  632. if isLabel name
  633. list << Label.forName(codeOrigin, name)
  634. else
  635. list << LocalLabel.forName(codeOrigin, name)
  636. end
  637. @idx += 1
  638. elsif @tokens[@idx] == "include"
  639. @idx += 1
  640. parseError unless isIdentifier(@tokens[@idx])
  641. moduleName = @tokens[@idx].string
  642. fileName = @tokens[@idx].codeOrigin.fileName.dirname + (moduleName + ".asm")
  643. @idx += 1
  644. $stderr.puts "offlineasm: Including file #{fileName}"
  645. list << parse(fileName)
  646. else
  647. parseError "Expecting terminal #{final} #{comment}"
  648. end
  649. }
  650. Sequence.new(firstCodeOrigin, list)
  651. end
  652. end
  653. def parseData(data, fileName)
  654. parser = Parser.new(data, fileName)
  655. parser.parseSequence(nil, "")
  656. end
  657. def parse(fileName)
  658. parseData(IO::read(fileName), fileName)
  659. end
  660. def parseHash(fileName)
  661. dirHash(Pathname.new(fileName).dirname, /\.asm$/)
  662. end