]>
git.saurik.com Git - apple/javascriptcore.git/blob - offlineasm/parser.rb
04f70a4bd0ac0a66cce1591d1c499f56191af61e
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 raise "Lexer error at #{CodeOrigin.new(fileName, lineNumber).to_s}, unexpected sequence #{str[0..20].inspect}"
171 whitespaceFound
= false
178 # Token identification.
181 def isRegister(token
)
182 token
=~ REGISTER_PATTERN
185 def isInstruction(token
)
186 INSTRUCTION_SET
.member
? token
.string
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
195 def isIdentifier(token
)
196 token
=~
/\A[a-zA-Z]([a-zA-Z0-9_.]*)\Z/ and not isKeyword(token
)
200 token
=~
/\A_([a-zA-Z0-9_]*)\Z/
203 def isLocalLabel(token
)
204 token
=~
/\A\.([a-zA-Z0-9_]*)\Z/
207 def isVariable(token
)
208 isIdentifier(token
) or isRegister(token
)
216 # The parser. Takes an array of tokens and returns an AST. Methods
217 # other than parse(tokens) are not for public consumption.
221 def initialize(data, fileName
)
222 @tokens = lex(data, fileName
)
227 def parseError(*comment
)
229 @tokens[@idx].parseError(*comment
)
232 raise "Parse error at end of file"
234 raise "Parse error at end of file: #{comment[0]}"
241 parseError
unless @tokens[@idx] =~ regexp
243 parseError
unless @idx == @tokens.length
249 while @tokens[@idx] == "\n"
254 def parsePredicateAtom
255 if @tokens[@idx] == "not"
256 codeOrigin
= @tokens[@idx].codeOrigin
258 Not
.new(codeOrigin
, parsePredicateAtom
)
259 elsif @tokens[@idx] == "("
262 result
= parsePredicate
263 parseError
unless @tokens[@idx] == ")"
266 elsif @tokens[@idx] == "true"
267 result
= True
.instance
270 elsif @tokens[@idx] == "false"
271 result
= False
.instance
274 elsif isIdentifier
@tokens[@idx]
275 result
= Setting
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
)
283 def parsePredicateAnd
284 result
= parsePredicateAtom
285 while @tokens[@idx] == "and"
286 codeOrigin
= @tokens[@idx].codeOrigin
289 right
= parsePredicateAtom
290 result
= And
.new(codeOrigin
, result
, right
)
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)
301 result
= parsePredicateAnd
302 while @tokens[@idx] == "or"
303 codeOrigin
= @tokens[@idx].codeOrigin
306 right
= parsePredicateAnd
307 result
= Or
.new(codeOrigin
, result
, right
)
313 if isRegister(@tokens[@idx])
314 if @tokens[@idx] =~ FPR_PATTERN
315 result
= FPRegisterID
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
)
317 result
= RegisterID
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
)
319 elsif isIdentifier(@tokens[@idx])
320 result
= Variable
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
)
328 def parseAddress(offset
)
329 parseError
unless @tokens[@idx] == "["
330 codeOrigin
= @tokens[@idx].codeOrigin
332 # Three possibilities:
333 # [] -> AbsoluteAddress
335 # [a,b] -> BaseIndex with scale = 1
336 # [a,b,c] -> BaseIndex
339 if @tokens[@idx] == "]"
341 return AbsoluteAddress
.new(codeOrigin
, offset
)
344 if @tokens[@idx] == "]"
345 result
= Address
.new(codeOrigin
, a
, offset
)
347 parseError
unless @tokens[@idx] == ","
350 if @tokens[@idx] == "]"
351 result
= BaseIndex
.new(codeOrigin
, a
, b
, 1, offset
)
353 parseError
unless @tokens[@idx] == ","
355 parseError
unless ["1", "2", "4", "8"].member
? @tokens[@idx].string
356 c
= @tokens[@idx].string
.to_i
358 parseError
unless @tokens[@idx] == "]"
359 result
= BaseIndex
.new(codeOrigin
, a
, b
, c
, offset
)
368 codeOrigin
= @tokens[@idx].codeOrigin
369 parseError
unless isIdentifier
@tokens[@idx]
370 names
= [@tokens[@idx].string
]
372 while @tokens[@idx] == "::"
374 parseError
unless isIdentifier
@tokens[@idx]
375 names
<< @tokens[@idx].string
378 raise if names
.empty
?
382 def parseExpressionAtom
384 if @tokens[@idx] == "-"
386 NegImmediate
.new(@tokens[@idx - 1].codeOrigin
, parseExpressionAtom
)
387 elsif @tokens[@idx] == "~"
389 BitnotImmediate
.new(@tokens[@idx - 1].codeOrigin
, parseExpressionAtom
)
390 elsif @tokens[@idx] == "("
392 result
= parseExpression
393 parseError
unless @tokens[@idx] == ")"
396 elsif isInteger
@tokens[@idx]
397 result
= Immediate
.new(@tokens[@idx].codeOrigin
, @tokens[@idx].string
.to_i
)
400 elsif isIdentifier
@tokens[@idx]
401 codeOrigin
, names
= parseColonColon
403 StructOffset
.forField(codeOrigin
, names
[0..-2].join('::'), names
[-1])
405 Variable
.forName(codeOrigin
, names
[0])
407 elsif isRegister
@tokens[@idx]
409 elsif @tokens[@idx] == "sizeof"
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
))
417 elsif isLocalLabel
@tokens[@idx]
418 result
= LocalLabelReference
.new(@tokens[@idx].codeOrigin
, LocalLabel
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
))
426 def parseExpressionMul
428 result
= parseExpressionAtom
429 while @tokens[@idx] == "*"
430 if @tokens[@idx] == "*"
432 result
= MulImmediates
.new(@tokens[@idx - 1].codeOrigin
, result
, parseExpressionAtom
)
440 def couldBeExpression
441 @tokens[@idx] == "-" or @tokens[@idx] == "~" or @tokens[@idx] == "sizeof" or isInteger(@tokens[@idx]) or isVariable(@tokens[@idx]) or @tokens[@idx] == "("
444 def parseExpressionAdd
446 result
= parseExpressionMul
447 while @tokens[@idx] == "+" or @tokens[@idx] == "-"
448 if @tokens[@idx] == "+
"
450 result
= AddImmediates
.new(@tokens[@idx - 1].codeOrigin
, result
, parseExpressionMul
)
451 elsif @tokens[@idx] == "-"
453 result
= SubImmediates
.new(@tokens[@idx - 1].codeOrigin
, result
, parseExpressionMul
)
461 def parseExpressionAnd
463 result
= parseExpressionAdd
464 while @tokens[@idx] == "&"
466 result
= AndImmediates
.new(@tokens[@idx - 1].codeOrigin
, result
, parseExpressionAdd
)
473 result
= parseExpressionAnd
474 while @tokens[@idx] == "|" or @tokens[@idx] == "^"
475 if @tokens[@idx] == "|"
477 result
= OrImmediates
.new(@tokens[@idx - 1].codeOrigin
, result
, parseExpressionAnd
)
478 elsif @tokens[@idx] == "^"
480 result
= XorImmediates
.new(@tokens[@idx - 1].codeOrigin
, result
, parseExpressionAnd
)
488 def parseOperand(comment
)
491 expr
= parseExpression
492 if @tokens[@idx] == "["
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
))
503 elsif isLocalLabel
@tokens[@idx]
504 result
= LocalLabelReference
.new(@tokens[@idx].codeOrigin
, LocalLabel
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
))
512 def parseMacroVariables
518 if @tokens[@idx] == ")"
521 elsif isIdentifier(@tokens[@idx])
522 variables
<< Variable
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
)
525 if @tokens[@idx] == ")"
528 elsif @tokens[@idx] == ","
540 def parseSequence(final
, comment
)
541 firstCodeOrigin
= @tokens[@idx].codeOrigin
544 if (@idx == @tokens.length
and not final
) or (final
and @tokens[@idx] =~ final
)
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
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.
555 codeOrigin
= @tokens[@idx].codeOrigin
556 annotationOpcode
= (@tokens[@idx].type
== :global) ? "globalAnnotation" : "localAnnotation"
557 list
<< Instruction
.new(codeOrigin
, annotationOpcode
, [], @tokens[@idx].string
)
559 @idx +
= 2 # Consume the newline as well.
560 elsif @tokens[@idx] == "\n"
563 elsif @tokens[@idx] == "const"
565 parseError
unless isVariable
@tokens[@idx]
566 variable
= Variable
.forName(@tokens[@idx].codeOrigin
, @tokens[@idx].string
)
568 parseError
unless @tokens[@idx] == "="
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
)
575 elsif @tokens[@idx] == "if"
576 codeOrigin
= @tokens[@idx].codeOrigin
579 predicate
= parsePredicate
580 consume(/\A((then)|(\n))\Z/)
582 ifThenElse
= IfThenElse
.new(codeOrigin
, predicate
, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\""))
584 while @tokens[@idx] == "elsif"
585 codeOrigin
= @tokens[@idx].codeOrigin
588 predicate
= parsePredicate
589 consume(/\A((then)|(\n))\Z/)
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
595 if @tokens[@idx] == "else"
597 ifThenElse
.elseCase
= parseSequence(/\Aend\Z/, "while inside of else case for \"if #{predicate.dump}\"")
600 parseError
unless @tokens[@idx] == "end"
603 elsif @tokens[@idx] == "macro"
604 codeOrigin
= @tokens[@idx].codeOrigin
607 parseError
unless isIdentifier(@tokens[@idx])
608 name
= @tokens[@idx].string
610 variables
= parseMacroVariables
611 body
= parseSequence(/\Aend\Z/, "while inside of macro #{name}")
613 list
<< Macro
.new(codeOrigin
, name
, variables
, body
)
614 elsif @tokens[@idx] == "global"
615 codeOrigin
= @tokens[@idx].codeOrigin
618 parseError
unless isLabel(@tokens[@idx])
619 name
= @tokens[@idx].string
621 Label
.setAsGlobal(codeOrigin
, name
)
622 elsif isInstruction
@tokens[@idx]
623 codeOrigin
= @tokens[@idx].codeOrigin
624 name
= @tokens[@idx].string
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)
631 elsif @tokens[@idx].is_a
? Annotation
632 list
<< Instruction
.new(codeOrigin
, name
, [], @tokens[@idx].string
)
634 @idx +
= 2 # Consume the newline as well.
635 elsif @tokens[@idx] == "\n"
636 # Zero operand instruction.
637 list
<< Instruction
.new(codeOrigin
, name
, [], @annotation)
641 # It's definitely an instruction, and it has at least one operand.
643 endOfSequence
= false
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.
650 elsif @tokens[@idx] == ","
651 # Has another operand.
653 elsif @tokens[@idx].is_a
? Annotation
654 @annotation = @tokens[@idx].string
655 @idx +
= 2 # Consume the newline as well.
657 elsif @tokens[@idx] == "\n"
658 # The end of the instruction.
662 parseError("Expected a comma, newline, or #{final} after #{operands.last.dump}")
665 list
<< Instruction
.new(codeOrigin
, name
, operands
, @annotation)
672 # Check for potential macro invocation:
673 elsif isIdentifier
@tokens[@idx]
674 codeOrigin
= @tokens[@idx].codeOrigin
675 name
= @tokens[@idx].string
677 if @tokens[@idx] == "("
682 if @tokens[@idx] == ")"
687 if @tokens[@idx] == "macro"
688 # It's a macro lambda!
689 codeOriginInner
= @tokens[@idx].codeOrigin
691 variables
= parseMacroVariables
692 body
= parseSequence(/\Aend\Z/, "while inside of anonymous macro passed as argument to #{name}")
694 operands
<< Macro
.new(codeOriginInner
, nil, variables
, body
)
696 operands
<< parseOperand("while inside of macro call to #{name}")
699 if @tokens[@idx] == ")"
702 elsif @tokens[@idx] == ","
705 parseError
"Unexpected #{@tokens[@idx].string.inspect} while parsing invocation of macro #{name}"
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.
714 list
<< MacroCall
.new(codeOrigin
, name
, operands
, @annotation)
717 parseError
"Expected \"(\" after #{name}"
719 elsif isLabel
@tokens[@idx] or isLocalLabel
@tokens[@idx]
720 codeOrigin
= @tokens[@idx].codeOrigin
721 name
= @tokens[@idx].string
723 parseError
unless @tokens[@idx] == ":"
726 list
<< Label
.forName(codeOrigin
, name
, true)
728 list
<< LocalLabel
.forName(codeOrigin
, name
)
731 elsif @tokens[@idx] == "include"
733 parseError
unless isIdentifier(@tokens[@idx])
734 moduleName
= @tokens[@idx].string
735 fileName
= IncludeFile
.new(moduleName
, @tokens[@idx].codeOrigin
.fileName
.dirname
).fileName
737 $stderr.puts
"offlineasm: Including file #{fileName}"
738 list
<< parse(fileName
)
740 parseError
"Expecting terminal #{final} #{comment}"
743 Sequence
.new(firstCodeOrigin
, list
)
746 def parseIncludes(final
, comment
)
747 firstCodeOrigin
= @tokens[@idx].codeOrigin
749 fileList
<< @tokens[@idx].codeOrigin
.fileName
751 if (@idx == @tokens.length
and not final
) or (final
and @tokens[@idx] =~ final
)
753 elsif @tokens[@idx] == "include"
755 parseError
unless isIdentifier(@tokens[@idx])
756 moduleName
= @tokens[@idx].string
757 fileName
= IncludeFile
.new(moduleName
, @tokens[@idx].codeOrigin
.fileName
.dirname
).fileName
770 def parseData(data, fileName
)
771 parser
= Parser
.new(data, fileName
)
772 parser
.parseSequence(nil, "")
776 parseData(IO
::read(fileName
), fileName
)
779 def parseHash(fileName
)
780 parser
= Parser
.new(IO
::read(fileName
), fileName
)
781 fileList
= parser
.parseIncludes(nil, "")
782 fileListHash(fileList
)