blob: fb7118c2fafbed65b7bbcc1f691542ba3f46231e [file] [log] [blame]
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdlib.h>
#include <string.h>
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#define CFC_NEED_BASE_STRUCT_DEF
#include "CFCBase.h"
#include "CFCDocuComment.h"
#include "CFCUtil.h"
struct CFCDocuComment {
CFCBase base;
char *description;
char *brief;
char *long_des;
char **param_names;
char **param_docs;
char *retval;
};
/** Remove comment open, close, and left border from raw comment text.
*/
static void
S_strip(char *comment) {
size_t len = strlen(comment);
char *scratch = (char*)MALLOCATE(len + 1);
// Establish that comment text begins with "/**" and ends with "*/".
if (strstr(comment, "/**") != comment
|| strstr(comment, "*/") != (comment + len - 2)
) {
croak("Malformed comment");
}
// Capture text minus beginning "/**", ending "*/", and left border.
size_t i = 3;
size_t max = len - 2;
while ((isspace(comment[i]) || comment[i] == '*') && i < max) {
i++;
}
size_t j = 0;
for (; i < max; i++) {
while (comment[i] == '\n' && i < max) {
scratch[j++] = comment[i];
i++;
while (isspace(comment[i]) && comment[i] != '\n' && i < max) {
i++;
}
if (comment[i] == '*') { i++; }
if (comment[i] == ' ') { i++; }
}
if (i < max) {
scratch[j++] = comment[i];
}
}
// Modify original string in place.
for (i = 0; i < j; i++) {
comment[i] = scratch[i];
}
comment[j] = '\0';
// Clean up.
FREEMEM(scratch);
}
CFCDocuComment*
CFCDocuComment_parse(const char *raw_text) {
char *text = CFCUtil_strdup(raw_text);
CFCDocuComment *self
= (CFCDocuComment*)CFCBase_allocate(sizeof(CFCDocuComment),
"Clownfish::DocuComment");
// Strip whitespace, comment open, close, and left border.
CFCUtil_trim_whitespace(text);
S_strip(text);
// Extract the brief description.
{
char *ptr = text;
size_t len = strlen(text);
char *limit = strchr(ptr, '@');
if (!limit) {
limit = text + len;
}
while (ptr < limit) {
if (*ptr == '.'
&& ((ptr == limit - 1) || isspace(*(ptr + 1)))
) {
ptr++;
size_t brief_len = ptr - text;
self->brief = CFCUtil_strdup(text);
self->brief[brief_len] = '\0';
break;
}
ptr++;
}
}
if (!self->brief) {
self->brief = CFCUtil_strdup("");
}
// Extract @param directives.
size_t num_params = 0;
self->param_names = (char**)CALLOCATE(num_params + 1, sizeof(char*));
self->param_docs = (char**)CALLOCATE(num_params + 1, sizeof(char*));
{
char *candidate = strstr(text, "@param");
size_t len = strlen(text);
char *limit = text + len;
while (candidate) {
// Extract param name.
char *ptr = candidate + sizeof("@param") - 1;
if (!isspace(*ptr) || ptr > limit) {
croak("Malformed @param directive in '%s'", raw_text);
}
while (isspace(*ptr) && ptr < limit) { ptr++; }
char *param_name = ptr;
while ((isalnum(*ptr) || *ptr == '_') && ptr < limit) { ptr++; }
size_t param_name_len = ptr - param_name;
if (!param_name_len) {
croak("Malformed @param directive in '%s'", raw_text);
}
// Extract param description.
while (isspace(*ptr) && ptr < limit) { ptr++; }
char *param_doc = ptr;
while (ptr < limit) {
if (*ptr == '@') { break; }
else if (*ptr == '\n' && ptr < limit) {
ptr++;
while (ptr < limit && *ptr != '\n' && isspace(*ptr)) {
ptr++;
}
if (*ptr == '\n' || *ptr == '@') { break; }
}
else {
ptr++;
}
}
size_t param_doc_len = ptr - param_doc;
num_params++;
size_t size = (num_params + 1) * sizeof(char*);
self->param_names = (char**)REALLOCATE(self->param_names, size);
self->param_docs = (char**)REALLOCATE(self->param_docs, size);
self->param_names[num_params - 1]
= CFCUtil_strndup(param_name, param_name_len);
self->param_docs[num_params - 1]
= CFCUtil_strndup(param_doc, param_doc_len);
CFCUtil_trim_whitespace(self->param_names[num_params - 1]);
CFCUtil_trim_whitespace(self->param_docs[num_params - 1]);
self->param_names[num_params] = NULL;
self->param_docs[num_params] = NULL;
if (ptr == limit) {
break;
}
else if (ptr > limit) {
croak("Overran end of string while parsing '%s'", raw_text);
}
candidate = strstr(ptr, "@param");
}
}
// Extract full description.
self->description = CFCUtil_strdup(text);
{
char *terminus = strstr(self->description, "@param");
if (!terminus) {
terminus = strstr(self->description, "@return");
}
if (terminus) {
*terminus = '\0';
}
}
CFCUtil_trim_whitespace(self->description);
// Extract long description.
self->long_des = CFCUtil_strdup(self->description + strlen(self->brief));
CFCUtil_trim_whitespace(self->long_des);
// Extract @return directive.
char *maybe_retval = strstr(text, "@return ");
if (maybe_retval) {
self->retval = CFCUtil_strdup(maybe_retval + sizeof("@return ") - 1);
char *terminus = strchr(self->retval, '@');
if (terminus) {
*terminus = '\0';
}
CFCUtil_trim_whitespace(self->retval);
}
FREEMEM(text);
return self;
}
void
CFCDocuComment_destroy(CFCDocuComment *self) {
size_t i;
if (self->param_names) {
for (i = 0; self->param_names[i] != NULL; i++) {
FREEMEM(self->param_names[i]);
}
FREEMEM(self->param_names);
}
if (self->param_docs) {
for (i = 0; self->param_docs[i] != NULL; i++) {
FREEMEM(self->param_docs[i]);
}
FREEMEM(self->param_docs);
}
FREEMEM(self->description);
FREEMEM(self->brief);
FREEMEM(self->long_des);
FREEMEM(self->retval);
CFCBase_destroy((CFCBase*)self);
}
const char*
CFCDocuComment_get_description(CFCDocuComment *self) {
return self->description;
}
const char*
CFCDocuComment_get_brief(CFCDocuComment *self) {
return self->brief;
}
const char*
CFCDocuComment_get_long(CFCDocuComment *self) {
return self->long_des;
}
const char**
CFCDocuComment_get_param_names(CFCDocuComment *self) {
return (const char**)self->param_names;
}
const char**
CFCDocuComment_get_param_docs(CFCDocuComment *self) {
return (const char**)self->param_docs;
}
const char*
CFCDocuComment_get_retval(CFCDocuComment *self) {
return self->retval;
}