]>
git.saurik.com Git - apple/javascriptcore.git/blob - offlineasm/parser.rb
1 # Copyright (C) 2011 Apple Inc. All rights reserved.
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
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.
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.
26 require "instructions"
32 attr_reader
:fileName, :lineNumber
34 def initialize(fileName
, lineNumber
)
36 @lineNumber = lineNumber
40 "#{fileName}:#{lineNumber}"
49 def initialize(moduleName
, defaultDir
)
53 fileName
= includePath +
(moduleName +
".asm")
54 directory
= includePath
unless not File
.file
?(fileName
)
57 directory
= defaultDir
60 @fileName = directory +
(moduleName +
".asm")
63 def self.processIncludeOptions()
65 path
= ARGV.shift
[2..-1]
69 @
@includeDirs << (path +
"/")
75 attr_reader
:codeOrigin, :string
77 def initialize(codeOrigin
, string
)
78 @codeOrigin = codeOrigin
84 @string == other
.string
95 "#{@string.inspect} at #{codeOrigin}"
98 def parseError(*comment
)
100 raise "Parse error: #{to_s}"
102 raise "Parse error: #{to_s}: #{comment[0]}"
108 attr_reader
:codeOrigin, :type, :string
109 def initialize(codeOrigin
, type
, string
)
110 @codeOrigin = codeOrigin
117 # The lexer. Takes a string and returns an array of tokens.
120 def lex(str
, fileName
)
121 fileName
= Pathname
.new(fileName
)
125 whitespaceFound
= false
130 when /\A\/\
/\ ?([^\n]*)/
133 annotationType
= whitespaceFound
? :local : :global
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.
141 result
<< Annotation
.new(CodeOrigin
.new(fileName
, lineNumber
),
142 annotationType
, annotation
)
145 result
<< Token
.new(CodeOrigin
.new(fileName
, lineNumber
), $
&)
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
), $
&)
155 whitespaceFound
= true
158 when /\A0x([0-9a-fA-F]+)/
159 result
<< Token
.new(CodeOrigin
.new(fileName
, lineNumber
), $
&.hex
.to_s
)
161 result
<< Token
.new(CodeOrigin
.new(fileName
, lineNumber
), $
&.oct
.to_s
)
163 result
<< Token
.new(CodeOrigin
.new(fileName
, lineNumber
), $
&)
165 result
<< Token
.new(CodeOrigin
.new(fileName
, lineNumber
), $
&)
166 when /\A[:,\(\)\[\]=\+\-~\|&^*]/
167 result
<< Token
.new(CodeOrigin
.new(fileName
, lineNumber
), $
&)
169 result
<< Token
.new(CodeOrigin
.new(fileName
, lineNumber
), $
&)
171 raise "Lexer error at #{CodeOrigin.new(fileName, lineNumber).to_s}, unexpected sequence #{str[0..20].inspect}"
173 whitespaceFound
= false
180 # Token identification.
183 def isRegister(token
)
184 token
=~ REGISTER_PATTERN
187 def isInstruction(token
)
188 INSTRUCTION_SET
.member
? token
.string
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
197 def isIdentifier(token
)
198 token
=~
/\A[a-zA-Z]([a-zA-Z0-9_.]*)\Z/ and not isKeyword(token
)
202 token
=~
/\A_([a-zA-Z0-9_]*)\Z/
205 def isLocalLabel(token
)
206 token
=~
/\A\.([a-zA-Z0-9_]*)\Z/
209 def isVariable(token
)
210 isIdentifier(token
) or isRegister(token
)
222 # The parser. Takes an array of tokens and returns an AST. Methods
223 # other than parse(tokens) are not for public consumption.
227 def initialize(data, fileName
)
228 @tokens = lex(data, fileName
)
233 def parseError(*comment
)
235 @tokens[@idx].parseError(*comment
)
238 raise "Parse error at end of file"
240 raise "Parse error at end of file: #{comment[0]}"
247 parseError
unless @tokens[@idx] =~ regexp
249 parseError
unless @idx == @tokens.length
255 while @tokens[@idx] == "\n"
260 def parsePredicateAtom
261 if @tokens[@idx] == "not"
262 codeOrigin
= @tokens[@idx].codeOrigin
264 Not
.new(codeOrigin
, parsePredicateAtom
)
265 elsif @tokens[@idx] == "("
268 result
= parsePredicate
269 parseError
unless @tokens[@idx] == ")"
272 elsif @tokens[@idx] == "true"
273 result
= True
.instance
276 elsif @tokens[@idx] == "false"
277 result
= False
.instance
280 elsif isIdentifier
@tokens[@idx]
281 result
= Setting
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
)
289 def parsePredicateAnd
290 result
= parsePredicateAtom
291 while @tokens[@idx] == "and"
292 codeOrigin
= @tokens[@idx].codeOrigin
295 right
= parsePredicateAtom
296 result
= And
.new(codeOrigin
, result
, right
)
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)
307 result
= parsePredicateAnd
308 while @tokens[@idx] == "or"
309 codeOrigin
= @tokens[@idx].codeOrigin
312 right
= parsePredicateAnd
313 result
= Or
.new(codeOrigin
, result
, right
)
319 if isRegister(@tokens[@idx])
320 if @tokens[@idx] =~ FPR_PATTERN
321 result
= FPRegisterID
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
)
323 result
= RegisterID
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
)
325 elsif isIdentifier(@tokens[@idx])
326 result
= Variable
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
)
334 def parseAddress(offset
)
335 parseError
unless @tokens[@idx] == "["
336 codeOrigin
= @tokens[@idx].codeOrigin
338 # Three possibilities:
339 # [] -> AbsoluteAddress
341 # [a,b] -> BaseIndex with scale = 1
342 # [a,b,c] -> BaseIndex
345 if @tokens[@idx] == "]"
347 return AbsoluteAddress
.new(codeOrigin
, offset
)
350 if @tokens[@idx] == "]"
351 result
= Address
.new(codeOrigin
, a
, offset
)
353 parseError
unless @tokens[@idx] == ","
356 if @tokens[@idx] == "]"
357 result
= BaseIndex
.new(codeOrigin
, a
, b
, 1, offset
)
359 parseError
unless @tokens[@idx] == ","
361 parseError
unless ["1", "2", "4", "8"].member
? @tokens[@idx].string
362 c
= @tokens[@idx].string
.to_i
364 parseError
unless @tokens[@idx] == "]"
365 result
= BaseIndex
.new(codeOrigin
, a
, b
, c
, offset
)
374 codeOrigin
= @tokens[@idx].codeOrigin
375 parseError
unless isIdentifier
@tokens[@idx]
376 names
= [@tokens[@idx].string
]
378 while @tokens[@idx] == "::"
380 parseError
unless isIdentifier
@tokens[@idx]
381 names
<< @tokens[@idx].string
384 raise if names
.empty
?
388 def parseExpressionAtom
390 if @tokens[@idx] == "-"
392 NegImmediate
.new(@tokens[@idx - 1].codeOrigin
, parseExpressionAtom
)
393 elsif @tokens[@idx] == "~"
395 BitnotImmediate
.new(@tokens[@idx - 1].codeOrigin
, parseExpressionAtom
)
396 elsif @tokens[@idx] == "("
398 result
= parseExpression
399 parseError
unless @tokens[@idx] == ")"
402 elsif isInteger
@tokens[@idx]
403 result
= Immediate
.new(@tokens[@idx].codeOrigin
, @tokens[@idx].string
.to_i
)
406 elsif isString
@tokens[@idx]
407 result
= StringLiteral
.new(@tokens[@idx].codeOrigin
, @tokens[@idx].string
)
410 elsif isIdentifier
@tokens[@idx]
411 codeOrigin
, names
= parseColonColon
413 StructOffset
.forField(codeOrigin
, names
[0..-2].join('::'), names
[-1])
415 Variable
.forName(codeOrigin
, names
[0])
417 elsif isRegister
@tokens[@idx]
419 elsif @tokens[@idx] == "sizeof"
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
))
427 elsif isLocalLabel
@tokens[@idx]
428 result
= LocalLabelReference
.new(@tokens[@idx].codeOrigin
, LocalLabel
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
))
436 def parseExpressionMul
438 result
= parseExpressionAtom
439 while @tokens[@idx] == "*"
440 if @tokens[@idx] == "*"
442 result
= MulImmediates
.new(@tokens[@idx - 1].codeOrigin
, result
, parseExpressionAtom
)
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] == "("
454 def parseExpressionAdd
456 result
= parseExpressionMul
457 while @tokens[@idx] == "+" or @tokens[@idx] == "-"
458 if @tokens[@idx] == "+
"
460 result
= AddImmediates
.new(@tokens[@idx - 1].codeOrigin
, result
, parseExpressionMul
)
461 elsif @tokens[@idx] == "-"
463 result
= SubImmediates
.new(@tokens[@idx - 1].codeOrigin
, result
, parseExpressionMul
)
471 def parseExpressionAnd
473 result
= parseExpressionAdd
474 while @tokens[@idx] == "&"
476 result
= AndImmediates
.new(@tokens[@idx - 1].codeOrigin
, result
, parseExpressionAdd
)
483 result
= parseExpressionAnd
484 while @tokens[@idx] == "|" or @tokens[@idx] == "^"
485 if @tokens[@idx] == "|"
487 result
= OrImmediates
.new(@tokens[@idx - 1].codeOrigin
, result
, parseExpressionAnd
)
488 elsif @tokens[@idx] == "^"
490 result
= XorImmediates
.new(@tokens[@idx - 1].codeOrigin
, result
, parseExpressionAnd
)
498 def parseOperand(comment
)
501 expr
= parseExpression
502 if @tokens[@idx] == "["
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
))
513 elsif isLocalLabel
@tokens[@idx]
514 result
= LocalLabelReference
.new(@tokens[@idx].codeOrigin
, LocalLabel
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
))
522 def parseMacroVariables
528 if @tokens[@idx] == ")"
531 elsif isIdentifier(@tokens[@idx])
532 variables
<< Variable
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
)
535 if @tokens[@idx] == ")"
538 elsif @tokens[@idx] == ","
550 def parseSequence(final
, comment
)
551 firstCodeOrigin
= @tokens[@idx].codeOrigin
554 if (@idx == @tokens.length
and not final
) or (final
and @tokens[@idx] =~ final
)
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
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.
565 codeOrigin
= @tokens[@idx].codeOrigin
566 annotationOpcode
= (@tokens[@idx].type
== :global) ? "globalAnnotation" : "localAnnotation"
567 list
<< Instruction
.new(codeOrigin
, annotationOpcode
, [], @tokens[@idx].string
)
569 @idx +
= 2 # Consume the newline as well.
570 elsif @tokens[@idx] == "\n"
573 elsif @tokens[@idx] == "const"
575 parseError
unless isVariable
@tokens[@idx]
576 variable
= Variable
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
)
578 parseError
unless @tokens[@idx] == "="
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
)
585 elsif @tokens[@idx] == "if"
586 codeOrigin
= @tokens[@idx].codeOrigin
589 predicate
= parsePredicate
590 consume(/\A((then)|(\n))\Z/)
592 ifThenElse
= IfThenElse
.new(codeOrigin
, predicate
, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\""))
594 while @tokens[@idx] == "elsif"
595 codeOrigin
= @tokens[@idx].codeOrigin
598 predicate
= parsePredicate
599 consume(/\A((then)|(\n))\Z/)
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
605 if @tokens[@idx] == "else"
607 ifThenElse
.elseCase
= parseSequence(/\Aend\Z/, "while inside of else case for \"if #{predicate.dump}\"")
610 parseError
unless @tokens[@idx] == "end"
613 elsif @tokens[@idx] == "macro"
614 codeOrigin
= @tokens[@idx].codeOrigin
617 parseError
unless isIdentifier(@tokens[@idx])
618 name
= @tokens[@idx].string
620 variables
= parseMacroVariables
621 body
= parseSequence(/\Aend\Z/, "while inside of macro #{name}")
623 list
<< Macro
.new(codeOrigin
, name
, variables
, body
)
624 elsif @tokens[@idx] == "global"
625 codeOrigin
= @tokens[@idx].codeOrigin
628 parseError
unless isLabel(@tokens[@idx])
629 name
= @tokens[@idx].string
631 Label
.setAsGlobal(codeOrigin
, name
)
632 elsif isInstruction
@tokens[@idx]
633 codeOrigin
= @tokens[@idx].codeOrigin
634 name
= @tokens[@idx].string
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)
641 elsif @tokens[@idx].is_a
? Annotation
642 list
<< Instruction
.new(codeOrigin
, name
, [], @tokens[@idx].string
)
644 @idx +
= 2 # Consume the newline as well.
645 elsif @tokens[@idx] == "\n"
646 # Zero operand instruction.
647 list
<< Instruction
.new(codeOrigin
, name
, [], @annotation)
651 # It's definitely an instruction, and it has at least one operand.
653 endOfSequence
= false
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.
660 elsif @tokens[@idx] == ","
661 # Has another operand.
663 elsif @tokens[@idx].is_a
? Annotation
664 @annotation = @tokens[@idx].string
665 @idx +
= 2 # Consume the newline as well.
667 elsif @tokens[@idx] == "\n"
668 # The end of the instruction.
672 parseError("Expected a comma, newline, or #{final} after #{operands.last.dump}")
675 list
<< Instruction
.new(codeOrigin
, name
, operands
, @annotation)
682 # Check for potential macro invocation:
683 elsif isIdentifier
@tokens[@idx]
684 codeOrigin
= @tokens[@idx].codeOrigin
685 name
= @tokens[@idx].string
687 if @tokens[@idx] == "("
692 if @tokens[@idx] == ")"
697 if @tokens[@idx] == "macro"
698 # It's a macro lambda!
699 codeOriginInner
= @tokens[@idx].codeOrigin
701 variables
= parseMacroVariables
702 body
= parseSequence(/\Aend\Z/, "while inside of anonymous macro passed as argument to #{name}")
704 operands
<< Macro
.new(codeOriginInner
, nil, variables
, body
)
706 operands
<< parseOperand("while inside of macro call to #{name}")
709 if @tokens[@idx] == ")"
712 elsif @tokens[@idx] == ","
715 parseError
"Unexpected #{@tokens[@idx].string.inspect} while parsing invocation of macro #{name}"
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.
724 list
<< MacroCall
.new(codeOrigin
, name
, operands
, @annotation)
727 parseError
"Expected \"(\" after #{name}"
729 elsif isLabel
@tokens[@idx] or isLocalLabel
@tokens[@idx]
730 codeOrigin
= @tokens[@idx].codeOrigin
731 name
= @tokens[@idx].string
733 parseError
unless @tokens[@idx] == ":"
736 list
<< Label
.forName(codeOrigin
, name
, true)
738 list
<< LocalLabel
.forName(codeOrigin
, name
)
741 elsif @tokens[@idx] == "include"
743 parseError
unless isIdentifier(@tokens[@idx])
744 moduleName
= @tokens[@idx].string
745 fileName
= IncludeFile
.new(moduleName
, @tokens[@idx].codeOrigin
.fileName
.dirname
).fileName
747 $stderr.puts
"offlineasm: Including file #{fileName}"
748 list
<< parse(fileName
)
750 parseError
"Expecting terminal #{final} #{comment}"
753 Sequence
.new(firstCodeOrigin
, list
)
756 def parseIncludes(final
, comment
)
757 firstCodeOrigin
= @tokens[@idx].codeOrigin
759 fileList
<< @tokens[@idx].codeOrigin
.fileName
761 if (@idx == @tokens.length
and not final
) or (final
and @tokens[@idx] =~ final
)
763 elsif @tokens[@idx] == "include"
765 parseError
unless isIdentifier(@tokens[@idx])
766 moduleName
= @tokens[@idx].string
767 fileName
= IncludeFile
.new(moduleName
, @tokens[@idx].codeOrigin
.fileName
.dirname
).fileName
780 def parseData(data, fileName
)
781 parser
= Parser
.new(data, fileName
)
782 parser
.parseSequence(nil, "")
786 parseData(IO
::read(fileName
), fileName
)
789 def parseHash(fileName
)
790 parser
= Parser
.new(IO
::read(fileName
), fileName
)
791 fileList
= parser
.parseIncludes(nil, "")
792 fileListHash(fileList
)