1 | # Copyright (C) 2011-2021 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 SourceFile
|
---|
32 | @@fileNames = []
|
---|
33 |
|
---|
34 | attr_reader :name, :basename, :fileNumber
|
---|
35 |
|
---|
36 | def SourceFile.outputDotFileList(outp)
|
---|
37 | @@fileNames.each_index {
|
---|
38 | | index |
|
---|
39 | $asm.putStr "\".file #{index+1} \\\"#{@@fileNames[index]}\\\"\\n\""
|
---|
40 | }
|
---|
41 | end
|
---|
42 |
|
---|
43 | def initialize(fileName)
|
---|
44 | @name = Pathname.new(fileName)
|
---|
45 | @basename = File.basename(fileName)
|
---|
46 | pathName = "#{@name.realpath}"
|
---|
47 | fileNumber = @@fileNames.index(pathName)
|
---|
48 | if not fileNumber
|
---|
49 | @@fileNames << pathName
|
---|
50 | fileNumber = @@fileNames.length
|
---|
51 | else
|
---|
52 | fileNumber += 1 # File numbers are 1 based
|
---|
53 | end
|
---|
54 | @fileNumber = fileNumber
|
---|
55 | end
|
---|
56 | end
|
---|
57 |
|
---|
58 | class CodeOrigin
|
---|
59 | attr_reader :lineNumber
|
---|
60 |
|
---|
61 | def initialize(sourceFile, lineNumber)
|
---|
62 | @sourceFile = sourceFile
|
---|
63 | @lineNumber = lineNumber
|
---|
64 | end
|
---|
65 |
|
---|
66 | def fileName
|
---|
67 | @sourceFile.name
|
---|
68 | end
|
---|
69 |
|
---|
70 | def debugDirective
|
---|
71 | $emitWinAsm ? nil : "\".loc #{@sourceFile.fileNumber} #{lineNumber}\\n\""
|
---|
72 | end
|
---|
73 |
|
---|
74 | def to_s
|
---|
75 | "#{@sourceFile.basename}:#{lineNumber}"
|
---|
76 | end
|
---|
77 | end
|
---|
78 |
|
---|
79 | class IncludeFile
|
---|
80 | @@includeDirs = []
|
---|
81 |
|
---|
82 | attr_reader :fileName
|
---|
83 |
|
---|
84 | def initialize(moduleName, defaultDir)
|
---|
85 | directory = nil
|
---|
86 | @@includeDirs.each {
|
---|
87 | | includePath |
|
---|
88 | fileName = File.join(includePath, moduleName + ".asm")
|
---|
89 | directory = includePath unless not File.file?(fileName)
|
---|
90 | }
|
---|
91 | if not directory
|
---|
92 | directory = defaultDir
|
---|
93 | end
|
---|
94 |
|
---|
95 | @fileName = File.join(directory, moduleName + ".asm")
|
---|
96 | end
|
---|
97 |
|
---|
98 | def self.processIncludeOptions()
|
---|
99 | while ARGV[0][/^-I/]
|
---|
100 | path = ARGV.shift[2..-1]
|
---|
101 | if not path
|
---|
102 | path = ARGV.shift
|
---|
103 | end
|
---|
104 | @@includeDirs << (path + "/")
|
---|
105 | end
|
---|
106 | end
|
---|
107 | end
|
---|
108 |
|
---|
109 | class Token
|
---|
110 | attr_reader :codeOrigin, :string
|
---|
111 |
|
---|
112 | def initialize(codeOrigin, string)
|
---|
113 | @codeOrigin = codeOrigin
|
---|
114 | @string = string
|
---|
115 | end
|
---|
116 |
|
---|
117 | def ==(other)
|
---|
118 | if other.is_a? Token
|
---|
119 | @string == other.string
|
---|
120 | else
|
---|
121 | @string == other
|
---|
122 | end
|
---|
123 | end
|
---|
124 |
|
---|
125 | def =~(other)
|
---|
126 | @string =~ other
|
---|
127 | end
|
---|
128 |
|
---|
129 | def to_s
|
---|
130 | "#{@string.inspect} at #{codeOrigin}"
|
---|
131 | end
|
---|
132 |
|
---|
133 | def parseError(*comment)
|
---|
134 | if comment.empty?
|
---|
135 | raise "Parse error: #{to_s}"
|
---|
136 | else
|
---|
137 | raise "Parse error: #{to_s}: #{comment[0]}"
|
---|
138 | end
|
---|
139 | end
|
---|
140 | end
|
---|
141 |
|
---|
142 | class Annotation
|
---|
143 | attr_reader :codeOrigin, :type, :string
|
---|
144 | def initialize(codeOrigin, type, string)
|
---|
145 | @codeOrigin = codeOrigin
|
---|
146 | @type = type
|
---|
147 | @string = string
|
---|
148 | end
|
---|
149 | end
|
---|
150 |
|
---|
151 | #
|
---|
152 | # The lexer. Takes a string and returns an array of tokens.
|
---|
153 | #
|
---|
154 |
|
---|
155 | def lex(str, file)
|
---|
156 | result = []
|
---|
157 | lineNumber = 1
|
---|
158 | annotation = nil
|
---|
159 | whitespaceFound = false
|
---|
160 | while not str.empty?
|
---|
161 | case str
|
---|
162 | when /\A\#([^\n]*)/
|
---|
163 | # comment, ignore
|
---|
164 | when /\A\/\/\ ?([^\n]*)/
|
---|
165 | # annotation
|
---|
166 | annotation = $1
|
---|
167 | annotationType = whitespaceFound ? :local : :global
|
---|
168 | when /\A\n/
|
---|
169 | # We've found a '\n'. Emit the last comment recorded if appropriate:
|
---|
170 | # We need to parse annotations regardless of whether the backend does
|
---|
171 | # anything with them or not. This is because the C++ backend may make
|
---|
172 | # use of this for its cloopDo debugging utility even if
|
---|
173 | # enableInstrAnnotations is not enabled.
|
---|
174 | if annotation
|
---|
175 | result << Annotation.new(CodeOrigin.new(file, lineNumber),
|
---|
176 | annotationType, annotation)
|
---|
177 | annotation = nil
|
---|
178 | end
|
---|
179 | result << Token.new(CodeOrigin.new(file, lineNumber), $&)
|
---|
180 | lineNumber += 1
|
---|
181 | when /\A[a-zA-Z%]([a-zA-Z0-9_.%]*)/
|
---|
182 | result << Token.new(CodeOrigin.new(file, lineNumber), $&)
|
---|
183 | when /\A\.([a-zA-Z0-9_]*)/
|
---|
184 | result << Token.new(CodeOrigin.new(file, lineNumber), $&)
|
---|
185 | when /\A_([a-zA-Z0-9_%]*)/
|
---|
186 | result << Token.new(CodeOrigin.new(file, lineNumber), $&)
|
---|
187 | when /\A([ \t]+)/
|
---|
188 | # whitespace, ignore
|
---|
189 | whitespaceFound = true
|
---|
190 | str = $~.post_match
|
---|
191 | next
|
---|
192 | when /\A0x([0-9a-fA-F]+)/
|
---|
193 | result << Token.new(CodeOrigin.new(file, lineNumber), $&.hex.to_s)
|
---|
194 | when /\A0([0-7]+)/
|
---|
195 | result << Token.new(CodeOrigin.new(file, lineNumber), $&.oct.to_s)
|
---|
196 | when /\A([0-9]+)/
|
---|
197 | result << Token.new(CodeOrigin.new(file, lineNumber), $&)
|
---|
198 | when /\A::/
|
---|
199 | result << Token.new(CodeOrigin.new(file, lineNumber), $&)
|
---|
200 | when /\A[:,\(\)\[\]=\+\-~\|&^*]/
|
---|
201 | result << Token.new(CodeOrigin.new(file, lineNumber), $&)
|
---|
202 | when /\A".*"/
|
---|
203 | result << Token.new(CodeOrigin.new(file, lineNumber), $&)
|
---|
204 | when /\?/
|
---|
205 | result << Token.new(CodeOrigin.new(file, lineNumber), $&)
|
---|
206 | else
|
---|
207 | raise "Lexer error at #{CodeOrigin.new(file, lineNumber).to_s}, unexpected sequence #{str[0..20].inspect}"
|
---|
208 | end
|
---|
209 | whitespaceFound = false
|
---|
210 | str = $~.post_match
|
---|
211 | end
|
---|
212 | result
|
---|
213 | end
|
---|
214 |
|
---|
215 | #
|
---|
216 | # Token identification.
|
---|
217 | #
|
---|
218 |
|
---|
219 | def isRegister(token)
|
---|
220 | token =~ REGISTER_PATTERN
|
---|
221 | end
|
---|
222 |
|
---|
223 | def isInstruction(token)
|
---|
224 | INSTRUCTION_SET.member? token.string
|
---|
225 | end
|
---|
226 |
|
---|
227 | def isKeyword(token)
|
---|
228 | token =~ /\A((true)|(false)|(if)|(then)|(else)|(elsif)|(end)|(and)|(or)|(not)|(global)|(macro)|(const)|(constexpr)|(sizeof)|(error)|(include))\Z/ or
|
---|
229 | token =~ REGISTER_PATTERN or
|
---|
230 | isInstruction(token)
|
---|
231 | end
|
---|
232 |
|
---|
233 | def isIdentifier(token)
|
---|
234 | token =~ /\A[a-zA-Z%]([a-zA-Z0-9_.%]*)\Z/ and not isKeyword(token)
|
---|
235 | end
|
---|
236 |
|
---|
237 | def isLabel(token)
|
---|
238 | token =~ /\A_([a-zA-Z0-9_%]*)\Z/
|
---|
239 | end
|
---|
240 |
|
---|
241 | def isLocalLabel(token)
|
---|
242 | token =~ /\A\.([a-zA-Z0-9_]*)\Z/
|
---|
243 | end
|
---|
244 |
|
---|
245 | def isVariable(token)
|
---|
246 | isIdentifier(token) or isRegister(token)
|
---|
247 | end
|
---|
248 |
|
---|
249 | def isInteger(token)
|
---|
250 | token =~ /\A[0-9]/
|
---|
251 | end
|
---|
252 |
|
---|
253 | def isString(token)
|
---|
254 | token =~ /\A".*"/
|
---|
255 | end
|
---|
256 |
|
---|
257 | #
|
---|
258 | # The parser. Takes an array of tokens and returns an AST. Methods
|
---|
259 | # other than parse(tokens) are not for public consumption.
|
---|
260 | #
|
---|
261 |
|
---|
262 | class Parser
|
---|
263 | def initialize(data, fileName, options, sources=nil)
|
---|
264 | @tokens = lex(data, fileName)
|
---|
265 | @idx = 0
|
---|
266 | @annotation = nil
|
---|
267 | # FIXME: CMake does not currently set BUILT_PRODUCTS_DIR.
|
---|
268 | # https://bugs.webkit.org/show_bug.cgi?id=229340
|
---|
269 | @buildProductsDirectory = ENV['BUILT_PRODUCTS_DIR'];
|
---|
270 | @headersFolderPath = ENV['WK_LIBRARY_HEADERS_FOLDER_PATH'];
|
---|
271 | @options = options
|
---|
272 | @sources = sources
|
---|
273 | end
|
---|
274 |
|
---|
275 | def parseError(*comment)
|
---|
276 | if @tokens[@idx]
|
---|
277 | @tokens[@idx].parseError(*comment)
|
---|
278 | else
|
---|
279 | if comment.empty?
|
---|
280 | raise "Parse error at end of file"
|
---|
281 | else
|
---|
282 | raise "Parse error at end of file: #{comment[0]}"
|
---|
283 | end
|
---|
284 | end
|
---|
285 | end
|
---|
286 |
|
---|
287 | def consume(regexp)
|
---|
288 | if regexp
|
---|
289 | parseError unless @tokens[@idx] =~ regexp
|
---|
290 | else
|
---|
291 | parseError unless @idx == @tokens.length
|
---|
292 | end
|
---|
293 | @idx += 1
|
---|
294 | end
|
---|
295 |
|
---|
296 | def skipNewLine
|
---|
297 | while @tokens[@idx] == "\n"
|
---|
298 | @idx += 1
|
---|
299 | end
|
---|
300 | end
|
---|
301 |
|
---|
302 | def parsePredicateAtom
|
---|
303 | if @tokens[@idx] == "not"
|
---|
304 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
305 | @idx += 1
|
---|
306 | Not.new(codeOrigin, parsePredicateAtom)
|
---|
307 | elsif @tokens[@idx] == "("
|
---|
308 | @idx += 1
|
---|
309 | skipNewLine
|
---|
310 | result = parsePredicate
|
---|
311 | parseError unless @tokens[@idx] == ")"
|
---|
312 | @idx += 1
|
---|
313 | result
|
---|
314 | elsif @tokens[@idx] == "true"
|
---|
315 | result = True.instance
|
---|
316 | @idx += 1
|
---|
317 | result
|
---|
318 | elsif @tokens[@idx] == "false"
|
---|
319 | result = False.instance
|
---|
320 | @idx += 1
|
---|
321 | result
|
---|
322 | elsif isIdentifier @tokens[@idx]
|
---|
323 | result = Setting.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
|
---|
324 | @idx += 1
|
---|
325 | result
|
---|
326 | else
|
---|
327 | parseError
|
---|
328 | end
|
---|
329 | end
|
---|
330 |
|
---|
331 | def parsePredicateAnd
|
---|
332 | result = parsePredicateAtom
|
---|
333 | while @tokens[@idx] == "and"
|
---|
334 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
335 | @idx += 1
|
---|
336 | skipNewLine
|
---|
337 | right = parsePredicateAtom
|
---|
338 | result = And.new(codeOrigin, result, right)
|
---|
339 | end
|
---|
340 | result
|
---|
341 | end
|
---|
342 |
|
---|
343 | def parsePredicate
|
---|
344 | # some examples of precedence:
|
---|
345 | # not a and b -> (not a) and b
|
---|
346 | # a and b or c -> (a and b) or c
|
---|
347 | # a or b and c -> a or (b and c)
|
---|
348 |
|
---|
349 | result = parsePredicateAnd
|
---|
350 | while @tokens[@idx] == "or"
|
---|
351 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
352 | @idx += 1
|
---|
353 | skipNewLine
|
---|
354 | right = parsePredicateAnd
|
---|
355 | result = Or.new(codeOrigin, result, right)
|
---|
356 | end
|
---|
357 | result
|
---|
358 | end
|
---|
359 |
|
---|
360 | def parseVariable
|
---|
361 | if isRegister(@tokens[@idx])
|
---|
362 | if @tokens[@idx] =~ FPR_PATTERN || @tokens[@idx] =~ WASM_FPR_PATTERN
|
---|
363 | result = FPRegisterID.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
|
---|
364 | else
|
---|
365 | result = RegisterID.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
|
---|
366 | end
|
---|
367 | elsif isIdentifier(@tokens[@idx])
|
---|
368 | result = Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
|
---|
369 | else
|
---|
370 | parseError
|
---|
371 | end
|
---|
372 | @idx += 1
|
---|
373 | result
|
---|
374 | end
|
---|
375 |
|
---|
376 | def parseConstExpr
|
---|
377 | if @tokens[@idx] == "constexpr"
|
---|
378 | @idx += 1
|
---|
379 | skipNewLine
|
---|
380 | if @tokens[@idx] == "("
|
---|
381 | codeOrigin, text = parseTextInParens
|
---|
382 | text = text.join
|
---|
383 | else
|
---|
384 | codeOrigin, text = parseColonColon
|
---|
385 | text = text.join("::")
|
---|
386 | end
|
---|
387 | ConstExpr.forName(codeOrigin, text)
|
---|
388 | else
|
---|
389 | parseError
|
---|
390 | end
|
---|
391 | end
|
---|
392 |
|
---|
393 | def parseAddress(offset)
|
---|
394 | parseError unless @tokens[@idx] == "["
|
---|
395 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
396 |
|
---|
397 | # Three possibilities:
|
---|
398 | # [] -> AbsoluteAddress
|
---|
399 | # [a] -> Address
|
---|
400 | # [a,b] -> BaseIndex with scale = 1
|
---|
401 | # [a,b,c] -> BaseIndex
|
---|
402 |
|
---|
403 | @idx += 1
|
---|
404 | if @tokens[@idx] == "]"
|
---|
405 | @idx += 1
|
---|
406 | return AbsoluteAddress.new(codeOrigin, offset)
|
---|
407 | end
|
---|
408 | a = parseVariable
|
---|
409 | if @tokens[@idx] == "]"
|
---|
410 | result = Address.new(codeOrigin, a, offset)
|
---|
411 | else
|
---|
412 | parseError unless @tokens[@idx] == ","
|
---|
413 | @idx += 1
|
---|
414 | b = parseVariable
|
---|
415 | if @tokens[@idx] == "]"
|
---|
416 | result = BaseIndex.new(codeOrigin, a, b, Immediate.new(codeOrigin, 1), offset)
|
---|
417 | else
|
---|
418 | parseError unless @tokens[@idx] == ","
|
---|
419 | @idx += 1
|
---|
420 | if ["1", "2", "4", "8"].member? @tokens[@idx].string
|
---|
421 | c = Immediate.new(codeOrigin, @tokens[@idx].string.to_i)
|
---|
422 | @idx += 1
|
---|
423 | elsif @tokens[@idx] == "constexpr"
|
---|
424 | c = parseConstExpr
|
---|
425 | else
|
---|
426 | c = parseVariable
|
---|
427 | end
|
---|
428 | parseError unless @tokens[@idx] == "]"
|
---|
429 | result = BaseIndex.new(codeOrigin, a, b, c, offset)
|
---|
430 | end
|
---|
431 | end
|
---|
432 | @idx += 1
|
---|
433 | result
|
---|
434 | end
|
---|
435 |
|
---|
436 | def parseColonColon
|
---|
437 | skipNewLine
|
---|
438 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
439 | parseError unless isIdentifier @tokens[@idx]
|
---|
440 | names = [@tokens[@idx].string]
|
---|
441 | @idx += 1
|
---|
442 | while @tokens[@idx] == "::"
|
---|
443 | @idx += 1
|
---|
444 | parseError unless isIdentifier @tokens[@idx]
|
---|
445 | names << @tokens[@idx].string
|
---|
446 | @idx += 1
|
---|
447 | end
|
---|
448 | raise if names.empty?
|
---|
449 | [codeOrigin, names]
|
---|
450 | end
|
---|
451 |
|
---|
452 | def parseTextInParens
|
---|
453 | skipNewLine
|
---|
454 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
455 | raise unless @tokens[@idx] == "("
|
---|
456 | @idx += 1
|
---|
457 | # need at least one item
|
---|
458 | raise if @tokens[@idx] == ")"
|
---|
459 | numEnclosedParens = 0
|
---|
460 | text = []
|
---|
461 | while @tokens[@idx] != ")" || numEnclosedParens > 0
|
---|
462 | if @tokens[@idx] == "("
|
---|
463 | numEnclosedParens += 1
|
---|
464 | elsif @tokens[@idx] == ")"
|
---|
465 | numEnclosedParens -= 1
|
---|
466 | end
|
---|
467 |
|
---|
468 | text << @tokens[@idx].string
|
---|
469 | @idx += 1
|
---|
470 | end
|
---|
471 | @idx += 1
|
---|
472 | return [codeOrigin, text]
|
---|
473 | end
|
---|
474 |
|
---|
475 |
|
---|
476 | def parseExpressionAtom
|
---|
477 | skipNewLine
|
---|
478 | if @tokens[@idx] == "-"
|
---|
479 | @idx += 1
|
---|
480 | NegImmediate.new(@tokens[@idx - 1].codeOrigin, parseExpressionAtom)
|
---|
481 | elsif @tokens[@idx] == "~"
|
---|
482 | @idx += 1
|
---|
483 | BitnotImmediate.new(@tokens[@idx - 1].codeOrigin, parseExpressionAtom)
|
---|
484 | elsif @tokens[@idx] == "("
|
---|
485 | @idx += 1
|
---|
486 | result = parseExpression
|
---|
487 | parseError unless @tokens[@idx] == ")"
|
---|
488 | @idx += 1
|
---|
489 | result
|
---|
490 | elsif isInteger @tokens[@idx]
|
---|
491 | result = Immediate.new(@tokens[@idx].codeOrigin, @tokens[@idx].string.to_i)
|
---|
492 | @idx += 1
|
---|
493 | result
|
---|
494 | elsif isString @tokens[@idx]
|
---|
495 | result = StringLiteral.new(@tokens[@idx].codeOrigin, @tokens[@idx].string)
|
---|
496 | @idx += 1
|
---|
497 | result
|
---|
498 | elsif isIdentifier @tokens[@idx]
|
---|
499 | codeOrigin, names = parseColonColon
|
---|
500 | if names.size > 1
|
---|
501 | StructOffset.forField(codeOrigin, names[0..-2].join('::'), names[-1])
|
---|
502 | else
|
---|
503 | Variable.forName(codeOrigin, names[0])
|
---|
504 | end
|
---|
505 | elsif isRegister @tokens[@idx]
|
---|
506 | parseVariable
|
---|
507 | elsif @tokens[@idx] == "sizeof"
|
---|
508 | @idx += 1
|
---|
509 | codeOrigin, names = parseColonColon
|
---|
510 | Sizeof.forName(codeOrigin, names.join('::'))
|
---|
511 | elsif @tokens[@idx] == "constexpr"
|
---|
512 | parseConstExpr
|
---|
513 | elsif isLabel @tokens[@idx]
|
---|
514 | result = LabelReference.new(@tokens[@idx].codeOrigin, Label.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
|
---|
515 | @idx += 1
|
---|
516 | result
|
---|
517 | elsif isLocalLabel @tokens[@idx]
|
---|
518 | result = LocalLabelReference.new(@tokens[@idx].codeOrigin, LocalLabel.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
|
---|
519 | @idx += 1
|
---|
520 | result
|
---|
521 | else
|
---|
522 | parseError
|
---|
523 | end
|
---|
524 | end
|
---|
525 |
|
---|
526 | def parseExpressionMul
|
---|
527 | skipNewLine
|
---|
528 | result = parseExpressionAtom
|
---|
529 | while @tokens[@idx] == "*"
|
---|
530 | if @tokens[@idx] == "*"
|
---|
531 | @idx += 1
|
---|
532 | result = MulImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAtom)
|
---|
533 | else
|
---|
534 | raise
|
---|
535 | end
|
---|
536 | end
|
---|
537 | result
|
---|
538 | end
|
---|
539 |
|
---|
540 | def couldBeExpression
|
---|
541 | @tokens[@idx] == "-" or @tokens[@idx] == "~" or @tokens[@idx] == "sizeof" or @tokens[@idx] == "constexpr" or isInteger(@tokens[@idx]) or isString(@tokens[@idx]) or isVariable(@tokens[@idx]) or isLabel(@tokens[@idx]) or @tokens[@idx] == "("
|
---|
542 | end
|
---|
543 |
|
---|
544 | def parseExpressionAdd
|
---|
545 | skipNewLine
|
---|
546 | result = parseExpressionMul
|
---|
547 | while @tokens[@idx] == "+" or @tokens[@idx] == "-"
|
---|
548 | if @tokens[@idx] == "+"
|
---|
549 | @idx += 1
|
---|
550 | result = AddImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionMul)
|
---|
551 | elsif @tokens[@idx] == "-"
|
---|
552 | @idx += 1
|
---|
553 | result = SubImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionMul)
|
---|
554 | else
|
---|
555 | raise
|
---|
556 | end
|
---|
557 | end
|
---|
558 | result
|
---|
559 | end
|
---|
560 |
|
---|
561 | def parseExpressionAnd
|
---|
562 | skipNewLine
|
---|
563 | result = parseExpressionAdd
|
---|
564 | while @tokens[@idx] == "&"
|
---|
565 | @idx += 1
|
---|
566 | result = AndImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAdd)
|
---|
567 | end
|
---|
568 | result
|
---|
569 | end
|
---|
570 |
|
---|
571 | def parseExpression
|
---|
572 | skipNewLine
|
---|
573 | result = parseExpressionAnd
|
---|
574 | while @tokens[@idx] == "|" or @tokens[@idx] == "^"
|
---|
575 | if @tokens[@idx] == "|"
|
---|
576 | @idx += 1
|
---|
577 | result = OrImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAnd)
|
---|
578 | elsif @tokens[@idx] == "^"
|
---|
579 | @idx += 1
|
---|
580 | result = XorImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAnd)
|
---|
581 | else
|
---|
582 | raise
|
---|
583 | end
|
---|
584 | end
|
---|
585 | result
|
---|
586 | end
|
---|
587 |
|
---|
588 | def parseOperand(comment)
|
---|
589 | skipNewLine
|
---|
590 | if couldBeExpression
|
---|
591 | expr = parseExpression
|
---|
592 | if @tokens[@idx] == "["
|
---|
593 | parseAddress(expr)
|
---|
594 | else
|
---|
595 | expr
|
---|
596 | end
|
---|
597 | elsif @tokens[@idx] == "["
|
---|
598 | parseAddress(Immediate.new(@tokens[@idx].codeOrigin, 0))
|
---|
599 | elsif isLocalLabel @tokens[@idx]
|
---|
600 | result = LocalLabelReference.new(@tokens[@idx].codeOrigin, LocalLabel.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string))
|
---|
601 | @idx += 1
|
---|
602 | result
|
---|
603 | else
|
---|
604 | parseError(comment)
|
---|
605 | end
|
---|
606 | end
|
---|
607 |
|
---|
608 | def parseMacroVariables
|
---|
609 | skipNewLine
|
---|
610 | consume(/\A\(\Z/)
|
---|
611 | variables = []
|
---|
612 | loop {
|
---|
613 | skipNewLine
|
---|
614 | if @tokens[@idx] == ")"
|
---|
615 | @idx += 1
|
---|
616 | break
|
---|
617 | elsif isIdentifier(@tokens[@idx])
|
---|
618 | variables << Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
|
---|
619 | @idx += 1
|
---|
620 | skipNewLine
|
---|
621 | if @tokens[@idx] == ")"
|
---|
622 | @idx += 1
|
---|
623 | break
|
---|
624 | elsif @tokens[@idx] == ","
|
---|
625 | @idx += 1
|
---|
626 | else
|
---|
627 | parseError
|
---|
628 | end
|
---|
629 | else
|
---|
630 | parseError
|
---|
631 | end
|
---|
632 | }
|
---|
633 | variables
|
---|
634 | end
|
---|
635 |
|
---|
636 | def parseSequence(final, comment)
|
---|
637 | firstCodeOrigin = @tokens[@idx].codeOrigin
|
---|
638 | list = []
|
---|
639 | loop {
|
---|
640 | if @tokens[@idx].is_a? Annotation
|
---|
641 | # This is the only place where we can encounter a global
|
---|
642 | # annotation, and hence need to be able to distinguish between
|
---|
643 | # them.
|
---|
644 | # globalAnnotations are the ones that start from column 0. All
|
---|
645 | # others are considered localAnnotations. The only reason to
|
---|
646 | # distinguish between them is so that we can format the output
|
---|
647 | # nicely as one would expect.
|
---|
648 |
|
---|
649 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
650 | annotationOpcode = (@tokens[@idx].type == :global) ? "globalAnnotation" : "localAnnotation"
|
---|
651 | list << Instruction.new(codeOrigin, annotationOpcode, [], @tokens[@idx].string)
|
---|
652 | @annotation = nil
|
---|
653 | @idx += 2 # Consume the newline as well.
|
---|
654 | elsif (@idx == @tokens.length and not final) or (final and @tokens[@idx] =~ final)
|
---|
655 | break
|
---|
656 | elsif @tokens[@idx] == "\n"
|
---|
657 | # ignore
|
---|
658 | @idx += 1
|
---|
659 | elsif @tokens[@idx] == "const"
|
---|
660 | @idx += 1
|
---|
661 | parseError unless isVariable @tokens[@idx]
|
---|
662 | variable = Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)
|
---|
663 | @idx += 1
|
---|
664 | parseError unless @tokens[@idx] == "="
|
---|
665 | @idx += 1
|
---|
666 | value = parseOperand("while inside of const #{variable.name}")
|
---|
667 | list << ConstDecl.new(@tokens[@idx].codeOrigin, variable, value)
|
---|
668 | elsif @tokens[@idx] == "error"
|
---|
669 | list << Error.new(@tokens[@idx].codeOrigin)
|
---|
670 | @idx += 1
|
---|
671 | elsif @tokens[@idx] == "if"
|
---|
672 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
673 | @idx += 1
|
---|
674 | skipNewLine
|
---|
675 | predicate = parsePredicate
|
---|
676 | consume(/\A((then)|(\n))\Z/)
|
---|
677 | skipNewLine
|
---|
678 | ifThenElse = IfThenElse.new(codeOrigin, predicate, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\""))
|
---|
679 | list << ifThenElse
|
---|
680 | while @tokens[@idx] == "elsif"
|
---|
681 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
682 | @idx += 1
|
---|
683 | skipNewLine
|
---|
684 | predicate = parsePredicate
|
---|
685 | consume(/\A((then)|(\n))\Z/)
|
---|
686 | skipNewLine
|
---|
687 | elseCase = IfThenElse.new(codeOrigin, predicate, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\""))
|
---|
688 | ifThenElse.elseCase = elseCase
|
---|
689 | ifThenElse = elseCase
|
---|
690 | end
|
---|
691 | if @tokens[@idx] == "else"
|
---|
692 | @idx += 1
|
---|
693 | ifThenElse.elseCase = parseSequence(/\Aend\Z/, "while inside of else case for \"if #{predicate.dump}\"")
|
---|
694 | @idx += 1
|
---|
695 | else
|
---|
696 | parseError unless @tokens[@idx] == "end"
|
---|
697 | @idx += 1
|
---|
698 | end
|
---|
699 | elsif @tokens[@idx] == "macro"
|
---|
700 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
701 | @idx += 1
|
---|
702 | skipNewLine
|
---|
703 | parseError unless isIdentifier(@tokens[@idx])
|
---|
704 | name = @tokens[@idx].string
|
---|
705 | @idx += 1
|
---|
706 | variables = parseMacroVariables
|
---|
707 | body = parseSequence(/\Aend\Z/, "while inside of macro #{name}")
|
---|
708 | @idx += 1
|
---|
709 | list << Macro.new(codeOrigin, name, variables, body)
|
---|
710 | elsif @tokens[@idx] == "global"
|
---|
711 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
712 | @idx += 1
|
---|
713 | skipNewLine
|
---|
714 | parseError unless isLabel(@tokens[@idx])
|
---|
715 | name = @tokens[@idx].string
|
---|
716 | @idx += 1
|
---|
717 | Label.setAsGlobal(codeOrigin, name)
|
---|
718 | elsif isInstruction @tokens[@idx]
|
---|
719 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
720 | name = @tokens[@idx].string
|
---|
721 | @idx += 1
|
---|
722 | if (not final and @idx == @tokens.size) or (final and @tokens[@idx] =~ final)
|
---|
723 | # Zero operand instruction, and it's the last one.
|
---|
724 | list << Instruction.new(codeOrigin, name, [], @annotation)
|
---|
725 | @annotation = nil
|
---|
726 | break
|
---|
727 | elsif @tokens[@idx].is_a? Annotation
|
---|
728 | list << Instruction.new(codeOrigin, name, [], @tokens[@idx].string)
|
---|
729 | @annotation = nil
|
---|
730 | @idx += 2 # Consume the newline as well.
|
---|
731 | elsif @tokens[@idx] == "\n"
|
---|
732 | # Zero operand instruction.
|
---|
733 | list << Instruction.new(codeOrigin, name, [], @annotation)
|
---|
734 | @annotation = nil
|
---|
735 | @idx += 1
|
---|
736 | else
|
---|
737 | # It's definitely an instruction, and it has at least one operand.
|
---|
738 | operands = []
|
---|
739 | endOfSequence = false
|
---|
740 | loop {
|
---|
741 | operands << parseOperand("while inside of instruction #{name}")
|
---|
742 | if (not final and @idx == @tokens.size) or (final and @tokens[@idx] =~ final)
|
---|
743 | # The end of the instruction and of the sequence.
|
---|
744 | endOfSequence = true
|
---|
745 | break
|
---|
746 | elsif @tokens[@idx] == ","
|
---|
747 | # Has another operand.
|
---|
748 | @idx += 1
|
---|
749 | elsif @tokens[@idx].is_a? Annotation
|
---|
750 | @annotation = @tokens[@idx].string
|
---|
751 | @idx += 2 # Consume the newline as well.
|
---|
752 | break
|
---|
753 | elsif @tokens[@idx] == "\n"
|
---|
754 | # The end of the instruction.
|
---|
755 | @idx += 1
|
---|
756 | break
|
---|
757 | else
|
---|
758 | parseError("Expected a comma, newline, or #{final} after #{operands.last.dump}")
|
---|
759 | end
|
---|
760 | }
|
---|
761 | list << Instruction.new(codeOrigin, name, operands, @annotation)
|
---|
762 | @annotation = nil
|
---|
763 | if endOfSequence
|
---|
764 | break
|
---|
765 | end
|
---|
766 | end
|
---|
767 |
|
---|
768 | # Check for potential macro invocation:
|
---|
769 | elsif isIdentifier @tokens[@idx]
|
---|
770 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
771 | name = @tokens[@idx].string
|
---|
772 | @idx += 1
|
---|
773 | if @tokens[@idx] == "("
|
---|
774 | # Macro invocation.
|
---|
775 | @idx += 1
|
---|
776 | operands = []
|
---|
777 | skipNewLine
|
---|
778 | if @tokens[@idx] == ")"
|
---|
779 | @idx += 1
|
---|
780 | else
|
---|
781 | loop {
|
---|
782 | skipNewLine
|
---|
783 | if @tokens[@idx] == "macro"
|
---|
784 | # It's a macro lambda!
|
---|
785 | codeOriginInner = @tokens[@idx].codeOrigin
|
---|
786 | @idx += 1
|
---|
787 | variables = parseMacroVariables
|
---|
788 | body = parseSequence(/\Aend\Z/, "while inside of anonymous macro passed as argument to #{name}")
|
---|
789 | @idx += 1
|
---|
790 | operands << Macro.new(codeOriginInner, nil, variables, body)
|
---|
791 | else
|
---|
792 | operands << parseOperand("while inside of macro call to #{name}")
|
---|
793 | end
|
---|
794 | skipNewLine
|
---|
795 | if @tokens[@idx] == ")"
|
---|
796 | @idx += 1
|
---|
797 | break
|
---|
798 | elsif @tokens[@idx] == ","
|
---|
799 | @idx += 1
|
---|
800 | else
|
---|
801 | parseError "Unexpected #{@tokens[@idx].string.inspect} while parsing invocation of macro #{name}"
|
---|
802 | end
|
---|
803 | }
|
---|
804 | end
|
---|
805 | # Check if there's a trailing annotation after the macro invoke:
|
---|
806 | if @tokens[@idx].is_a? Annotation
|
---|
807 | @annotation = @tokens[@idx].string
|
---|
808 | @idx += 2 # Consume the newline as well.
|
---|
809 | end
|
---|
810 | list << MacroCall.new(codeOrigin, name, operands, @annotation)
|
---|
811 | @annotation = nil
|
---|
812 | else
|
---|
813 | parseError "Expected \"(\" after #{name}"
|
---|
814 | end
|
---|
815 | elsif isLabel @tokens[@idx] or isLocalLabel @tokens[@idx]
|
---|
816 | codeOrigin = @tokens[@idx].codeOrigin
|
---|
817 | name = @tokens[@idx].string
|
---|
818 | @idx += 1
|
---|
819 | parseError unless @tokens[@idx] == ":"
|
---|
820 | # It's a label.
|
---|
821 | if isLabel name
|
---|
822 | list << Label.forName(codeOrigin, name, true)
|
---|
823 | else
|
---|
824 | list << LocalLabel.forName(codeOrigin, name)
|
---|
825 | end
|
---|
826 | @idx += 1
|
---|
827 | elsif @tokens[@idx] == "include"
|
---|
828 | @idx += 1
|
---|
829 | isOptional = false
|
---|
830 | if @tokens[@idx] == "?"
|
---|
831 | isOptional = true
|
---|
832 | @idx += 1
|
---|
833 | end
|
---|
834 | parseError unless isIdentifier(@tokens[@idx])
|
---|
835 | moduleName = @tokens[@idx].string
|
---|
836 | @idx += 1
|
---|
837 | if @options[:webkit_additions_path]
|
---|
838 | additionsDirectoryName = @options[:webkit_additions_path]
|
---|
839 | else
|
---|
840 | additionsDirectoryName = "#{@buildProductsDirectory}#{@headersFolderPath}/WebKitAdditions/"
|
---|
841 | end
|
---|
842 | fileName = IncludeFile.new(moduleName, additionsDirectoryName).fileName
|
---|
843 | if not File.exists?(fileName)
|
---|
844 | fileName = IncludeFile.new(moduleName, @tokens[@idx].codeOrigin.fileName.dirname).fileName
|
---|
845 | end
|
---|
846 | fileExists = File.exists?(fileName)
|
---|
847 | raise "File not found: #{fileName}" if not fileExists and not isOptional
|
---|
848 | list << parse(fileName, @options, @sources) if fileExists
|
---|
849 | else
|
---|
850 | parseError "Expecting terminal #{final} #{comment}"
|
---|
851 | end
|
---|
852 | }
|
---|
853 | Sequence.new(firstCodeOrigin, list)
|
---|
854 | end
|
---|
855 |
|
---|
856 | def parseIncludes(final, comment, options)
|
---|
857 | firstCodeOrigin = @tokens[@idx].codeOrigin
|
---|
858 | fileList = []
|
---|
859 | fileList << @tokens[@idx].codeOrigin.fileName
|
---|
860 | loop {
|
---|
861 | if (@idx == @tokens.length and not final) or (final and @tokens[@idx] =~ final)
|
---|
862 | break
|
---|
863 | elsif @tokens[@idx] == "include"
|
---|
864 | @idx += 1
|
---|
865 | isOptional = false
|
---|
866 | if @tokens[@idx] == "?"
|
---|
867 | isOptional = true
|
---|
868 | @idx += 1
|
---|
869 | end
|
---|
870 | parseError unless isIdentifier(@tokens[@idx])
|
---|
871 | moduleName = @tokens[@idx].string
|
---|
872 | @idx += 1
|
---|
873 | if @options[:webkit_additions_path]
|
---|
874 | additionsDirectoryName = @options[:webkit_additions_path]
|
---|
875 | else
|
---|
876 | additionsDirectoryName = "#{@buildProductsDirectory}#{@headersFolderPath}/WebKitAdditions/"
|
---|
877 | end
|
---|
878 | fileName = IncludeFile.new(moduleName, additionsDirectoryName).fileName
|
---|
879 | if not File.exists?(fileName)
|
---|
880 | fileName = IncludeFile.new(moduleName, @tokens[@idx].codeOrigin.fileName.dirname).fileName
|
---|
881 | end
|
---|
882 | fileExists = File.exists?(fileName)
|
---|
883 | raise "File not found: #{fileName}" if not fileExists and not isOptional
|
---|
884 | if fileExists
|
---|
885 | parser = Parser.new(readTextFile(fileName), SourceFile.new(fileName), options)
|
---|
886 | fileList << parser.parseIncludes(nil, "", options)
|
---|
887 | end
|
---|
888 | else
|
---|
889 | @idx += 1
|
---|
890 | end
|
---|
891 | }
|
---|
892 |
|
---|
893 | return fileList
|
---|
894 | end
|
---|
895 | end
|
---|
896 |
|
---|
897 | def readTextFile(fileName)
|
---|
898 | data = IO::read(fileName)
|
---|
899 |
|
---|
900 | # On Windows, files may contain CRLF line endings (for example, git client might
|
---|
901 | # automatically replace \n with \r\n on Windows) which will fail our parsing.
|
---|
902 | # Thus, we'll just remove all \r from the data (keeping just the \n characters)
|
---|
903 | data.delete!("\r")
|
---|
904 |
|
---|
905 | return data
|
---|
906 | end
|
---|
907 |
|
---|
908 | def parseData(data, fileName, options, sources)
|
---|
909 | parser = Parser.new(data, SourceFile.new(fileName), options, sources)
|
---|
910 | parser.parseSequence(nil, "")
|
---|
911 | end
|
---|
912 |
|
---|
913 | def parse(fileName, options, sources=nil)
|
---|
914 | sources << fileName if sources
|
---|
915 | parseData(readTextFile(fileName), fileName, options, sources)
|
---|
916 | end
|
---|
917 |
|
---|
918 | def parseHash(fileName, options)
|
---|
919 | parser = Parser.new(readTextFile(fileName), SourceFile.new(fileName), options)
|
---|
920 | fileList = parser.parseIncludes(nil, "", options)
|
---|
921 | fileList.flatten!
|
---|
922 | fileListHash(fileList)
|
---|
923 | end
|
---|
924 |
|
---|