//===-- AppleObjCTypeEncodingParser.cpp -------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "AppleObjCTypeEncodingParser.h"

#include "lldb/Symbol/ClangASTContext.h"
#include "lldb/Symbol/ClangUtil.h"
#include "lldb/Symbol/CompilerType.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/StringLexer.h"

#include <vector>

using namespace lldb_private;
using namespace lldb_utility;

AppleObjCTypeEncodingParser::AppleObjCTypeEncodingParser (ObjCLanguageRuntime& runtime) :
    ObjCLanguageRuntime::EncodingToType(),
    m_runtime(runtime)
{
    if (!m_scratch_ast_ctx_ap)
        m_scratch_ast_ctx_ap.reset(new ClangASTContext(runtime.GetProcess()->GetTarget().GetArchitecture().GetTriple().str().c_str()));
}

std::string
AppleObjCTypeEncodingParser::ReadStructName(lldb_utility::StringLexer& type)
{
    StreamString buffer;
    while (type.HasAtLeast(1) && type.Peek() != '=')
        buffer.Printf("%c",type.Next());
    return buffer.GetString();
}

std::string
AppleObjCTypeEncodingParser::ReadQuotedString(lldb_utility::StringLexer& type)
{
    StreamString buffer;
    while (type.HasAtLeast(1) && type.Peek() != '"')
        buffer.Printf("%c",type.Next());
    StringLexer::Character next = type.Next();
    UNUSED_IF_ASSERT_DISABLED(next);
    assert (next == '"');
    return buffer.GetString();
}

uint32_t
AppleObjCTypeEncodingParser::ReadNumber (lldb_utility::StringLexer& type)
{
    uint32_t total = 0;
    while (type.HasAtLeast(1) && isdigit(type.Peek()))
           total = 10*total + (type.Next() - '0');
    return total;
}

// as an extension to the published grammar recent runtimes emit structs like this:
// "{CGRect=\"origin\"{CGPoint=\"x\"d\"y\"d}\"size\"{CGSize=\"width\"d\"height\"d}}"

AppleObjCTypeEncodingParser::StructElement::StructElement() :
name(""),
type(clang::QualType()),
bitfield(0)
{}

AppleObjCTypeEncodingParser::StructElement
AppleObjCTypeEncodingParser::ReadStructElement (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression)
{
    StructElement retval;
    if (type.NextIf('"'))
        retval.name = ReadQuotedString(type);
    if (!type.NextIf('"'))
        return retval;
    uint32_t bitfield_size = 0;
    retval.type = BuildType(ast_ctx, type, for_expression, &bitfield_size);
    retval.bitfield = bitfield_size;
    return retval;
}

clang::QualType
AppleObjCTypeEncodingParser::BuildStruct (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression)
{
    return BuildAggregate(ast_ctx, type, for_expression, '{', '}', clang::TTK_Struct);
}

clang::QualType
AppleObjCTypeEncodingParser::BuildUnion (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression)
{
    return BuildAggregate(ast_ctx, type, for_expression, '(', ')', clang::TTK_Union);
}

clang::QualType
AppleObjCTypeEncodingParser::BuildAggregate (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression, char opener, char closer, uint32_t kind)
{
    if (!type.NextIf(opener))
        return clang::QualType();
    std::string name(ReadStructName(type));
    
    // We do not handle templated classes/structs at the moment.
    // If the name has a < in it, we are going to abandon this.
    // We're still obliged to parse it, so we just set a flag that
    // means "Don't actually build anything."
    
    const bool is_templated = name.find('<') != std::string::npos;
    
    if (!type.NextIf('='))
        return clang::QualType();
    bool in_union = true;
    std::vector<StructElement> elements;
    while (in_union && type.HasAtLeast(1))
    {
        if (type.NextIf(closer))
        {
            in_union = false;
            break;
        }
        else
        {
            auto element = ReadStructElement(ast_ctx, type, for_expression);
            if (element.type.isNull())
                break;
            else
                elements.push_back(element);
        }
    }
    if (in_union)
        return clang::QualType();
    
    if (is_templated)
        return clang::QualType(); // This is where we bail out.  Sorry!
    
    ClangASTContext *lldb_ctx = ClangASTContext::GetASTContext(&ast_ctx);
    if (!lldb_ctx)
        return clang::QualType();
    CompilerType union_type(lldb_ctx->CreateRecordType(nullptr, lldb::eAccessPublic, name.c_str(), kind, lldb::eLanguageTypeC));
    if (union_type)
    {
        ClangASTContext::StartTagDeclarationDefinition(union_type);
        
        unsigned int count = 0;
        for (auto element: elements)
        {
            if (element.name.empty())
            {
                StreamString elem_name;
                elem_name.Printf("__unnamed_%u",count);
                element.name = std::string(elem_name.GetData());
            }
            ClangASTContext::AddFieldToRecordType(union_type, element.name.c_str(), CompilerType(&ast_ctx, element.type), lldb::eAccessPublic, element.bitfield);
            ++count;
        }
        ClangASTContext::CompleteTagDeclarationDefinition(union_type);
    }
    return ClangUtil::GetQualType(union_type);
}

