diff --git a/src/core/ulib_doc.cpp b/src/core/ulib_doc.cpp index 17ec78d9d..5dad46560 100644 --- a/src/core/ulib_doc.cpp +++ b/src/core/ulib_doc.cpp @@ -285,6 +285,384 @@ string type2url( const string & type, const vector & groups ) +//----------------------------------------------------------------------------- +// name: class CKDocJSONOutput | 1.5.2.5 (terry feng) added +// desc: JSON output +//----------------------------------------------------------------------------- +class CKDocJSONOutput : public CKDocOutput +{ +private: + Chuck_Func * m_func; + std::string m_title; + Chuck_VM * m_vm_ref; + Chuck_Env * m_env_ref; + vector< pair > m_args; + +public: + CKDocJSONOutput( Chuck_VM * vm ) : m_func(NULL), m_vm_ref(vm) + { + // get the env + m_env_ref = vm != NULL ? vm->env() : NULL; + } + // file extension + virtual string fileExtension() const { return ".json"; } + // render JSON index + virtual string renderIndex( const string & indexTitle, const vector & groups ) + { + ostringstream sout; + sout << "{\n"; + + for( t_CKINT i = 0; i < groups.size(); ++i ) + { + sout << " \"" << groups[i]->name << "\": [\n"; + for( t_CKINT j = 0; j < groups[i]->types.size(); ++j ) + { + sout << " \"" << groups[i]->types[j]->base_name << "\""; + if(j < groups[i]->types.size() - 1) + { + sout << ","; + } + sout << "\n"; + } + sout << " ]"; + if(i < groups.size() - 1) + { + sout << ","; + } + sout << "\n"; + } + sout << "}\n"; + return sout.str(); + } + +public: + void begin( const string & title ) + { + m_title = trim(title); + // check length + if( m_title.length() == 0 ) m_title = ""; + // string to return + m_outputStr += "{\n"; + m_outputStr += " \"title\": \"" + m_title + "\",\n"; + m_outputStr += " \"groups\": [\n"; + } + void heading() { } + void end() { m_outputStr += " ]\n}\n"; } + void title(const std::string &_title) { } + void begin_body() { } + void end_body() + { removeTrailingComma(m_outputStr); } + void begin_toc() { } + void toc_class(Chuck_Type * type) { } + void end_toc() { } + void begin_classes( CKDocGroup * group ) { } + void end_classes() { } + void begin_class(Chuck_Type * type, const vector & groups) + { + m_outputStr += " {\n"; + m_outputStr += " \"name\": \"" + type->base_name + "\",\n"; + m_outputStr += " \"description\": \"" + jsonStringify(type->doc) + "\",\n"; + + // type heirarchy, iterate through parents + Chuck_Type * parent = type->parent; + // check if there is a parent class + if( parent != NULL ) + { + m_outputStr += " \"inherits\": ["; + } + while( parent != NULL ) + { + m_outputStr += "\"" + parent->base_name + "\""; + parent = parent->parent; + if( parent != NULL ) + { + m_outputStr += ", "; + } + } + if( type->parent != NULL ) m_outputStr += "],\n"; + } + + void end_class() { + removeTrailingComma(m_outputStr); + m_outputStr += " },\n"; + } + void begin_examples() { + m_outputStr += " \"examples\": [\n"; + } + void end_examples() { + removeTrailingComma(m_outputStr); + m_outputStr += " ],\n"; + } + void begin_static_member_vars() { + m_outputStr += " \"static member variables\": [\n"; + } + void end_static_member_vars() { + removeTrailingComma(m_outputStr); + m_outputStr += " ],\n"; + } + void begin_member_vars() { + m_outputStr += " \"member variables\": [\n"; + } + void end_member_vars() { + removeTrailingComma(m_outputStr); + m_outputStr += " ],\n"; + } + void begin_static_member_funcs() { + m_outputStr += " \"static member functions\": [\n"; + } + void end_static_member_funcs() { + removeTrailingComma(m_outputStr); + m_outputStr += " ],\n"; + } + void begin_ctors() { + m_outputStr += " \"constructors\": [\n"; + } + void end_ctors() { + removeTrailingComma(m_outputStr); + m_outputStr += " ],\n"; + } + void begin_dtor() { } + void begin_member_funcs() { + m_outputStr += " \"member functions\": [\n"; + } + void end_member_funcs() { + removeTrailingComma(m_outputStr); + m_outputStr += " ],\n"; + } + void example(const std::string &name, const std::string &url) + { + // remove the "../" + m_outputStr += " \"" + EXAMPLES_URL_BASE + url.substr(3) + "\",\n"; + } + + void static_member_var(Chuck_Value * var) + { + m_outputStr += " {\n"; + m_outputStr += " \"name\": \"" + var->name + "\",\n"; + m_outputStr += " \"type\": \"" + var->type->base_name + array_depth_to_brackets(var->type->array_depth) + "\",\n"; + m_outputStr += " \"description\": \"" + jsonStringify(var->doc) + "\"\n"; + m_outputStr += " },\n"; + } + + void member_var(Chuck_Value * var) + { + m_outputStr += " {\n"; + m_outputStr += " \"name\": \"" + var->name + "\",\n"; + m_outputStr += " \"type\": \"" + var->type->base_name + array_depth_to_brackets(var->type->array_depth) + "\",\n"; + m_outputStr += " \"description\": \"" + jsonStringify(var->doc) + "\"\n"; + m_outputStr += " },\n"; + } + + void begin_static_member_func(Chuck_Func * func) + { + // begin + m_outputStr += " {\n"; + // function name + m_outputStr += " \"static member function\": \"" + func->base_name + "\",\n"; + // return type + string ret_type = func->def()->ret_type->base_name; + string ret_brackets = func->def() && func->def()->ret_type->array_depth ? array_depth_to_brackets(func->def()->ret_type->array_depth) : ""; + m_outputStr += " \"return type\": \"" + ret_type + ret_brackets + "\",\n"; + // save for end_ctor() + m_func = func; + // clear func arg list + m_args.clear(); + } + + void end_static_member_func() + { + // verify + if( !m_func ) return; + // output constructor arguments + if (m_args.empty()) + { + m_outputStr += " \"arguments\": [],\n"; + } + else + { + m_outputStr += " \"arguments\": [\n"; + for (vector< pair >::iterator arg_it = m_args.begin(); arg_it != m_args.end(); ++arg_it) + { + m_outputStr += " {\n"; + m_outputStr += " \"type\": \"" + arg_it->first + "\",\n"; + m_outputStr += " \"name\": \"" + arg_it->second + "\"\n"; + m_outputStr += " },\n"; + } + removeTrailingComma(m_outputStr); + m_outputStr += " ],\n"; + } + // description + m_outputStr += " \"description\": \"" + jsonStringify(capitalize_and_periodize(m_func->doc)) + "\"\n"; + // finish output + m_outputStr += " },\n"; + // zero out + m_func = NULL; + } + + void begin_member_func(Chuck_Func * func) + { + // begin + m_outputStr += " {\n"; + // function name + m_outputStr += " \"member function\": \"" + func->base_name + "\",\n"; + // return type + string ret_type = func->def()->ret_type->base_name; + string ret_brackets = func->def() && func->def()->ret_type->array_depth ? array_depth_to_brackets(func->def()->ret_type->array_depth) : ""; + m_outputStr += " \"return type\": \"" + ret_type + ret_brackets + "\",\n"; + // save for end_member_func() + m_func = func; + // clear func arg list + m_args.clear(); + } + + void end_member_func() + { + // verify + if( !m_func ) return; + // output constructor arguments + if (m_args.empty()) + { + m_outputStr += " \"arguments\": [],\n"; + } + else + { + m_outputStr += " \"arguments\": [\n"; + for (vector< pair >::iterator arg_it = m_args.begin(); arg_it != m_args.end(); ++arg_it) + { + m_outputStr += " {\n"; + m_outputStr += " \"type\": \"" + arg_it->first + "\",\n"; + m_outputStr += " \"name\": \"" + arg_it->second + "\"\n"; + m_outputStr += " },\n"; + } + removeTrailingComma(m_outputStr); + m_outputStr += " ],\n"; + } + // description + m_outputStr += " \"description\": \"" + jsonStringify(capitalize_and_periodize(m_func->doc)) + "\"\n"; + // finish output + m_outputStr += " },\n"; + // zero out + m_func = NULL; + } + + void begin_ctor( Chuck_Func * func ) + { + // begin + m_outputStr += " {\n"; + // function name + m_outputStr += " \"constructor\": \"" + func->base_name + "\",\n"; + // save for end_ctor() + m_func = func; + // clear func arg list + m_args.clear(); + } + + void end_ctor() + { + // verify + if( !m_func ) return; + // output constructor arguments + if (m_args.empty()) + { + m_outputStr += " \"arguments\": [],\n"; + } + else + { + m_outputStr += " \"arguments\": [\n"; + for (vector< pair >::iterator arg_it = m_args.begin(); arg_it != m_args.end(); ++arg_it) + { + m_outputStr += " {\n"; + m_outputStr += " \"type\": \"" + arg_it->first + "\",\n"; + m_outputStr += " \"name\": \"" + arg_it->second + "\"\n"; + m_outputStr += " },\n"; + } + removeTrailingComma(m_outputStr); + m_outputStr += " ],\n"; + } + // description + m_outputStr += " \"description\": \"" + jsonStringify(capitalize_and_periodize(m_func->doc)) + "\"\n"; + // finish output + m_outputStr += " },\n"; + // zero out + m_func = NULL; + } + + void func_arg( a_Arg_List arg ) + { + // argument type + string arg_type = arg->type->base_name; + string arg_brackets = arg->type->array_depth ? array_depth_to_brackets(arg->type->array_depth) : ""; + string type = arg_type + arg_brackets; + // argument name + string name = varnameclean(S_name(arg->var_decl->xid)); + + m_args.push_back(make_pair(type, name)); + } + +public: + // compute array_depth and return brackets string + static string array_depth_to_brackets( int depth ) + { + string brackets = ""; + for( int i = 0; i < depth; i++ ) + { + brackets += "[]"; + } + return brackets; + } + + // remove brackets from varname + static std::string varnameclean( std::string vn ) + { + // strip [] + vn.erase(std::remove(vn.begin(), vn.end(), '['), vn.end()); + vn.erase(std::remove(vn.begin(), vn.end(), ']'), vn.end()); + return vn; + } + + // make valid JSON string + static std::string jsonStringify( const std::string & str ) + { + // copy + string jsonStr = string(str); + // convert b-slash to double b-slash + replaceAll(jsonStr, std::string("\\"), string("\\\\")); + // convert quotes + replaceAll(jsonStr, std::string("\""), string("\\\"")); + // convert newlines to spaces + replace(jsonStr.begin(), jsonStr.end(), '\n', ' '); + // remove double spaces + replaceAll(jsonStr, std::string(" "), string(" ")); + return jsonStr; + } + + // replace string `from` instances with `to` + static void replaceAll( std::string & str, const std::string & from, const std::string & to ) { + if (from.empty()) return; + size_t pos = 0; + while ((pos = str.find(from, pos)) != std::string::npos) + { + str.replace(pos, from.length(), to); + pos += to.length(); + } + } + + // remove trailing comma if present + static void removeTrailingComma( std::string & str ) + { + // if last or second to last character is a comma, remove it + // accounts for the possibility of a comma followed by a newline + if (str[str.length() - 1] == ',') + { + str.erase(str.length() - 1, 1); + } + else if (str[str.length() - 2] == ',') + { + str.erase(str.length() - 2, 1); + } + } +}; + //----------------------------------------------------------------------------- // name: class CKDocHTMLOutput | 1.5.0.0 (ge, spencer) added @@ -474,10 +852,15 @@ class CKDocHTMLOutput : public CKDocOutput m_outputStr += "

