///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// CompilationResult.h                                                       //
// Copyright (C) Microsoft Corporation. All rights reserved.                 //
// This file is distributed under the University of Illinois Open Source     //
// License. See LICENSE.TXT for details.                                     //
//                                                                           //
// This file provides a class to parse a translation unit and return the     //
// diagnostic results and AST tree.                                          //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#include <algorithm>
#include <atomic>
#include <cassert>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>

#include "dxc/Support/WinIncludes.h"
#include "dxc/Support/microcom.h"

#include "dxc/Support/dxcapi.use.h"
#include "dxc/dxcapi.h"
#include "dxc/dxcisense.h"
#include "llvm/Support/Atomic.h"

inline HRESULT IFE(HRESULT hr) {
  if (FAILED(hr)) {
    throw std::runtime_error("COM call failed");
  }
  return hr;
}

inline HRESULT GetFirstChildFromCursor(IDxcCursor *cursor,
                                       IDxcCursor **pResult) {
  HRESULT hr;
  IDxcCursor **children = nullptr;
  unsigned childrenCount;
  hr = cursor->GetChildren(0, 1, &childrenCount, &children);
  if (SUCCEEDED(hr) && childrenCount == 1) {
    *pResult = children[0];
  } else {
    *pResult = nullptr;
    hr = E_FAIL;
  }
  CoTaskMemFree(children);
  return hr;
}

class TrivialDxcUnsavedFile : public IDxcUnsavedFile {
private:
  DXC_MICROCOM_REF_FIELD(m_dwRef)
  LPCSTR m_fileName;
  LPCSTR m_contents;
  unsigned m_length;

public:
  DXC_MICROCOM_ADDREF_RELEASE_IMPL(m_dwRef)
  TrivialDxcUnsavedFile(LPCSTR fileName, LPCSTR contents)
      : m_dwRef(0), m_fileName(fileName), m_contents(contents) {
    m_length = (unsigned)strlen(m_contents);
  }

  static HRESULT Create(LPCSTR fileName, LPCSTR contents,
                        IDxcUnsavedFile **pResult) {
    CComPtr<TrivialDxcUnsavedFile> pNewValue =
        new TrivialDxcUnsavedFile(fileName, contents);
    return pNewValue.QueryInterface(pResult);
  }

  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid,
                                           void **ppvObject) override {
    if (ppvObject == nullptr)
      return E_POINTER;
    if (IsEqualIID(iid, __uuidof(IUnknown)) ||
        IsEqualIID(iid, __uuidof(INoMarshal)) ||
        IsEqualIID(iid, __uuidof(IDxcUnsavedFile))) {
      *ppvObject = reinterpret_cast<IDxcUnsavedFile *>(this);
      reinterpret_cast<IDxcUnsavedFile *>(this)->AddRef();
      return S_OK;
    }

    return E_NOINTERFACE;
  }
  HRESULT STDMETHODCALLTYPE GetFileName(LPSTR *pFileName) override {
    *pFileName = (LPSTR)CoTaskMemAlloc(1 + strlen(m_fileName));
    strcpy(*pFileName, m_fileName);
    return S_OK;
  }
  HRESULT STDMETHODCALLTYPE GetContents(LPSTR *pContents) override {
    *pContents = (LPSTR)CoTaskMemAlloc(m_length + 1);
    memcpy(*pContents, m_contents, m_length + 1);
    return S_OK;
  }
  HRESULT STDMETHODCALLTYPE GetLength(unsigned *pLength) override {
    *pLength = m_length;
    return S_OK;
  }
};

class HlslIntellisenseSupport : public dxc::DxCompilerDllLoader {
public:
  HlslIntellisenseSupport() {}

  HRESULT CreateIntellisense(IDxcIntelliSense **pResult) {
    return CreateInstance(CLSID_DxcIntelliSense, pResult);
  }
};

/// Summary of the results of a clang compilation operation.
class CompilationResult {
private:
  // Keep Intellisense alive.
  std::shared_ptr<HlslIntellisenseSupport> IsenseSupport;

  /// The top-level Intellisense object.
  CComPtr<IDxcIntelliSense> Intellisense;
  /// The libclang index for the compilation operation.
  CComPtr<IDxcIndex> Index;

  /// The number of diagnostic messages emitted.
  unsigned NumDiagnostics;