clang::QualType
AppleObjCTypeEncodingParser::BuildArray (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression)
{
    if (!type.NextIf('['))
        return clang::QualType();
    uint32_t size = ReadNumber(type);
    clang::QualType element_type(BuildType(ast_ctx, type, for_expression));
    if (!type.NextIf(']'))
        return clang::QualType();
    ClangASTContext *lldb_ctx = ClangASTContext::GetASTContext(&ast_ctx);
    if (!lldb_ctx)
        return clang::QualType();
    CompilerType array_type(lldb_ctx->CreateArrayType(CompilerType(&ast_ctx, element_type), size, false));
    return ClangUtil::GetQualType(array_type);
}

// the runtime can emit these in the form of @"SomeType", giving more specifics
// this would be interesting for expression parser interop, but since we actually try
// to avoid exposing the ivar info to the expression evaluator, consume but ignore the type info
// and always return an 'id'; if anything, dynamic typing will resolve things for us anyway
clang::QualType
AppleObjCTypeEncodingParser::BuildObjCObjectPointerType (clang::ASTContext &ast_ctx, lldb_utility::StringLexer& type, bool for_expression)
{
    if (!type.NextIf('@'))
        return clang::QualType();
    
    std::string name;
    
    if (type.NextIf('"'))
    {
        // We have to be careful here.  We're used to seeing
        //   @"NSString"
        // but in records it is possible that the string following an @ is the name of the next field and @ means "id".
        // This is the case if anything unquoted except for "}", the end of the type, or another name follows the quoted string.
        //
        // E.g.
        // - @"NSString"@ means "id, followed by a field named NSString of type id"
        // - @"NSString"} means "a pointer to NSString and the end of the struct"
        // - @"NSString""nextField" means "a pointer to NSString and a field named nextField"
        // - @"NSString" followed by the end of the string means "a pointer to NSString"
        //
        // As a result, the rule is: If we see @ followed by a quoted string, we peek.
        // - If we see }, ), ], the end of the string, or a quote ("), the quoted string is a class name.
        // - If we see anything else, the quoted string is a field name and we push it back onto type.

        name = ReadQuotedString(type);
        
        if (type.HasAtLeast(1))
        {
            switch (type.Peek())
            {
            default:
                // roll back
                type.PutBack(name.length() + 2); // undo our consumption of the string and of the quotes
                name.clear();
                break;
            case '}':
            case ')':
            case ']':
            case '"':
                // the quoted string is a class name – see the rule
                break;
            }
        }
        else
        {
            // the quoted string is a class name – see the rule
        }
    }
    
    if (for_expression && !name.empty())
    {
        size_t less_than_pos = name.find('<');
        
        if (less_than_pos != std::string::npos)
        {
            if (less_than_pos == 0)
                return ast_ctx.getObjCIdType();
            else
                name.erase(less_than_pos);
        }
        
        DeclVendor *decl_vendor = m_runtime.GetDeclVendor();
        
        assert (decl_vendor); // how are we parsing type encodings for expressions if a type vendor isn't in play?
        
        const bool append = false;
        const uint32_t max_matches = 1;
        std::vector<clang::NamedDecl *> decls;
        
        uint32_t num_types = decl_vendor->FindDecls(ConstString(name),
                                                    append,
                                                    max_matches,
                                                    decls);

        // The user can forward-declare something that has no definition.  The runtime doesn't prohibit this at all.
        // This is a rare and very weird case.  We keep this assert in debug builds so we catch other weird cases.
#ifdef LLDB_CONFIGURATION_DEBUG
        assert(num_types);
#else
        if (!num_types)
            return ast_ctx.getObjCIdType();
#endif

        return ClangUtil::GetQualType(ClangASTContext::GetTypeForDecl(decls[0]).GetPointerType());
    }
    else
    {
        // We're going to resolve this dynamically anyway, so just smile and wave.
        return ast_ctx.getObjCIdType();
    }
}

