ebc9881999-05-06Henrik Grubbström (Grubba) /*
51f5902000-09-27Per Hedbor  * $Id: Roxen.pmod,v 1.42 2000/09/26 23:12:10 per Exp $
ebc9881999-05-06Henrik Grubbström (Grubba)  * * Various helper functions. * * Henrik Grubbström 1999-05-03 */
69e4cd2000-03-09Martin Stjernholm #include <config.h>
88bff12000-09-12Per Hedbor #include <version.h>
69e4cd2000-03-09Martin Stjernholm inherit "roxenlib";
796fb62000-08-12Per Hedbor // Low-level C-roxen optimization functions. #if constant( _Roxen ) inherit _Roxen; #endif
ebc9881999-05-06Henrik Grubbström (Grubba) /* * TODO: * * o Quota: Fix support for the index file. * */ #ifdef QUOTA_DEBUG #define QD_WRITE(X) werror(X) #else /* !QUOTA_DEBUG */ #define QD_WRITE(X) #endif /* QUOTA_DEBUG */
7e9a0b2000-09-16Per Hedbor #undef CACHE #undef NOCACHE #define CACHE(id,X) ([mapping(string:mixed)]id->misc)->cacheable=min(([mapping(string:mixed)]id->misc)->cacheable,X) #define NOCACHE(id) ([mapping(string:mixed)]id->misc)->cacheable=0
ebc9881999-05-06Henrik Grubbström (Grubba) class QuotaDB { #if constant(create_thread) object(Thread.Mutex) lock = Thread.Mutex();
2543b72000-07-02Henrik Grubbström (Grubba) #define LOCK() mixed key__; catch { key__ = lock->lock(); }
ebc9881999-05-06Henrik Grubbström (Grubba) #define UNLOCK() do { if (key__) destruct(key__); } while(0) #else /* !constant(create_thread) */ #define LOCK() #define UNLOCK() #endif /* constant(create_thread) */ constant READ_BUF_SIZE = 256; constant CACHE_SIZE_LIMIT = 512;
a3e4271999-05-11Henrik Grubbström (Grubba)  string base;
ebc9881999-05-06Henrik Grubbström (Grubba)  object catalog_file; object data_file; mapping(string:int) new_entries_cache = ([]); mapping(string:object) active_objects = ([]); array(int) index;
e9857a1999-05-08Henrik Grubbström (Grubba)  array(string) index_acc; int acc_scale;
ebc9881999-05-06Henrik Grubbström (Grubba)  int next_offset; static class QuotaEntry { string name; int data_offset;
eed7141999-05-12Henrik Grubbström (Grubba)  static int usage;
ebc9881999-05-06Henrik Grubbström (Grubba)  static int quota; static void store() { LOCK();
eed7141999-05-12Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaEntry::store(): Usage for %O is now %O(%O)\n", name, usage, quota));
ebc9881999-05-06Henrik Grubbström (Grubba)  data_file->seek(data_offset);
eed7141999-05-12Henrik Grubbström (Grubba)  data_file->write(sprintf("%4c", usage));
ebc9881999-05-06Henrik Grubbström (Grubba)  UNLOCK(); } static void read() { LOCK(); data_file->seek(data_offset); string s = data_file->read(4);
eed7141999-05-12Henrik Grubbström (Grubba)  usage = 0; sscanf(s, "%4c", usage);
ebc9881999-05-06Henrik Grubbström (Grubba) 
01f6801999-06-20Henrik Grubbström (Grubba)  if (usage < 0) { // No negative usage. usage = 0; }
eed7141999-05-12Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaEntry::read(): Usage for %O is %O(%O)\n", name, usage, quota));
ebc9881999-05-06Henrik Grubbström (Grubba)  UNLOCK(); }
eed7141999-05-12Henrik Grubbström (Grubba)  void create(string n, int d_o, int q)
ebc9881999-05-06Henrik Grubbström (Grubba)  {
eed7141999-05-12Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaEntry(%O, %O, %O)\n", n, d_o, q));
ebc9881999-05-06Henrik Grubbström (Grubba)  name = n; data_offset = d_o;
eed7141999-05-12Henrik Grubbström (Grubba)  quota = q;
ebc9881999-05-06Henrik Grubbström (Grubba)  read(); } int check_quota(string uri, int amount) {
eed7141999-05-12Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaEntry::check_quota(%O, %O): usage:%d(%d)\n", uri, amount, usage, quota)); if (!quota) { // No quota at all. return 0; }
ebc9881999-05-06Henrik Grubbström (Grubba)  if (amount == 0x7fffffff) { // Workaround for FTP. return 1; }
eed7141999-05-12Henrik Grubbström (Grubba)  return(usage + amount <= quota);
ebc9881999-05-06Henrik Grubbström (Grubba)  } int allocate(string uri, int amount) {
eed7141999-05-12Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaEntry::allocate(%O, %O): usage:%d => %d(%d)\n", uri, amount, usage, usage + amount, quota));
ebc9881999-05-06Henrik Grubbström (Grubba) 
eed7141999-05-12Henrik Grubbström (Grubba)  usage += amount;
ebc9881999-05-06Henrik Grubbström (Grubba) 
01f6801999-06-20Henrik Grubbström (Grubba)  if (usage < 0) { // No negative usage... usage = 0; }
ebc9881999-05-06Henrik Grubbström (Grubba)  store();
eed7141999-05-12Henrik Grubbström (Grubba)  return(usage <= quota);
ebc9881999-05-06Henrik Grubbström (Grubba)  } int deallocate(string uri, int amount) { return(allocate(uri, -amount)); }
eed7141999-05-12Henrik Grubbström (Grubba)  int get_usage(string uri)
ebc9881999-05-06Henrik Grubbström (Grubba)  {
eed7141999-05-12Henrik Grubbström (Grubba)  return usage;
ebc9881999-05-06Henrik Grubbström (Grubba)  }
eed7141999-05-12Henrik Grubbström (Grubba)  void set_usage(string uri, int amount)
ebc9881999-05-06Henrik Grubbström (Grubba)  {
eed7141999-05-12Henrik Grubbström (Grubba)  usage = amount;
ebc9881999-05-06Henrik Grubbström (Grubba)  store(); } #if !constant(set_weak_flag) static int refs; void add_ref() { refs++; } void free_ref() { if (!(--refs)) { destruct(); } } } static class QuotaProxy { static object(QuotaEntry) master; function(string, int:int) check_quota; function(string, int:int) allocate; function(string, int:int) deallocate;
eed7141999-05-12Henrik Grubbström (Grubba)  function(string, int:void) set_usage; function(string:int) get_usage;
ebc9881999-05-06Henrik Grubbström (Grubba)  void create(object(QuotaEntry) m) { master = m; master->add_ref(); check_quota = master->check_quota; allocate = master->allocate; deallocate = master->deallocate;
eed7141999-05-12Henrik Grubbström (Grubba)  set_usage = master->set_usage; get_usage = master->get_usage;
ebc9881999-05-06Henrik Grubbström (Grubba)  } void destroy() { master->free_ref(); } #endif /* !constant(set_weak_flag) */ }
eed7141999-05-12Henrik Grubbström (Grubba)  static object read_entry(int offset, int|void quota)
ebc9881999-05-06Henrik Grubbström (Grubba)  {
eed7141999-05-12Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaDB::read_entry(%O, %O)\n", offset, quota));
ebc9881999-05-06Henrik Grubbström (Grubba)  catalog_file->seek(offset); string data = catalog_file->read(READ_BUF_SIZE); if (data == "") {
eed7141999-05-12Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaDB::read_entry(%O, %O): At EOF\n", offset, quota));
ebc9881999-05-06Henrik Grubbström (Grubba)  return 0; } int len; int data_offset; string key; sscanf(data[..7], "%4c%4c", len, data_offset); if (len > sizeof(data)) { key = data[8..] + catalog_file->read(len - sizeof(data)); len -= 8; if (sizeof(key) != len) { error(sprintf("Failed to read catalog entry at offset %d.\n" "len: %d, sizeof(key):%d\n", offset, len, sizeof(key))); } } else { key = data[8..len-1];
6fa7e81999-05-14Henrik Grubbström (Grubba)  catalog_file->seek(offset + 8 + sizeof(key));
ebc9881999-05-06Henrik Grubbström (Grubba)  }
eed7141999-05-12Henrik Grubbström (Grubba)  return QuotaEntry(key, data_offset, quota);
ebc9881999-05-06Henrik Grubbström (Grubba)  } static object open(string fname, int|void create_new) { object f = Stdio.File(); string mode = create_new?"rwc":"rw"; if (!f->open(fname, mode)) { error(sprintf("Failed to open quota file %O.\n", fname)); } if (f->try_lock && !f->try_lock()) { error(sprintf("Failed to lock quota file %O.\n", fname)); } return(f); }
e9857a1999-05-08Henrik Grubbström (Grubba)  static void init_index_acc() { /* Set up the index accellerator. * sizeof(index_acc) ~ sqrt(sizeof(index)) */ acc_scale = 1; if (sizeof(index)) { int i = sizeof(index)/2; while (i) { i /= 4;
8b5f0c1999-05-16Henrik Grubbström (Grubba)  acc_scale *= 2;
e9857a1999-05-08Henrik Grubbström (Grubba)  } }
8b5f0c1999-05-16Henrik Grubbström (Grubba)  index_acc = allocate((sizeof(index) + acc_scale -1)/acc_scale);
e9857a1999-05-08Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaDB()::init_index_acc(): " "sizeof(index):%d, sizeof(index_acc):%d acc_scale:%d\n", sizeof(index), sizeof(index_acc), acc_scale)); }
1a7b001999-05-14Henrik Grubbström (Grubba)  void rebuild_index()
ebc9881999-05-06Henrik Grubbström (Grubba)  {
a3e4271999-05-11Henrik Grubbström (Grubba)  array(string) new_keys = sort(indices(new_entries_cache)); int prev; array(int) new_index = ({}); foreach(new_keys, string key) {
8b5f0c1999-05-16Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaDB::rebuild_index(): key:%O lo:0 hi:%d\n", key, sizeof(index_acc)));
a3e4271999-05-11Henrik Grubbström (Grubba)  int lo; int hi = sizeof(index_acc);
eed7141999-05-12Henrik Grubbström (Grubba)  if (hi) { do {
366b791999-05-16Henrik Grubbström (Grubba)  // Loop invariants: // hi is an element > key. // lo-1 is an element < key.
eed7141999-05-12Henrik Grubbström (Grubba)  int probe = (lo + hi)/2;
8b5f0c1999-05-16Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaDB::rebuild_index(): acc: " "key:%O lo:%d probe:%d hi:%d\n", key, lo, probe, hi));
eed7141999-05-12Henrik Grubbström (Grubba)  if (!index_acc[probe]) { object e = read_entry(index[probe * acc_scale]); index_acc[probe] = e->name; } if (index_acc[probe] < key) {
366b791999-05-16Henrik Grubbström (Grubba)  lo = probe + 1;
eed7141999-05-12Henrik Grubbström (Grubba)  } else if (index_acc[probe] > key) { hi = probe; } else { /* Found */ // Shouldn't happen... break; }
366b791999-05-16Henrik Grubbström (Grubba)  } while(lo < hi);
eed7141999-05-12Henrik Grubbström (Grubba) 
366b791999-05-16Henrik Grubbström (Grubba)  if (lo < hi) {
eed7141999-05-12Henrik Grubbström (Grubba)  // Found... // Shouldn't happen, but... // Skip to the next key... continue;
a3e4271999-05-11Henrik Grubbström (Grubba)  }
8b5f0c1999-05-16Henrik Grubbström (Grubba)  if (hi) { hi *= acc_scale; lo = hi - acc_scale;
eed7141999-05-12Henrik Grubbström (Grubba) 
8b5f0c1999-05-16Henrik Grubbström (Grubba)  if (hi > sizeof(index)) { hi = sizeof(index); }
366b791999-05-16Henrik Grubbström (Grubba) 
8b5f0c1999-05-16Henrik Grubbström (Grubba)  do { // Same loop invariants as above. int probe = (lo + hi)/2; QD_WRITE(sprintf("QuotaDB::rebuild_index(): " "key:%O lo:%d probe:%d hi:%d\n", key, lo, probe, hi)); object e = read_entry(index[probe]); if (e->name < key) { lo = probe + 1; } else if (e->name > key) { hi = probe; } else { /* Found */ // Shouldn't happen... break; } } while (lo < hi); if (lo < hi) { // Found... // Shouldn't happen, but... // Skip to the next key... continue;
eed7141999-05-12Henrik Grubbström (Grubba)  }
a3e4271999-05-11Henrik Grubbström (Grubba)  }
366b791999-05-16Henrik Grubbström (Grubba)  new_index += index[prev..hi-1] + ({ new_entries_cache[key] });
eed7141999-05-12Henrik Grubbström (Grubba)  prev = hi; } else { new_index += ({ new_entries_cache[key] });
a3e4271999-05-11Henrik Grubbström (Grubba)  } }
8b5f0c1999-05-16Henrik Grubbström (Grubba)  // Add the trailing elements... new_index += index[prev..]; QD_WRITE("Index rebuilt.\n");
a3e4271999-05-11Henrik Grubbström (Grubba)  LOCK(); object index_file = open(base + ".index.new", 1); string to_write = sprintf("%@4c", new_index); if (index_file->write(to_write) != sizeof(to_write)) { index_file->close(); rm(base + ".index.new"); } else { mv(base + ".index.new", base + ".index"); } index = new_index; init_index_acc(); UNLOCK(); foreach(new_keys, string key) { m_delete(new_entries_cache, key); }
ebc9881999-05-06Henrik Grubbström (Grubba)  }
eed7141999-05-12Henrik Grubbström (Grubba)  static object low_lookup(string key, int quota)
ebc9881999-05-06Henrik Grubbström (Grubba)  {
eed7141999-05-12Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaDB::low_lookup(%O, %O)\n", key, quota));
ebc9881999-05-06Henrik Grubbström (Grubba) 
a3e4271999-05-11Henrik Grubbström (Grubba)  int cat_offset;
ebc9881999-05-06Henrik Grubbström (Grubba) 
a3e4271999-05-11Henrik Grubbström (Grubba)  if (!zero_type(cat_offset = new_entries_cache[key])) {
eed7141999-05-12Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaDB::low_lookup(%O, %O): " "Found in new entries cache.\n", key, quota)); return read_entry(cat_offset, quota);
e9857a1999-05-08Henrik Grubbström (Grubba)  } /* Try the index file. */
ebc9881999-05-06Henrik Grubbström (Grubba) 
e9857a1999-05-08Henrik Grubbström (Grubba)  /* First use the accellerated index. */ int lo; int hi = sizeof(index_acc);
eed7141999-05-12Henrik Grubbström (Grubba)  if (hi) { do {
366b791999-05-16Henrik Grubbström (Grubba)  // Loop invariants: // hi is an element that is > key. // lo-1 is an element that is < key.
eed7141999-05-12Henrik Grubbström (Grubba)  int probe = (lo + hi)/2;
ebc9881999-05-06Henrik Grubbström (Grubba) 
366b791999-05-16Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaDB:low_lookup(%O): " "In acc: lo:%d, probe:%d, hi:%d\n", key, lo, probe, hi));
eed7141999-05-12Henrik Grubbström (Grubba)  if (!index_acc[probe]) { object e = read_entry(index[probe * acc_scale], quota);
e9857a1999-05-08Henrik Grubbström (Grubba) 
eed7141999-05-12Henrik Grubbström (Grubba)  index_acc[probe] = e->name;
e9857a1999-05-08Henrik Grubbström (Grubba) 
eed7141999-05-12Henrik Grubbström (Grubba)  if (key == e->name) { /* Found in e */ QD_WRITE(sprintf("QuotaDB:low_lookup(%O): In acc: Found at %d\n", key, probe * acc_scale)); return e; } } if (index_acc[probe] < key) {
366b791999-05-16Henrik Grubbström (Grubba)  lo = probe + 1;
eed7141999-05-12Henrik Grubbström (Grubba)  } else if (index_acc[probe] > key) { hi = probe; } else { /* Found */
e9857a1999-05-08Henrik Grubbström (Grubba)  QD_WRITE(sprintf("QuotaDB:low_lookup(%O): In acc: Found at %d\n", key, probe * acc_scale));
eed7141999-05-12Henrik Grubbström (Grubba)  return read_entry(index[probe * acc_scale], quota);
e9857a1999-05-08Henrik Grubbström (Grubba)  }
366b791999-05-16Henrik Grubbström (Grubba)  } while(lo < hi); // At this point hi is the first element that is > key. // Not in the accellerated index.
ebc9881999-05-06Henrik Grubbström (Grubba) 
366b791999-05-16Henrik Grubbström (Grubba)  if (hi) { // Go to disk hi *= acc_scale; lo = hi - acc_scale;
8b5f0c1999-05-16Henrik Grubbström (Grubba)  if (hi > sizeof(index)) { hi = sizeof(index); }
366b791999-05-16Henrik Grubbström (Grubba)  do { // Same loop invariant as above. int probe = (lo + hi)/2; QD_WRITE(sprintf("QuotaDB:low_lookup(%O): lo:%d, probe:%d, hi:%d\n", key, lo, probe, hi)); object e = read_entry(index[probe], quota);
eed7141999-05-12Henrik Grubbström (Grubba) 
366b791999-05-16Henrik Grubbström (Grubba)  if (e->name < key) { lo = probe + 1; } else if (e->name > key) { hi = probe; } else { /* Found */ QD_WRITE(sprintf("QuotaDB:low_lookup(%O): Found at %d\n", key, probe)); return e; } } while (lo < hi); }
e9857a1999-05-08Henrik Grubbström (Grubba)  } QD_WRITE(sprintf("QuotaDB::low_lookup(%O): Not found\n", key)); return 0;
ebc9881999-05-06Henrik Grubbström (Grubba)  } object lookup(string key, int quota) { QD_WRITE(sprintf("QuotaDB::lookup(%O, %O)\n", key, quota)); LOCK(); object res; if (res = active_objects[key]) { QD_WRITE(sprintf("QuotaDB::lookup(%O, %O): User in active objects.\n", key, quota)); #if constant(set_weak_flag) return res; #else /* !constant(set_weak_flag) */ return QuotaProxy(res); #endif /* constant(set_weak_flag) */ }
eed7141999-05-12Henrik Grubbström (Grubba)  if (res = low_lookup(key, quota)) {
e9857a1999-05-08Henrik Grubbström (Grubba)  active_objects[key] = res; #if constant(set_weak_flag)
ebc9881999-05-06Henrik Grubbström (Grubba)  return res;
e9857a1999-05-08Henrik Grubbström (Grubba) #else /* !constant(set_weak_flag) */ return QuotaProxy(res); #endif /* constant(set_weak_flag) */
ebc9881999-05-06Henrik Grubbström (Grubba)  } QD_WRITE(sprintf("QuotaDB::lookup(%O, %O): New user.\n", key, quota)); // Search to EOF. data_file->seek(-1); data_file->read(1); catalog_file->seek(next_offset); // We should now be at EOF. int data_offset = data_file->tell(); // Initialize.
eed7141999-05-12Henrik Grubbström (Grubba)  if (data_file->write(sprintf("%4c", 0)) != 4) {
ebc9881999-05-06Henrik Grubbström (Grubba)  error(sprintf("write() failed for quota data file!\n")); } string entry = sprintf("%4c%4c%s", sizeof(key)+8, data_offset, key); if (catalog_file->write(entry) != sizeof(entry)) { error(sprintf("write() failed for quota catalog file!\n")); } new_entries_cache[key] = next_offset; next_offset = catalog_file->tell(); if (sizeof(new_entries_cache) > CACHE_SIZE_LIMIT) { rebuild_index(); } // low_lookup will always succeed at this point.
eed7141999-05-12Henrik Grubbström (Grubba)  return low_lookup(key, quota);
ebc9881999-05-06Henrik Grubbström (Grubba)  } void create(string base_name, int|void create_new) {
a3e4271999-05-11Henrik Grubbström (Grubba)  base = base_name;
ebc9881999-05-06Henrik Grubbström (Grubba)  catalog_file = open(base_name + ".cat", create_new); data_file = open(base_name + ".data", create_new);
a3e4271999-05-11Henrik Grubbström (Grubba)  object index_file = open(base_name + ".index", 1);
ebc9881999-05-06Henrik Grubbström (Grubba)  #if constant(set_weak_flag) set_weak_flag(active_objects, 1); #endif /* constant(set_weak_flag) */ /* Initialize the new_entries table. */ array index_st = index_file->stat(); if (!index_st || !sizeof(index_st)) { error(sprintf("stat() failed for quota index file!\n")); } array data_st = data_file->stat(); if (!data_st || !sizeof(data_st)) { error(sprintf("stat() failed for quota data file!\n")); } if (index_st[1] < 0) { error("quota index file isn't a regular file!\n"); } if (data_st[1] < 0) { error("quota data file isn't a regular file!\n"); } if (data_st[1] < index_st[1]) { error("quota data file is shorter than the index file!\n"); } if (index_st[1] & 3) { error("quota index file has odd length!\n"); } if (data_st[1] & 3) { error("quota data file has odd length!\n"); } /* Read the index, and find the last entry in the catalog file. */ int i; array(string) index_str = index_file->read()/4; index = allocate(sizeof(index_str));
a3e4271999-05-11Henrik Grubbström (Grubba)  if (sizeof(index_str) && (sizeof(index_str[-1]) != 4)) {
ebc9881999-05-06Henrik Grubbström (Grubba)  error("Truncated read of the index file!\n"); } foreach(index_str, string offset_str) { int offset; sscanf(offset_str, "%4c", offset); index[i++] = offset; if (offset > next_offset) { next_offset = offset; } }
e9857a1999-05-08Henrik Grubbström (Grubba)  init_index_acc();
ebc9881999-05-06Henrik Grubbström (Grubba)  if (sizeof(index)) { /* Skip past the last entry in the catalog file */ mixed entry = read_entry(next_offset); next_offset = catalog_file->tell(); } if (index_st[1] < data_st[1]) { /* Put everything else in the new_entries_cache */ while (mixed entry = read_entry(next_offset)) { new_entries_cache[entry->name] = next_offset; next_offset = catalog_file->tell(); } /* Clean up the index. */ rebuild_index(); } } }
69e4cd2000-03-09Martin Stjernholm 
df6c032000-08-09Per Hedbor #define CTX() class EScope { string scope; void delete( string var ) { RXML.Context ctx = RXML.get_context( ); ctx->delete_var( var, scope ); } string name() { RXML.Context ctx = RXML.get_context( ); return scope == "_" ? ctx->current_scope() : scope; }
c166222000-08-09Per Hedbor  /*static*/ mixed `[]( string what )
df6c032000-08-09Per Hedbor  { RXML.Context ctx = RXML.get_context( ); return ctx->get_var( what, scope ); }
c166222000-08-09Per Hedbor  /*static*/ mixed `->( string what )
df6c032000-08-09Per Hedbor  { return `[]( what ); }
c166222000-08-09Per Hedbor  /*static*/ mixed `[]=( string what, mixed nval )
df6c032000-08-09Per Hedbor  { RXML.Context ctx = RXML.get_context( ); ctx->set_var( what, nval, scope ); return nval; }
c166222000-08-09Per Hedbor  /*static*/ mixed `->=( string what, mixed nval )
df6c032000-08-09Per Hedbor  { return `[]=( what, nval ); }
c166222000-08-09Per Hedbor  /*static*/ array(string) _indices( )
df6c032000-08-09Per Hedbor  { RXML.Context ctx = RXML.get_context( ); return ctx->list_var( scope ); }
c166222000-08-09Per Hedbor  /*static*/ array(string) _values( )
df6c032000-08-09Per Hedbor  { RXML.Context ctx = RXML.get_context( ); return map( ctx->list_var( scope ), `[] ); }
c166222000-08-09Per Hedbor  /*static*/ void create( string _scope )
df6c032000-08-09Per Hedbor  { scope = _scope; } } class SRestore { mapping osc = ([]); void destroy() { foreach( indices( osc ), string o ) add_constant( o, osc[o] ); add_constant( "roxen", roxenp() ); } } SRestore add_scope_constants( string|void name ) { SRestore res = SRestore(); mapping ac = all_constants(); if(!name) name = "";
85a1bb2000-08-10Per Hedbor  if( RXML.get_context() )
df6c032000-08-09Per Hedbor  {
85a1bb2000-08-10Per Hedbor  foreach( RXML.get_context()->list_scopes()|({"_"}), string scope ) { res->osc[ name+scope ] = ac[ name+scope ]; add_constant( name+scope, EScope( scope ) ); }
df6c032000-08-09Per Hedbor  } return res; }
7035032000-08-23Martin Nilsson //! A mapping suitable for Parser.HTML.add_entities to initialize it //! to transform the standard character reference entities. mapping(string:string) parser_charref_table = lambda () { mapping(string:string) table = ([]); for (int i = 0; i < sizeof (replace_entities); i++) { string chref = replace_entities[i]; table[chref[1..sizeof (chref) - 2]] = replace_values[i]; } return table; }();
f4c4d52000-08-27Martin Stjernholm //! The inverse mapping to parser_charref_table. mapping(string:string) inverse_charref_table = lambda () { mapping(string:string) table = ([]); for (int i = 0; i < sizeof (replace_entities); i++) { string chref = replace_entities[i]; table[replace_values[i]] = chref[1..sizeof (chref) - 2]; } return table; }(); string decode_charref (string chref) //! Decodes a character reference entity either on symbolic or numeric //! form. Returns zero if the reference isn't recognized. { if (sizeof (chref) <= 2 || chref[0] != '&' || chref[-1] != ';') return 0; if (chref[1] != '#') return parser_charref_table[chref[1..sizeof (chref) - 2]]; if ((<'x', 'X'>)[chref[2]]) { if (sscanf (chref, "&%*2s%x;%*c", int c) == 2) return sprintf ("%c", c); } else if (sscanf (chref, "&%*c%d;%*c", int c) == 2) return sprintf ("%c", c); return 0; }
425fc62000-09-21Per Hedbor string|program safe_compile( string code ) { program ret;
51f5902000-09-27Per Hedbor  roxenloader.LowErrorContainer ec = roxenloader.LowErrorContainer();
425fc62000-09-21Per Hedbor  roxenloader.push_compile_error_handler( ec ); catch(ret = compile_string( code ));
51f5902000-09-27Per Hedbor  roxenloader.pop_compile_error_handler( );
425fc62000-09-21Per Hedbor  if( !ret ) return ec->get(); return ret; }
f4c4d52000-08-27Martin Stjernholm string encode_charref (string char) //! Encodes a character to a character reference entity. The symbolic
d3beb02000-08-27Martin Stjernholm //! form is preferred over the numeric. The decimal variety of the //! numeric form is used (since it's understood better than the //! hexadecimal form by at least Netscape 4).
f4c4d52000-08-27Martin Stjernholm { if (string chref = inverse_charref_table[char]) return "&" + chref + ";";
d3beb02000-08-27Martin Stjernholm  return sprintf ("&#%d;", char[0]);
f4c4d52000-08-27Martin Stjernholm }
7035032000-08-23Martin Nilsson 
df6c032000-08-09Per Hedbor 
69e4cd2000-03-09Martin Stjernholm // RXML complementary stuff shared between configurations. class ScopeRoxen { inherit RXML.Scope; string pike_version=predef::version(); int ssl_strength=0; #if constant(SSL) void create() { ssl_strength=40;
c4ee172000-04-06Henrik Grubbström (Grubba) #if constant(SSL.constants.CIPHER_des)
69e4cd2000-03-09Martin Stjernholm  if(SSL.constants.CIPHER_algorithms[SSL.constants.CIPHER_des]) ssl_strength=128; if(SSL.constants.CIPHER_algorithms[SSL.constants.CIPHER_3des]) ssl_strength=168;
c4ee172000-04-06Henrik Grubbström (Grubba) #endif /* !constant(SSL.constants.CIPHER_des) */
69e4cd2000-03-09Martin Stjernholm  } #endif
7035032000-08-23Martin Nilsson  mixed `[] (string var, void|RXML.Context c, void|string scope) {
69e4cd2000-03-09Martin Stjernholm  switch(var) { case "uptime":
7e9a0b2000-09-16Per Hedbor  CACHE(c->id,1);
dbf9912000-03-19Martin Nilsson  return (time(1)-roxenp()->start_time);
69e4cd2000-03-09Martin Stjernholm  case "uptime-days":
7e9a0b2000-09-16Per Hedbor  CACHE(c->id,3600*2);
dbf9912000-03-19Martin Nilsson  return (time(1)-roxenp()->start_time)/3600/24;
69e4cd2000-03-09Martin Stjernholm  case "uptime-hours":
7e9a0b2000-09-16Per Hedbor  CACHE(c->id,1800);
dbf9912000-03-19Martin Nilsson  return (time(1)-roxenp()->start_time)/3600;
69e4cd2000-03-09Martin Stjernholm  case "uptime-minutes":
7e9a0b2000-09-16Per Hedbor  CACHE(c->id,60);
dbf9912000-03-19Martin Nilsson  return (time(1)-roxenp()->start_time)/60;
69e4cd2000-03-09Martin Stjernholm  case "hits-per-minute":
7e9a0b2000-09-16Per Hedbor  CACHE(c->id,2);
dbf9912000-03-19Martin Nilsson  return c->id->conf->requests / ((time(1)-roxenp()->start_time)/60 + 1);
69e4cd2000-03-09Martin Stjernholm  case "hits":
7e9a0b2000-09-16Per Hedbor  NOCACHE(c->id);
69e4cd2000-03-09Martin Stjernholm  return c->id->conf->requests; case "sent-mb":
7e9a0b2000-09-16Per Hedbor  CACHE(c->id,10);
69e4cd2000-03-09Martin Stjernholm  return sprintf("%1.2f",c->id->conf->sent / (1024.0*1024.0)); case "sent":
7e9a0b2000-09-16Per Hedbor  NOCACHE(c->id);
69e4cd2000-03-09Martin Stjernholm  return c->id->conf->sent; case "sent-per-minute":
7e9a0b2000-09-16Per Hedbor  CACHE(c->id,2);
dbf9912000-03-19Martin Nilsson  return c->id->conf->sent / ((time(1)-roxenp()->start_time)/60 || 1);
69e4cd2000-03-09Martin Stjernholm  case "sent-kbit-per-second":
7e9a0b2000-09-16Per Hedbor  CACHE(c->id,2);
69e4cd2000-03-09Martin Stjernholm  return sprintf("%1.2f",((c->id->conf->sent*8)/1024.0/
dbf9912000-03-19Martin Nilsson  (time(1)-roxenp()->start_time || 1)));
69e4cd2000-03-09Martin Stjernholm  case "ssl-strength": return ssl_strength; case "pike-version": return pike_version; case "version":
dbf9912000-03-19Martin Nilsson  return roxenp()->version();
88bff12000-09-12Per Hedbor  case "base-version": return __roxen_version__; case "build": return __roxen_build__;
69e4cd2000-03-09Martin Stjernholm  case "time":
7e9a0b2000-09-16Per Hedbor  CACHE(c->id,1);
69e4cd2000-03-09Martin Stjernholm  return time(1); case "server": return c->id->conf->query("MyWorldLocation");
9705b92000-07-02Martin Nilsson  case "domain": string tmp=c->id->conf->query("MyWorldLocation"); sscanf(tmp, "%*s//%s", tmp); sscanf(tmp, "%s:", tmp); sscanf(tmp, "%s/", tmp); return tmp;
7035032000-08-23Martin Nilsson  case "locale":
7e9a0b2000-09-16Per Hedbor  NOCACHE(c->id);
7035032000-08-23Martin Nilsson  return roxenp()->locale->get(); default: return RXML.nil;
69e4cd2000-03-09Martin Stjernholm  } :: `[] (var, c, scope); } array(string) _indices() { return ({"uptime", "uptime-days", "uptime-hours", "uptime-minutes", "hits-per-minute", "hits", "sent-mb", "sent", "sent-per-minute", "sent-kbit-per-second", "ssl-strength",
7035032000-08-23Martin Nilsson  "pike-version", "version", "time", "server", "domain", "locale"});
69e4cd2000-03-09Martin Stjernholm  } string _sprintf() { return "RXML.Scope(roxen)"; } } class ScopePage { inherit RXML.Scope; constant converter=(["fgcolor":"fgcolor", "bgcolor":"bgcolor", "theme-bgcolor":"theme_bgcolor", "theme-fgcolor":"theme_fgcolor", "theme-language":"theme_language"]); constant in_defines=aggregate_multiset(@indices(converter)); mixed `[] (string var, void|RXML.Context c, void|string scope) {
7e9a0b2000-09-16Per Hedbor  NOCACHE(c->id);
971ed32000-03-20Martin Stjernholm  switch (var) { case "pathinfo": return c->id->misc->path_info; }
69e4cd2000-03-09Martin Stjernholm  if(in_defines[var]) return c->id->misc->defines[converter[var]]; if(objectp(c->id->misc->scope_page[var])) return c->id->misc->scope_page[var]->rxml_var_eval(c, var, "page"); return c->id->misc->scope_page[var]; } mixed `[]= (string var, mixed val, void|RXML.Context c, void|string scope_name) {
971ed32000-03-20Martin Stjernholm  switch (var) { case "pathinfo": return c->id->misc->path_info = val; }
69e4cd2000-03-09Martin Stjernholm  if(in_defines[var]) return c->id->misc->defines[converter[var]]=val; return c->id->misc->scope_page[var]=val; } array(string) _indices(void|RXML.Context c) { if(!c) return ({});
7e9a0b2000-09-16Per Hedbor  NOCACHE(c->id);
69e4cd2000-03-09Martin Stjernholm  array ind=indices(c->id->misc->scope_page); foreach(indices(in_defines), string def) if(c->id->misc->defines[converter[def]]) ind+=({def});
a458af2000-03-20Martin Stjernholm  return ind + ({"pathinfo"});
69e4cd2000-03-09Martin Stjernholm  } void m_delete (string var, void|RXML.Context c, void|string scope_name) { if(!c) return;
971ed32000-03-20Martin Stjernholm  switch (var) { case "pathinfo": predef::m_delete (c->id->misc, "pathinfo"); return; }
69e4cd2000-03-09Martin Stjernholm  if(in_defines[var]) { if(var[0..4]=="theme") predef::m_delete(c->id->misc->defines, converter[var]); else ::m_delete(var, c, scope_name);
971ed32000-03-20Martin Stjernholm  return;
69e4cd2000-03-09Martin Stjernholm  } predef::m_delete(c->id->misc->scope_page, var); } string _sprintf() { return "RXML.Scope(page)"; } }
7035032000-08-23Martin Nilsson class ScopeCookie { inherit RXML.Scope;
69e4cd2000-03-09Martin Stjernholm 
7035032000-08-23Martin Nilsson  mixed `[] (string var, void|RXML.Context c, void|string scope) { if(!c) return RXML.nil;
7e9a0b2000-09-16Per Hedbor  NOCACHE(c->id);
7035032000-08-23Martin Nilsson  return c->id->cookies[var]; } mixed `[]= (string var, mixed val, void|RXML.Context c, void|string scope_name) { if(c && c->id->cookies[var]!=val) { c->id->cookies[var]=val; add_http_header(c->id->misc->defines[" _extra_heads"], "Set-Cookie", http_encode_cookie(var)+ "="+http_encode_cookie( (string)(val||"") )+ "; expires="+http_date(time(1)+(3600*24*365*2))+"; path=/");
9bf1182000-08-12Martin Stjernholm  }
7035032000-08-23Martin Nilsson  return RXML.nil; } array(string) _indices(void|RXML.Context c) { if(!c) return ({});
7e9a0b2000-09-16Per Hedbor  NOCACHE(c->id);
7035032000-08-23Martin Nilsson  return indices(c->id->cookies); } void m_delete (string var, void|RXML.Context c, void|string scope_name) { if(!c || !c->id->cookies[var]) return; predef::m_delete(c->id->cookies, var); add_http_header(c->id->misc->defines[" _extra_heads"], "Set-Cookie", http_encode_cookie(var)+"=; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/"); } string _sprintf() { return "RXML.Scope(Cookie)"; } } RXML.Scope scope_roxen=ScopeRoxen(); RXML.Scope scope_page=ScopePage(); RXML.Scope scope_cookie=ScopeCookie();
9bf1182000-08-12Martin Stjernholm 
69e4cd2000-03-09Martin Stjernholm RXML.TagSet entities_tag_set = class // This tag set always has the lowest priority. { inherit RXML.TagSet; void prepare_context (RXML.Context c) { c->add_scope("roxen",scope_roxen); c->id->misc->scope_page=([]); c->add_scope("page",scope_page);
7035032000-08-23Martin Nilsson  c->add_scope("cookie", scope_cookie);
69e4cd2000-03-09Martin Stjernholm  c->add_scope("form", c->id->variables);
7035032000-08-23Martin Nilsson  c->add_scope("client", c->id->client_var);
69e4cd2000-03-09Martin Stjernholm  c->add_scope("var", ([]) ); } void create (string name) { ::create (name);
7eeec72000-07-06Martin Stjernholm  // Note: No string entities are replaced when the result type for // the parser is t_xml or t_html.
9bf1182000-08-12Martin Stjernholm  add_string_entities (parser_charref_table);
69e4cd2000-03-09Martin Stjernholm  } } ("entities_tag_set");
dbf9912000-03-19Martin Nilsson 
b92a1c2000-08-14Martin Stjernholm 
dbf9912000-03-19Martin Nilsson constant monthnum=(["Jan":0, "Feb":1, "Mar":2, "Apr":3, "May":4, "Jun":5, "Jul":6, "Aug":7, "Sep":8, "Oct":9, "Nov":10, "Dec":11, "jan":0, "feb":1, "mar":2, "apr":3, "may":4, "jun":5, "jul":6, "aug":7, "sep":8, "oct":9, "nov":10, "dec":11,]); #define MAX_SINCE_CACHE 16384 static mapping(string:int) since_cache=([ ]); array(int) parse_since(string date) { if(!date || sizeof(date)<14) return({0,-1}); int t=0, length = -1; #if constant(mktime)
f6deda2000-07-11Martin Nilsson  string dat=lower_case(date); sscanf(dat+"; length=", "%*s, %s; length=%d", dat, length);
dbf9912000-03-19Martin Nilsson  if(!(t=since_cache[dat])) { int day, year = -1, month, hour, minute, second; string m; if(sscanf(dat, "%d-%s-%d %d:%d:%d", day, m, year, hour, minute, second)>2) { month=monthnum[m]; } else if(dat[2]==',') { // I bet a buck that this never happens sscanf(dat, "%*s, %d %s %d %d:%d:%d", day, m, year, hour, minute, second); month=monthnum[m]; } else if(!(int)dat) { sscanf(dat, "%*[^ ] %s %d %d:%d:%d %d", m, day, hour, minute, second, year); month=monthnum[m]; } else { sscanf(dat, "%d %s %d %d:%d:%d", day, m, year, hour, minute, second); month=monthnum[m]; } if(year >= 0) { // Fudge year to be localtime et al compatible. if (year < 60) { // Assume year 0 - 59 is really year 2000 - 2059. // Can't people stop using two digit years? year += 100; } else if (year >= 1900) { year -= 1900; } catch {
df6c032000-08-09Per Hedbor  t = mktime(second, minute, hour, day, month, year, 0, 0);
dbf9912000-03-19Martin Nilsson  }; } else { report_debug("Could not parse \""+date+"\" to a time int."); } if (sizeof(since_cache) > MAX_SINCE_CACHE) since_cache = ([]); since_cache[dat]=t; } #endif /* constant(mktime) */ return ({ t, length }); } // OBSOLETED by parse_since() int is_modified(string a, int t, void|int len) { array vals=parse_since(a); if(len && len!=vals[1]) return 0; if(vals[0]<t) return 0; return 1; } int httpdate_to_time(string date) { return parse_since(date)[0]||-1; }
31d1642000-03-26Martin Nilsson 
3580f52000-09-03Per Hedbor void set_cookie( RequestID id, string name, string value, int|void expire_time_delta, string|void domain, string|void path ) //! Set the cookie specified by 'name' to 'value'. //! Sends a Set-Cookie header. //! //! The expire_time_delta, domain and path arguments are optional. //! //! If the expire_time_delta variable is -1, the cookie is set to //! //! expire five years in the future. If it is 0 or ommited, no expire //! information is sent to the client. This usualy results in the cookie //! being kept until the browser is exited. { if( expire_time_delta = -1 ) expire_time_delta = (3600*(24*365*5)); string cookie = (http_encode_cookie( name )+"="+ http_encode_cookie( value )); if( expire_time_delta ) cookie += "; expires="+http_date( expire_time_delta+time(1) ); if( domain ) cookie += "; domain="+http_encode_cookie( domain ); if( path ) cookie += "; path="+http_encode_cookie( path ); if(!id->misc->moreheads) id->misc->moreheads = ([]); add_http_header( id->misc->moreheads, "Set-Cookie",cookie ); } void remove_cookie( RequestID id, string name, string value, string|void domain, string|void path ) //! Remove the cookie specified by 'name'. //! Sends a Set-Cookie header with an expire time of 00:00 1/1 1970. //! The domain and path arguments are optional. { set_cookie( id, name, value, -time(1), domain, path ); }
c3c6322000-09-20Per Hedbor void add_cache_callback( RequestID id,function(RequestID,object:int) callback )
a456472000-08-31Per Hedbor //! The request id object is not yet fully initialized in this callback. //! The only valid fields are raw_url and request_headers.
c3c6322000-09-20Per Hedbor //! The second argument is the cache key. Destroying it will enforce //! exiration of the entry from the data cache.
a456472000-08-31Per Hedbor { while( id->misc->orig ) id = id->misc->orig;
862f382000-09-25Per Hedbor  if( !id->misc->_cachecallbacks ) return;
a456472000-08-31Per Hedbor  id->misc->_cachecallbacks |= ({ callback }); }
862f382000-09-25Per Hedbor string get_server_url(object c) {
31d1642000-03-26Martin Nilsson  string url=c->query("MyWorldLocation"); if(stringp(url) && sizeof(url)) return url; array(string) urls=c->query("URLs"); return get_world(urls); }
862f382000-09-25Per Hedbor string get_world(array(string) urls) {
31d1642000-03-26Martin Nilsson  if(!sizeof(urls)) return 0; string url=urls[0]; foreach( ({"http:","fhttp:","https:","ftp:"}), string p) foreach(urls, string u) if(u[0..sizeof(p)-1]==p) { url=u; break; }
f70aa72000-03-28Martin Nilsson  string protocol, server, path="";
31d1642000-03-26Martin Nilsson  int port; if(sscanf(url, "%s://%s:%d/%s", protocol, server, port, path)!=4 &&
f70aa72000-03-28Martin Nilsson  sscanf(url, "%s://%s:%d", protocol, server, port)!=3 && sscanf(url, "%s://%s/%s", protocol, server, path)!=3 && sscanf(url, "%s://%s", protocol, server)!=2 )
31d1642000-03-26Martin Nilsson  return 0; if(protocol=="fhttp") protocol="http"; array hosts=({ gethostname() }), dns; catch(dns=Protocols.DNS.client()->gethostbyname(hosts[0])); if(dns && sizeof(dns)) hosts+=dns[2]+dns[1]; foreach(hosts, string host) if(glob(server, host)) { server=host; break; } if(port) return sprintf("%s://%s:%d/%s", protocol, server, port, path); return sprintf("%s://%s/%s", protocol, server, path); }
c166222000-08-09Per Hedbor 
b92a1c2000-08-14Martin Stjernholm RoxenModule get_owning_module (object|function thing) //! Tries to find out which module the thing belongs to, if any. The //! thing can be e.g. a module object, a Tag object or a simple_tag //! callback. { if (functionp (thing)) thing = function_object (thing); if (objectp (thing)) { if (thing->is_module) return thing; if (object parent = functionp (object_program (thing)) && function_object (object_program (thing))) { // FIXME: This way of finding the module for a tag is ugly. if (parent->is_module) return parent; } } return 0; } Configuration get_owning_config (object|function thing) //! Tries to find out which configuration the thing belongs to, if //! any. The thing can be e.g. a config or module object, a Tag object //! or a simple_tag callback. { if (RoxenModule mod = get_owning_module (thing)) return mod->my_configuration(); if (functionp (thing)) thing = function_object (thing); if (objectp (thing)) { if (thing->is_configuration) return thing; if (object parent = functionp (object_program (thing)) && function_object (object_program (thing))) { // This is mainly for finding tags defined in rxml.pike. if (parent->is_configuration) return parent; } } return 0; } #ifdef REQUEST_TRACE
acc6dd2000-08-15Martin Stjernholm static string trace_msg (RequestID id, string msg, string name)
b92a1c2000-08-14Martin Stjernholm {
acc6dd2000-08-15Martin Stjernholm  msg = html_decode_string (
b92a1c2000-08-14Martin Stjernholm  Parser.HTML()->_set_tag_callback (lambda (object p, string s) {return "";})-> finish (msg)->read());
acc6dd2000-08-15Martin Stjernholm  array(string) lines = msg / "\n"; if (lines[-1] == "") lines = lines[..sizeof (lines) - 2]; if (sizeof (lines)) report_debug ("%s%s%-40s %s\n", map (lines[..sizeof (lines) - 2], lambda (string s) { return sprintf ("%s%*s%s\n", id->misc->trace_id_prefix, id->misc->trace_level + 1, "", s); }) * "", id->misc->trace_id_prefix, sprintf ("%*s%s", id->misc->trace_level + 1, "", lines[-1]), name); } void trace_enter (RequestID id, string msg, object|function thing) { if (!id->misc->trace_level) {
f4c4d52000-08-27Martin Stjernholm  id->misc->trace_id_prefix = ({"%%", "##", "§§", "**", "@@", "$$", "¤¤"})[
acc6dd2000-08-15Martin Stjernholm  all_constants()->id_trace_level_rotate_counter++ % 7]; report_debug ("%s%s Request handled by: %O\n", id->misc->trace_id_prefix, id->misc->trace_id_prefix[..0], id->conf); }
b92a1c2000-08-14Martin Stjernholm  string name; if (thing) { name = get_modfullname (get_owning_module (thing)); if (name) name = "mod: " + html_decode_string ( Parser.HTML()->_set_tag_callback (lambda (object p, string s) {return "";})-> finish (name)->read()); else if (Configuration conf = get_owning_config (thing)) name = "conf: " + conf->query_name();
c7fb942000-09-05Martin Stjernholm  else
b92a1c2000-08-14Martin Stjernholm  name = sprintf ("obj: %O", thing); } else name = "";
acc6dd2000-08-15Martin Stjernholm  trace_msg (id, msg, name);
b92a1c2000-08-14Martin Stjernholm  id->misc->trace_level++; if(function(string,mixed ...:void) _trace_enter = [function(string,mixed ...:void)]([mapping(string:mixed)]id->misc)->trace_enter) _trace_enter (msg, thing); } void trace_leave (RequestID id, string desc) { if (id->misc->trace_level) id->misc->trace_level--;
acc6dd2000-08-15Martin Stjernholm  if (sizeof (desc)) trace_msg (id, desc, "");
b92a1c2000-08-14Martin Stjernholm  if(function(string:void) _trace_leave = [function(string:void)]([mapping(string:mixed)]id->misc)->trace_leave) _trace_leave (desc); } #endif
c166222000-08-09Per Hedbor  #if !constant(Parser.C)
b92a1c2000-08-14Martin Stjernholm object _Parser =
c166222000-08-09Per Hedbor  class { object C = class { mapping(string:string) global_groupings=(["{":"}","(":")","[":"]"]); array(string) split(string data) { int start; int line=1; array(string) ret=({}); int pos; data += "\n\0"; /* End sentinel. */ while(1) { int start=pos; // werror("::::%c\n",data[pos]); switch(data[pos]) { case '\0': return ret; case '#': { pos=search(data,"\n",pos); if(pos==-1) error("Failed to find end of preprocessor statement.\n"); while(data[pos-1]=='\\') pos=search(data,"\n",pos+1); break; case 'a'..'z': case 'A'..'Z': case 128..65536: // Lets simplify things for now... case '_': while(1) { switch(data[pos]) { case '$': // allowed in some C (notably digital) case 'a'..'z': case 'A'..'Z': case '0'..'9': case 128..65536: // Lets simplify things for now... case '_': pos++; continue; } break; } break; case '.': if(data[start..start+2]=="...") { pos+=3; break; } if(data[start..start+1]=="..") { pos+=3; break; } case '0'..'9': if(data[pos]=='0' && (data[pos+1]=='x' || data[pos+1]=='X')) { pos+=2; while(1) { switch(data[pos]) { case '0'..'9': case 'a'..'f': case 'A'..'F': pos++; continue; } break; } break; } while(data[pos]>='0' && data[pos]<='9') pos++; if(data[pos]=='.') { pos++; while(data[pos]>='0' && data[pos]<='9') pos++; if(data[pos]=='e' || data[pos]=='E') { pos++; while(data[pos]>='0' && data[pos]<='9') pos++; } } break; default: werror("%O\n",ret); werror("Unknown token %O\n",data[pos..pos+20]); exit(1); case '`': while(data[pos]=='`') data[pos]++; case '\\': pos++; continue; /* IGNORED */ case '/': case '{': case '}': case '[': case ']': case '(': case ')': case ';': case ',': case '*': case '%': case '?': case ':': case '&': case '|': case '^': case '!': case '~': case '=': case '@': case '+': case '-': case '<': case '>': switch(data[pos..pos+1]) { case "//": pos=search(data,"\n",pos); break; case "/*": pos=search(data,"*/",pos); pos+=2; break; case "<<": case ">>": if(data[pos+2]=='=') pos++; case "==": case "<=": case ">=": case "*=": case "/=": case "%=": case "&=": case "|=": case "^=": case "+=": case "-=": case "++": case "--": case "&&": case "||": case "->": pos++; default: pos++; } break; case ' ': case '\n': case '\r': case '\t': case '\14': while(1) { switch(data[pos]) { case ' ': case '\n': case '\r': case '\t': case '\14': pos++; continue; } break; } break; case '\'': pos++; if(data[pos]=='\\') pos++; pos=search(data, "'", pos+1)+1; break; case '"': { int q,s; while(1) { q=search(data,"\"",pos+1); s=search(data,"\\",pos+1); if(q==-1) q=strlen(data)-1; if(s==-1) s=strlen(data)-1; if(q<s) { pos=q+1; break; }else{ pos=s+1; } } break; } } } ret+=({ data[start..pos-1] }); } } class Token { int line; string text; string file; string trailing_whitespaces=""; void create(string t, int l, void|string f, void|string space) { text=t; line=l; file=f; if(space) trailing_whitespaces=space; } string _sprintf(int how) { switch(how) { case 's': return text; case 'O': return sprintf("Token(%O,%O,%d)",text,file,line); } } int `==(mixed foo) { return (objectp(foo) ? foo->text : foo) == text; } string `+(string ... s) { return predef::`+(text,@s); } string ``+(string ... s) { return predef::`+(@s,text); } mixed cast(string to) { if(to=="string") return text; } } array(Token) tokenize(array(string) s, void|string file) { array(Token) ret=allocate(sizeof(s)); int line=1; for(int e=0;e<sizeof(s);e++) { ret[e]=Token(s[e],line,file); if(s[e][0]=='#') { sscanf(s[e],"#%*[ \t\14]%d%*[ \t\14]\"%s\"", line,file); sscanf(s[e],"#%*[ \t\14]line%*[ \t\14]%d%*[ \t\14]\"%s\"", line,file); line--; } line+=sizeof(s[e]/"\n")-1; } return ret; } array group(array(string|Token) tokens, void|mapping groupings) { Stack.stack stack=Stack.stack(); array(Token) ret=({}); mapping actions=([]); if(!groupings) groupings=global_groupings; foreach((array)groupings,[string x, string y]) { actions[x]=1; actions[y]=2; } foreach(tokens, Token token) { switch(actions[(string)token]) { case 0: ret+=({token}); break; case 1: stack->push(ret); ret=({token}); break; case 2: if (!sizeof(ret) || !stack->ptr || (groupings[(string)ret[0]] != (string)token)) { // Mismatch werror(sprintf("**** Grouping mismatch token=%O\n" "**** tokens: ({ %{%O, %}})\n" "**** ret: ({ %{%O, %}})\n" "**** stackdepth: %d\n", token->text, tokens->text, ret->text, stack->ptr)); return ret; } ret=stack->pop()+({ ret + ({token}) }); } } return ret; } /* FIXME: * This actually strips all preprocessing tokens */ array strip_line_statements(array tokens) { array(Token) ret=({}); foreach(tokens, array|object(Token) t) { if(arrayp(t)) { ret+=({ strip_line_statements(t) }); }else{ if( ((string)t) [0] != '#') ret+=({t}); } } return ret; } array hide_whitespaces(array tokens) { array(Token) ret=({tokens[0]}); foreach(tokens[1..], array|object(Token) t) { if(arrayp(t)) { ret+=({ hide_whitespaces(t) }); }else{ switch( ((string)t) [0]) { case ' ': case '\t': case '\14': case '\n': mixed tmp=ret[-1]; while(arrayp(tmp)) tmp=tmp[-1]; tmp->trailing_whitespaces+=(string)t; break; default: ret+=({t}); } } } return ret; } array flatten(array a) { array ret=({}); foreach(a, a) ret+=arrayp(a)?flatten(a):({a}); return ret; } string simple_reconstitute(array(string|object(Token)|array) tokens) { string ret=""; foreach(flatten(tokens), mixed tok) { if(objectp(tok)) tok=tok->text + tok->trailing_whitespaces; ret+=tok; } return ret; } string reconstitute_with_line_numbers(array(string|object(Token)|array) tokens) { int line=1; string file; string ret=""; foreach(flatten(tokens), mixed tok) { if(objectp(tok)) { if((tok->line && tok->line != line) || (tok->file && tok->file != file)) { if(strlen(ret) && ret[-1]!='\n') ret+="\n"; line=tok->line; if(tok->file) file=tok->file; ret+=sprintf("#line %d %O\n",line,file); } tok=tok->text + tok->trailing_whitespaces; } ret+=tok; line+=sizeof(tok/"\n")-1; } return ret; } }(); }(); #endif