  /// The number of diagnostic messages emitted that indicate errors.
  unsigned NumErrorDiagnostics;

  /// The diagnostic messages emitted.
  std::vector<std::string> DiagnosticMessages;

  /// The severity of diagnostic messages.
  std::vector<DxcDiagnosticSeverity> DiagnosticSeverities;

  // Hide the copy constructor.
  CompilationResult(const CompilationResult &);

public:
  CompilationResult(std::shared_ptr<HlslIntellisenseSupport> support,
                    IDxcIntelliSense *isense, IDxcIndex *index,
                    IDxcTranslationUnit *tu)
      : IsenseSupport(support), Intellisense(isense), Index(index),
        NumErrorDiagnostics(0), TU(tu) {
    if (tu) {
      IFE(tu->GetNumDiagnostics(&NumDiagnostics));
    } else {
      NumDiagnostics = 0;
    }

    DxcDiagnosticDisplayOptions diagnosticOptions;
    IFE(isense->GetDefaultDiagnosticDisplayOptions(&diagnosticOptions));
    for (unsigned i = 0; i < NumDiagnostics; i++) {
      CComPtr<IDxcDiagnostic> diagnostic;
      IFE(tu->GetDiagnostic(i, &diagnostic));

      LPSTR format;
      IFE(diagnostic->FormatDiagnostic(diagnosticOptions, &format));
      DiagnosticMessages.push_back(std::string(format));
      CoTaskMemFree(format);

      DxcDiagnosticSeverity severity;
      IFE(diagnostic->GetSeverity(&severity));
      DiagnosticSeverities.push_back(severity);
      if (IsErrorSeverity(severity)) {
        NumErrorDiagnostics++;
      }
    }

    assert(NumErrorDiagnostics <= NumDiagnostics &&
           "else counting code in loop is incorrect");
    assert(DiagnosticMessages.size() == NumDiagnostics &&
           "else some diagnostics have no message");
    assert(DiagnosticSeverities.size() == NumDiagnostics &&
           "else some diagnostics have no severity");
  }

  CompilationResult(CompilationResult &&other)
      : IsenseSupport(std::move(other.IsenseSupport)) {
    // Allow move constructor.
    Intellisense = other.Intellisense;
    other.Intellisense = nullptr;
    Index = other.Index;
    other.Index = nullptr;
    TU = other.TU;
    other.TU = nullptr;
    NumDiagnostics = other.NumDiagnostics;
    NumErrorDiagnostics = other.NumErrorDiagnostics;
    DiagnosticMessages = std::move(other.DiagnosticMessages);
    DiagnosticSeverities = std::move(other.DiagnosticSeverities);
  }

  ~CompilationResult() { Dispose(); }

  /// The translation unit resulting from the compilation.
  CComPtr<IDxcTranslationUnit> TU;

  static const char *getDefaultFileName() { return "filename.hlsl"; }

  static std::shared_ptr<HlslIntellisenseSupport> DefaultHlslSupport;

  static std::shared_ptr<HlslIntellisenseSupport> GetHlslSupport() {
    if (DefaultHlslSupport.get() != nullptr) {
      return DefaultHlslSupport;
    }
    std::shared_ptr<HlslIntellisenseSupport> result =
        std::make_shared<HlslIntellisenseSupport>();
    IFE(result->Initialize());
    return result;
  }

  static CompilationResult
  CreateForProgramAndArgs(const char *text, size_t textLen,
                          const char *commandLineArgs[],
                          unsigned commandLineArgsCount,
                          DxcTranslationUnitFlags *options = nullptr) {
    std::shared_ptr<HlslIntellisenseSupport> support(GetHlslSupport());

    CComPtr<IDxcIntelliSense> isense;
    IFE(support->CreateIntellisense(&isense));
    CComPtr<IDxcIndex> tuIndex;
    CComPtr<IDxcTranslationUnit> tu;
    const char *fileName = getDefaultFileName();
    if (textLen == 0)
      textLen = strlen(text);

    IFE(isense->CreateIndex(&tuIndex));

    CComPtr<IDxcUnsavedFile> unsavedFile;
    IFE(TrivialDxcUnsavedFile::Create(fileName, text, &unsavedFile));

    DxcTranslationUnitFlags localOptions;
    if (options == nullptr) {
      IFE(isense->GetDefaultEditingTUOptions(&localOptions));
    } else {
      localOptions = *options;
    }
    IFE(tuIndex->ParseTranslationUnit(fileName, commandLineArgs,
                                      commandLineArgsCount, &(unsavedFile.p), 1,
                                      localOptions, &tu));

    return CompilationResult(support, isense.p, tuIndex.p, tu.p);
  }

