diff --git a/.gitattributes b/.gitattributes index adde843db5e5d43a4096c8b8c44ed9d0b370bdf7..38369dbb392b55fcde745b06f789da3fc89a9cbb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -24,6 +24,7 @@ testfont binary /bin/make_ci.pike foreign_ident /bin/mkpeep.pike foreign_ident /bin/mkwmml.pike foreign_ident +/bin/pikedoc.pike foreign_ident /bin/test_pike.pike foreign_ident /lib/master.pike.in foreign_ident /lib/modules/ADT.pmod/Table.pmod foreign_ident diff --git a/bin/pikedoc.pike b/bin/pikedoc.pike new file mode 100644 index 0000000000000000000000000000000000000000..53e2cda2cd8672786692989a23bcc7d9a9e96e7a --- /dev/null +++ b/bin/pikedoc.pike @@ -0,0 +1,832 @@ +#!/usr/local/bin/pike +//. +//. File: pikedoc.pike +//. RCSID: $Id: pikedoc.pike,v 1.1 1999/07/05 10:36:40 grubba Exp $ +//. Author: David Kågedal (kg@infovav.se) +//. +//. Synopsis: The Pikedoc parser. +//. +//. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +//. +//. This object converts pike source commented according to the +//. pikedoc standard to a HTML file. + +#include <stdio.h> + +#ifdef ROXEN +#include <roxen.h> +#else +void perror(string s, mixed ... args) +{ + werror(sprintf(s, @args)); +} +#endif + +//. o Pikedoc +//. +//. This class contains the documentation in a Pike source file. +//. +class Pikedoc +{ +#ifndef ROXEN + void perror(string s, mixed ... args) + { + werror(sprintf(s, @args)); + } +#endif + + //. o Function + //. + //. A class containing documentation about a function. + //. + class Function + { + //. + name + //. The name of the function. + string name; + //. + ret_type + //. The return type of the function. + string ret_type; + //. + args + //. The arguments as an array of arrrays. + array args; + //. + locals + //. An array of objects describing local variables. + array locals; + //. + doc + //. The documentation string. + string doc; + //. + rest + //. The remaining string. + string rest; + + //. - create + //. > _name + //. The name of the function. + //. > _ret_type + //. The return type of the function. + //. > _args + //. The arguments as an array of arrrays. + //. > _locals + //. An array of objects describing local variables. + //. > _doc + //. The documentation string. + //. > _rest + //. The remaining string. + void create(string _name, string _ret_type, array _args, + array _locals, string _doc, string _rest) + { + name = _name; + ret_type = _ret_type; + args = _args; + locals = _locals; + doc = _doc; + rest = _rest; + } + + //. - magic + //. + //. Output the function documentation in magic format. + //. + string magic() + { + string tag, result; + if (name == "create") + tag = "constructor"; + else + tag = "function"; + result = "<"+tag+" name='"+name+ + "' type='"+ret_type+ "'>\n"; + foreach(args, array a) + result += "<arg name='"+a[1]+"' type='"+a[0]+"'>"+ + (stringp(a[2])?a[2]:a[2]->doc)+ + "</arg>\n"; + result += doc; + foreach(locals, mapping p) + result += p->magic(); + + result += "</"+tag+">\n"; + return result; + } + } + + //. o Variable + //. + //. A class containing documentation about a variable. + //. + class Variable + { + string name, var_type, doc, rest; + + //. - create + //. + void create(string _name, string _var_type, string _doc, string _rest) + { + name = _name; + var_type = _var_type; + doc = _doc; + rest = _rest; + } + + //. - magic + //. + //. Output the function documentation in magic format. + //. + string magic() + { + return "<variable name="+name+ + " type='"+var_type+"'>\n" + + doc + + "</variable>\n"; + } + } + + //. o Class + //. + //. A class containing documentation about a class. + //. + class Class + { + string name, doc, rest; + array locals; + + //. - create + //. + void create(string _name, array _locals, string _doc, string _rest) + { + name = _name; + locals = _locals; + doc = _doc; + rest = _rest; + } + + //. - magic + //. + //. Output the function documentation in magic format. + //. + string magic() + { + string result = "<class name="+name+">\n" + doc; + foreach(locals, mapping p) + result += p->magic(); + result += "</class>\n"; + return result; + } + } + + //. o Module + //. + //. A class containing documentation about a module. + //. + class Module + { + string name, doc, rest; + array locals; + + //. - create + //. + void create(string _name, array _locals, string _doc, string _rest) + { + name = _name; + locals = _locals; + doc = _doc; + rest = _rest; + } + + //. - magic + //. + //. Output the function documentation in magic format. + //. + string magic() + { + string result = "<module name='"+name+"'>\n" + doc; + foreach(locals, mapping p) + result += p->magic(); + result += "</module>\n"; + return result; + } + } + + private int in_header = 0; + + //. + headers + //. + //. A mapping from header name to header content. + //. + mapping(string:string) headers = ([ ]); + + //. + parts + //. + //. An array of the parts of the file. + array(mapping(string:mixed)) parts = ({ }); + + //. + functions + //. + //. A mapping from function name to function documentation. + //. + mapping(string:mixed) functions = ([ ]); + + //. + variables + //. + //. A mapping from variable name to variable documentation. + //. + mapping(string:mixed) variables = ([ ]); + + //. + classes + //. + //. A mapping from variable name to variable documentation. + //. + mapping(string:mixed) classes = ([ ]); + + //. * Functions + + array (string) find_type(string in); + array parse(string s); + + //. - match_brace + //. + //. Find the closing brace for the first opening brase in a string. + //. + int match_brace(string s) + { + int level = 0, pos, len=sizeof(s), i, slash; + for(pos=0;pos<len;pos++) + { + switch(s[pos]) + { + case '\'': + for(i=pos+1;i<len;i++) + switch(s[i]) + { + case '\'': + pos = i; i = len; break; + case '\\': + i++; break; + } + slash = 0; + break; + case '"': + for(i=pos+1;i<len;i++) + switch(s[i]) + { + case '"': + pos = i; i = len; break; + case '\\': + i++; break; + } + slash = 0; + break; + case '/': + if (slash) + { + pos = search(s, "\n", pos); + if (pos == -1) + pos = len; + } + slash = !slash; + break; + case '*': + if (slash) + pos = search(s, "*/", pos); + slash = 0; + break; + case '{': + level++; + slash = 0; + break; + case '}': + level--; + if (level == 0) + return pos+1; + slash = 0; + break; + default: + slash = 0; + } + } + perror("No matching brace found!\n"); + return len; + } + + //. - parse_function + //. + //. Parse a function and its documentation. + //. + object(Function) parse_function(string rest) + { + string fn, as, tp, n, doc = "", fn_doc, s, argname; + array tmp, args; + mapping arg_doc = 0; + int pos; + + sscanf(rest[1..],"%*[ \t]%[^\n]\n%s",n,rest); + while (1) + { + sscanf(rest, "%*[ \t]%s", rest); + if (sscanf(rest, "//.%[^\n]\n%s", s, rest) == 2) + { + sscanf(s,"%*[ \t]%s", s); + + // Is this an argument doc? + if (sizeof(s) && (s[0] == '>')) + { + if (arg_doc) + arg_doc[argname] = doc; + else + { + fn_doc = doc; + arg_doc = ([ ]); + } + sscanf(s[1..], "%*[ \t]%s", argname); + doc = ""; + } + else + doc += s+"\n"; + } + else if (sscanf(rest, "%*[ \t]\n%s", rest) == 2) + ; + else + break; + } + if (arg_doc) + arg_doc[argname] = doc; + else + { + fn_doc = doc; + arg_doc = ([ ]); + } + + // Find out the return type + tmp = find_type(rest); + + tmp[2] -= " "; + if (tmp[2] != n) + perror("Name mismatch: '%s' <-> '%s'\n", n, tmp[2]); + tp = tmp[1]; + args = ({ }); + if (sscanf(tmp[3],"%*[ ];%*s") == 2) { + // variable. + werror(sprintf("%s is a variable, and not a function.\n", n)); + + if ((sscanf(tp, "function%*[ ](%s", tp) == 2) && + (tp[-1] == ')')) { + tp = tp[..sizeof(tp)-2]; + + string funargs, rettype; + sscanf(reverse(tp), "%s:%s", rettype, funargs); + if (rettype) { + tp = reverse(rettype); + + if (funargs) { + funargs = reverse(funargs); + + args += ({ funargs, "unknown", "" }); + } + } else { + tp = "mixed"; + } + } + } else { + sscanf(tmp[3], "%*[ ](%s", as); + if (as) { + while(sscanf(as, "%*[ ])%*s") != 2) + { + tmp = find_type(as); + if (!tmp) + { + perror("find_type returned 0 while parsing parameters for %s\n", n); + break; + } + // perror("argument: %s %s\n", tmp[1], tmp[2]); + sscanf(tmp[2], "%*[ ]%s", tmp[2]); + args += ({ tmp[1..2]+({ arg_doc[tmp[2]] || "" }) }); + as = tmp[3]; + rest = tmp[3]; + } + } + // perror("%O\n", args); + pos = match_brace(rest); + } + return Function(n, tp, args, parse(rest[..pos-1]), fn_doc, rest[pos..]); + } + + //. - parse_variable + //. + //. Parse a variable and its documentation. + //. + //. > rest + //. A string starting with the variable to parse. + //. + object(Variable) parse_variable(string rest) + { + string n, s, doc="", typ; + array tmp; + + sscanf(rest[1..],"%*[ \t]%[^\n]\n%s",n,rest); + while (1) + { + sscanf(rest, "%*[ \t]%s", rest); + if (sscanf(rest, "//.%[^\n]\n%s", s, rest) == 2) + { + doc += s+"\n"; + } + else if (sscanf(rest, "%*[ \t]\n%s", rest) == 2) + ; + else + break; + } + tmp = find_type(rest); + if (!tmp) + perror("find_type returned 0\n"); + else + { + typ = tmp[1]; + rest = tmp[3]; + } + return Variable(n, typ, doc, rest); + } + + //. - parse_class + //. + //. Parse a class definition and its documentation. + //. + //. > rest + //. A string starting with the class to parse. + //. + object(Class) parse_class(string rest) + { + string fn, as, tp, n, doc = "", s; + array tmp, args; + mapping arg_doc = ([ ]); + int pos; + + sscanf(rest[1..],"%*[ \t]%[^\n]\n%s",n,rest); + while (1) + { + sscanf(rest, "%*[ \t]%s", rest); + if (sscanf(rest, "//.%[^\n]\n%s", s, rest) == 2) + { + doc += s+"\n"; + } + else if (sscanf(rest, "%*[ \t]\n%s", rest) == 2) + ; + else + break; + } + + pos = match_brace(rest); + + return Class(n, parse(rest[..pos-1]), doc, rest[pos..]); + } + + //. - parse_module + //. + //. Parse a module definition and its documentation. + //. + //. > rest + //. A string starting with the class to parse. + //. + object(Module) parse_module(string rest) + { + string fn, as, tp, n, doc = "", s; + array tmp, args; + mapping arg_doc = ([ ]); + int pos; + + sscanf(rest[1..],"%*[ \t]%[^\n]\n%s",n,rest); + while (1) + { + sscanf(rest, "%*[ \t]%s", rest); + if ((sscanf(rest, "//.%[^\n]\n%s", s, rest) == 2) + && (s[..1] != "o ")) + { + doc += s+"\n"; + } + else if (sscanf(rest, "%*[ \t]\n%s", rest) == 2) + ; + else + break; + } + + return Module(n, parse(rest), doc, ""); + } + + //. - parse + //. + //. The parser function. It takes a source text mass and returns an + //. HTML text. It is a really ugly piece of work and would do good + //. from a total rewrite. + //. + //. > s + //. + //. The string to parse. + //. + array parse(string s) + { + // array(string) lines = filedata/"\n"; + string header, value, ss, line; + int level = 0; + array(mixed) stack = allocate(10); + object part; + array(object) parts; + + parts = ({ }); + + s += "\n"; + while(sizeof(s)) + { + sscanf(s,"%[^\n]\n%s", line,s); + sscanf(line,"%*[ \t]%s",line); + + if((sizeof(line)>2) && (line[0..2] == "//.")) + { + sscanf(line[3..],"%*[ \t]%s",line); + if (sizeof(line)) + { + if (in_header) + { + if (line[0] == '+') + { + in_header = 0; + } + else if (sscanf(line, "%s:%*[ \t]%s", + header, value) == 3) + { + header = lower_case(header); + headers[header] = value; + } + } + else + { + switch (line[0]) + { + case '-': + part = parse_function(line+"\n"+s); + parts += ({ part }); + s = part->rest; + break; + case '+': + part = parse_variable(line+"\n"+s); + parts += ({ part }); + s = part->rest; + break; + case 'o': + part = parse_class(line+"\n"+s); + parts += ({ part }); + s = part->rest; + break; + case '*': + part = parse_module(line+"\n"+s); + parts += ({ part }); + s = part->rest; + break; + default: + } + } + } + } + else // Not a pikedoc comment + { + // if (part->type != "code") + // { + // part = ([ "type":"code", "code":"" ]); + // parts += ({ part }); + // } + // part->code += line + "\n"; + } + } + return parts; + } + + //. - create + //. + void create(string filedata) + { + parts = parse(filedata); + } + + //. - tohtml + //. + //. Turn the documentation into HTML. + //. + string tohtml() + { + array(string) result; + string header; + mapping(string:mixed) part; + + result = ({ "<h1>"+headers->file+"</h1>\n" + + headers->synopsis + + "<hr>\n<table>\n" }); + + foreach(indices(headers)-({"file","synopsis"}), header) + { + result += ({ "<tr><td>"+header+":</th><td>"+ + headers[header]+"</td></tr>" }); + } + + result += ({ "\n</table>\n" }); + + foreach(parts, part) + { + switch (part->type) + { + case "doc": + result += ({ part->doc }); + break; + case "class": + result += ({ "<h3><i>class</i> "+part->name+"</h3>\n"+part->doc }); + break; + case "function": + result += ({ "<h3><i>function</i> "+part->name+"</h3>\n"+part->doc }); + break; + case "variable": + result += ({ "<h3><i>variable</i> "+part->name+"</h3>\n"+part->doc }); + break; + case "code": + result += ({ ("<dl><dd>\n"+"<pike-example light>" + + part->code + + "</pike-example>" + "</dl>\n" + ) }); + break; + } + } + return result*""; + } + + //. - tomagic + //. + //. Turn the documentation into magic markup. + //. + string tomagic() + { + string result; + string header; + mapping(string:mixed) part; + + result = "<pike-doc name='"+headers->file-".pike"+"'>\n"; + // result = ""; + result += (headers->synopsis||"")+"<p>"; + + foreach(indices(headers)-({"file","synopsis"}), header) + result += "<em>"+header+":</em> "+ headers[header]+"<br>"; + result += "<p>"; + + foreach(parts, part) + result += part->magic(); + + return result + "<index></pike-doc>\n"; + } + + constant types=({"mapping","function","multiset","array","object","program","float","int","mixed","string","void"}); + + //. - find_decl + //. + //. Parse stuff + //. + array (string) find_decl(string in) + { + string pre,decl,p2; + sscanf(in, "%[ \t\r\n]%s", pre, in); + if(!strlen(in)) return ({"",pre+in}); + if(in[0]==')') // Cast + return ({"",pre+in}); + if(sscanf(in, "%[^(),; \t\n\r]%s", decl,in)==2) + return ({ pre+decl, in }); + return ({ "", pre+in }); + } + + string find_complex_type(string post) + { + string p=""; + if(strlen(post) && post[0]=='(') + { + int level=1, i=1; + while(level && i < strlen(post)) + { + switch(post[i++]) + { + case '(': level++;break; + case ')': level--;break; + } + } + p = p+post[..i-1]; + post = post[i..]; + if(sscanf(post, "|%s", post)) + { + string q; + if(sscanf(post, "%s(", q)) + { + p+=q; + post = post[strlen(q)..]; + } else if(sscanf(post, "%s%*[ \t\n\r]", post)>1) { + p+="|"+post; + return p; + } + p+="|"+find_complex_type(post); + } + return p; + } + if(sscanf(post, "|%s", post)) + { + string q; + if(sscanf(post, "%[^,();| \t]", q) && search(types,q)!=-1) + { + p+=q; + post = post[strlen(q)..]; + sscanf(post, "%*[ \t\n\r]%s", post); + return p+"|"+find_complex_type(post); + } + } + if(sscanf(post, "...%s", post)) + return "..."; + return p; + } + + array (string) find_type(string in) + { + string s,pre,post,decl; + int min=10000000000,i; + string mt; + + while (sscanf(in, "%*[ ]//%*[^\n]\n%s", in) == 3); + foreach(types, s) + if(((i=search(in, s))!=-1) && i<min) + { + if(i) switch(in[i-1]) + { + default: + //perror("Invalid type thingie: '"+in[i..i]+"'\n"); + continue; + case ' ': + case '\n': + case '\r': + case '\t': + case '(': + case ')': + case '|': + case '.': + } + min = i; + mt = s; + } + + if(!(s=mt)) return 0; + + if(sscanf(in, "%s"+s+"%s", pre, post)==2) + { + string op = post; + string p=""; + sscanf(post, "%[ \t\n\r]%s", p, post); + +// if(post[0]=='|') post=post[1..]; + + p += find_complex_type(post); + + p = op[..strlen(p)-1]; + post = op[strlen(p)..]; + // perror("Found type: '%s' (left: %s)\n", s+p, post-"\n"); + return ({ pre, s+p, @find_decl(post) }); + } + } +}; + +//. - parse +//. +//. Called from Roxen to produce the answer. +//. +mixed parse(object id) +{ + object f, doc; + string filename = id->query; + + if (!filename) + return "No file"; + id->not_query = filename; + f = File(); + f->open(filename,"r"); + doc = Pikedoc(f->read(100000)); + f->close(); + return doc->tomagic(); +} + +//. - main + +#ifndef ROXEN +int main(int argc, array argv) +{ + object f; + if (argc == 0) + f = stdin; + else + { + f = File(); + f->open(argv[1],"r"); + } + object doc; + doc = Pikedoc(f->read(100000)); + f->close(); + write(doc->tomagic()); +} +#endif