1//===-- TemplateBase.h - Core classes for C++ templates ---------*- C++ -*-===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10//  This file provides definitions which are common for all kinds of
11//  template representation.
12//
13//===----------------------------------------------------------------------===//
14
15#ifndef LLVM_CLANG_AST_TEMPLATEBASE_H
16#define LLVM_CLANG_AST_TEMPLATEBASE_H
17
18#include "clang/AST/TemplateName.h"
19#include "clang/AST/Type.h"
20#include "llvm/ADT/APSInt.h"
21#include "llvm/ADT/SmallVector.h"
22#include "llvm/ADT/iterator_range.h"
23#include "llvm/Support/Compiler.h"
24#include "llvm/Support/ErrorHandling.h"
25
26namespace llvm {
27  class FoldingSetNodeID;
28}
29
30namespace clang {
31
32class DiagnosticBuilder;
33class Expr;
34struct PrintingPolicy;
35class TypeSourceInfo;
36class ValueDecl;
37
38/// \brief Represents a template argument.
39class TemplateArgument {
40public:
41  /// \brief The kind of template argument we're storing.
42  enum ArgKind {
43    /// \brief Represents an empty template argument, e.g., one that has not
44    /// been deduced.
45    Null = 0,
46    /// The template argument is a type.
47    Type,
48    /// The template argument is a declaration that was provided for a pointer,
49    /// reference, or pointer to member non-type template parameter.
50    Declaration,
51    /// The template argument is a null pointer or null pointer to member that
52    /// was provided for a non-type template parameter.
53    NullPtr,
54    /// The template argument is an integral value stored in an llvm::APSInt
55    /// that was provided for an integral non-type template parameter.
56    Integral,
57    /// The template argument is a template name that was provided for a
58    /// template template parameter.
59    Template,
60    /// The template argument is a pack expansion of a template name that was
61    /// provided for a template template parameter.
62    TemplateExpansion,
63    /// The template argument is an expression, and we've not resolved it to one
64    /// of the other forms yet, either because it's dependent or because we're
65    /// representing a non-canonical template argument (for instance, in a
66    /// TemplateSpecializationType). Also used to represent a non-dependent
67    /// __uuidof expression (a Microsoft extension).
68    Expression,
69    /// The template argument is actually a parameter pack. Arguments are stored
70    /// in the Args struct.
71    Pack
72  };
73
74private:
75  /// \brief The kind of template argument we're storing.
76
77  struct DA {
78    unsigned Kind;
79    void *QT;
80    ValueDecl *D;
81  };
82  struct I {
83    unsigned Kind;
84    // We store a decomposed APSInt with the data allocated by ASTContext if
85    // BitWidth > 64. The memory may be shared between multiple
86    // TemplateArgument instances.
87    unsigned BitWidth : 31;
88    unsigned IsUnsigned : 1;
89    union {
90      uint64_t VAL;          ///< Used to store the <= 64 bits integer value.
91      const uint64_t *pVal;  ///< Used to store the >64 bits integer value.
92    };
93    void *Type;
94  };
95  struct A {
96    unsigned Kind;
97    unsigned NumArgs;
98    const TemplateArgument *Args;
99  };
100  struct TA {
101    unsigned Kind;
102    unsigned NumExpansions;
103    void *Name;
104  };
105  struct TV {
106    unsigned Kind;
107    uintptr_t V;
108  };
109  union {
110    struct DA DeclArg;
111    struct I Integer;
112    struct A Args;
113    struct TA TemplateArg;
114    struct TV TypeOrValue;
115  };
116
117  TemplateArgument(TemplateName, bool) = delete;
118
119public:
120  /// \brief Construct an empty, invalid template argument.
121  TemplateArgument() {
122    TypeOrValue.Kind = Null;
123    TypeOrValue.V = 0;
124  }
125
126  /// \brief Construct a template type argument.
127  TemplateArgument(QualType T, bool isNullPtr = false) {
128    TypeOrValue.Kind = isNullPtr ? NullPtr : Type;
129    TypeOrValue.V = reinterpret_cast<uintptr_t>(T.getAsOpaquePtr());
130  }
131
132  /// \brief Construct a template argument that refers to a
133  /// declaration, which is either an external declaration or a
134  /// template declaration.
135  TemplateArgument(ValueDecl *D, QualType QT) {
136    assert(D && "Expected decl");
137    DeclArg.Kind = Declaration;
138    DeclArg.QT = QT.getAsOpaquePtr();
139    DeclArg.D = D;
140  }
141
142  /// \brief Construct an integral constant template argument. The memory to
143  /// store the value is allocated with Ctx.
144  TemplateArgument(ASTContext &Ctx, const llvm::APSInt &Value, QualType Type);
145
146  /// \brief Construct an integral constant template argument with the same
147  /// value as Other but a different type.
148  TemplateArgument(const TemplateArgument &Other, QualType Type) {
149    Integer = Other.Integer;
150    Integer.Type = Type.getAsOpaquePtr();
151  }
152
153  /// \brief Construct a template argument that is a template.
154  ///
155  /// This form of template argument is generally used for template template
156  /// parameters. However, the template name could be a dependent template
157  /// name that ends up being instantiated to a function template whose address
158  /// is taken.
159  ///
160  /// \param Name The template name.
161  TemplateArgument(TemplateName Name) {
162    TemplateArg.Kind = Template;
163    TemplateArg.Name = Name.getAsVoidPointer();
164    TemplateArg.NumExpansions = 0;
165  }
166
167  /// \brief Construct a template argument that is a template pack expansion.
168  ///
169  /// This form of template argument is generally used for template template
170  /// parameters. However, the template name could be a dependent template
171  /// name that ends up being instantiated to a function template whose address
172  /// is taken.
173  ///
174  /// \param Name The template name.
175  ///
176  /// \param NumExpansions The number of expansions that will be generated by
177  /// instantiating
178  TemplateArgument(TemplateName Name, Optional<unsigned> NumExpansions) {
179    TemplateArg.Kind = TemplateExpansion;
180    TemplateArg.Name = Name.getAsVoidPointer();
181    if (NumExpansions)
182      TemplateArg.NumExpansions = *NumExpansions + 1;
183    else
184      TemplateArg.NumExpansions = 0;
185  }
186
187  /// \brief Construct a template argument that is an expression.
188  ///
189  /// This form of template argument only occurs in template argument
190  /// lists used for dependent types and for expression; it will not
191  /// occur in a non-dependent, canonical template argument list.
192  TemplateArgument(Expr *E) {
193    TypeOrValue.Kind = Expression;
194    TypeOrValue.V = reinterpret_cast<uintptr_t>(E);
195  }
196
197  /// \brief Construct a template argument that is a template argument pack.
198  ///
199  /// We assume that storage for the template arguments provided
200  /// outlives the TemplateArgument itself.
201  explicit TemplateArgument(ArrayRef<TemplateArgument> Args) {
202    this->Args.Kind = Pack;
203    this->Args.Args = Args.data();
204    this->Args.NumArgs = Args.size();
205  }
206
207  static TemplateArgument getEmptyPack() { return TemplateArgument(None); }
208
209  /// \brief Create a new template argument pack by copying the given set of
210  /// template arguments.
211  static TemplateArgument CreatePackCopy(ASTContext &Context,
212                                         ArrayRef<TemplateArgument> Args);
213
214  /// \brief Return the kind of stored template argument.
215  ArgKind getKind() const { return (ArgKind)TypeOrValue.Kind; }
216
217  /// \brief Determine whether this template argument has no value.
218  bool isNull() const { return getKind() == Null; }
219
220  /// \brief Whether this template argument is dependent on a template
221  /// parameter such that its result can change from one instantiation to
222  /// another.
223  bool isDependent() const;
224
225  /// \brief Whether this template argument is dependent on a template
226  /// parameter.
227  bool isInstantiationDependent() const;
228
229  /// \brief Whether this template argument contains an unexpanded
230  /// parameter pack.
231  bool containsUnexpandedParameterPack() const;
232
233  /// \brief Determine whether this template argument is a pack expansion.
234  bool isPackExpansion() const;
235
236  /// \brief Retrieve the type for a type template argument.
237  QualType getAsType() const {
238    assert(getKind() == Type && "Unexpected kind");
239    return QualType::getFromOpaquePtr(reinterpret_cast<void*>(TypeOrValue.V));
240  }
241
242  /// \brief Retrieve the declaration for a declaration non-type
243  /// template argument.
244  ValueDecl *getAsDecl() const {
245    assert(getKind() == Declaration && "Unexpected kind");
246    return DeclArg.D;
247  }
248
249  QualType getParamTypeForDecl() const {
250    assert(getKind() == Declaration && "Unexpected kind");
251    return QualType::getFromOpaquePtr(DeclArg.QT);
252  }
253
254  /// \brief Retrieve the type for null non-type template argument.
255  QualType getNullPtrType() const {
256    assert(getKind() == NullPtr && "Unexpected kind");
257    return QualType::getFromOpaquePtr(reinterpret_cast<void*>(TypeOrValue.V));
258  }
259
260  /// \brief Retrieve the template name for a template name argument.
261  TemplateName getAsTemplate() const {
262    assert(getKind() == Template && "Unexpected kind");
263    return TemplateName::getFromVoidPointer(TemplateArg.Name);
264  }
265
266  /// \brief Retrieve the template argument as a template name; if the argument
267  /// is a pack expansion, return the pattern as a template name.
268  TemplateName getAsTemplateOrTemplatePattern() const {
269    assert((getKind() == Template || getKind() == TemplateExpansion) &&
270           "Unexpected kind");
271
272    return TemplateName::getFromVoidPointer(TemplateArg.Name);
273  }
274
275  /// \brief Retrieve the number of expansions that a template template argument
276  /// expansion will produce, if known.
277  Optional<unsigned> getNumTemplateExpansions() const;
278
279  /// \brief Retrieve the template argument as an integral value.
280  // FIXME: Provide a way to read the integral data without copying the value.
281  llvm::APSInt getAsIntegral() const {
282    assert(getKind() == Integral && "Unexpected kind");
283    using namespace llvm;
284    if (Integer.BitWidth <= 64)
285      return APSInt(APInt(Integer.BitWidth, Integer.VAL), Integer.IsUnsigned);
286
287    unsigned NumWords = APInt::getNumWords(Integer.BitWidth);
288    return APSInt(APInt(Integer.BitWidth, makeArrayRef(Integer.pVal, NumWords)),
289                  Integer.IsUnsigned);
290  }
291
292  /// \brief Retrieve the type of the integral value.
293  QualType getIntegralType() const {
294    assert(getKind() == Integral && "Unexpected kind");
295    return QualType::getFromOpaquePtr(Integer.Type);
296  }
297
298  void setIntegralType(QualType T) {
299    assert(getKind() == Integral && "Unexpected kind");
300    Integer.Type = T.getAsOpaquePtr();
301  }
302
303  /// \brief Retrieve the template argument as an expression.
304  Expr *getAsExpr() const {
305    assert(getKind() == Expression && "Unexpected kind");
306    return reinterpret_cast<Expr *>(TypeOrValue.V);
307  }
308
309  /// \brief Iterator that traverses the elements of a template argument pack.
310  typedef const TemplateArgument * pack_iterator;
311
312  /// \brief Iterator referencing the first argument of a template argument
313  /// pack.
314  pack_iterator pack_begin() const {
315    assert(getKind() == Pack);
316    return Args.Args;
317  }
318
319  /// \brief Iterator referencing one past the last argument of a template
320  /// argument pack.
321  pack_iterator pack_end() const {
322    assert(getKind() == Pack);
323    return Args.Args + Args.NumArgs;
324  }
325
326  /// \brief Iterator range referencing all of the elements of a template
327  /// argument pack.
328  llvm::iterator_range<pack_iterator> pack_elements() const {
329    return llvm::make_range(pack_begin(), pack_end());
330  }
331
332  /// \brief The number of template arguments in the given template argument
333  /// pack.
334  unsigned pack_size() const {
335    assert(getKind() == Pack);
336    return Args.NumArgs;
337  }
338
339  /// \brief Return the array of arguments in this template argument pack.
340  ArrayRef<TemplateArgument> getPackAsArray() const {
341    assert(getKind() == Pack);
342    return llvm::makeArrayRef(Args.Args, Args.NumArgs);
343  }
344
345  /// \brief Determines whether two template arguments are superficially the
346  /// same.
347  bool structurallyEquals(const TemplateArgument &Other) const;
348
349  /// \brief When the template argument is a pack expansion, returns
350  /// the pattern of the pack expansion.
351  TemplateArgument getPackExpansionPattern() const;
352
353  /// \brief Print this template argument to the given output stream.
354  void print(const PrintingPolicy &Policy, raw_ostream &Out) const;
355
356  /// \brief Used to insert TemplateArguments into FoldingSets.
357  void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) const;
358};
359
360/// Location information for a TemplateArgument.
361struct TemplateArgumentLocInfo {
362private:
363
364  struct T {
365    // FIXME: We'd like to just use the qualifier in the TemplateName,
366    // but template arguments get canonicalized too quickly.
367    NestedNameSpecifier *Qualifier;
368    void *QualifierLocData;
369    unsigned TemplateNameLoc;
370    unsigned EllipsisLoc;
371  };
372
373  union {
374    struct T Template;
375    Expr *Expression;
376    TypeSourceInfo *Declarator;
377  };
378
379public:
380  TemplateArgumentLocInfo();
381
382  TemplateArgumentLocInfo(TypeSourceInfo *TInfo) : Declarator(TInfo) {}
383
384  TemplateArgumentLocInfo(Expr *E) : Expression(E) {}
385
386  TemplateArgumentLocInfo(NestedNameSpecifierLoc QualifierLoc,
387                          SourceLocation TemplateNameLoc,
388                          SourceLocation EllipsisLoc)
389  {
390    Template.Qualifier = QualifierLoc.getNestedNameSpecifier();
391    Template.QualifierLocData = QualifierLoc.getOpaqueData();
392    Template.TemplateNameLoc = TemplateNameLoc.getRawEncoding();
393    Template.EllipsisLoc = EllipsisLoc.getRawEncoding();
394  }
395
396  TypeSourceInfo *getAsTypeSourceInfo() const {
397    return Declarator;
398  }
399
400  Expr *getAsExpr() const {
401    return Expression;
402  }
403
404  NestedNameSpecifierLoc getTemplateQualifierLoc() const {
405    return NestedNameSpecifierLoc(Template.Qualifier,
406                                  Template.QualifierLocData);
407  }
408
409  SourceLocation getTemplateNameLoc() const {
410    return SourceLocation::getFromRawEncoding(Template.TemplateNameLoc);
411  }
412
413  SourceLocation getTemplateEllipsisLoc() const {
414    return SourceLocation::getFromRawEncoding(Template.EllipsisLoc);
415  }
416};
417
418/// Location wrapper for a TemplateArgument.  TemplateArgument is to
419/// TemplateArgumentLoc as Type is to TypeLoc.
420class TemplateArgumentLoc {
421  TemplateArgument Argument;
422  TemplateArgumentLocInfo LocInfo;
423
424public:
425  TemplateArgumentLoc() {}
426
427  TemplateArgumentLoc(const TemplateArgument &Argument,
428                      TemplateArgumentLocInfo Opaque)
429    : Argument(Argument), LocInfo(Opaque) {
430  }
431
432  TemplateArgumentLoc(const TemplateArgument &Argument, TypeSourceInfo *TInfo)
433    : Argument(Argument), LocInfo(TInfo) {
434    assert(Argument.getKind() == TemplateArgument::Type);
435  }
436
437  TemplateArgumentLoc(const TemplateArgument &Argument, Expr *E)
438    : Argument(Argument), LocInfo(E) {
439    assert(Argument.getKind() == TemplateArgument::Expression);
440  }
441
442  TemplateArgumentLoc(const TemplateArgument &Argument,
443                      NestedNameSpecifierLoc QualifierLoc,
444                      SourceLocation TemplateNameLoc,
445                      SourceLocation EllipsisLoc = SourceLocation())
446    : Argument(Argument), LocInfo(QualifierLoc, TemplateNameLoc, EllipsisLoc) {
447    assert(Argument.getKind() == TemplateArgument::Template ||
448           Argument.getKind() == TemplateArgument::TemplateExpansion);
449  }
450
451  /// \brief - Fetches the primary location of the argument.
452  SourceLocation getLocation() const {
453    if (Argument.getKind() == TemplateArgument::Template ||
454        Argument.getKind() == TemplateArgument::TemplateExpansion)
455      return getTemplateNameLoc();
456
457    return getSourceRange().getBegin();
458  }
459
460  /// \brief - Fetches the full source range of the argument.
461  SourceRange getSourceRange() const LLVM_READONLY;
462
463  const TemplateArgument &getArgument() const {
464    return Argument;
465  }
466
467  TemplateArgumentLocInfo getLocInfo() const {
468    return LocInfo;
469  }
470
471  TypeSourceInfo *getTypeSourceInfo() const {
472    assert(Argument.getKind() == TemplateArgument::Type);
473    return LocInfo.getAsTypeSourceInfo();
474  }
475
476  Expr *getSourceExpression() const {
477    assert(Argument.getKind() == TemplateArgument::Expression);
478    return LocInfo.getAsExpr();
479  }
480
481  Expr *getSourceDeclExpression() const {
482    assert(Argument.getKind() == TemplateArgument::Declaration);
483    return LocInfo.getAsExpr();
484  }
485
486  Expr *getSourceNullPtrExpression() const {
487    assert(Argument.getKind() == TemplateArgument::NullPtr);
488    return LocInfo.getAsExpr();
489  }
490
491  Expr *getSourceIntegralExpression() const {
492    assert(Argument.getKind() == TemplateArgument::Integral);
493    return LocInfo.getAsExpr();
494  }
495
496  NestedNameSpecifierLoc getTemplateQualifierLoc() const {
497    assert(Argument.getKind() == TemplateArgument::Template ||
498           Argument.getKind() == TemplateArgument::TemplateExpansion);
499    return LocInfo.getTemplateQualifierLoc();
500  }
501
502  SourceLocation getTemplateNameLoc() const {
503    assert(Argument.getKind() == TemplateArgument::Template ||
504           Argument.getKind() == TemplateArgument::TemplateExpansion);
505    return LocInfo.getTemplateNameLoc();
506  }
507
508  SourceLocation getTemplateEllipsisLoc() const {
509    assert(Argument.getKind() == TemplateArgument::TemplateExpansion);
510    return LocInfo.getTemplateEllipsisLoc();
511  }
512};
513
514/// A convenient class for passing around template argument
515/// information.  Designed to be passed by reference.
516class TemplateArgumentListInfo {
517  SmallVector<TemplateArgumentLoc, 8> Arguments;
518  SourceLocation LAngleLoc;
519  SourceLocation RAngleLoc;
520
521  // This can leak if used in an AST node, use ASTTemplateArgumentListInfo
522  // instead.
523  void *operator new(size_t bytes, ASTContext &C) = delete;
524
525public:
526  TemplateArgumentListInfo() {}
527
528  TemplateArgumentListInfo(SourceLocation LAngleLoc,
529                           SourceLocation RAngleLoc)
530    : LAngleLoc(LAngleLoc), RAngleLoc(RAngleLoc) {}
531
532  SourceLocation getLAngleLoc() const { return LAngleLoc; }
533  SourceLocation getRAngleLoc() const { return RAngleLoc; }
534
535  void setLAngleLoc(SourceLocation Loc) { LAngleLoc = Loc; }
536  void setRAngleLoc(SourceLocation Loc) { RAngleLoc = Loc; }
537
538  unsigned size() const { return Arguments.size(); }
539
540  const TemplateArgumentLoc *getArgumentArray() const {
541    return Arguments.data();
542  }
543
544  llvm::ArrayRef<TemplateArgumentLoc> arguments() const {
545    return Arguments;
546  }
547
548  const TemplateArgumentLoc &operator[](unsigned I) const {
549    return Arguments[I];
550  }
551
552  TemplateArgumentLoc &operator[](unsigned I) {
553    return Arguments[I];
554  }
555
556  void addArgument(const TemplateArgumentLoc &Loc) {
557    Arguments.push_back(Loc);
558  }
559};
560
561/// \brief Represents an explicit template argument list in C++, e.g.,
562/// the "<int>" in "sort<int>".
563/// This is safe to be used inside an AST node, in contrast with
564/// TemplateArgumentListInfo.
565struct ASTTemplateArgumentListInfo {
566  /// \brief The source location of the left angle bracket ('<').
567  SourceLocation LAngleLoc;
568
569  /// \brief The source location of the right angle bracket ('>').
570  SourceLocation RAngleLoc;
571
572  union {
573    /// \brief The number of template arguments in TemplateArgs.
574    /// The actual template arguments (if any) are stored after the
575    /// ExplicitTemplateArgumentList structure.
576    unsigned NumTemplateArgs;
577
578    /// Force ASTTemplateArgumentListInfo to the right alignment
579    /// for the following array of TemplateArgumentLocs.
580    llvm::AlignedCharArray<
581        llvm::AlignOf<TemplateArgumentLoc>::Alignment, 1> Aligner;
582  };
583
584  /// \brief Retrieve the template arguments
585  TemplateArgumentLoc *getTemplateArgs() {
586    return reinterpret_cast<TemplateArgumentLoc *> (this + 1);
587  }
588
589  /// \brief Retrieve the template arguments
590  const TemplateArgumentLoc *getTemplateArgs() const {
591    return reinterpret_cast<const TemplateArgumentLoc *> (this + 1);
592  }
593
594  const TemplateArgumentLoc &operator[](unsigned I) const {
595    return getTemplateArgs()[I];
596  }
597
598  static const ASTTemplateArgumentListInfo *Create(ASTContext &C,
599                                          const TemplateArgumentListInfo &List);
600
601  void initializeFrom(const TemplateArgumentListInfo &List);
602  void initializeFrom(const TemplateArgumentListInfo &List,
603                      bool &Dependent, bool &InstantiationDependent,
604                      bool &ContainsUnexpandedParameterPack);
605  void copyInto(TemplateArgumentListInfo &List) const;
606  static std::size_t sizeFor(unsigned NumTemplateArgs);
607};
608
609/// \brief Extends ASTTemplateArgumentListInfo with the source location
610/// information for the template keyword; this is used as part of the
611/// representation of qualified identifiers, such as S<T>::template apply<T>.
612struct ASTTemplateKWAndArgsInfo : public ASTTemplateArgumentListInfo {
613  typedef ASTTemplateArgumentListInfo Base;
614
615  // NOTE: the source location of the (optional) template keyword is
616  // stored after all template arguments.
617
618  /// \brief Get the source location of the template keyword.
619  SourceLocation getTemplateKeywordLoc() const {
620    return *reinterpret_cast<const SourceLocation*>
621      (getTemplateArgs() + NumTemplateArgs);
622  }
623
624  /// \brief Sets the source location of the template keyword.
625  void setTemplateKeywordLoc(SourceLocation TemplateKWLoc) {
626    *reinterpret_cast<SourceLocation*>
627      (getTemplateArgs() + NumTemplateArgs) = TemplateKWLoc;
628  }
629
630  static const ASTTemplateKWAndArgsInfo*
631  Create(ASTContext &C, SourceLocation TemplateKWLoc,
632         const TemplateArgumentListInfo &List);
633
634  void initializeFrom(SourceLocation TemplateKWLoc,
635                      const TemplateArgumentListInfo &List);
636  void initializeFrom(SourceLocation TemplateKWLoc,
637                      const TemplateArgumentListInfo &List,
638                      bool &Dependent, bool &InstantiationDependent,
639                      bool &ContainsUnexpandedParameterPack);
640  void initializeFrom(SourceLocation TemplateKWLoc);
641
642  static std::size_t sizeFor(unsigned NumTemplateArgs);
643};
644
645const DiagnosticBuilder &operator<<(const DiagnosticBuilder &DB,
646                                    const TemplateArgument &Arg);
647
648inline TemplateSpecializationType::iterator
649    TemplateSpecializationType::end() const {
650  return getArgs() + getNumArgs();
651}
652
653inline DependentTemplateSpecializationType::iterator
654    DependentTemplateSpecializationType::end() const {
655  return getArgs() + getNumArgs();
656}
657
658inline const TemplateArgument &
659    TemplateSpecializationType::getArg(unsigned Idx) const {
660  assert(Idx < getNumArgs() && "Template argument out of range");
661  return getArgs()[Idx];
662}
663
664inline const TemplateArgument &
665    DependentTemplateSpecializationType::getArg(unsigned Idx) const {
666  assert(Idx < getNumArgs() && "Template argument out of range");
667  return getArgs()[Idx];
668}
669
670} // end namespace clang
671
672#endif
673