  static CompilationResult
  CreateForProgram(const char *text, size_t textLen,
                   DxcTranslationUnitFlags *options = nullptr) {
    const char *commandLineArgs[] = {"-c", "-ferror-limit=200"};
    unsigned commandLineArgsCount = _countof(commandLineArgs);
    return CreateForProgramAndArgs(text, textLen, commandLineArgs,
                                   commandLineArgsCount, options);
  }

  static CompilationResult CreateForCommandLine(char *arguments,
                                                const char *fileName) {
    std::shared_ptr<HlslIntellisenseSupport> support(GetHlslSupport());
    return CreateForCommandLine(arguments, fileName, support);
  }

  static CompilationResult
  CreateForCommandLine(char *arguments, const char *fileName,
                       std::shared_ptr<HlslIntellisenseSupport> support) {
    CComPtr<IDxcIntelliSense> isense;
    IFE(support->CreateIntellisense(&isense));
    CComPtr<IDxcIndex> tuIndex;
    CComPtr<IDxcTranslationUnit> tu;
    const char *commandLineArgs[32];
    unsigned commandLineArgsCount = 0;
    char *nextArg = arguments;

    // Set a very high number of errors to avoid giving up too early.
    commandLineArgs[commandLineArgsCount++] = "-ferror-limit=2000";

    // Turn on spell checking for compatibility. This produces error messages
    // that suggest corrected spellings.
    commandLineArgs[commandLineArgsCount++] = "-fspell-checking";

    // Turn off color diagnostics to avoid control characters in diagnostic
    // stream.
    commandLineArgs[commandLineArgsCount++] = "-fno-color-diagnostics";

    IFE(isense->CreateIndex(&tuIndex));

    // Split command line arguments by spaces
    if (nextArg) {
      // skip leading spaces
      while (*nextArg == ' ')
        nextArg++;
      commandLineArgs[commandLineArgsCount++] = nextArg;
      while ((*nextArg != '\0')) {
        if (*nextArg == ' ') {
          *nextArg = 0;
          commandLineArgs[commandLineArgsCount++] = nextArg + 1;
        }
        nextArg++;
      }
    }

    DxcTranslationUnitFlags options;
    IFE(isense->GetDefaultEditingTUOptions(&options));
    IFE(tuIndex->ParseTranslationUnit(fileName, commandLineArgs,
                                      commandLineArgsCount, nullptr, 0, options,
                                      &tu));

    return CompilationResult(support, isense.p, tuIndex.p, tu.p);
  }

  bool IsTUAvailable() const { return TU != nullptr; }
  bool ParseSucceeded() const {
    return TU != nullptr && NumErrorDiagnostics == 0;
  }

  static bool IsErrorSeverity(DxcDiagnosticSeverity severity) {
    return (severity == DxcDiagnostic_Error || severity == DxcDiagnostic_Fatal);
  }

  /// Gets a string with all error messages concatenated, one per line.
  std::string GetTextForErrors() const {
    assert(DiagnosticMessages.size() == NumDiagnostics &&
           "otherwise some diagnostics have no message");
    assert(DiagnosticSeverities.size() == NumDiagnostics &&
           "otherwise some diagnostics have no severity");

    std::stringstream ostr;
    for (size_t i = 0; i < DiagnosticMessages.size(); i++) {
      if (IsErrorSeverity(DiagnosticSeverities[i])) {
        ostr << DiagnosticMessages[i] << '\n';
      }
    }

    return ostr.str();
  }

  /// Releases resources, including the translation unit AST.
  void Dispose() {
    TU = nullptr;
    Index = nullptr;
    Intellisense = nullptr;
    IsenseSupport = nullptr;
  }

private:
#if SUPPORTS_CURSOR_WALK
  struct CursorStringData {
    std::stringstream &ostr;
    std::vector<CXCursor> cursors;
    CursorStringData(std::stringstream &the_ostr) : ostr(the_ostr) {}
  };

