]>
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 | ||
81345200 A |
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 | ||
6fe7ccc8 A |
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 | ||
93a37866 A |
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 | ||
6fe7ccc8 A |
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 | |
93a37866 A |
124 | annotation = nil |
125 | whitespaceFound = false | |
6fe7ccc8 A |
126 | while not str.empty? |
127 | case str | |
128 | when /\A\#([^\n]*)/ | |
129 | # comment, ignore | |
93a37866 A |
130 | when /\A\/\/\ ?([^\n]*)/ |
131 | # annotation | |
132 | annotation = $1 | |
133 | annotationType = whitespaceFound ? :local : :global | |
6fe7ccc8 | 134 | when /\A\n/ |
93a37866 A |
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 | |
6fe7ccc8 A |
145 | result << Token.new(CodeOrigin.new(fileName, lineNumber), $&) |
146 | lineNumber += 1 | |
81345200 | 147 | when /\A[a-zA-Z]([a-zA-Z0-9_.]*)/ |
6fe7ccc8 A |
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 | |
93a37866 A |
155 | whitespaceFound = true |
156 | str = $~.post_match | |
157 | next | |
6fe7ccc8 A |
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 | |
93a37866 | 171 | whitespaceFound = false |
6fe7ccc8 A |
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) | |
81345200 | 186 | INSTRUCTION_SET.member? token.string |
6fe7ccc8 A |
187 | end |
188 | ||
189 | def isKeyword(token) | |
81345200 | 190 | token =~ /\A((true)|(false)|(if)|(then)|(else)|(elsif)|(end)|(and)|(or)|(not)|(global)|(macro)|(const)|(sizeof)|(error)|(include))\Z/ or |
6fe7ccc8 | 191 | token =~ REGISTER_PATTERN or |
81345200 | 192 | isInstruction(token) |
6fe7ccc8 A |
193 | end |
194 | ||
195 | def isIdentifier(token) | |
81345200 | 196 | token =~ /\A[a-zA-Z]([a-zA-Z0-9_.]*)\Z/ and not isKeyword(token) |
6fe7ccc8 A |
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 | |
93a37866 | 224 | @annotation = nil |
6fe7ccc8 A |
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" | |
93a37866 | 256 | codeOrigin = @tokens[@idx].codeOrigin |
6fe7ccc8 | 257 | @idx += 1 |
93a37866 | 258 | Not.new(codeOrigin, parsePredicateAtom) |
6fe7ccc8 A |
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('::')) | |
81345200 A |
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 | |
6fe7ccc8 A |
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 | |
93a37866 A |
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. | |
6fe7ccc8 A |
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) | |
81345200 A |
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) | |
6fe7ccc8 A |
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. | |
93a37866 A |
628 | list << Instruction.new(codeOrigin, name, [], @annotation) |
629 | @annotation = nil | |
6fe7ccc8 | 630 | break |
93a37866 A |
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. | |
6fe7ccc8 A |
635 | elsif @tokens[@idx] == "\n" |
636 | # Zero operand instruction. | |
93a37866 A |
637 | list << Instruction.new(codeOrigin, name, [], @annotation) |
638 | @annotation = nil | |
6fe7ccc8 A |
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 | |
93a37866 A |
653 | elsif @tokens[@idx].is_a? Annotation |
654 | @annotation = @tokens[@idx].string | |
655 | @idx += 2 # Consume the newline as well. | |
656 | break | |
6fe7ccc8 A |
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 | } | |
93a37866 A |
665 | list << Instruction.new(codeOrigin, name, operands, @annotation) |
666 | @annotation = nil | |
6fe7ccc8 A |
667 | if endOfSequence |
668 | break | |
669 | end | |
670 | end | |
93a37866 A |
671 | |
672 | # Check for potential macro invocation: | |
6fe7ccc8 A |
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 | |
93a37866 A |
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 | |
6fe7ccc8 A |
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 | |
81345200 | 726 | list << Label.forName(codeOrigin, name, true) |
6fe7ccc8 A |
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 | |
81345200 | 735 | fileName = IncludeFile.new(moduleName, @tokens[@idx].codeOrigin.fileName.dirname).fileName |
6fe7ccc8 A |
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 | |
81345200 A |
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 | |
6fe7ccc8 A |
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) | |
81345200 A |
780 | parser = Parser.new(IO::read(fileName), fileName) |
781 | fileList = parser.parseIncludes(nil, "") | |
782 | fileListHash(fileList) | |
6fe7ccc8 A |
783 | end |
784 |