]> git.saurik.com Git - apple/javascriptcore.git/blob - offlineasm/parser.rb
JavaScriptCore-7601.1.46.3.tar.gz
[apple/javascriptcore.git] / offlineasm / parser.rb
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
24 require "config"
25 require "ast"
26 require "instructions"
27 require "pathname"
28 require "registers"
29 require "self_hash"
30
31 class 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
42 end
43
44 class 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
72 end
73
74 class 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
105 end
106
107 class Annotation
108 attr_reader :codeOrigin, :type, :string
109 def initialize(codeOrigin, type, string)
110 @codeOrigin = codeOrigin
111 @type = type
112 @string = string
113 end
114 end
115
116 #
117 # The lexer. Takes a string and returns an array of tokens.
118 #
119
120 def 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 when /\A".*"/
169 result << Token.new(CodeOrigin.new(fileName, lineNumber), $&)
170 else
171 raise "Lexer error at #{CodeOrigin.new(fileName, lineNumber).to_s}, unexpected sequence #{str[0..20].inspect}"
172 end
173 whitespaceFound = false
174 str = $~.post_match
175 end
176 result
177 end
178
179 #
180 # Token identification.
181 #
182
183 def isRegister(token)
184 token =~ REGISTER_PATTERN
185 end
186
187 def isInstruction(token)
188 INSTRUCTION_SET.member? token.string
189 end
190
191 def isKeyword(token)
192 token =~ /\A((true)|(false)|(if)|(then)|(else)|(elsif)|(end)|(and)|(or)|(not)|(global)|(macro)|(const)|(sizeof)|(error)|(include))\Z/ or
193 token =~ REGISTER_PATTERN or
194 isInstruction(token)
195 end
196
197 def isIdentifier(token)
198 token =~ /\A[a-zA-Z]([a-zA-Z0-9_.]*)\Z/ and not isKeyword(token)
199 end
200
201 def isLabel(token)
202 token =~ /\A_([a-zA-Z0-9_]*)\Z/
203 end
204
205 def isLocalLabel(token)
206 token =~ /\A\.([a-zA-Z0-9_]*)\Z/
207 end
208
209 def isVariable(token)
210 isIdentifier(token) or isRegister(token)
211 end
212
213 def isInteger(token)
214 token =~ /\A[0-9]/
215 end
216
217 def isString(token)
218 token =~ /\A".*"/
219 end
220
221 #
222 # The parser. Takes an array of tokens and returns an AST. Methods
223 # other than parse(tokens) are not for public consumption.
224 #
225
226 class Parser
227 def initialize(data, fileName)
228 @tokens = lex(data, fileName)
229 @idx = 0
230 @annotation = nil
231 end
232
233 def parseError(*comment)
234 if @tokens[@idx]
235 @tokens[@idx].parseError(*comment)
236 else
237 if comment.empty?
238 raise "Parse error at end of file"
239 else
240 raise "Parse error at end of file: #{comment[0]}"
241 end
242 end
243 end
244
245 def consume(regexp)
246 if regexp
247 parseError unless @tokens[@idx] =~ regexp
248 else
249 parseError unless @idx == @tokens.length
250 end
251 @idx += 1
252 end
253
254 def skipNewLine
255 while @tokens[@idx] == "\n"
256 @idx += 1
257 end
258 end
259
260 def parsePredicateAtom
261 if @tokens[@idx] == "not"
262 codeOrigin = @tokens[@idx].codeOrigin
263 @idx += 1
264 Not.new(codeOrigin, parsePredicateAtom)
265 elsif @tokens[@idx] == "("
266 @idx += 1
267 skipNewLine
268 result = parsePredicate
269 parseError unless @tokens[@idx] == ")"
270 @idx += 1
271 result
272 elsif @tokens[@idx] == "true"
273 result = True.instance
274 @idx += 1
275 result
276 elsif @tokens[@idx] == "false"
277 result = False.instance
278 @idx += 1
279 result
280 elsif isIdentifier @tokens[@idx]
281 result = Setting.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
282 @idx += 1
283 result
284 else
285 parseError
286 end
287 end
288
289 def parsePredicateAnd
290 result = parsePredicateAtom
291 while @tokens[@idx] == "and"
292 codeOrigin = @tokens[@idx].codeOrigin
293 @idx += 1
294 skipNewLine
295 right = parsePredicateAtom
296 result = And.new(codeOrigin, result, right)
297 end
298 result
299 end
300
301 def parsePredicate
302 # some examples of precedence:
303 # not a and b -> (not a) and b
304 # a and b or c -> (a and b) or c
305 # a or b and c -> a or (b and c)
306
307 result = parsePredicateAnd
308 while @tokens[@idx] == "or"
309 codeOrigin = @tokens[@idx].codeOrigin
310 @idx += 1
311 skipNewLine
312 right = parsePredicateAnd
313 result = Or.new(codeOrigin, result, right)
314 end
315 result
316 end
317
318 def parseVariable
319 if isRegister(@tokens[@idx])
320 if @tokens[@idx] =~ FPR_PATTERN
321 result = FPRegisterID.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
322 else
323 result = RegisterID.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
324 end
325 elsif isIdentifier(@tokens[@idx])
326 result = Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
327 else
328 parseError
329 end
330 @idx += 1
331 result
332 end
333
334 def parseAddress(offset)
335 parseError unless @tokens[@idx] == "["
336 codeOrigin = @tokens[@idx].codeOrigin
337
338 # Three possibilities:
339 # [] -> AbsoluteAddress
340 # [a] -> Address
341 # [a,b] -> BaseIndex with scale = 1
342 # [a,b,c] -> BaseIndex
343
344 @idx += 1
345 if @tokens[@idx] == "]"
346 @idx += 1
347 return AbsoluteAddress.new(codeOrigin, offset)
348 end
349 a = parseVariable
350 if @tokens[@idx] == "]"
351 result = Address.new(codeOrigin, a, offset)
352 else
353 parseError unless @tokens[@idx] == ","
354 @idx += 1
355 b = parseVariable
356 if @tokens[@idx] == "]"
357 result = BaseIndex.new(codeOrigin, a, b, 1, offset)
358 else
359 parseError unless @tokens[@idx] == ","
360 @idx += 1
361 parseError unless ["1", "2", "4", "8"].member? @tokens[@idx].string
362 c = @tokens[@idx].string.to_i
363 @idx += 1
364 parseError unless @tokens[@idx] == "]"
365 result = BaseIndex.new(codeOrigin, a, b, c, offset)
366 end
367 end
368 @idx += 1
369 result
370 end
371
372 def parseColonColon
373 skipNewLine
374 codeOrigin = @tokens[@idx].codeOrigin
375 parseError unless isIdentifier @tokens[@idx]
376 names = [@tokens[@idx].string]
377 @idx += 1
378 while @tokens[@idx] == "::"
379 @idx += 1
380 parseError unless isIdentifier @tokens[@idx]
381 names << @tokens[@idx].string
382 @idx += 1
383 end
384 raise if names.empty?
385 [codeOrigin, names]
386 end
387
388 def parseExpressionAtom
389 skipNewLine
390 if @tokens[@idx] == "-"
391 @idx += 1
392 NegImmediate.new(@tokens[@idx - 1].codeOrigin, parseExpressionAtom)
393 elsif @tokens[@idx] == "~"
394 @idx += 1
395 BitnotImmediate.new(@tokens[@idx - 1].codeOrigin, parseExpressionAtom)
396 elsif @tokens[@idx] == "("
397 @idx += 1
398 result = parseExpression
399 parseError unless @tokens[@idx] == ")"
400 @idx += 1
401 result
402 elsif isInteger @tokens[@idx]
403 result = Immediate.new(@tokens[@idx].codeOrigin, @tokens[@idx].string.to_i)
404 @idx += 1
405 result
406 elsif isString @tokens[@idx]
407 result = StringLiteral.new(@tokens[@idx].codeOrigin, @tokens[@idx].string)
408 @idx += 1
409 result
410 elsif isIdentifier @tokens[@idx]
411 codeOrigin, names = parseColonColon
412 if names.size > 1
413 StructOffset.forField(codeOrigin, names[0..-2].join('::'), names[-1])
414 else
415 Variable.forName(codeOrigin, names[0])
416 end
417 elsif isRegister @tokens[@idx]
418 parseVariable
419 elsif @tokens[@idx] == "sizeof"
420 @idx += 1
421 codeOrigin, names = parseColonColon
422 Sizeof.forName(codeOrigin, names.join('::'))
423 elsif isLabel @tokens[@idx]
424 result = LabelReference.new(@tokens[@idx].codeOrigin, Label.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
425 @idx += 1
426 result
427 elsif isLocalLabel @tokens[@idx]
428 result = LocalLabelReference.new(@tokens[@idx].codeOrigin, LocalLabel.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
429 @idx += 1
430 result
431 else
432 parseError
433 end
434 end
435
436 def parseExpressionMul
437 skipNewLine
438 result = parseExpressionAtom
439 while @tokens[@idx] == "*"
440 if @tokens[@idx] == "*"
441 @idx += 1
442 result = MulImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAtom)
443 else
444 raise
445 end
446 end
447 result
448 end
449
450 def couldBeExpression
451 @tokens[@idx] == "-" or @tokens[@idx] == "~" or @tokens[@idx] == "sizeof" or isInteger(@tokens[@idx]) or isString(@tokens[@idx]) or isVariable(@tokens[@idx]) or @tokens[@idx] == "("
452 end
453
454 def parseExpressionAdd
455 skipNewLine
456 result = parseExpressionMul
457 while @tokens[@idx] == "+" or @tokens[@idx] == "-"
458 if @tokens[@idx] == "+"
459 @idx += 1
460 result = AddImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionMul)
461 elsif @tokens[@idx] == "-"
462 @idx += 1
463 result = SubImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionMul)
464 else
465 raise
466 end
467 end
468 result
469 end
470
471 def parseExpressionAnd
472 skipNewLine
473 result = parseExpressionAdd
474 while @tokens[@idx] == "&"
475 @idx += 1
476 result = AndImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAdd)
477 end
478 result
479 end
480
481 def parseExpression
482 skipNewLine
483 result = parseExpressionAnd
484 while @tokens[@idx] == "|" or @tokens[@idx] == "^"
485 if @tokens[@idx] == "|"
486 @idx += 1
487 result = OrImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAnd)
488 elsif @tokens[@idx] == "^"
489 @idx += 1
490 result = XorImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAnd)
491 else
492 raise
493 end
494 end
495 result
496 end
497
498 def parseOperand(comment)
499 skipNewLine
500 if couldBeExpression
501 expr = parseExpression
502 if @tokens[@idx] == "["
503 parseAddress(expr)
504 else
505 expr
506 end
507 elsif @tokens[@idx] == "["
508 parseAddress(Immediate.new(@tokens[@idx].codeOrigin, 0))
509 elsif isLabel @tokens[@idx]
510 result = LabelReference.new(@tokens[@idx].codeOrigin, Label.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
511 @idx += 1
512 result
513 elsif isLocalLabel @tokens[@idx]
514 result = LocalLabelReference.new(@tokens[@idx].codeOrigin, LocalLabel.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
515 @idx += 1
516 result
517 else
518 parseError(comment)
519 end
520 end
521
522 def parseMacroVariables
523 skipNewLine
524 consume(/\A\(\Z/)
525 variables = []
526 loop {
527 skipNewLine
528 if @tokens[@idx] == ")"
529 @idx += 1
530 break
531 elsif isIdentifier(@tokens[@idx])
532 variables << Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
533 @idx += 1
534 skipNewLine
535 if @tokens[@idx] == ")"
536 @idx += 1
537 break
538 elsif @tokens[@idx] == ","
539 @idx += 1
540 else
541 parseError
542 end
543 else
544 parseError
545 end
546 }
547 variables
548 end
549
550 def parseSequence(final, comment)
551 firstCodeOrigin = @tokens[@idx].codeOrigin
552 list = []
553 loop {
554 if (@idx == @tokens.length and not final) or (final and @tokens[@idx] =~ final)
555 break
556 elsif @tokens[@idx].is_a? Annotation
557 # This is the only place where we can encounter a global
558 # annotation, and hence need to be able to distinguish between
559 # them.
560 # globalAnnotations are the ones that start from column 0. All
561 # others are considered localAnnotations. The only reason to
562 # distinguish between them is so that we can format the output
563 # nicely as one would expect.
564
565 codeOrigin = @tokens[@idx].codeOrigin
566 annotationOpcode = (@tokens[@idx].type == :global) ? "globalAnnotation" : "localAnnotation"
567 list << Instruction.new(codeOrigin, annotationOpcode, [], @tokens[@idx].string)
568 @annotation = nil
569 @idx += 2 # Consume the newline as well.
570 elsif @tokens[@idx] == "\n"
571 # ignore
572 @idx += 1
573 elsif @tokens[@idx] == "const"
574 @idx += 1
575 parseError unless isVariable @tokens[@idx]
576 variable = Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
577 @idx += 1
578 parseError unless @tokens[@idx] == "="
579 @idx += 1
580 value = parseOperand("while inside of const #{variable.name}")
581 list << ConstDecl.new(@tokens[@idx].codeOrigin, variable, value)
582 elsif @tokens[@idx] == "error"
583 list << Error.new(@tokens[@idx].codeOrigin)
584 @idx += 1
585 elsif @tokens[@idx] == "if"
586 codeOrigin = @tokens[@idx].codeOrigin
587 @idx += 1
588 skipNewLine
589 predicate = parsePredicate
590 consume(/\A((then)|(\n))\Z/)
591 skipNewLine
592 ifThenElse = IfThenElse.new(codeOrigin, predicate, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\""))
593 list << ifThenElse
594 while @tokens[@idx] == "elsif"
595 codeOrigin = @tokens[@idx].codeOrigin
596 @idx += 1
597 skipNewLine
598 predicate = parsePredicate
599 consume(/\A((then)|(\n))\Z/)
600 skipNewLine
601 elseCase = IfThenElse.new(codeOrigin, predicate, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\""))
602 ifThenElse.elseCase = elseCase
603 ifThenElse = elseCase
604 end
605 if @tokens[@idx] == "else"
606 @idx += 1
607 ifThenElse.elseCase = parseSequence(/\Aend\Z/, "while inside of else case for \"if #{predicate.dump}\"")
608 @idx += 1
609 else
610 parseError unless @tokens[@idx] == "end"
611 @idx += 1
612 end
613 elsif @tokens[@idx] == "macro"
614 codeOrigin = @tokens[@idx].codeOrigin
615 @idx += 1
616 skipNewLine
617 parseError unless isIdentifier(@tokens[@idx])
618 name = @tokens[@idx].string
619 @idx += 1
620 variables = parseMacroVariables
621 body = parseSequence(/\Aend\Z/, "while inside of macro #{name}")
622 @idx += 1
623 list << Macro.new(codeOrigin, name, variables, body)
624 elsif @tokens[@idx] == "global"
625 codeOrigin = @tokens[@idx].codeOrigin
626 @idx += 1
627 skipNewLine
628 parseError unless isLabel(@tokens[@idx])
629 name = @tokens[@idx].string
630 @idx += 1
631 Label.setAsGlobal(codeOrigin, name)
632 elsif isInstruction @tokens[@idx]
633 codeOrigin = @tokens[@idx].codeOrigin
634 name = @tokens[@idx].string
635 @idx += 1
636 if (not final and @idx == @tokens.size) or (final and @tokens[@idx] =~ final)
637 # Zero operand instruction, and it's the last one.
638 list << Instruction.new(codeOrigin, name, [], @annotation)
639 @annotation = nil
640 break
641 elsif @tokens[@idx].is_a? Annotation
642 list << Instruction.new(codeOrigin, name, [], @tokens[@idx].string)
643 @annotation = nil
644 @idx += 2 # Consume the newline as well.
645 elsif @tokens[@idx] == "\n"
646 # Zero operand instruction.
647 list << Instruction.new(codeOrigin, name, [], @annotation)
648 @annotation = nil
649 @idx += 1
650 else
651 # It's definitely an instruction, and it has at least one operand.
652 operands = []
653 endOfSequence = false
654 loop {
655 operands << parseOperand("while inside of instruction #{name}")
656 if (not final and @idx == @tokens.size) or (final and @tokens[@idx] =~ final)
657 # The end of the instruction and of the sequence.
658 endOfSequence = true
659 break
660 elsif @tokens[@idx] == ","
661 # Has another operand.
662 @idx += 1
663 elsif @tokens[@idx].is_a? Annotation
664 @annotation = @tokens[@idx].string
665 @idx += 2 # Consume the newline as well.
666 break
667 elsif @tokens[@idx] == "\n"
668 # The end of the instruction.
669 @idx += 1
670 break
671 else
672 parseError("Expected a comma, newline, or #{final} after #{operands.last.dump}")
673 end
674 }
675 list << Instruction.new(codeOrigin, name, operands, @annotation)
676 @annotation = nil
677 if endOfSequence
678 break
679 end
680 end
681
682 # Check for potential macro invocation:
683 elsif isIdentifier @tokens[@idx]
684 codeOrigin = @tokens[@idx].codeOrigin
685 name = @tokens[@idx].string
686 @idx += 1
687 if @tokens[@idx] == "("
688 # Macro invocation.
689 @idx += 1
690 operands = []
691 skipNewLine
692 if @tokens[@idx] == ")"
693 @idx += 1
694 else
695 loop {
696 skipNewLine
697 if @tokens[@idx] == "macro"
698 # It's a macro lambda!
699 codeOriginInner = @tokens[@idx].codeOrigin
700 @idx += 1
701 variables = parseMacroVariables
702 body = parseSequence(/\Aend\Z/, "while inside of anonymous macro passed as argument to #{name}")
703 @idx += 1
704 operands << Macro.new(codeOriginInner, nil, variables, body)
705 else
706 operands << parseOperand("while inside of macro call to #{name}")
707 end
708 skipNewLine
709 if @tokens[@idx] == ")"
710 @idx += 1
711 break
712 elsif @tokens[@idx] == ","
713 @idx += 1
714 else
715 parseError "Unexpected #{@tokens[@idx].string.inspect} while parsing invocation of macro #{name}"
716 end
717 }
718 end
719 # Check if there's a trailing annotation after the macro invoke:
720 if @tokens[@idx].is_a? Annotation
721 @annotation = @tokens[@idx].string
722 @idx += 2 # Consume the newline as well.
723 end
724 list << MacroCall.new(codeOrigin, name, operands, @annotation)
725 @annotation = nil
726 else
727 parseError "Expected \"(\" after #{name}"
728 end
729 elsif isLabel @tokens[@idx] or isLocalLabel @tokens[@idx]
730 codeOrigin = @tokens[@idx].codeOrigin
731 name = @tokens[@idx].string
732 @idx += 1
733 parseError unless @tokens[@idx] == ":"
734 # It's a label.
735 if isLabel name
736 list << Label.forName(codeOrigin, name, true)
737 else
738 list << LocalLabel.forName(codeOrigin, name)
739 end
740 @idx += 1
741 elsif @tokens[@idx] == "include"
742 @idx += 1
743 parseError unless isIdentifier(@tokens[@idx])
744 moduleName = @tokens[@idx].string
745 fileName = IncludeFile.new(moduleName, @tokens[@idx].codeOrigin.fileName.dirname).fileName
746 @idx += 1
747 $stderr.puts "offlineasm: Including file #{fileName}"
748 list << parse(fileName)
749 else
750 parseError "Expecting terminal #{final} #{comment}"
751 end
752 }
753 Sequence.new(firstCodeOrigin, list)
754 end
755
756 def parseIncludes(final, comment)
757 firstCodeOrigin = @tokens[@idx].codeOrigin
758 fileList = []
759 fileList << @tokens[@idx].codeOrigin.fileName
760 loop {
761 if (@idx == @tokens.length and not final) or (final and @tokens[@idx] =~ final)
762 break
763 elsif @tokens[@idx] == "include"
764 @idx += 1
765 parseError unless isIdentifier(@tokens[@idx])
766 moduleName = @tokens[@idx].string
767 fileName = IncludeFile.new(moduleName, @tokens[@idx].codeOrigin.fileName.dirname).fileName
768 @idx += 1
769
770 fileList << fileName
771 else
772 @idx += 1
773 end
774 }
775
776 return fileList
777 end
778 end
779
780 def parseData(data, fileName)
781 parser = Parser.new(data, fileName)
782 parser.parseSequence(nil, "")
783 end
784
785 def parse(fileName)
786 parseData(IO::read(fileName), fileName)
787 end
788
789 def parseHash(fileName)
790 parser = Parser.new(IO::read(fileName), fileName)
791 fileList = parser.parseIncludes(nil, "")
792 fileListHash(fileList)
793 end
794