2 # preprocess source file
4 # Copyright (c) 2007 - 2014, Intel Corporation. All rights reserved.<BR>
6 # This program and the accompanying materials
7 # are licensed and made available under the terms and conditions of the BSD License
8 # which accompanies this distribution. The full text of the license may be found at
9 # http://opensource.org/licenses/bsd-license.php
11 # THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
20 import Common
.LongFilePathOs
as os
24 from CLexer
import CLexer
25 from CParser
import CParser
28 from CodeFragment
import Comment
29 from CodeFragment
import PP_Directive
30 from ParserWarning
import Warning
33 ##define T_CHAR_SPACE ' '
34 ##define T_CHAR_NULL '\0'
35 ##define T_CHAR_CR '\r'
36 ##define T_CHAR_TAB '\t'
37 ##define T_CHAR_LF '\n'
38 ##define T_CHAR_SLASH '/'
39 ##define T_CHAR_BACKSLASH '\\'
40 ##define T_CHAR_DOUBLE_QUOTE '\"'
41 ##define T_CHAR_SINGLE_QUOTE '\''
42 ##define T_CHAR_STAR '*'
43 ##define T_CHAR_HASH '#'
45 (T_CHAR_SPACE
, T_CHAR_NULL
, T_CHAR_CR
, T_CHAR_TAB
, T_CHAR_LF
, T_CHAR_SLASH
, \
46 T_CHAR_BACKSLASH
, T_CHAR_DOUBLE_QUOTE
, T_CHAR_SINGLE_QUOTE
, T_CHAR_STAR
, T_CHAR_HASH
) = \
47 (' ', '\0', '\r', '\t', '\n', '/', '\\', '\"', '\'', '*', '#')
49 SEPERATOR_TUPLE
= ('=', '|', ',', '{', '}')
51 (T_COMMENT_TWO_SLASH
, T_COMMENT_SLASH_STAR
) = (0, 1)
53 (T_PP_INCLUDE
, T_PP_DEFINE
, T_PP_OTHERS
) = (0, 1, 2)
55 ## The collector for source code fragments.
57 # PreprocessFile method should be called prior to ParseFile
59 # GetNext*** procedures mean these procedures will get next token first, then make judgement.
60 # Get*** procedures mean these procedures will make judgement on current token only.
62 class CodeFragmentCollector
:
65 # @param self The object pointer
66 # @param FileName The file that to be parsed
68 def __init__(self
, FileName
):
69 self
.Profile
= FileProfile
.FileProfile(FileName
)
70 self
.Profile
.FileLinesList
.append(T_CHAR_LF
)
71 self
.FileName
= FileName
72 self
.CurrentLineNumber
= 1
73 self
.CurrentOffsetWithinLine
= 0
76 self
.__SkippedChars
= ""
78 ## __IsWhiteSpace() method
80 # Whether char at current FileBufferPos is whitespace
82 # @param self The object pointer
83 # @param Char The char to test
84 # @retval True The char is a kind of white space
85 # @retval False The char is NOT a kind of white space
87 def __IsWhiteSpace(self
, Char
):
88 if Char
in (T_CHAR_NULL
, T_CHAR_CR
, T_CHAR_SPACE
, T_CHAR_TAB
, T_CHAR_LF
):
93 ## __SkipWhiteSpace() method
95 # Skip white spaces from current char, return number of chars skipped
97 # @param self The object pointer
98 # @retval Count The number of chars skipped
100 def __SkipWhiteSpace(self
):
102 while not self
.__EndOfFile
():
104 if self
.__CurrentChar
() in (T_CHAR_NULL
, T_CHAR_CR
, T_CHAR_LF
, T_CHAR_SPACE
, T_CHAR_TAB
):
105 self
.__SkippedChars
+= str(self
.__CurrentChar
())
112 ## __EndOfFile() method
114 # Judge current buffer pos is at file end
116 # @param self The object pointer
117 # @retval True Current File buffer position is at file end
118 # @retval False Current File buffer position is NOT at file end
120 def __EndOfFile(self
):
121 NumberOfLines
= len(self
.Profile
.FileLinesList
)
122 SizeOfLastLine
= NumberOfLines
123 if NumberOfLines
> 0:
124 SizeOfLastLine
= len(self
.Profile
.FileLinesList
[-1])
126 if self
.CurrentLineNumber
== NumberOfLines
and self
.CurrentOffsetWithinLine
>= SizeOfLastLine
- 1:
128 elif self
.CurrentLineNumber
> NumberOfLines
:
133 ## __EndOfLine() method
135 # Judge current buffer pos is at line end
137 # @param self The object pointer
138 # @retval True Current File buffer position is at line end
139 # @retval False Current File buffer position is NOT at line end
141 def __EndOfLine(self
):
142 SizeOfCurrentLine
= len(self
.Profile
.FileLinesList
[self
.CurrentLineNumber
- 1])
143 if self
.CurrentOffsetWithinLine
>= SizeOfCurrentLine
- 1:
150 # Reset file data buffer to the initial state
152 # @param self The object pointer
155 self
.CurrentLineNumber
= 1
156 self
.CurrentOffsetWithinLine
= 0
158 ## __UndoOneChar() method
160 # Go back one char in the file buffer
162 # @param self The object pointer
163 # @retval True Successfully go back one char
164 # @retval False Not able to go back one char as file beginning reached
166 def __UndoOneChar(self
):
168 if self
.CurrentLineNumber
== 1 and self
.CurrentOffsetWithinLine
== 0:
170 elif self
.CurrentOffsetWithinLine
== 0:
171 self
.CurrentLineNumber
-= 1
172 self
.CurrentOffsetWithinLine
= len(self
.__CurrentLine
()) - 1
174 self
.CurrentOffsetWithinLine
-= 1
177 ## __GetOneChar() method
179 # Move forward one char in the file buffer
181 # @param self The object pointer
183 def __GetOneChar(self
):
184 if self
.CurrentOffsetWithinLine
== len(self
.Profile
.FileLinesList
[self
.CurrentLineNumber
- 1]) - 1:
185 self
.CurrentLineNumber
+= 1
186 self
.CurrentOffsetWithinLine
= 0
188 self
.CurrentOffsetWithinLine
+= 1
190 ## __CurrentChar() method
192 # Get the char pointed to by the file buffer pointer
194 # @param self The object pointer
195 # @retval Char Current char
197 def __CurrentChar(self
):
198 CurrentChar
= self
.Profile
.FileLinesList
[self
.CurrentLineNumber
- 1][self
.CurrentOffsetWithinLine
]
199 # if CurrentChar > 255:
200 # raise Warning("Non-Ascii char found At Line %d, offset %d" % (self.CurrentLineNumber, self.CurrentOffsetWithinLine), self.FileName, self.CurrentLineNumber)
203 ## __NextChar() method
205 # Get the one char pass the char pointed to by the file buffer pointer
207 # @param self The object pointer
208 # @retval Char Next char
210 def __NextChar(self
):
211 if self
.CurrentOffsetWithinLine
== len(self
.Profile
.FileLinesList
[self
.CurrentLineNumber
- 1]) - 1:
212 return self
.Profile
.FileLinesList
[self
.CurrentLineNumber
][0]
214 return self
.Profile
.FileLinesList
[self
.CurrentLineNumber
- 1][self
.CurrentOffsetWithinLine
+ 1]
216 ## __SetCurrentCharValue() method
218 # Modify the value of current char
220 # @param self The object pointer
221 # @param Value The new value of current char
223 def __SetCurrentCharValue(self
, Value
):
224 self
.Profile
.FileLinesList
[self
.CurrentLineNumber
- 1][self
.CurrentOffsetWithinLine
] = Value
226 ## __SetCharValue() method
228 # Modify the value of current char
230 # @param self The object pointer
231 # @param Value The new value of current char
233 def __SetCharValue(self
, Line
, Offset
, Value
):
234 self
.Profile
.FileLinesList
[Line
- 1][Offset
] = Value
236 ## __CurrentLine() method
238 # Get the list that contains current line contents
240 # @param self The object pointer
241 # @retval List current line contents
243 def __CurrentLine(self
):
244 return self
.Profile
.FileLinesList
[self
.CurrentLineNumber
- 1]
246 ## __InsertComma() method
248 # Insert ',' to replace PP
250 # @param self The object pointer
251 # @retval List current line contents
253 def __InsertComma(self
, Line
):
256 if self
.Profile
.FileLinesList
[Line
- 1][0] != T_CHAR_HASH
:
257 BeforeHashPart
= str(self
.Profile
.FileLinesList
[Line
- 1]).split(T_CHAR_HASH
)[0]
258 if BeforeHashPart
.rstrip().endswith(T_CHAR_COMMA
) or BeforeHashPart
.rstrip().endswith(';'):
261 if Line
- 2 >= 0 and str(self
.Profile
.FileLinesList
[Line
- 2]).rstrip().endswith(','):
264 if Line
- 2 >= 0 and str(self
.Profile
.FileLinesList
[Line
- 2]).rstrip().endswith(';'):
267 if str(self
.Profile
.FileLinesList
[Line
]).lstrip().startswith(',') or str(self
.Profile
.FileLinesList
[Line
]).lstrip().startswith(';'):
270 self
.Profile
.FileLinesList
[Line
- 1].insert(self
.CurrentOffsetWithinLine
, ',')
272 ## PreprocessFile() method
274 # Preprocess file contents, replace comments with spaces.
275 # In the end, rewind the file buffer pointer to the beginning
276 # BUGBUG: No !include statement processing contained in this procedure
277 # !include statement should be expanded at the same FileLinesList[CurrentLineNumber - 1]
279 # @param self The object pointer
281 def PreprocessFile(self
):
285 DoubleSlashComment
= False
289 PPDirectiveObj
= None
290 # HashComment in quoted string " " is ignored.
292 InCharLiteral
= False
294 self
.Profile
.FileLinesList
= [list(s
) for s
in self
.Profile
.FileLinesListFromFile
]
295 while not self
.__EndOfFile
():
297 if not InComment
and self
.__CurrentChar
() == T_CHAR_DOUBLE_QUOTE
:
298 InString
= not InString
300 if not InComment
and self
.__CurrentChar
() == T_CHAR_SINGLE_QUOTE
:
301 InCharLiteral
= not InCharLiteral
302 # meet new line, then no longer in a comment for // and '#'
303 if self
.__CurrentChar
() == T_CHAR_LF
:
304 if HashComment
and PPDirectiveObj
!= None:
305 if PPDirectiveObj
.Content
.rstrip(T_CHAR_CR
).endswith(T_CHAR_BACKSLASH
):
306 PPDirectiveObj
.Content
+= T_CHAR_LF
311 EndLinePos
= (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
)
313 if InComment
and DoubleSlashComment
:
315 DoubleSlashComment
= False
316 CommentObj
.Content
+= T_CHAR_LF
317 CommentObj
.EndPos
= EndLinePos
318 FileProfile
.CommentList
.append(CommentObj
)
320 if InComment
and HashComment
and not PPExtend
:
323 PPDirectiveObj
.Content
+= T_CHAR_LF
324 PPDirectiveObj
.EndPos
= EndLinePos
325 FileProfile
.PPDirectiveList
.append(PPDirectiveObj
)
326 PPDirectiveObj
= None
328 if InString
or InCharLiteral
:
329 CurrentLine
= "".join(self
.__CurrentLine
())
330 if CurrentLine
.rstrip(T_CHAR_LF
).rstrip(T_CHAR_CR
).endswith(T_CHAR_BACKSLASH
):
331 SlashIndex
= CurrentLine
.rindex(T_CHAR_BACKSLASH
)
332 self
.__SetCharValue
(self
.CurrentLineNumber
, SlashIndex
, T_CHAR_SPACE
)
334 if InComment
and not DoubleSlashComment
and not HashComment
:
335 CommentObj
.Content
+= T_CHAR_LF
336 self
.CurrentLineNumber
+= 1
337 self
.CurrentOffsetWithinLine
= 0
338 # check for */ comment end
339 elif InComment
and not DoubleSlashComment
and not HashComment
and self
.__CurrentChar
() == T_CHAR_STAR
and self
.__NextChar
() == T_CHAR_SLASH
:
340 CommentObj
.Content
+= self
.__CurrentChar
()
341 # self.__SetCurrentCharValue(T_CHAR_SPACE)
343 CommentObj
.Content
+= self
.__CurrentChar
()
344 # self.__SetCurrentCharValue(T_CHAR_SPACE)
345 CommentObj
.EndPos
= (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
)
346 FileProfile
.CommentList
.append(CommentObj
)
350 # set comments to spaces
353 # // follows hash PP directive
354 if self
.__CurrentChar
() == T_CHAR_SLASH
and self
.__NextChar
() == T_CHAR_SLASH
:
357 PPDirectiveObj
.EndPos
= (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
- 1)
358 FileProfile
.PPDirectiveList
.append(PPDirectiveObj
)
359 PPDirectiveObj
= None
362 PPDirectiveObj
.Content
+= self
.__CurrentChar
()
364 self
.__SetCurrentCharValue
(T_CHAR_SPACE
)
366 CommentObj
.Content
+= self
.__CurrentChar
()
367 # self.__SetCurrentCharValue(T_CHAR_SPACE)
369 # check for // comment
370 elif self
.__CurrentChar
() == T_CHAR_SLASH
and self
.__NextChar
() == T_CHAR_SLASH
:
372 DoubleSlashComment
= True
373 CommentObj
= Comment('', (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
), None, T_COMMENT_TWO_SLASH
)
374 # check for '#' comment
375 elif self
.__CurrentChar
() == T_CHAR_HASH
and not InString
and not InCharLiteral
:
378 PPDirectiveObj
= PP_Directive('', (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
), None)
379 # check for /* comment start
380 elif self
.__CurrentChar
() == T_CHAR_SLASH
and self
.__NextChar
() == T_CHAR_STAR
:
381 CommentObj
= Comment('', (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
), None, T_COMMENT_SLASH_STAR
)
382 CommentObj
.Content
+= self
.__CurrentChar
()
383 # self.__SetCurrentCharValue( T_CHAR_SPACE)
385 CommentObj
.Content
+= self
.__CurrentChar
()
386 # self.__SetCurrentCharValue( T_CHAR_SPACE)
392 EndLinePos
= (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
)
394 if InComment
and DoubleSlashComment
:
395 CommentObj
.EndPos
= EndLinePos
396 FileProfile
.CommentList
.append(CommentObj
)
397 if InComment
and HashComment
and not PPExtend
:
398 PPDirectiveObj
.EndPos
= EndLinePos
399 FileProfile
.PPDirectiveList
.append(PPDirectiveObj
)
403 def PreprocessFileWithClear(self
):
407 DoubleSlashComment
= False
411 PPDirectiveObj
= None
412 # HashComment in quoted string " " is ignored.
414 InCharLiteral
= False
416 self
.Profile
.FileLinesList
= [list(s
) for s
in self
.Profile
.FileLinesListFromFile
]
417 while not self
.__EndOfFile
():
419 if not InComment
and self
.__CurrentChar
() == T_CHAR_DOUBLE_QUOTE
:
420 InString
= not InString
422 if not InComment
and self
.__CurrentChar
() == T_CHAR_SINGLE_QUOTE
:
423 InCharLiteral
= not InCharLiteral
424 # meet new line, then no longer in a comment for // and '#'
425 if self
.__CurrentChar
() == T_CHAR_LF
:
426 if HashComment
and PPDirectiveObj
!= None:
427 if PPDirectiveObj
.Content
.rstrip(T_CHAR_CR
).endswith(T_CHAR_BACKSLASH
):
428 PPDirectiveObj
.Content
+= T_CHAR_LF
433 EndLinePos
= (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
)
435 if InComment
and DoubleSlashComment
:
437 DoubleSlashComment
= False
438 CommentObj
.Content
+= T_CHAR_LF
439 CommentObj
.EndPos
= EndLinePos
440 FileProfile
.CommentList
.append(CommentObj
)
442 if InComment
and HashComment
and not PPExtend
:
445 PPDirectiveObj
.Content
+= T_CHAR_LF
446 PPDirectiveObj
.EndPos
= EndLinePos
447 FileProfile
.PPDirectiveList
.append(PPDirectiveObj
)
448 PPDirectiveObj
= None
450 if InString
or InCharLiteral
:
451 CurrentLine
= "".join(self
.__CurrentLine
())
452 if CurrentLine
.rstrip(T_CHAR_LF
).rstrip(T_CHAR_CR
).endswith(T_CHAR_BACKSLASH
):
453 SlashIndex
= CurrentLine
.rindex(T_CHAR_BACKSLASH
)
454 self
.__SetCharValue
(self
.CurrentLineNumber
, SlashIndex
, T_CHAR_SPACE
)
456 if InComment
and not DoubleSlashComment
and not HashComment
:
457 CommentObj
.Content
+= T_CHAR_LF
458 self
.CurrentLineNumber
+= 1
459 self
.CurrentOffsetWithinLine
= 0
460 # check for */ comment end
461 elif InComment
and not DoubleSlashComment
and not HashComment
and self
.__CurrentChar
() == T_CHAR_STAR
and self
.__NextChar
() == T_CHAR_SLASH
:
462 CommentObj
.Content
+= self
.__CurrentChar
()
463 self
.__SetCurrentCharValue
(T_CHAR_SPACE
)
465 CommentObj
.Content
+= self
.__CurrentChar
()
466 self
.__SetCurrentCharValue
(T_CHAR_SPACE
)
467 CommentObj
.EndPos
= (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
)
468 FileProfile
.CommentList
.append(CommentObj
)
472 # set comments to spaces
475 # // follows hash PP directive
476 if self
.__CurrentChar
() == T_CHAR_SLASH
and self
.__NextChar
() == T_CHAR_SLASH
:
479 PPDirectiveObj
.EndPos
= (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
- 1)
480 FileProfile
.PPDirectiveList
.append(PPDirectiveObj
)
481 PPDirectiveObj
= None
484 PPDirectiveObj
.Content
+= self
.__CurrentChar
()
486 # self.__SetCurrentCharValue(T_CHAR_SPACE)
488 CommentObj
.Content
+= self
.__CurrentChar
()
489 self
.__SetCurrentCharValue
(T_CHAR_SPACE
)
491 # check for // comment
492 elif self
.__CurrentChar
() == T_CHAR_SLASH
and self
.__NextChar
() == T_CHAR_SLASH
:
494 DoubleSlashComment
= True
495 CommentObj
= Comment('', (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
), None, T_COMMENT_TWO_SLASH
)
496 # check for '#' comment
497 elif self
.__CurrentChar
() == T_CHAR_HASH
and not InString
and not InCharLiteral
:
500 PPDirectiveObj
= PP_Directive('', (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
), None)
501 # check for /* comment start
502 elif self
.__CurrentChar
() == T_CHAR_SLASH
and self
.__NextChar
() == T_CHAR_STAR
:
503 CommentObj
= Comment('', (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
), None, T_COMMENT_SLASH_STAR
)
504 CommentObj
.Content
+= self
.__CurrentChar
()
505 self
.__SetCurrentCharValue
( T_CHAR_SPACE
)
507 CommentObj
.Content
+= self
.__CurrentChar
()
508 self
.__SetCurrentCharValue
( T_CHAR_SPACE
)
514 EndLinePos
= (self
.CurrentLineNumber
, self
.CurrentOffsetWithinLine
)
516 if InComment
and DoubleSlashComment
:
517 CommentObj
.EndPos
= EndLinePos
518 FileProfile
.CommentList
.append(CommentObj
)
519 if InComment
and HashComment
and not PPExtend
:
520 PPDirectiveObj
.EndPos
= EndLinePos
521 FileProfile
.PPDirectiveList
.append(PPDirectiveObj
)
524 ## ParseFile() method
526 # Parse the file profile buffer to extract fd, fv ... information
527 # Exception will be raised if syntax error found
529 # @param self The object pointer
532 self
.PreprocessFile()
533 # restore from ListOfList to ListOfString
534 self
.Profile
.FileLinesList
= ["".join(list) for list in self
.Profile
.FileLinesList
]
535 FileStringContents
= ''
536 for fileLine
in self
.Profile
.FileLinesList
:
537 FileStringContents
+= fileLine
538 cStream
= antlr3
.StringStream(FileStringContents
)
539 lexer
= CLexer(cStream
)
540 tStream
= antlr3
.CommonTokenStream(lexer
)
541 parser
= CParser(tStream
)
542 parser
.translation_unit()
544 def ParseFileWithClearedPPDirective(self
):
545 self
.PreprocessFileWithClear()
546 # restore from ListOfList to ListOfString
547 self
.Profile
.FileLinesList
= ["".join(list) for list in self
.Profile
.FileLinesList
]
548 FileStringContents
= ''
549 for fileLine
in self
.Profile
.FileLinesList
:
550 FileStringContents
+= fileLine
551 cStream
= antlr3
.StringStream(FileStringContents
)
552 lexer
= CLexer(cStream
)
553 tStream
= antlr3
.CommonTokenStream(lexer
)
554 parser
= CParser(tStream
)
555 parser
.translation_unit()
557 def CleanFileProfileBuffer(self
):
558 FileProfile
.CommentList
= []
559 FileProfile
.PPDirectiveList
= []
560 FileProfile
.PredicateExpressionList
= []
561 FileProfile
.FunctionDefinitionList
= []
562 FileProfile
.VariableDeclarationList
= []
563 FileProfile
.EnumerationDefinitionList
= []
564 FileProfile
.StructUnionDefinitionList
= []
565 FileProfile
.TypedefDefinitionList
= []
566 FileProfile
.FunctionCallingList
= []
568 def PrintFragments(self
):
570 print '################# ' + self
.FileName
+ '#####################'
572 print '/****************************************/'
573 print '/*************** COMMENTS ***************/'
574 print '/****************************************/'
575 for comment
in FileProfile
.CommentList
:
576 print str(comment
.StartPos
) + comment
.Content
578 print '/****************************************/'
579 print '/********* PREPROCESS DIRECTIVES ********/'
580 print '/****************************************/'
581 for pp
in FileProfile
.PPDirectiveList
:
582 print str(pp
.StartPos
) + pp
.Content
584 print '/****************************************/'
585 print '/********* VARIABLE DECLARATIONS ********/'
586 print '/****************************************/'
587 for var
in FileProfile
.VariableDeclarationList
:
588 print str(var
.StartPos
) + var
.Modifier
+ ' '+ var
.Declarator
590 print '/****************************************/'
591 print '/********* FUNCTION DEFINITIONS *********/'
592 print '/****************************************/'
593 for func
in FileProfile
.FunctionDefinitionList
:
594 print str(func
.StartPos
) + func
.Modifier
+ ' '+ func
.Declarator
+ ' ' + str(func
.NamePos
)
596 print '/****************************************/'
597 print '/************ ENUMERATIONS **************/'
598 print '/****************************************/'
599 for enum
in FileProfile
.EnumerationDefinitionList
:
600 print str(enum
.StartPos
) + enum
.Content
602 print '/****************************************/'
603 print '/*********** STRUCTS/UNIONS *************/'
604 print '/****************************************/'
605 for su
in FileProfile
.StructUnionDefinitionList
:
606 print str(su
.StartPos
) + su
.Content
608 print '/****************************************/'
609 print '/********* PREDICATE EXPRESSIONS ********/'
610 print '/****************************************/'
611 for predexp
in FileProfile
.PredicateExpressionList
:
612 print str(predexp
.StartPos
) + predexp
.Content
614 print '/****************************************/'
615 print '/************** TYPEDEFS ****************/'
616 print '/****************************************/'
617 for typedef
in FileProfile
.TypedefDefinitionList
:
618 print str(typedef
.StartPos
) + typedef
.ToType
620 if __name__
== "__main__":
622 collector
= CodeFragmentCollector(sys
.argv
[1])
623 collector
.PreprocessFile()