clang::QualType
AppleObjCTypeEncodingParser::BuildType (clang::ASTContext &ast_ctx, StringLexer& type, bool for_expression, uint32_t *bitfield_bit_size)
{
    if (!type.HasAtLeast(1))
        return clang::QualType();
    
    switch (type.Peek())
    {
    default:
        break;
    case '{':
        return BuildStruct(ast_ctx, type, for_expression);
    case '[':
        return BuildArray(ast_ctx, type, for_expression);
    case '(':
        return BuildUnion(ast_ctx, type, for_expression);
    case '@':
        return BuildObjCObjectPointerType(ast_ctx, type, for_expression);
    }
    
    switch (type.Next())
    {
    default:
        type.PutBack(1);
        return clang::QualType();
    case 'c':
        return ast_ctx.CharTy;
    case 'i':
        return ast_ctx.IntTy;
    case 's':
        return ast_ctx.ShortTy;
    case 'l':
        return ast_ctx.getIntTypeForBitwidth(32, true);
        // this used to be done like this:
        //   ClangASTContext *lldb_ctx = ClangASTContext::GetASTContext(&ast_ctx);
        //   if (!lldb_ctx)
        //      return clang::QualType();
        //   return lldb_ctx->GetIntTypeFromBitSize(32, true).GetQualType();
        // which uses one of the constants if one is available, but we don't think all this work is necessary.
    case 'q':
        return ast_ctx.LongLongTy;
    case 'C':
        return ast_ctx.UnsignedCharTy;
    case 'I':
        return ast_ctx.UnsignedIntTy;
    case 'S':
        return ast_ctx.UnsignedShortTy;
    case 'L':
        return ast_ctx.getIntTypeForBitwidth(32, false);
        // see note for 'l'
    case 'Q':
        return ast_ctx.UnsignedLongLongTy;
    case 'f':
        return ast_ctx.FloatTy;
    case 'd':
        return ast_ctx.DoubleTy;
    case 'B':
        return ast_ctx.BoolTy;
    case 'v':
        return ast_ctx.VoidTy;
    case '*':
        return ast_ctx.getPointerType(ast_ctx.CharTy);
    case '#':
        return ast_ctx.getObjCClassType();
    case ':':
        return ast_ctx.getObjCSelType();
    case 'b':
        {
            uint32_t size = ReadNumber(type);
            if (bitfield_bit_size)
            {
                *bitfield_bit_size = size;
                return ast_ctx.UnsignedIntTy; // FIXME: the spec is fairly vague here.
            }
            else
                return clang::QualType();
        }
    case 'r':
        {
            clang::QualType target_type = BuildType(ast_ctx, type, for_expression);
            if (target_type.isNull())
                return clang::QualType();
            else if (target_type == ast_ctx.UnknownAnyTy)
                return ast_ctx.UnknownAnyTy;
            else
                return ast_ctx.getConstType(target_type);
        }
    case '^':
        {
            if (!for_expression && type.NextIf('?'))
            {
                // if we are not supporting the concept of unknownAny, but what is being created here is an unknownAny*, then
                // we can just get away with a void*
                // this is theoretically wrong (in the same sense as 'theoretically nothing exists') but is way better than outright failure
                // in many practical cases
                return ast_ctx.VoidPtrTy;
            }
            else
            {
                clang::QualType target_type = BuildType(ast_ctx, type, for_expression);
                if (target_type.isNull())
                    return clang::QualType();
                else if (target_type == ast_ctx.UnknownAnyTy)
                    return ast_ctx.UnknownAnyTy;
                else
                    return ast_ctx.getPointerType(target_type);
            }
        }
    case '?':
        return for_expression ? ast_ctx.UnknownAnyTy : clang::QualType();
    }
}

CompilerType
AppleObjCTypeEncodingParser::RealizeType (clang::ASTContext &ast_ctx, const char* name, bool for_expression)
{
    if (name && name[0])
    {
        StringLexer lexer(name);
        clang::QualType qual_type = BuildType(ast_ctx, lexer, for_expression);
        return CompilerType(&ast_ctx, qual_type);
    }
    return CompilerType();
}

