#! /usr/local/bin/pike constant TYPE_SCRIPTLET = 1; constant TYPE_DECLARATION = 2; constant TYPE_INLINE = 3; constant TYPE_DIRECTIVE = 4; string document_root = ""; private int includes = 0; // should this be configurable? int max_includes = 100; program compile_psp_string(string code, string realfile) { string psp = parse_psp(code, realfile); return compile_string(psp, realfile); } array(Block) psp_to_blocks(string file, string realfile) { int file_len = strlen(file); int in_tag = 0; int sp = 0; int old_sp = 0; int start_line = 1; array contents = ({}); /* remove #! line if it exists */ if (file[0..1] == "#!") { old_sp = search(file,"\n") + 1; start_line++; } do { #ifdef DEBUG werror("starting point: %O, len: %O\n", sp, file_len); #endif sp = search(file, "<%", sp); if(sp == -1) { sp = file_len; if(old_sp!=sp) { string s = file[old_sp..sp-1]; int l = sizeof(s) - sizeof(s-"\n"); Block b = TextBlock(s, realfile); b->start = start_line; b->end = (start_line+=l); contents += ({b}); } }// no starting point, skip to the end. else if(sp >= 0) // have a starting code. { int end; if(in_tag) { error("invalid format: nested tags!\n"); } if(old_sp>=0) { string s = file[old_sp..sp-1]; int l = sizeof(s) - sizeof(s-"\n"); Block b = TextBlock(s, realfile); b->start = start_line; b->end = (start_line+=l); contents += ({b}); } if((sp == 0) || (sp > 0 && file[sp-1] != '<')) { in_tag = 1; end = find_end(file, sp); } else { sp = sp + 2; continue; } // the start was escaped. if(end == -1) error("invalid format: missing end tag.\n"); else { in_tag = 0; string s = file[sp..end]; Block b = PikeBlock(s, realfile); int l = sizeof(s) - sizeof(s-"\n"); b->start = start_line; b->end = (start_line+=l); contents += ({b}); sp = end + 1; old_sp = sp; } } } while (sp < file_len); return contents; } string parse_psp(string file, string realname) { // now, let's render some pike! string pikescript = ""; string header = ""; pikescript+="#line "+(__LINE__+1)+" \""+__FILE__+":(preamble)\"\n"; pikescript+="inherit __psphelpers;\n"; pikescript+="mapping headers = ([ \"Content-Type\" : \"text/html\" ]);\n"; pikescript+="String.Buffer out = String.Buffer();\n"; pikescript+="int write(mixed ... args) { out->add(sprintf(@args)); };\n"; pikescript+="void main(int argc, array(string)argv){\n"; array(Block) contents = psp_to_blocks(file, realname); /* foreach(contents, Block b) { werror("%O\n" , b); } */ string ps, h; [ps, h] = render_psp(contents, "", ""); pikescript += ps; header += h; pikescript += "Stdio.stdout->write(\"%s\\n\\n%s\",((array)headers)[*]*\": \"*\"\\n\",out->get());\n }\n"; return header + "\n\n" + pikescript; } array render_psp(array(Block) contents, string pikescript, string header) { foreach(contents, object e) { if(e->get_type() == TYPE_DECLARATION) header += e->render(); else if(e->get_type() == TYPE_DIRECTIVE) { mixed ren = e->render(); if(arrayp(ren)) [pikescript, header] = render_psp(ren, pikescript, header); } else pikescript += e->render(); } return ({pikescript, header}); } int main(int argc, array(string) argv) { string file = Stdio.read_file(argv[1]); if(!file) { werror("input file %s does not exist.\n", argv[1]); return 1;} string pikescript = parse_psp(file, argv[1]); // write(pikescript);exit(0); add_constant("__psphelpers", Helpers); program p = compile_string(pikescript, argv[1]); add_constant("__psphelpers"); p()->main(argc, argv); return 0; } int find_end(string f, int start) { int ret; do { int p = search(f, "%>", start); #ifdef DEBUG werror("p: %O\n", p); #endif if(p == -1) return 0; else if(f[p-1] == '%') { #ifdef DEBUG werror("escaped!\n"); #endif start = start + 2; continue; } // (escaped!) else { #ifdef DEBUG werror("got the end!\n"); #endif ret = p + 1;} } while(!ret); #ifdef DEBUG werror("returning: %O\n", ret); #endif return ret; } class Block(string contents, string filename) { int start; int end; int get_type() { return 0; } string _sprintf(mixed type) { return "Block(" + contents + ")"; } array(Block) | string render(); } class TextBlock { inherit Block; array in = ({"\\", "\"", "\n"}); array out = ({"\\\\", "\\\"", "\\n"}); string render() { return "{\n" + escape_string(contents) + "}\n"; } string escape_string(string c) { string retval = ""; int cl = start; int atend=0; int current=0; do { string line; int end = search(c, "\n", current); if(end != -1) { line = c[current..end]; if(end == (strlen(c) -1)) atend = 1; else current = end + 1; } if(end == -1) { line = c[current..]; atend = 1; } line = replace(line, in, out); if(strlen(line)) { retval+=("#line " + cl + " \"" + filename + "\"\n out->add(\"" + line + "\");\n"); cl++; } } while(!atend); return retval; } } class PikeBlock { inherit Block; int get_type() { if(has_prefix(contents, "<%=")) return TYPE_INLINE; if(has_prefix(contents, "<%!")) return TYPE_DECLARATION; if(has_prefix(contents, "<%@")) return TYPE_DIRECTIVE; else return TYPE_SCRIPTLET; } array(Block) | string render() { if(has_prefix(contents, "<%!")) { string expr = contents[3..strlen(contents)-3]; return("// "+ start + " - " + end + "\n#line " + start + " \"" + filename + "\"\n" + expr); } else if(has_prefix(contents, "<%@")) { string expr = contents[3..strlen(contents)-3]; return parse_directive(expr); } else if(has_prefix(contents, "<%=")) { string expr = String.trim_all_whites(contents[3..strlen(contents)-3]); return("// "+ start + " - " + end + "\n#line " + start + " \"" + filename + "\"\nout->add((string)(" + expr + "));"); } else { string expr = contents[2..strlen(contents)-3]; return "// "+ start + " - " + end + "\n#line " + start + " \"" + filename + "\"\n" + expr + "\n"; } } string|array(Block) parse_directive(string exp) { exp = String.trim_all_whites(exp); if(search(exp, "\n")!=-1) throw(Error.Generic("PSP format error: invalid directive format.\n")); // format of a directive is: keyword option="value" ... string keyword; int r = sscanf(exp, "%[A-Za-z0-9\-] %s", keyword, exp); switch(keyword) { case "include": return process_include(exp); break; default: throw(Error.Generic("PSP format error: unknown directive " + keyword + ".\n")); } } // we don't handle absolute includes yet. array(Block) process_include(string exp) { string file; string contents; if(includes > max_includes) throw(Error.Generic("PSP Error: too many includes, possible recursion!\n")); includes++; int r = sscanf(exp, "%*sfile=\"%s\"%*s", file); if(r != 3) throw(Error.Generic("PSP format error: unknown include format.\n")); string realfile; if(file[0] = '/') { realfile = Stdio.append_path(document_root, (file/"/" - ({""})) * "/"); } else { realfile = file; } contents = Stdio.read_file(realfile); //werror("contents: %O\n", contents); if(contents) { array x = psp_to_blocks(contents, file); //werror("blocks: %O\n", x); return x; } } } class Helpers { function html_encode_string = _Roxen.html_encode_string; function http_decode_string = _Roxen.http_decode_string; //! method mapping(string:string|array(string)) http_decode_urlencoded_query(string query,void|mapping dest) //! Decodes an URL-encoded query into a mapping. mapping(string:string|array(string)) http_decode_urlencoded_query(string query, void|mapping dest) { if (!dest) dest=([]); foreach (query/"&",string s) { string i,v; if (sscanf(s,"%s=%s",i,v)<2) v=i=http_decode_string(s); else i=http_decode_string(replace(i,"+"," ")),v=http_decode_string(replace(v,"+"," ")); if (dest[i]) if (arrayp(dest[i])) dest[i]+=({v}); else dest[i]=({dest[i],v}); else dest[i]=v; } return dest; } string http_encode_query(mapping(string:int|string) variables) { return Array.map((array)variables, lambda(array(string|int|array(string)) v) { if (intp(v[1])) return http_encode_string(v[0]); if (arrayp(v[1])) return map(v[1], lambda (string val) { return http_encode_string(v[0])+"="+ http_encode_string(val); })*"&"; return http_encode_string(v[0])+"="+ http_encode_string(v[1]); })*"&"; } //! This protects all odd - see @[http_encode_query()] - //! characters for transfer in HTTP. //! //! Do not use this function to protect URLs, since //! it will protect URL characters like @expr{'/'@} and @expr{'?'@}. //! @param in //! The string to encode //! @returns //! The HTTP encoded string string http_encode_string(string in) { return replace( in, ({ "\000", "\001", "\002", "\003", "\004", "\005", "\006", "\007", "\010", "\011", "\012", "\013", "\014", "\015", "\016", "\017", "\020", "\021", "\022", "\023", "\024", "\025", "\026", "\027", "\030", "\031", "\032", "\033", "\034", "\035", "\036", "\037", "\177", "\200", "\201", "\202", "\203", "\204", "\205", "\206", "\207", "\210", "\211", "\212", "\213", "\214", "\215", "\216", "\217", "\220", "\221", "\222", "\223", "\224", "\225", "\226", "\227", "\230", "\231", "\232", "\233", "\234", "\235", "\236", "\237", " ", "%", "'", "\"", "+", "&", "=", "/", "#", ";", "\\", "<", ">", "\t", "\n", "\r", "@" }), ({ "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f", "%7f", "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87", "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f", "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", "%20", "%25", "%27", "%22", "%2b", "%26", "%3d", "%2f", "%23", "%3b", "%5c", "%3c", "%3e", "%09", "%0a", "%0d", "%40" })); } }