  static CXChildVisitResult
  AppendCursorStringCallback(CXCursor cursor, CXCursor parent,
                             CXClientData client_data) {
    CursorStringData *d = (CursorStringData *)client_data;
    auto cursorsStart = std::begin(d->cursors);
    auto cursorsEnd = std::end(d->cursors);
    auto parentLocation = std::find(cursorsStart, cursorsEnd, parent);
    assert(parentLocation != cursorsEnd &&
           "otherwise the parent was not visited previously");
    AppendCursorString(cursor, parentLocation - cursorsStart, d->ostr);
    d->cursors.resize(1 + (parentLocation - cursorsStart));
    d->cursors.push_back(cursor);
    return CXChildVisit_Recurse;
  }
#endif
  static void AppendCursorString(IDxcCursor *cursor, size_t indent,
                                 std::stringstream &ostr) {
    if (indent > 0) {
      std::streamsize prior = ostr.width();
      ostr.width(indent);
      ostr << ' ';
      ostr.width(prior);
    }

    CComPtr<IDxcType> type;
    DxcCursorKind kind;
    cursor->GetKind(&kind);
    cursor->GetCursorType(&type);
    switch (kind) {
    case DxcCursor_UnexposedDecl:
      ostr << "UnexposedDecl";
      break;
    case DxcCursor_StructDecl:
      ostr << "StructDecl";
      break;
    case DxcCursor_UnionDecl:
      ostr << "UnionDecl";
      break;
    case DxcCursor_ClassDecl:
      ostr << "ClassDecl";
      break;
    case DxcCursor_EnumDecl:
      ostr << "EnumDecl";
      break;
    case DxcCursor_FieldDecl:
      ostr << "FieldDecl";
      break;
    case DxcCursor_EnumConstantDecl:
      ostr << "EnumConstantDecl";
      break;
    case DxcCursor_FunctionDecl:
      ostr << "FunctionDecl";
      break;
    case DxcCursor_VarDecl:
      ostr << "VarDecl";
      break;
    case DxcCursor_ParmDecl:
      ostr << "ParmDecl";
      break;
    case DxcCursor_ObjCInterfaceDecl:
      ostr << "ObjCInterfaceDecl";
      break;
    case DxcCursor_ObjCCategoryDecl:
      ostr << "ObjCCategoryDecl";
      break;
    case DxcCursor_ObjCProtocolDecl:
      ostr << "ObjCProtocolDecl";
      break;
    case DxcCursor_ObjCPropertyDecl:
      ostr << "ObjCPropertyDecl";
      break;
    case DxcCursor_ObjCIvarDecl:
      ostr << "ObjCIvarDecl";
      break;
    case DxcCursor_ObjCInstanceMethodDecl:
      ostr << "ObjCInstanceMethodDecl";
      break;
    case DxcCursor_ObjCClassMethodDecl:
      ostr << "ObjCClassMethodDecl";
      break;
    case DxcCursor_ObjCImplementationDecl:
      ostr << "ObjCImplementationDecl";
      break;
    case DxcCursor_ObjCCategoryImplDecl:
      ostr << "ObjCCategoryImplDecl";
      break;
    case DxcCursor_TypedefDecl:
      ostr << "TypedefDecl";
      break;
    case DxcCursor_CXXMethod:
      ostr << "CXXMethod";
      break;
    case DxcCursor_Namespace:
      ostr << "Namespace";
      break;
    case DxcCursor_LinkageSpec:
      ostr << "LinkageSpec";
      break;
    case DxcCursor_Constructor:
      ostr << "Constructor";
      break;
    case DxcCursor_Destructor:
      ostr << "Destructor";
      break;
    case DxcCursor_ConversionFunction:
      ostr << "ConversionFunction";
      break;
    case DxcCursor_TemplateTypeParameter:
      ostr << "TemplateTypeParameter";
      break;
    case DxcCursor_NonTypeTemplateParameter:
      ostr << "NonTypeTemplateParameter";
      break;
    case DxcCursor_TemplateTemplateParameter:
      ostr << "TemplateTemplateParameter";
      break;
    case DxcCursor_FunctionTemplate:
      ostr << "FunctionTemplate";
      break;
    case DxcCursor_ClassTemplate:
      ostr << "ClassTemplate";
      break;
    case DxcCursor_ClassTemplatePartialSpecialization:
      ostr << "ClassTemplatePartialSpecialization";
      break;
    case DxcCursor_NamespaceAlias:
      ostr << "NamespaceAlias";
      break;
    case DxcCursor_UsingDirective:
      ostr << "UsingDirective";
      break;
    case DxcCursor_UsingDeclaration:
      ostr << "UsingDeclaration";
      break;
    case DxcCursor_TypeAliasDecl:
      ostr << "TypeAliasDecl";
      break;
    case DxcCursor_ObjCSynthesizeDecl:
      ostr << "ObjCSynthesizeDecl";
      break;
    case DxcCursor_ObjCDynamicDecl:
      ostr << "ObjCDynamicDecl";
      break;
    case DxcCursor_CXXAccessSpecifier:
      ostr << "CXXAccessSpecifier";
      break;
    case DxcCursor_ObjCSuperClassRef:
      ostr << "ObjCSuperClassRef";
      break;
    case DxcCursor_ObjCProtocolRef:
      ostr << "ObjCProtocolRef";
      break;
    case DxcCursor_ObjCClassRef:
      ostr << "ObjCClassRef";
      break;
    case DxcCursor_TypeRef:
      ostr << "TypeRef";
      break;
    case DxcCursor_CXXBaseSpecifier:
      ostr << "CXXBaseSpecifier";
      break;
    case DxcCursor_TemplateRef:
      ostr << "TemplateRef";
      break;
    case DxcCursor_NamespaceRef:
      ostr << "NamespaceRef";
      break;
    case DxcCursor_MemberRef:
      ostr << "MemberRef";
      break;
    case DxcCursor_LabelRef:
      ostr << "LabelRef";
      break;
    case DxcCursor_OverloadedDeclRef:
      ostr << "OverloadedDeclRef";
      break;
    case DxcCursor_VariableRef:
      ostr << "VariableRef";
      break;
    case DxcCursor_InvalidFile:
      ostr << "InvalidFile";
      break;
    case DxcCursor_NoDeclFound:
      ostr << "NoDeclFound";
      break;
    case DxcCursor_NotImplemented:
      ostr << "NotImplemented";
      break;
    case DxcCursor_InvalidCode:
      ostr << "InvalidCode";
      break;
    case DxcCursor_UnexposedExpr:
      ostr << "UnexposedExpr";
      break;
    case DxcCursor_DeclRefExpr:
      ostr << "DeclRefExpr";
      break;
    case DxcCursor_MemberRefExpr:
      ostr << "MemberRefExpr";
      break;
    case DxcCursor_CallExpr:
      ostr << "CallExpr";
      break;
    case DxcCursor_ObjCMessageExpr:
      ostr << "ObjCMessageExpr";
      break;
    case DxcCursor_BlockExpr:
      ostr << "BlockExpr";
      break;
    case DxcCursor_IntegerLiteral:
      ostr << "IntegerLiteral";
      break;
    case DxcCursor_FloatingLiteral:
      ostr << "FloatingLiteral";
      break;
    case DxcCursor_ImaginaryLiteral:
      ostr << "ImaginaryLiteral";
      break;
    case DxcCursor_StringLiteral:
      ostr << "StringLiteral";
      break;
    case DxcCursor_CharacterLiteral:
      ostr << "CharacterLiteral";
      break;
    case DxcCursor_ParenExpr:
      ostr << "ParenExpr";
      break;
    case DxcCursor_UnaryOperator:
      ostr << "UnaryOperator";
      break;
    case DxcCursor_ArraySubscriptExpr:
      ostr << "ArraySubscriptExpr";
      break;
    case DxcCursor_BinaryOperator:
      ostr << "BinaryOperator";
      break;
    case DxcCursor_CompoundAssignOperator:
      ostr << "CompoundAssignOperator";
      break;
    case DxcCursor_ConditionalOperator:
      ostr << "ConditionalOperator";
      break;
    case DxcCursor_CStyleCastExpr:
      ostr << "CStyleCastExpr";
      break;
    case DxcCursor_CompoundLiteralExpr:
      ostr << "CompoundLiteralExpr";
      break;
    case DxcCursor_InitListExpr:
      ostr << "InitListExpr";
      break;
    case DxcCursor_AddrLabelExpr:
      ostr << "AddrLabelExpr";
      break;
    case DxcCursor_StmtExpr:
      ostr << "StmtExpr";
      break;
    case DxcCursor_GenericSelectionExpr:
      ostr << "GenericSelectionExpr";
      break;
    case DxcCursor_GNUNullExpr:
      ostr << "GNUNullExpr";
      break;
    case DxcCursor_CXXStaticCastExpr:
      ostr << "CXXStaticCastExpr";
      break;
    case DxcCursor_CXXDynamicCastExpr:
      ostr << "CXXDynamicCastExpr";
      break;
    case DxcCursor_CXXReinterpretCastExpr:
      ostr << "CXXReinterpretCastExpr";
      break;
    case DxcCursor_CXXConstCastExpr:
      ostr << "CXXConstCastExpr";
      break;
    case DxcCursor_CXXFunctionalCastExpr:
      ostr << "CXXFunctionalCastExpr";
      break;
    case DxcCursor_CXXTypeidExpr:
      ostr << "CXXTypeidExpr";
      break;
    case DxcCursor_CXXBoolLiteralExpr:
      ostr << "CXXBoolLiteralExpr";
      break;
    case DxcCursor_CXXNullPtrLiteralExpr:
      ostr << "CXXNullPtrLiteralExpr";
      break;
    case DxcCursor_CXXThisExpr:
      ostr << "CXXThisExpr";
      break;
    case DxcCursor_CXXThrowExpr:
      ostr << "CXXThrowExpr";
      break;
    case DxcCursor_CXXNewExpr:
      ostr << "CXXNewExpr";
      break;
    case DxcCursor_CXXDeleteExpr:
      ostr << "CXXDeleteExpr";
      break;
    case DxcCursor_UnaryExpr:
      ostr << "UnaryExpr";
      break;
    case DxcCursor_ObjCStringLiteral:
      ostr << "ObjCStringLiteral";
      break;
    case DxcCursor_ObjCEncodeExpr:
      ostr << "ObjCEncodeExpr";
      break;
    case DxcCursor_ObjCSelectorExpr:
      ostr << "ObjCSelectorExpr";
      break;
    case DxcCursor_ObjCProtocolExpr:
      ostr << "ObjCProtocolExpr";
      break;
    case DxcCursor_ObjCBridgedCastExpr:
      ostr << "ObjCBridgedCastExpr";
      break;
    case DxcCursor_PackExpansionExpr:
      ostr << "PackExpansionExpr";
      break;
    case DxcCursor_SizeOfPackExpr:
      ostr << "SizeOfPackExpr";
      break;
    case DxcCursor_LambdaExpr:
      ostr << "LambdaExpr";
      break;
    case DxcCursor_ObjCBoolLiteralExpr:
      ostr << "ObjCBoolLiteralExpr";
      break;
    case DxcCursor_ObjCSelfExpr:
      ostr << "ObjCSelfExpr";
      break;
    case DxcCursor_UnexposedStmt:
      ostr << "UnexposedStmt";
      break;
    case DxcCursor_LabelStmt:
      ostr << "LabelStmt";
      break;
    case DxcCursor_CompoundStmt:
      ostr << "CompoundStmt";
      break;
    case DxcCursor_CaseStmt:
      ostr << "CaseStmt";
      break;
    case DxcCursor_DefaultStmt:
      ostr << "DefaultStmt";
      break;
    case DxcCursor_IfStmt:
      ostr << "IfStmt";
      break;
    case DxcCursor_SwitchStmt:
      ostr << "SwitchStmt";
      break;
    case DxcCursor_WhileStmt:
      ostr << "WhileStmt";
      break;
    case DxcCursor_DoStmt:
      ostr << "DoStmt";
      break;
    case DxcCursor_ForStmt:
      ostr << "ForStmt";
      break;
    case DxcCursor_GotoStmt:
      ostr << "GotoStmt";
      break;
    case DxcCursor_IndirectGotoStmt:
      ostr << "IndirectGotoStmt";
      break;
    case DxcCursor_ContinueStmt:
      ostr << "ContinueStmt";
      break;
    case DxcCursor_BreakStmt:
      ostr << "BreakStmt";
      break;
    case DxcCursor_ReturnStmt:
      ostr << "ReturnStmt";
      break;
    case DxcCursor_GCCAsmStmt:
      ostr << "GCCAsmStmt";
      break;
    case DxcCursor_ObjCAtTryStmt:
      ostr << "ObjCAtTryStmt";
      break;
    case DxcCursor_ObjCAtCatchStmt:
      ostr << "ObjCAtCatchStmt";
      break;
    case DxcCursor_ObjCAtFinallyStmt:
      ostr << "ObjCAtFinallyStmt";
      break;
    case DxcCursor_ObjCAtThrowStmt:
      ostr << "ObjCAtThrowStmt";
      break;
    case DxcCursor_ObjCAtSynchronizedStmt:
      ostr << "ObjCAtSynchronizedStmt";
      break;
    case DxcCursor_ObjCAutoreleasePoolStmt:
      ostr << "ObjCAutoreleasePoolStmt";
      break;
    case DxcCursor_ObjCForCollectionStmt:
      ostr << "ObjCForCollectionStmt";
      break;
    case DxcCursor_CXXCatchStmt:
      ostr << "CXXCatchStmt";
      break;
    case DxcCursor_CXXTryStmt:
      ostr << "CXXTryStmt";
      break;
    case DxcCursor_CXXForRangeStmt:
      ostr << "CXXForRangeStmt";
      break;
    case DxcCursor_SEHTryStmt:
      ostr << "SEHTryStmt";
      break;
    case DxcCursor_SEHExceptStmt:
      ostr << "SEHExceptStmt";
      break;
    case DxcCursor_SEHFinallyStmt:
      ostr << "SEHFinallyStmt";
      break;
    case DxcCursor_MSAsmStmt:
      ostr << "MSAsmStmt";
      break;
    case DxcCursor_NullStmt:
      ostr << "NullStmt";
      break;
    case DxcCursor_DeclStmt:
      ostr << "DeclStmt";
      break;
    case DxcCursor_OMPParallelDirective:
      ostr << "OMPParallelDirective";
      break;
    case DxcCursor_TranslationUnit:
      ostr << "TranslationUnit";
      break;
    case DxcCursor_UnexposedAttr:
      ostr << "UnexposedAttr";
      break;
#if 0
  CXCursor_IBActionAttr                  = 401,
  CXCursor_IBOutletAttr                  = 402,
  CXCursor_IBOutletCollectionAttr        = 403,
  CXCursor_CXXFinalAttr                  = 404,
  CXCursor_CXXOverrideAttr               = 405,
  CXCursor_AnnotateAttr                  = 406,
  CXCursor_AsmLabelAttr                  = 407,
  CXCursor_PackedAttr                    = 408,
     
  /* Preprocessing */
  CXCursor_PreprocessingDirective        = 500,
  CXCursor_MacroDefinition               = 501,
  CXCursor_MacroExpansion                = 502,
  CXCursor_InclusionDirective            = 503,
    case CXCursor_FunctionDecl:
      break;
    case CXCursor_ClassDecl:
      ostr << "class decl";
      break;
    case CXCursor_TranslationUnit:
      ostr << "translation unit";
      break;
#endif
    default:
      ostr << "unknown/unhandled cursor kind " << kind;
      break;
    }

    DxcTypeKind typeKind;
    if (type != nullptr && SUCCEEDED(type->GetKind(&typeKind)) &&
        typeKind != DxcTypeKind_Invalid) {
      LPSTR name;
      type->GetSpelling(&name);
      ostr << " [type " << name << "]";
      CoTaskMemFree(name);
    }

    ostr << '\n';

    // Recurse.
    IDxcCursor **children = nullptr;
    unsigned childrenCount;
    cursor->GetChildren(0, 64, &childrenCount, &children);
    for (unsigned i = 0; i < childrenCount; i++) {
      AppendCursorString(children[i], indent + 1, ostr);
      children[i]->Release();
    }
    CoTaskMemFree(children);
  }

public:
  std::string BuildASTString() {
    if (TU == nullptr) {
      return "<failed to build - TU is null>";
    }

    CComPtr<IDxcCursor> cursor;
    std::stringstream ostr;
    this->TU->GetCursor(&cursor);
    AppendCursorString(cursor, 0, ostr);
    return ostr.str();
  }
};
