]> git.saurik.com Git - apple/javascriptcore.git/blob - offlineasm/parser.rb
JavaScriptCore-7600.1.4.11.8.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 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
175 end
176
177 #
178 # Token identification.
179 #
180
181 def isRegister(token)
182 token =~ REGISTER_PATTERN
183 end
184
185 def isInstruction(token)
186 INSTRUCTION_SET.member? token.string
187 end
188
189 def 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)
193 end
194
195 def isIdentifier(token)
196 token =~ /\A[a-zA-Z]([a-zA-Z0-9_.]*)\Z/ and not isKeyword(token)
197 end
198
199 def isLabel(token)
200 token =~ /\A_([a-zA-Z0-9_]*)\Z/
201 end
202
203 def isLocalLabel(token)
204 token =~ /\A\.([a-zA-Z0-9_]*)\Z/
205 end
206
207 def isVariable(token)
208 isIdentifier(token) or isRegister(token)
209 end
210
211 def isInteger(token)
212 token =~ /\A[0-9]/
213 end
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
220 class 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
768 end
769
770 def parseData(data, fileName)
771 parser = Parser.new(data, fileName)
772 parser.parseSequence(nil, "")
773 end
774
775 def parse(fileName)
776 parseData(IO::read(fileName), fileName)
777 end
778
779 def parseHash(fileName)
780 parser = Parser.new(IO::read(fileName), fileName)
781 fileList = parser.parseIncludes(nil, "")
782 fileListHash(fileList)
783 end
784