constructors

\n
\n"; } + void end_ctors() // 1.5.2.5 + { + m_outputStr += "
\n"; + } + void begin_dtor() // 1.5.2.0 { m_outputStr += "

destructor

\n
\n"; - } + } void begin_member_funcs() { @@ -534,7 +917,7 @@ class CKDocHTMLOutput : public CKDocOutput } m_outputStr += " "; - // function name + // member name m_outputStr += "" + var->name + "

"; if(var->doc.size() > 0) @@ -1010,13 +1393,16 @@ t_CKBOOL CKDoc::setOutputFormat( t_CKINT which ) case FORMAT_HTML: m_output = new CKDocHTMLOutput( m_vm_ref ); break; + case FORMAT_JSON: + m_output = new CKDocJSONOutput( m_vm_ref ); + break; // currently unsupported case FORMAT_TEXT: case FORMAT_MARKDOWN: - case FORMAT_JSON: EM_error3( "[CKDoc]: unsupported format '%s'...", formats[which] ); goto error; + break; // unrecognized default: @@ -1347,7 +1733,7 @@ string CKDoc::genType( Chuck_Type * type, t_CKBOOL clearOutput ) // constructors | 1.5.2.0 (ge) added if( ctors.size() || insertDefaultCtor ) { - // begin member functions + // begin constructors output->begin_ctors(); // add default constructor, if non-explicitly specified @@ -1377,12 +1763,12 @@ string CKDoc::genType( Chuck_Type * type, t_CKBOOL clearOutput ) output->func_arg(args); args = args->next; } - // end the func + // end the constructor output->end_ctor(); } // end member functions - output->end_member_funcs(); + output->end_ctors(); } // destructor | 1.5.2.0 (ge) added but not added @@ -1505,10 +1891,17 @@ t_CKBOOL CKDoc::outputToDir( const string & outputDir, const string & indexTitle // gen index if( !outputToFile( path + "index" + m_output->fileExtension(), genIndex( indexTitle ) ) ) goto error; - // gen CSS - if( !outputToFile( path + "ckdoc.css", genCSS() ) ) goto error; + + // if format is HTML + if( getOutputFormat() == FORMAT_HTML ) + { + // gen CSS + if( !outputToFile( path + "ckdoc.css", genCSS() ) ) goto error; + } + // gen groups genGroups( groupOutput ); + // for each group for( t_CKINT i = 0; i < m_groups.size(); i++ ) { diff --git a/src/core/ulib_doc.h b/src/core/ulib_doc.h index 93a9e5d79..a883432e2 100644 --- a/src/core/ulib_doc.h +++ b/src/core/ulib_doc.h @@ -47,6 +47,8 @@ struct Chuck_Type; struct CKDocGroup; class CKDocHTMLOutput; +// URL to the chuck examples hosted @ Stanford +static std::string EXAMPLES_URL_BASE = "https://chuck.stanford.edu/doc/"; @@ -108,6 +110,7 @@ class CKDocOutput virtual void begin_static_member_funcs() = 0; virtual void end_static_member_funcs() = 0; virtual void begin_ctors() = 0; + virtual void end_ctors() = 0; virtual void begin_member_funcs() = 0; virtual void end_member_funcs() = 0; diff --git a/src/core/util_string.cpp b/src/core/util_string.cpp index dcac1a6aa..086fc3d50 100644 --- a/src/core/util_string.cpp +++ b/src/core/util_string.cpp @@ -185,7 +185,7 @@ string capitalize( const string & s ) //----------------------------------------------------------------------------- // name: capitalize_and_periodize() -// desc: capiitalize first character and ensure trailing period +// desc: capitalize first character and ensure trailing period //----------------------------------------------------------------------------- string capitalize_and_periodize( const string & s ) { diff --git a/src/scripts/ckdoc/gen-all.ck b/src/scripts/ckdoc/gen-all.ck index 36b64fbbb..1f5b72f95 100644 --- a/src/scripts/ckdoc/gen-all.ck +++ b/src/scripts/ckdoc/gen-all.ck @@ -162,6 +162,9 @@ doc.addGroup( "Base chugins library offering unit generators and utilities." ); +// to generate JSON +// doc.outputFormat(CKDoc.JSON); + // generate doc.outputToDir( ".", "ChucK Class Library Reference" );