Branch: Tag:

2000-08-10

2000-08-10 22:45:52 by Martin Stjernholm <mast@lysator.liu.se>

Some design changes. Updated doc. Use the standard RXML error system.
Fixed line number tracking. Separated the old <pike> container to use
the old code for compatibility and removed the compatibility stuff
from the <?pike ... ?> tag.

Rev: server/modules/scripting/piketag.pike:2.14

7:   // return "Hello world!\n";   // </pike>    - constant cvs_version = "$Id: piketag.pike,v 2.13 2000/08/09 11:12:42 per Exp $"; + constant cvs_version = "$Id: piketag.pike,v 2.14 2000/08/10 22:45:52 mast Exp $";   constant thread_safe=1;      
23:      constant module_type = MODULE_PARSER;   constant module_name = "Pike tag"; - constant module_doc = - #"This module adds a new tag, &lt;pike&gt;&lt;/pike&gt;. It makes it - possible to insert some pike code directly in the document. <br> - <img src=\"internal-roxen-err_2\" align=\"left\" alt=\"Warning\"> <br>NOTE: Enabling this - module is the same thing as letting your users run programs with the - same right as the server! Example:<p><pre> &lt;pike&gt; return - \"Hello world!\\n\"; &lt;/pike&gt;\n</pre> <p>Arguments: Any, all - arguments are passed to the script in the mapping args. There are also - a few helper functions available, output(string fmt, mixed ... args) - is a fast way to add new data to a dynamic buffer, flush() returns the - contents of the buffer as a string. A flush() is done automatically - if the script does not return any data, thus, another way to write the - hello world script is <tt>&lt;pike&gt;output(\"Hello %s\n\", - \"World\");&lt/pike&gt</tt><p> The request id is available as id."; + constant module_doc = #" + This module adds a processing instruction tag, <code>&lt;?pike ... + ?&gt;</code>, for evaluating Pike code directly in the document.    -  + <p><img src=\"internal-roxen-err_2\" align=\"left\" alt=\"Warning\"> + NOTE: Enabling this module is the same thing as letting your users + run programs with the same right as the server!    -  + <p>Example: +  + <p><pre>&lt;?pike write (\"Hello world!\\n\"); ?&gt;\n</pre> +  + <p>There are a few helper functions available: +  + <dl> +  <dt><code>write(string fmt, mixed ... args)</code></dt> +  <dd>Formats and appends a string to the output buffer.</dd> +  +  <dt><code>flush()</code></dt> +  <dd>Returns the contents of the output buffer and resets it.</dd> +  +  <dt><code>rxml(string rxmlcode)</code></dt> +  <dd>Parses the string with the rxml parser.</dd> + </dl> +  + <p>When the pike tag returns, the contents of the output buffer is + inserted into the page. It is not reparsed with the RXML parser. +  + <p>These special constructs are also recognized: +  + <dl> +  <dt><code>//O ...</code> or <code>/*O ... */</code></dt> +  <dd>A Pike comment with an 'O' (the letter, not the number) as the +  very first character treats the rest of the text in the comment as +  output text that's written directly to the output buffer.</dd> +  +  <dt><code>//X ...</code> or <code>/*X ... */</code></dt> +  <dd>A Pike comment with an 'X' as the very first character treats +  the rest of the text in the comment as RXML code that's parsed +  with the RXML parser and then written to the output buffer.</dd> +  +  <dt><code>#include \"...\"</code></dt> +  <dd>An <code>#include</code> preprocessor directive includes the +  specified file in the virtual filesystem.</dd> +  +  <dt><code>#inherit \"...\"</code></dt> +  <dd>An <code>#inherit</code> preprocessor directive puts a +  corresponding inherit declaration in the class that's generated +  by the Pike code in the tag.</dd> + </dl> +  + <p>Note that every RXML fragment is parsed by itself, so you can't + have unmatched RXML tags in them. E.g. the following does not work: +  + <p><pre>&lt;?pike +  //X &lt;gtext&gt; +  write (\"Foo\"); +  //X &lt;/gtext&gt; + ?&gt;\n</pre> +  + <p>Adjacent 'X' comments are concatenated however, so the following + works: +  + <p><pre>&lt;?pike +  //X &lt;gtext&gt; +  //X Foo +  //X &lt;/gtext&gt; + ?&gt;\n</pre> +  + <p>For compatibility this module also adds the Pike container tag, + <code>&lt;pike&gt;...&lt;/pike&gt;</code>. It behaves exactly as it + did in Roxen 2.0 and earlier and the functionality mentioned above + does not apply to it. The use of the container tag is deprecated."; +    void create()   { -  defvar ( -  "debugmode", "Log", "Error messages", TYPE_STRING_LIST | VAR_MORE, -  "How to report errors (e.g. backtraces generated by the Pike code):\n" -  "\n" -  "<p><ul>\n" -  "<li><i>Off</i> - Silent.</li>\n" -  "<li><i>Log</i> - System debug log.</li>\n" -  "<li><i>HTML comment</i> - Include in the generated page as an HTML comment.</li>\n" -  "<li><i>HTML text</i> - Include in the generated page as normal text.</li>\n" -  "</ul>\n", -  ({"Off", "Log", "HTML comment", "HTML text"})); -  +     defvar("program_cache_limit", 256, "Program cache limit", TYPE_INT|VAR_MORE,    "Maximum size of the cache for compiled programs.");   }    - string reporterr (string header, string dump) - { -  if (query("debugmode") == "Off") return ""; -  -  report_error( header + dump + "\n" ); -  switch (query("debugmode")) -  { -  case "HTML comment": -  return "\n<!-- " + Roxen.html_encode_string(header + dump) + "\n-->\n"; -  case "HTML text": -  return "\n<br><font color=\"red\"><b><pre>" + Roxen.html_encode_string (header) + -  "</b></pre></font><pre>\n"+Roxen.html_encode_string (dump) + "</pre><br />\n"; -  default: -  return ""; -  } - } -  +    // Helper functions, to be used in the pike script.   class Helpers   {
129:    constant Privs=0;   }    - string helpers() - { -  add_constant( "__ps_magic_helpers", Helpers ); -  add_constant( "__ps_magic_protos", HProtos ); -  return "inherit __ps_magic_helpers;\nimport Roxen;\n"; - } -  - string helper_prototypes( ) - { -  return "inherit __ps_magic_protos;\nimport Roxen;\n"; - } -  - private static mapping(string:program) program_cache = ([]); -  - string simple_pi_tag_pike( string tag, mapping m, string s,RequestID id ) - { -  return simpletag_pike( tag, ([]), s, id ); - } -  - string read_roxen_file( string what, object id ) - { -  // let there be magic -  return id->conf->open_file(Roxen.fix_relative(what,id),"rR",id,1) -  ->read()[0]; - } -  -  + #define PREFN "pike-tag(preamble)" + #define POSTFN "pike-tag(postamble)"   #define PS(X) (compile_string( "mixed foo(){ return "+(X)+";}")()->foo()) - #define SPLIT(X) PARSER_C.hide_whitespaces(PARSER_C.tokenize(PARSER_C.split(X))) + #define SPLIT(X,FN) PARSER_C.hide_whitespaces(PARSER_C.tokenize(PARSER_C.split(X),FN))   #define OCIP( ) \    if( cip ) \    { \
183:    flat[i]->text = flat[i]->text[3..]+"\n"; \    }    - #define R(X) PARSER_C.simple_reconstitute(X) + #define R(X) PARSER_C.reconstitute_with_line_numbers(X)    -  + array helpers() + { +  add_constant( "__ps_magic_helpers", Helpers ); +  add_constant( "__ps_magic_protos", HProtos ); +  return SPLIT("inherit __ps_magic_helpers;\nimport Roxen;\n",PREFN); + } +  + array helper_prototypes( ) + { +  return SPLIT("inherit __ps_magic_protos;\nimport Roxen;\n",PREFN); + } +  + private static mapping(string:program) program_cache = ([]); +  + string simple_pi_tag_pike( string tag, mapping m, string s,RequestID id ) + { +  program p; +  object o; +  string res; +  mixed err; +  +  id->misc->__added_helpers_in_tree=0; +  id->misc->cacheable=0; +  +  object e = ErrorContainer(); +  master()->set_inhibit_compile_errors(e); +  if(err=catch +  { +  p = my_compile_string( s,id,1,"pike-tag("+id->not_query+")" ); +  if (sizeof(program_cache) > query("program_cache_limit")) +  { +  array a = indices(program_cache); +  int i; +  +  // Zap somewhere between 25 & 50% of the cache. +  for(i = query("program_cache_limit")/2; i > 0; i--) +  m_delete(program_cache, a[random(sizeof(a))]); +  } +  }) +  { +  master()->set_inhibit_compile_errors(0); +  if (e->get()) +  RXML.parse_error ("Error compiling Pike code:\n%s", e->get()); +  else throw (err); +  } +  master()->set_inhibit_compile_errors(0); +  +  if(err = catch{ +  (o=p())->parse(id); +  }) +  RXML.run_error ("Error in Pike code: %s\n", describe_error (err)); +  +  res = (o && o->flush() || ""); +  +  if(o) +  destruct(o); +  +  return res; + } +  + string read_roxen_file( string what, object id ) + { +  // let there be magic +  return id->conf->open_file(Roxen.fix_relative(what,id),"rR",id,1) +  ->read()[0]; + } +    program my_compile_string(string s, object id, int dom, string fn)   {    if( program_cache[ s ] )    return program_cache[ s ];       object key = Roxen.add_scope_constants(); -  [array ip, array data] = parse_magic(s,id,dom); +  [array ip, array data] = parse_magic(s,id,dom,fn);    -  if( !fn )fn = "pike-tag("+id->not_query+")"; +     int cnt;    -  string pre; +  array pre;    if( !id->misc->__added_helpers_in_tree && !sizeof(ip))    {    id->misc->__added_helpers_in_tree=1;
208:    foreach( ip, program ipc )    {    add_constant( "____ih_"+cnt, ipc ); -  pre += "inherit ____ih_"+cnt++ + ";\n"; +  pre += SPLIT("inherit ____ih_"+cnt++ + ";\n",PREFN);    }    - // werror("parsed to\n%s\n", pre+R(data)); -  program p = compile_string(pre+R(data), fn); +  if (dom) +  pre += SPLIT("string|int parse(RequestID id)\n{\n",PREFN) + +  data + SPLIT("}",POSTFN); +  else +  pre += data; +  program p = compile_string(R(pre), fn);    program_cache[ s ] = p;       cnt=0;
225:   {    array err;    // ouch ouch ouch. -  return my_compile_string( read_roxen_file( what, id ),id,0,0); +  return my_compile_string( read_roxen_file( what, id ),id,0,what);   }    - array parse_magic( string data, RequestID id, int add_md ) + array parse_magic( string data, RequestID id, int add_md, string filename )   { -  array flat=SPLIT(data); +  array flat=SPLIT(data,filename);    object cip, cipup;    array inherits = ({});    for( int i = 0; i<sizeof( flat ); i++ )    {    switch( strlen(flat[i]->text) && flat[i]->text[0] )    { -  case 'i': -  OCIP(); OCIPUP(); -  if( flat[i] == "inherit" ) -  { -  flat[i]->text=""; -  int start = ++i; -  while( flat[++i] != ";" ) ; -  inherits += ({ handle_inherit( PS(R(flat[start..i])), id ) }); -  flat = flat[..start-1] + flat[i+1..]; -  i = start; -  } -  break; -  -  case 'p': -  OCIP(); OCIPUP(); -  if( flat[i] == "parse" ) -  add_md = 0; -  break; -  +     case '.':    OCIP(); OCIPUP();    if( flat[i] == "." )
263:    break;       case '/': -  if( flat[i]->text[..2] == "//#" ) +  if( flat[i]->text[2..2] == "X" )    { -  +  if (flat[i]->text[1] == '*') +  flat[i]->text = flat[i]->text[..sizeof (flat[i]->text) - 3];    OCIPUP();    CIP( cip );    } -  else if( flat[i]->text[..2] == "//@" ) +  else if( flat[i]->text[2..2] == "O" )    { -  +  if (flat[i]->text[1] == '*') +  flat[i]->text = flat[i]->text[..sizeof (flat[i]->text) - 3];    OCIP();    CIP( cipup );    }
282:       case '#':    OCIP(); OCIPUP(); -  if( sscanf( flat[i]->text, "#include%s", string fn) ) +  if( sscanf( flat[i]->text, "#%*[ \t]inherit%[ \t]%s", +  string ws, string fn) == 3 && sizeof (ws))    { -  +  inherits += ({ handle_inherit( PS(fn), id ) }); +  } +  else if( sscanf( flat[i]->text, "#%*[ \t]include%[ \t]%s", +  string ws, string fn) == 3 && sizeof (ws)) +  {    sscanf( fn, "%*s<%s>", fn );    sscanf( fn, "%*s\"%s\"", fn ); -  [array ih,flat[i]] = parse_magic(read_roxen_file(fn,id), id, 0); +  [array ih,flat[i]] = parse_magic(read_roxen_file(fn,id), id, 0, fn);    inherits += ih;    }    break;
298:    }    OCIP();    OCIPUP(); -  if( add_md ) +  return ({ inherits, flat }); + } +  +  + // ------------------------------------------------------------------- + // The old <pike> container, for compatibility. +  + // Helper functions, to be used in the pike script. + class CompatHelpers   { -  flat =SPLIT( "string|int parse(RequestID id)\n{\n" ) -  + flat -  + SPLIT( "}\n" ); +  inherit "roxenlib"; +  string data = ""; +  void output(mixed ... args) +  { +  if(!sizeof(args)) +  return; +  if(sizeof(args) > 1) +  data += sprintf(@args); +  else +  data += args[0];    } -  return ({ inherits, flat }); +  +  string flush() +  { +  string r = data; +  data =""; +  return r;    }    -  +  constant seteuid=0; +  constant setegid=0; +  constant setuid=0; +  constant setgid=0; +  constant call_out=0; +  constant all_constants=0; +  constant Privs=0; + } +  + string functions(string page, int line) + { +  add_constant( "__magic_helpers", CompatHelpers ); +  return +  "inherit __magic_helpers;\n" +  "#"+line+" \""+replace(page,"\"","\\\"")+"\"\n"; + } +  + // Preamble + string pre(string what, object id) + { +  if(search(what, "parse(") != -1) +  return functions(id->not_query, id->misc->line); +  if(search(what, "return") != -1) +  return functions(id->not_query, id->misc->line) + +  "string|int parse(RequestID id, mapping defines, object file, mapping args) { "; +  else +  return functions(id->not_query, id->misc->line) + +  "string|int parse(RequestID id, mapping defines, object file, mapping args) { return "; + } +  + // Will be added at the end... + string post(string what) + { +  if(search(what, "parse(") != -1) +  return ""; +  if (!strlen(what) || what[-1] != ';') +  return ";}"; +  else +  return "}"; + } +    // Compile and run the contents of the tag (in s) as a pike   // program. - string simpletag_pike(string tag, mapping m, string s,RequestID request_id ) + string container_pike(string tag, mapping m, string s, RequestID request_id, +  object file, mapping defs)   {    program p;    object o;    string res;    mixed err;    -  request_id->misc->__added_helpers_in_tree=0; +     request_id->misc->cacheable=0;       object e = ErrorContainer();    master()->set_inhibit_compile_errors(e);    if(err=catch    { -  p = my_compile_string( s,request_id,1,0 ); +  s = pre(s,request_id)+s+post(s); +  p = program_cache[s]; +  +  if (!p) +  { +  // Not in the program cache. +  p = compile_string(s, "Pike-tag("+request_id->not_query+":"+ +  request_id->misc->line+")");    if (sizeof(program_cache) > query("program_cache_limit"))    {    array a = indices(program_cache);
333:    for(i = query("program_cache_limit")/2; i > 0; i--)    m_delete(program_cache, a[random(sizeof(a))]);    } +  program_cache[s] = p; +  }    })    {    master()->set_inhibit_compile_errors(0); -  return reporterr(sprintf("Error handling <pike> tag in %s:\n%s\n" -  "%s\n\n", -  request_id->not_query, -  s, describe_backtrace(err[..1])), -  e->get()); +  if (e->get()) +  RXML.parse_error ("Error compiling Pike code:\n%s", e->get()); +  else throw (err);    }    master()->set_inhibit_compile_errors(0);       if(err = catch{ -  res = (o=p())->parse(request_id); +  res = (o=p())->parse(request_id, defs, file, m);    }) -  { -  return (res || "") + (o && o->flush() || "") + -  reporterr ("Error in <pike> tag in " + request_id->not_query + ":\n", -  (describe_backtrace (err) / "\n")[0..1] * "\n"); -  } +  RXML.run_error ("Error in Pike code: %s\n", describe_error (err));       res = (res || "") + (o && o->flush() || "");