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