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