/* -*- mode: c; encoding: utf-8; -*- |
|| This file is part of Pike. For copyright information see COPYRIGHT. |
|| Pike is distributed under GPL, LGPL and MPL. See the file COPYING |
|| for more information. |
*/ |
|
#include "global.h" |
#include "interpret.h" |
#include "svalue.h" |
#include "pike_macros.h" |
#include "object.h" |
#include "program.h" |
#include "array.h" |
#include "pike_error.h" |
#include "constants.h" |
#include "mapping.h" |
#include "stralloc.h" |
#include "multiset.h" |
#include "pike_types.h" |
#include "pike_memory.h" |
#include "threads.h" |
#include "module_support.h" |
#include "cyclic.h" |
#include "bignum.h" |
#include "main.h" |
#include "operators.h" |
#include "builtin_functions.h" |
#include "fsort.h" |
#include "pike_cpulib.h" |
#include "gc.h" |
#include "block_allocator.h" |
#include "pikecode.h" |
#include "opcodes.h" |
#include "whitespace.h" |
#include "sprintf.h" |
#include "pike_search.h" |
#include "pike_compiler.h" |
|
#include <errno.h> |
#include <math.h> |
#include <fcntl.h> |
|
#ifdef HAVE_ARPA_INET_H |
#include <arpa/inet.h> |
#endif /* HAVE_ARPA_INET_H */ |
|
#define DEFAULT_CMOD_STORAGE |
|
#ifdef STRUCT_TM_HAS___TM_GMTOFF |
struct tm_extra { }; |
#define tm_zone __tm_zone |
#define tm_gmtoff __tm_gmtoff |
#define GET_GMTOFF(TM) ((TM)->tm_gmtoff) |
#define GET_ZONE(this) ((this)->t.tm_zone) |
#define SET_GMTOFF(TM, VAL) (((TM)->tm_gmtoff) = (VAL)) |
#define SET_ZONE(this, VAL) ((this)->t.tm_zone = (VAL)) |
#elif defined(STRUCT_TM_HAS_GMTOFF) |
struct tm_extra { }; |
#define GET_GMTOFF(TM) ((TM)->tm_gmtoff) |
#define GET_ZONE(this) ((this)->t.tm_zone) |
#define SET_GMTOFF(TM, VAL) (((TM)->tm_gmtoff) = (VAL)) |
#define SET_ZONE(this, VAL) ((this)->t.tm_zone = (VAL)) |
#else |
struct tm_extra { const char *tm_zone; }; |
#define GET_GMTOFF(TM) 0 |
#define GET_ZONE(this) ((this)->extra.tm_zone) |
#define SET_GMTOFF(TM, VAL) (VAL) |
#define SET_ZONE(this, VAL) ((this)->extra.tm_zone = (VAL)) |
#endif |
|
static void gc_check_frame(struct pike_frame *f) |
{ |
if(f->flags & PIKE_FRAME_MALLOCED_LOCALS) |
{ |
if(f->current_object) |
debug_gc_check (f->current_object, " as current_object in a frame"); |
if(f->current_program) |
debug_gc_check (f->current_program, " as current_program in a frame"); |
debug_gc_check_svalues (f->locals, f->num_locals, " in locals of a frame"); |
if(f->scope && !debug_gc_check (f->scope, " as scope frame of a frame")) |
gc_check_frame(f->scope); |
} |
} |
|
static void gc_recurse_frame(struct pike_frame *f) |
{ |
if(f->current_object) |
gc_recurse_object(f->current_object); |
if(f->current_program) |
gc_recurse_program(f->current_program); |
if(f->flags & PIKE_FRAME_MALLOCED_LOCALS) |
gc_recurse_svalues(f->locals,f->num_locals); |
if(f->scope) |
gc_recurse_frame(f->scope); |
} |
|
DECLARATIONS |
|
/*! @module System |
*/ |
|
/*! @class TM |
*! A wrapper for the system struct tm time keeping structure. |
*! This can be used as a (very) lightweight alternative to Calendar. |
*/ |
PIKECLASS TM |
{ |
CVAR struct tm t; |
CVAR time_t unix_time; |
CVAR int modified; |
CVAR struct tm_extra extra; |
|
#define strftime_zone strftime |
#define strptime_zone strptime |
#define localtime_zone(X,Y) localtime(X) |
#ifndef HAVE_EXTERNAL_TIMEZONE |
#undef timezone |
#endif |
|
#define MODIFY(X) do{ THIS->modified = 1;THIS->t.X; }while(0) |
#define FIX_THIS() do { \ |
if(THIS->modified) \ |
fix_tm(THIS); \ |
} while(0) |
|
static void fix_tm(struct TM_struct *this) |
{ |
const char *tm_zone = GET_ZONE(this); |
int is_utc_zone = tm_zone && !strcmp(tm_zone, "UTC"); |
if (is_utc_zone) |
this->t.tm_isdst = 0; |
this->unix_time = mktime_zone(&this->t, is_utc_zone, 0); |
this->modified = 0; |
} |
|
#ifdef HAVE_STRPTIME |
/*! @decl int(0..1) strptime( string(1..255) format, string(1..255) data ) |
*! |
*! @note |
*! The @expr{format@} and @expr{data@} are reversed. |
*! |
*! @seealso |
*! @[strptime] |
*/ |
PIKEFUN int(0..1) strptime( string(1..255) format, string(1..255) data ) |
{ |
if( format->size_shift || data->size_shift ) |
Pike_error("Only 8bit strings are supported\n"); |
THIS->modified = 1; |
if( strptime_zone( data->str, format->str, &THIS->t ) == NULL ) |
RETURN 0; |
RETURN 1; |
} |
#endif /* HAVE_STRPTIME */ |
/*! @decl string(1..255) strftime( string(1..255) format ) |
*! |
*! @seealso |
*! @[strftime], @[Gettext.setlocale] |
*/ |
PIKEFUN string(1..255) strftime(string(1..255) format) |
{ |
char buffer[8192]; |
buffer[0] = 0; |
strftime_zone(buffer, sizeof(buffer), format->str, &THIS->t); |
push_text(buffer); |
} |
|
/*! @decl int(0..60) sec; |
*! @decl int(0..59) min; |
*! @decl int(0..59) hour; |
*! @decl int(1..31) mday; |
*! @decl int(0..11) mon; |
*! @decl int year; |
*! |
*! The various fields in the structure. Note that setting these |
*! might cause other fields to be recalculated, as an example, |
*! adding 1000 to the hour field would advance the 'mday', 'mon' |
*! and possibly 'year' fields. |
*! |
*! When read the fields are always normalized. |
*! |
*! Unlike the system struct tm the 'year' field is not year-1900, |
*! instead it is the actual year. |
*/ |
PIKEFUN int(0..60) `sec() { FIX_THIS();RETURN THIS->t.tm_sec; } |
PIKEFUN int(0..59) `min() { FIX_THIS();RETURN THIS->t.tm_min; } |
PIKEFUN int(0..23) `hour() { FIX_THIS();RETURN THIS->t.tm_hour; } |
PIKEFUN int(1..31) `mday() { FIX_THIS();RETURN THIS->t.tm_mday; } |
PIKEFUN int(0..11) `mon() { FIX_THIS();RETURN THIS->t.tm_mon; } |
|
PIKEFUN int `year() { FIX_THIS();RETURN THIS->t.tm_year+1900; } |
PIKEFUN int `sec=(int a) { MODIFY(tm_sec=a); } |
PIKEFUN int `min=(int a) { MODIFY(tm_min=a); } |
PIKEFUN int `hour=(int a){ MODIFY(tm_hour=a); } |
PIKEFUN int `mday=(int a){ MODIFY(tm_mday=a); } |
PIKEFUN int `year=(int a){ MODIFY(tm_year=a-1900); } |
PIKEFUN int `mon=(int a){ MODIFY(tm_mon=a); } |
|
/*! @decl int isdst |
*! |
*! True if daylight-saving is in effect. If this field is -1 |
*! (the default) it (and the timezone info) will be updated |
*! automatically using the timezone rules. |
*/ |
PIKEFUN int(-1..1) `isdst() { |
FIX_THIS(); |
RETURN THIS->t.tm_isdst; |
} |
|
/*! @decl int wday |
*! The day of the week, sunday is 0, saturday is 6. |
*! This is calculated from the other fields and can not be changed directly. |
*/ |
PIKEFUN int(0..6) `wday() { FIX_THIS(); RETURN THIS->t.tm_wday; } |
|
/*! @decl int yday |
*! The day of the year, from 0 (the first day) to 365 |
*! This is calculated from the other fields and can not be changed directly. |
*/ |
PIKEFUN int(0..365) `yday() { FIX_THIS(); RETURN THIS->t.tm_yday; } |
|
/*! @decl int unix_time() |
*! Return the unix time corresponding to this time_t. If no time |
*! can be parsed from the structure -1 is returned. |
*/ |
PIKEFUN int unix_time() |
{ |
FIX_THIS(); |
RETURN THIS->unix_time; |
} |
|
/*! @decl string asctime() |
*! Return a string representing the time. Mostly useful for debug |
*! purposes, the exact format is very locale (see |
*! @[Gettext.setlocale]) and OS dependent. |
*/ |
PIKEFUN string asctime() |
{ |
FIX_THIS(); |
{ |
#define STRFTIME_MAXSIZE 26 |
char s[STRFTIME_MAXSIZE]; |
if( !strftime(s, STRFTIME_MAXSIZE, "%c\n", &THIS->t) ) |
push_undefined(); |
else |
push_text(s); |
} |
} |
|
PIKEFUN string _sprintf( int flag, mapping options ) |
{ |
int post_sum = 0; |
switch( flag ) |
{ |
case 'O': |
push_static_text("System.TM("); |
post_sum = 1; |
/* fallthrough */ |
case 's': |
f_TM_asctime(0); |
push_static_text("\n"); |
if( GET_ZONE(THIS) ) |
{ |
push_static_text(" "); |
push_text( GET_ZONE(THIS) ); |
f_add( 2 ); |
} |
else |
push_static_text(""); |
f_replace( 3 ); |
break; |
case 'd': |
f_TM_unix_time(0); |
break; |
default: |
Pike_error("Can not format as %c", flag ); |
} |
if( post_sum ) |
{ |
push_static_text(")"); |
f_add(3); |
} |
|
} |
|
/*! @decl int|string cast(string to) |
*! |
*! Casted to an integer @[unix_time] will be returned. |
*! |
*! Casting to a string will call @[asctime]. |
*/ |
PIKEFUN int|string cast( string to ) |
flags ID_PROTECTED; |
{ |
if( to == literal_int_string ) |
{ |
f_TM_unix_time(0); |
return; |
} |
if( to == literal_string_string ) |
{ |
f_TM_asctime(0); |
return; |
} |
pop_stack(); |
push_undefined(); |
} |
|
/*! @decl string zone |
*! |
*! The timezone of this structure |
*/ |
PIKEFUN string `zone() { |
FIX_THIS(); |
if( GET_ZONE(THIS) ) |
push_text( GET_ZONE(THIS) ); |
else |
push_undefined(); |
} |
|
/*! @decl int gmtoff |
*! The offset from GMT for the time in this tm-struct |
*/ |
PIKEFUN int `gmtoff() { |
FIX_THIS(); |
push_int( GET_GMTOFF(&(THIS->t)) ); |
} |
|
/*! @decl int(0..1) localtime( int time ) |
*! Initialize the struct tm to the local time for the specified |
*! unix time_t. |
*/ |
PIKEFUN int(0..1) localtime( int _t ) |
{ |
time_t t = _t; |
struct tm *res = localtime_zone( &t, &THIS->t ); |
if( !res ) |
RETURN 0; |
|
THIS->t = *res; |
THIS->modified = 1; |
RETURN 1; |
} |
|
|
/*! @decl int(0..1) gmtime( int time ) |
*! Initialize the struct tm to the UTC time for the specified |
*! unix time_t. |
*/ |
PIKEFUN int(0..1) gmtime( int _t ) |
{ |
time_t t = _t; |
struct tm *res = gmtime( &t ); |
|
if( !res ) |
RETURN 0; |
|
THIS->t = *res; |
SET_ZONE(THIS, "UTC"); /* Override timezone */ |
THIS->modified = 1; |
RETURN 1; |
} |
|
/*! @decl int(0..1) gmtime( Gmp.mpz time ) |
*! Initialize the struct tm to the UTC time for the specified |
*! unix time_t. |
*/ |
PIKEFUN int(0..1) gmtime( object(Gmp.mpz)|object(Gmp.bignum) _t ) |
{ |
INT64 t_int64 = 0; |
time_t t; |
struct tm *res; |
|
if (!low_int64_from_bignum(&t_int64, _t)) |
RETURN 0; |
|
t = t_int64; |
res = gmtime( &t ); |
|
if( !res ) |
RETURN 0; |
|
THIS->t = *res; |
SET_ZONE(THIS, "UTC"); /* Override timezone */ |
THIS->modified = 1; |
RETURN 1; |
} |
|
/*! @decl void create(int|Gmp.mpz t) |
*! Create a new @[TM] initialized from a unix time_t. |
*! The timezone will always be UTC when using this function. |
*/ |
PIKEFUN void create( int|object(Gmp.mpz)|object(Gmp.bignum) _t ) |
{ |
f_TM_gmtime( 1 ); |
if( Pike_sp[-1].u.integer == 0 ) |
Pike_error("time out of range\n"); |
pop_stack(); |
} |
|
/*! @decl void create() |
*! Construct a new TM, all fields will be set to 0. |
*/ |
PIKEFUN void create( ) |
{ |
memset( &THIS->t, 0, sizeof( struct tm ) ); |
THIS->t.tm_isdst = -1; |
THIS->t.tm_mday = 1; |
THIS->unix_time = 0; |
THIS->modified = 1; |
} |
|
/*! @decl void create( int year, int(0..11) mon, int(1..31) mday, @ |
*! int(0..24) hour, int(0..59) min, int(0..59) sec, @ |
*! string|void timezone ) |
*! Construct a new time using the given values. |
*! Slightly faster than setting them individually. |
*/ |
PIKEFUN void create( int year, int(0..11) mon, int(1..31) mday, |
int(0..24) hour, int(0..59) min, int(0..59) sec, |
string|void timezone ) |
{ |
struct tm *t = &THIS->t; |
int use_utc = 0; |
t->tm_isdst = -1; |
t->tm_year = year - 1900; |
t->tm_mon = mon; |
t->tm_mday = mday; |
t->tm_hour = hour; |
t->tm_min = min; |
t->tm_sec = sec; |
if (timezone) { |
if (strcmp(timezone->str, "UTC")) |
Pike_error("Timezone must either be UTC or omitted.\n"); |
use_utc = 1; |
} |
if (use_utc) |
t->tm_isdst = 0; |
THIS->unix_time = mktime_zone(&THIS->t, use_utc, 0); |
/* Setting it to other timezones than UTC is not supported (yet) */ |
if (use_utc) |
SET_ZONE(THIS, "UTC"); |
pop_n_elems(args); |
} |
|
#ifdef PIKE_NULL_IS_SPECIAL |
INIT { |
THIS->modified = 0; |
} |
#endif |
} |
/*! @endclass |
*/ |
#undef FIX_THIS |
#ifdef STRUCT_TM_HAS___TM_GMTOFF |
#undef tm_zone |
#undef tm_gmtoff |
#endif |
|
/*! @endmodule |
*/ |
|
/*! @decl array(array(int|string|type)) describe_program(program p) |
*! @belongs Debug |
*! |
*! Debug function for showing the symbol table of a program. |
*! |
*! @returns |
*! Returns an array of arrays with the following information |
*! for each symbol in @[p]: |
*! @array |
*! @elem int modifiers |
*! Bitfield with the modifiers for the symbol. |
*! @elem string symbol_name |
*! Name of the symbol. |
*! @elem type value_type |
*! Value type for the symbol. |
*! @elem int symbol_type |
*! Type of symbol. |
*! @elem int symbol_offset |
*! Offset into the code or data area for the symbol. |
*! @elem int inherit_offset |
*! Offset in the inherit table to the inherit containing |
*! the symbol. |
*! @elem int inherit_level |
*! Depth in the inherit tree for the inherit containing |
*! the symbol. |
*! @endarray |
*! |
*! @note |
*! The API for this function is not fixed, and has changed |
*! since Pike 7.6. In particular it would make sense to return |
*! an array of objects instead, and more information about the |
*! symbols might be added. |
*/ |
PMOD_EXPORT |
PIKEFUN array(array(int|string)) _describe_program(mixed x) |
efun; |
{ |
struct program *p; |
struct array *res; |
int i; |
|
if (!(p = program_from_svalue(Pike_sp - args))) |
SIMPLE_ARG_TYPE_ERROR("_describe_program", 1, "program"); |
|
for (i=0; i < (int)p->num_identifier_references;i++) { |
struct reference *ref = p->identifier_references + i; |
struct identifier *id = ID_FROM_PTR(p, ref); |
struct inherit *inh = INHERIT_FROM_PTR(p, ref); |
push_int(ref->id_flags); |
ref_push_string(id->name); |
ref_push_type_value(id->type); |
push_int(id->identifier_flags); |
if (IDENTIFIER_IS_C_FUNCTION(id->identifier_flags)) { |
push_int(-2); |
} else { |
push_int(id->func.offset); |
} |
push_int(ref->inherit_offset); |
push_int(inh->inherit_level); |
f_aggregate(7); |
} |
f_aggregate(p->num_identifier_references); |
dmalloc_touch_svalue(Pike_sp-1); |
res = Pike_sp[-1].u.array; |
Pike_sp--; |
pop_n_elems(args); |
push_array(res); |
} |
|
/*! @decl string basetype(mixed x) |
*! |
*! Same as sprintf("%t",x); |
*! |
*! @seealso |
*! @[sprintf()] |
*/ |
PMOD_EXPORT |
PIKEFUN string basetype(mixed x) |
efun; |
optflags OPT_TRY_OPTIMIZE; |
{ |
int t = TYPEOF(*x); |
struct program *p; |
if(t == T_OBJECT && (p = x->u.object->prog)) |
{ |
ptrdiff_t fun = FIND_LFUN(p->inherits[SUBTYPEOF(*x)].prog, LFUN__SPRINTF); |
if(fun != -1) |
{ |
push_int('t'); |
f_aggregate_mapping(0); |
apply_low(x->u.object, |
fun + p->inherits[SUBTYPEOF(*x)].identifier_level, 2); |
if(TYPEOF(Pike_sp[-1]) == T_STRING) |
{ |
stack_swap(); |
pop_stack(); |
return; |
} else if (UNSAFE_IS_ZERO(Pike_sp-1)) { |
pop_n_elems(2); |
ref_push_string(literal_object_string); |
return; |
} else { |
Pike_error("Non-string returned from _sprintf()\n"); |
} |
} |
} |
pop_stack(); |
switch(t) |
{ |
case T_ARRAY: ref_push_string(literal_array_string); break; |
case T_FLOAT: ref_push_string(literal_float_string); break; |
case T_FUNCTION: ref_push_string(literal_function_string); break; |
case T_INT: ref_push_string(literal_int_string); break; |
case T_MAPPING: ref_push_string(literal_mapping_string); break; |
case T_MULTISET: ref_push_string(literal_multiset_string); break; |
case T_OBJECT: ref_push_string(literal_object_string); break; |
case T_PROGRAM: ref_push_string(literal_program_string); break; |
case T_STRING: ref_push_string(literal_string_string); break; |
case T_TYPE: ref_push_string(literal_type_string); break; |
case T_ZERO: push_constant_text("zero"); break; |
case T_VOID: push_constant_text("void"); break; |
/* The following are internal and shouldn't be applicable in normal use. */ |
case T_SVALUE_PTR: push_static_text("svalue_ptr"); break; |
case T_OBJ_INDEX: push_static_text("obj_index"); break; |
case T_MAPPING_DATA: push_static_text("mapping_data"); break; |
case T_PIKE_FRAME: push_static_text("pike_frame"); break; |
case T_MULTISET_DATA: push_static_text("multiset_data"); break; |
default: push_static_text("unknown"); break; |
} |
} |
|
/*! @decl int siphash24(string data, void|int key) |
*! @appears Crypto.siphash24 |
*! |
*! Hashes a string, with an optional key, to a 64 bit integer using |
*! the siphash-2-4 algorithm. Currently the 64 bit @[key] parameter |
*! is used both for the high and low part of the 128 bit key. |
*! |
*/ |
#include "siphash24.h" |
PIKEFUN int siphash24(string s, void|int key) |
{ |
mpz_t ret; |
mpz_init(ret); |
mpz_set_ui(ret, pike_string_siphash24(s, key && key->u.integer) ); |
push_bignum(ret); |
} |
|
/*! @decl string int2char(int x) |
*! @appears String.int2char |
*! |
*! Same as sprintf("%c",x); |
*! |
*! @seealso |
*! @[sprintf()] |
*/ |
PMOD_EXPORT |
PIKEFUN string int2char(int|object x) |
efun; |
optflags OPT_TRY_OPTIMIZE; |
rawtype tFunc(tSetvar(0, tOr(tInt,tObj)), tNStr(tVar(0))); |
{ |
int c; |
struct program *p; |
if(TYPEOF(*x) == T_OBJECT && (p = x->u.object->prog)) |
{ |
ptrdiff_t fun = FIND_LFUN(p->inherits[SUBTYPEOF(*x)].prog, LFUN__SPRINTF); |
if(fun != -1) |
{ |
push_int('c'); |
f_aggregate_mapping(0); |
apply_low(x->u.object, |
fun + p->inherits[SUBTYPEOF(*x)].identifier_level, 2); |
if(TYPEOF(Pike_sp[-1]) == T_STRING) |
{ |
stack_swap(); |
pop_stack(); |
return; |
} |
Pike_error("Non-string returned from _sprintf()\n"); |
} |
} |
if(TYPEOF(*x) != T_INT) |
SIMPLE_ARG_TYPE_ERROR("int2char", 1, "int"); |
|
c=x->u.integer; |
|
if(c>=0 && c<256) |
{ |
struct pike_string *s; |
s=begin_shared_string(1); |
s->str[0]=c; |
RETURN end_shared_string(s); |
}else{ |
struct string_builder tmp; |
init_string_builder(&tmp,0); |
string_builder_putchar(&tmp, c); |
RETURN finish_string_builder(&tmp); |
} |
} |
|
/*! @decl string int2hex(int x) |
*! @appears String.int2hex |
*! |
*! Same as @expr{sprintf("%x",x);@}, i.e. returns the integer @[x] in |
*! hexadecimal base using lower cased symbols. |
*! |
*! @seealso |
*! @[sprintf()] |
*/ |
PMOD_EXPORT |
PIKEFUN string int2hex(int|object x) |
efun; |
optflags OPT_TRY_OPTIMIZE; |
{ |
INT_TYPE c; |
unsigned INT_TYPE n; |
int len; |
struct pike_string *s; |
struct program *p; |
|
if(TYPEOF(*x) == T_OBJECT && (p = x->u.object->prog)) |
{ |
ptrdiff_t fun = FIND_LFUN(p->inherits[SUBTYPEOF(*x)].prog, LFUN__SPRINTF); |
if(fun != -1) |
{ |
push_int('x'); |
f_aggregate_mapping(0); |
apply_low(x->u.object, |
fun + p->inherits[SUBTYPEOF(*x)].identifier_level, 2); |
if(TYPEOF(Pike_sp[-1]) == T_STRING) |
{ |
stack_swap(); |
pop_stack(); |
return; |
} |
Pike_error("Non-string returned from _sprintf()\n"); |
} |
} |
if(TYPEOF(*x) != T_INT) |
SIMPLE_ARG_TYPE_ERROR("int2hex", 1, "int"); |
|
c=x->u.integer; |
|
len=1; |
if(c<0) { |
len++; |
n=(-c)&((unsigned INT_TYPE)(-1)); |
}else{ |
n=c; |
} |
while(n>65535) { n>>=16; len+=4; } |
while(n>15) { n>>=4; len++; } |
|
s=begin_shared_string(len); |
if(!c) |
{ |
s->str[0]='0'; |
}else{ |
if(c<0) |
{ |
s->str[0]='-'; |
n=(-c)&((unsigned INT_TYPE)(-1)); |
}else{ |
n=c; |
} |
while(len && n) |
{ |
s->str[--len]="0123456789abcdef"[n&0xf]; |
n>>=4; |
} |
} |
RETURN end_shared_string(s); |
} |
|
|
/*! @decl string string2hex(string data, void|int(0..) flags) |
*! @appears String.string2hex |
*! |
*! Convert a string of binary data to a hexadecimal string. |
*! |
*! @param flags |
*! The binary or of the following flags: |
*! @int |
*! @value 1 |
*! Use upper case characters. |
*! |
*! @value 2 |
*! The input is in little-endian byte order. |
*! @endint |
*! |
*! @seealso |
*! @[hex2string()] |
*/ |
|
static const char hexchar[] = { |
'0','1','2','3','4','5','6','7','8','9', |
'a','b','c','d','e','f' |
}; |
|
static const char HEXCHAR[] = { |
'0','1','2','3','4','5','6','7','8','9', |
'A','B','C','D','E','F' |
}; |
|
PMOD_EXPORT |
PIKEFUN string(48..102) string2hex(string s, void|int flags) |
efun; |
optflags OPT_TRY_OPTIMIZE; |
{ |
struct pike_string *hex; |
unsigned char *p,*st = (unsigned char *)s->str; |
const char *hextab = hexchar; |
int i, l, d = 1; |
|
if (s->size_shift) |
Pike_error("Bad argument 1 to string2hex(), expected 8-bit string.\n"); |
|
if (flags && (flags->u.integer & 1)) { |
hextab = HEXCHAR; |
} |
|
hex = begin_shared_string(2 * s->len); |
p = (unsigned char *)hex->str; |
l = s->len; |
|
if (flags && (flags->u.integer & 2)) { |
d = -1; |
st += l-1; |
} |
|
for (i=0; i<l; i++) { |
*p++ = hextab[*st>>4]; |
*p++ = hextab[*st&15]; |
st += d; |
} |
|
RETURN end_shared_string(hex); |
} |
|
extern const unsigned char hexdecode[256]; |
|
/*! @decl string(8bit) hex2string(string(8bit) hex) |
*! @appears String.hex2string |
*! |
*! Convert a string of hexadecimal digits to binary |
*! data. Non-hexadecimal characters will be ignored when between |
*! tuples. Eg. "00 00" is ok, but "0 000" isn't. |
*! |
*! @seealso |
*! @[string2hex()] |
*/ |
PMOD_EXPORT |
PIKEFUN string(0..255) hex2string(string hex) |
optflags OPT_TRY_OPTIMIZE; |
{ |
struct pike_string *s; |
int tmp, i; |
unsigned char *p, *q = (unsigned char *)hex->str; |
int l = i = hex->len; |
if(hex->size_shift) Pike_error("Wide strings are not allowed.\n"); |
|
s = begin_shared_string(l>>1); |
p = (unsigned char *)s->str; |
while( l > 1 ) |
{ |
tmp = hexdecode[*q++]; |
if( tmp==16 ) { l--; i--; continue; } |
*p = tmp<<4; |
tmp = hexdecode[*q++]; |
if( tmp==16 ) Pike_error("Illegal hex format.\n"); |
*p++ |= tmp; |
l -= 2; |
} |
if( l && hexdecode[*q]!=16 ) |
Pike_error("Illegal hex format.\n"); |
|
if( hex->len>>1 == i>>1 ) |
RETURN end_shared_string(s); |
else |
RETURN end_and_resize_shared_string(s, i>>1); |
} |
|
/*! @decl array(int) range(string s) |
*! @appears String.range |
*! |
*! Returns the character range of a string in an array of two |
*! elements. The first element contains the lower bound and the |
*! second the upper. The precision is only 8 bits, so for wide |
*! strings only character blocks are known. |
*/ |
PIKEFUN array(int) string_range(string s) |
errname range; |
optflags OPT_TRY_OPTIMIZE; |
{ |
int min, max; |
check_string_range(s, 0, &min, &max); |
pop_n_elems(args); |
push_int(min); |
push_int(max); |
f_aggregate(2); |
} |
|
/*! @decl array column(array data, mixed index) |
*! |
*! Extract a column from a two-dimensional array. |
*! |
*! This function is exactly equivalent to: |
*! @code |
*! map(@[data], lambda(mixed x,mixed y) { return x[y]; }, @[index]) |
*! @endcode |
*! |
*! Except of course it is a lot shorter and faster. |
*! That is, it indices every index in the array data on the value of |
*! the argument index and returns an array with the results. |
*! |
*! @seealso |
*! @[rows()] |
*/ |
PMOD_EXPORT |
PIKEFUN array column(array data, mixed index) |
efun; |
optflags OPT_TRY_OPTIMIZE; |
{ |
RETURN array_column (data, index, 1); |
} |
|
/*! @decl multiset mkmultiset(array a) |
*! |
*! This function creates a multiset from an array. |
*! |
*! @seealso |
*! @[aggregate_multiset()] |
*! |
*/ |
PMOD_EXPORT |
PIKEFUN multiset(1) mkmultiset(array(1=mixed) a) |
efun; |
optflags OPT_TRY_OPTIMIZE|OPT_EXTERNAL_DEPEND; |
{ |
RETURN mkmultiset(a); |
} |
|
/*! @decl int trace(int level, void|string facility, void|int all_threads) |
*! |
*! This function changes the trace level for the subsystem identified |
*! by @[facility] to @[level]. If @[facility] is zero or left out, it |
*! changes the global trace level which affects all subsystems. |
*! |
*! Enabling tracing causes messages to be printed to stderr. A higher |
*! trace level includes the output from all lower levels. The lowest |
*! level is zero which disables all trace messages. |
*! |
*! See the @tt{-t@} command-line option for more information. |
*! |
*! @param level |
*! If @[facility] is specified then there is typically only one |
*! trace level for it, i.e. it's an on-or-off toggle. The global |
*! trace levels, when @[facility] isn't specified, are: |
*! |
*! @int |
*! @value 1 |
*! Trace calls to Pike functions and garbage collector runs. |
*! @value 2 |
*! Trace calls to builtin functions. |
*! @value 3 |
*! Trace every interpreted opcode. |
*! @value 4 |
*! Also trace the opcode arguments. |
*! @endint |
*! |
*! @param facility |
*! Valid facilities are: |
*! |
*! @string |
*! @value "gc" |
*! Trace the doings of the garbage collector. The setting is |
*! never thread local. @[level] has two different meanings: |
*! @dl |
*! @item 1..2 |
*! Trace the start and end of each gc run. |
*! @item 3.. |
*! Additionally show info about the collected garbage, to aid |
*! hunting down garbage problems. This currently shows gc'd |
*! trampolines. Note that the output can be very bulky and is |
*! somewhat low-level technical. Also note that pike currently |
*! has to be configured with @expr{--with-rtldebug@} to enable |
*! this. |
*! @enddl |
*! @endstring |
*! |
*! @param all_threads |
*! Trace levels are normally thread local, so changes affect only |
*! the current thread. To change the level in all threads, pass a |
*! nonzero value in this argument. |
*! |
*! @returns |
*! The old trace level in the current thread is returned. |
*/ |
PMOD_EXPORT |
PIKEFUN int trace(int level, void|string facility, void|zero|int all_threads) |
efun; |
optflags OPT_SIDE_EFFECT; |
{ |
INT32 old_level; |
if (facility) { |
struct pike_string *gc_str; |
MAKE_CONST_STRING(gc_str, "gc"); |
if (facility == gc_str) { |
old_level = gc_trace; |
gc_trace = level; |
} |
else { |
bad_arg_error("trace", args, 2, |
"trace facility identifier", Pike_sp-args+1, |
"Bad argument 2 to trace(). Unknown trace facility."); |
} |
} |
else { |
old_level = Pike_interpreter.trace_level; |
#ifdef PIKE_THREADS |
if (!all_threads) |
Pike_interpreter.trace_level = level; |
else { |
struct thread_state *s; |
FOR_EACH_THREAD(s, s->state.trace_level = level); |
} |
#else |
Pike_interpreter.trace_level = level; |
#endif |
} |
RETURN old_level; |
} |
|
/*! @decl mapping(string:float) gc_parameters (void|mapping(string:mixed) params) |
*! @belongs Pike |
*! |
*! Set and get various parameters that control the operation of the |
*! garbage collector. The passed mapping contains the parameters to |
*! set. If a parameter is missing from the mapping, the current value |
*! will be filled in instead. The same mapping is returned. Thus an |
*! empty mapping, or no argument at all, causes a mapping with all |
*! current settings to be returned. |
*! |
*! The following parameters are recognized: |
*! |
*! @mapping |
*! @member int "enabled" |
*! If this is 1 then the gc is enabled as usual. If it's 0 then all |
*! automatically scheduled gc runs are disabled and the parameters |
*! below have no effect, but explicit runs through the @[gc] |
*! function still works as usual. If it's -1 then the gc is |
*! completely disabled so that even explicit @[gc] calls won't do |
*! anything. |
*! @member float "garbage_ratio_low" |
*! As long as the gc time is less than time_ratio below, aim to run |
*! the gc approximately every time the ratio between the garbage |
*! and the total amount of allocated things is this. |
*! @member float "time_ratio" |
*! When more than this fraction of the time is spent in the gc, aim |
*! for garbage_ratio_high instead of garbage_ratio_low. |
*! @member float "garbage_ratio_high" |
*! Upper limit for the garbage ratio - run the gc as often as it |
*! takes to keep it below this. |
*! @member float "min_gc_time_ratio" |
*! This puts an upper limit on the gc interval, in addition to the |
*! factors above. It is specified as the minimum amount of time |
*! spent doing gc, as a factor of the total time. The reason for |
*! this limit is that the current amount of garbage can only be |
*! measured in a gc run, and if the gc starts to run very seldom |
*! due to very little garbage, it might get too slow to react to an |
*! increase in garbage generation. Set to 0.0 to turn this limit |
*! off. |
*! @member float "average_slowness" |
*! When predicting the next gc interval, use a decaying average |
*! with this slowness factor. It should be a value between 0.0 and |
*! 1.0 that specifies the weight to give to the old average value. |
*! The remaining weight up to 1.0 is given to the last reading. |
*! @member function(:void) "pre_cb" |
*! This function is called when the gc starts. |
*! @member function(:void) "post_cb" |
*! This function is called when the mark and sweep pass of the gc |
*! is done. |
*! @member function(object,int,int:void) "destruct_cb" |
*! This function is called once for each object that is part of |
*! a cycle just before the gc will destruct it. |
*! The arguments are: |
*! @dl |
*! @item |
*! The object to be destructed. |
*! @item |
*! The reason for it being destructed. One of: |
*! @int |
*! @value Object.DESTRUCT_CLEANUP |
*! Destructed during exit. |
*! @value Object.DESTRUCT_GC |
*! Destructed during normal implicit or explicit @[gc()]. |
*! @endint |
*! @item |
*! The number of references it had. |
*! @enddl |
*! @member function(int:void) "done_cb" |
*! This function is called when the gc is done and about to exit. |
*! The argument is the same value as will be returned by gc(). |
*! @endmapping |
*! |
*! @seealso |
*! @[gc], @[Debug.gc_status] |
*/ |
PMOD_EXPORT |
PIKEFUN mapping(string:mixed) gc_parameters (void|mapping(string:mixed) params) |
optflags OPT_SIDE_EFFECT; |
{ |
struct pike_string *str; |
struct svalue *set; |
struct svalue get; |
|
if (!params) { |
push_mapping (allocate_mapping (6)); |
params = Pike_sp[-1].u.mapping; |
} |
|
#define HANDLE_PARAM(NAME, CHECK_AND_SET, GET) do { \ |
MAKE_CONST_STRING (str, NAME); \ |
if ((set = low_mapping_string_lookup (params, str))) { \ |
CHECK_AND_SET; \ |
} \ |
else { \ |
GET; \ |
mapping_string_insert (params, str, &get); \ |
} \ |
} while (0) |
|
#define HANDLE_FLOAT_FACTOR(NAME, VAR) \ |
HANDLE_PARAM (NAME, { \ |
if (TYPEOF(*set) != T_FLOAT || \ |
set->u.float_number < 0.0 || set->u.float_number > 1.0) \ |
SIMPLE_ARG_TYPE_ERROR ("gc_parameters", 1, \ |
"float between 0.0 and 1.0 for " NAME); \ |
VAR = (double) set->u.float_number; \ |
}, { \ |
SET_SVAL(get, T_FLOAT, 0, float_number, (FLOAT_TYPE) VAR); \ |
}); |
|
HANDLE_PARAM ("enabled", { |
if (TYPEOF(*set) != T_INT || set->u.integer < -1 || set->u.integer > 1) |
SIMPLE_ARG_TYPE_ERROR ("gc_parameters", 1, |
"integer in the range -1..1 for 'enabled'"); |
if (gc_enabled != set->u.integer) { |
if (gc_enabled > 0) { |
/* Disabling automatic gc - save the old alloc_threshold and set it to |
* the maximum value to avoid getting gc_evaluator_callback added. */ |
saved_alloc_threshold = alloc_threshold; |
alloc_threshold = GC_MAX_ALLOC_THRESHOLD; |
} |
else if (set->u.integer > 0) { |
/* Enabling automatic gc - restore the old alloc_threshold. If the |
* gc interval has gotten longer than it should be then the |
* multiplier calculation in do_gc should compensate. */ |
alloc_threshold = saved_alloc_threshold; |
} |
gc_enabled = set->u.integer; |
} |
}, { |
SET_SVAL(get, T_INT, NUMBER_NUMBER, integer, gc_enabled); |
}); |
HANDLE_FLOAT_FACTOR ("garbage_ratio_low", gc_garbage_ratio_low); |
HANDLE_FLOAT_FACTOR ("time_ratio", gc_time_ratio); |
HANDLE_FLOAT_FACTOR ("garbage_ratio_high", gc_garbage_ratio_high); |
HANDLE_FLOAT_FACTOR ("min_gc_time_ratio", gc_min_time_ratio); |
HANDLE_FLOAT_FACTOR ("average_slowness", gc_average_slowness); |
|
HANDLE_PARAM("pre_cb", { |
assign_svalue(&gc_pre_cb, set); |
}, { |
assign_svalue(&get, &gc_pre_cb); |
}); |
HANDLE_PARAM("post_cb", { |
assign_svalue(&gc_post_cb, set); |
}, { |
assign_svalue(&get, &gc_post_cb); |
}); |
HANDLE_PARAM("destruct_cb", { |
assign_svalue(&gc_destruct_cb, set); |
}, { |
assign_svalue(&get, &gc_destruct_cb); |
}); |
HANDLE_PARAM("done_cb", { |
assign_svalue(&gc_done_cb, set); |
}, { |
assign_svalue(&get, &gc_done_cb); |
}); |
|
#undef HANDLE_PARAM |
#undef HANDLE_FLOAT_FACTOR |
|
REF_RETURN params; |
} |
|
/*! @decl string ctime(int timestamp) |
*! |
*! Convert the output from a previous call to @[time()] into a readable |
*! string containing the current year, month, day and time. |
*! |
*! Like @[localtime], this function might throw an error if the |
*! ctime(2) call failed on the system. It's platform dependent what |
*! time ranges that function can handle, e.g. Windows doesn't handle |
*! a negative @[timestamp]. |
*! |
*! @seealso |
*! @[strftime()], @[time()], @[localtime()], @[gmtime()], @[mktime()] |
*/ |
PMOD_EXPORT |
PIKEFUN string ctime(longest timestamp) |
efun; |
optflags OPT_TRY_OPTIMIZE; |
{ |
time_t i; |
char *s; |
|
#if SIZEOF_TIME_T < SIZEOF_INT64 |
if (timestamp > MAX_TIME_T || timestamp < MIN_TIME_T) |
SIMPLE_ARG_ERROR ("ctime", 1, "Timestamp outside valid range."); |
#endif |
|
i = (time_t) timestamp; |
s = ctime (&i); |
if (!s) Pike_error ("ctime() on this system cannot handle " |
"the timestamp %ld.\n", (long) i); |
RETURN make_shared_string(s); |
} |
|
/*! @decl mapping mkmapping(array ind, array val) |
*! |
*! Make a mapping from two arrays. |
*! |
*! Makes a mapping @[ind[x]]:@[val[x]], @tt{0 <= x < sizeof(ind)@}. |
*! |
*! @[ind] and @[val] must have the same size. |
*! |
*! This is the inverse operation of @[indices()] and @[values()]. |
*! |
*! @seealso |
*! @[indices()], @[values()] |
*/ |
PMOD_EXPORT |
PIKEFUN mapping(1:2) mkmapping(array(1=mixed) ind, array(2=mixed) val) |
efun; |
optflags OPT_TRY_OPTIMIZE|OPT_EXTERNAL_DEPEND; |
{ |
if(ind->size != val->size) |
bad_arg_error("mkmapping", args, 2, "array", Pike_sp+1-args, |
"mkmapping called on arrays of different sizes (%d != %d)\n", |
ind->size, val->size); |
|
RETURN mkmapping(ind, val); |
} |
|
/*! @decl string secure(string str) |
*! @belongs String |
*! |
*! Marks the string as secure, which will clear the memory area |
*! before freeing the string. |
*! |
*! @seealso |
*! @[Object.secure()] |
*/ |
PIKEFUN string string_secure(string str) |
optflags OPT_SIDE_EFFECT; |
rawtype tFunc(tSetvar(0, tStr), tVar(0)); |
{ |
str->flags |= STRING_CLEAR_ON_EXIT; |
REF_RETURN str; |
} |
|
/*! @decl object secure(object str) |
*! @belongs Object |
*! |
*! Marks the object as secure, which will clear the memory area |
*! before freeing the object. |
*! |
*! @seealso |
*! @[String.secure()] |
*/ |
PIKEFUN object object_secure(object obj) |
optflags OPT_SIDE_EFFECT; |
rawtype tFunc(tSetvar(0, tObj), tVar(0)); |
{ |
obj->flags |= OBJECT_CLEAR_ON_EXIT; |
REF_RETURN obj; |
} |
|
/*! @decl int count(string haystack, string needle) |
*! @belongs String |
*! |
*! Count the number of non-overlapping times the string @[needle] |
*! occurs in the string @[haystack]. The special cases for the needle |
*! @expr{""@} is that it occurs one time in the empty string, zero |
*! times in a one character string and between every character |
*! (length-1) in any other string. |
*! |
*! @seealso |
*! @[search()], @[`/()] |
*/ |
PMOD_EXPORT |
PIKEFUN int string_count(string haystack, string needle) |
errname count; |
optflags OPT_TRY_OPTIMIZE; |
{ |
ptrdiff_t c = 0; |
ptrdiff_t i, j; |
|
switch (needle->len) |
{ |
case 0: |
switch (haystack->len) |
{ |
case 0: c=1; break; /* "" appears one time in "" */ |
case 1: c=0; break; /* "" doesn't appear in "x" */ |
default: c=haystack->len-1; /* one time between each character */ |
} |
break; |
case 1: |
/* maybe optimize? */ |
/* It is already fairly optimized in pike_search_engine. */ |
default: |
for (i=0; i<haystack->len; i++) |
{ |
j=string_search(haystack,needle,i); |
if (j==-1) break; |
i=j+needle->len-1; |
c++; |
} |
break; |
} |
RETURN (INT_TYPE)c; |
} |
|
/*! @decl string trim_whites (string s) |
*! @belongs String |
*! |
*! Trim leading and trailing spaces and tabs from the string @[s]. |
*/ |
PMOD_EXPORT |
PIKEFUN string string_trim_whites (string s) |
errname trim_whites; |
optflags OPT_TRY_OPTIMIZE; |
{ |
ptrdiff_t start = 0, end = s->len; |
int chr; |
switch (s->size_shift) { |
#define DO_IT(TYPE) \ |
{ \ |
for (; start < s->len; start++) { \ |
chr = ((TYPE *) s->str)[start]; \ |
if (chr != ' ' && chr != '\t') break; \ |
} \ |
while (--end > start) { \ |
chr = ((TYPE *) s->str)[end]; \ |
if (chr != ' ' && chr != '\t') break; \ |
} \ |
} |
case 0: DO_IT (p_wchar0); break; |
case 1: DO_IT (p_wchar1); break; |
case 2: DO_IT (p_wchar2); break; |
#undef DO_IT |
} |
RETURN string_slice (s, start, end + 1 - start); |
} |
|
/*! @decl string normalize_space (string s, string|void whitespace) |
*! @belongs String |
*! |
*! @param s |
*! Is returned after white space in it has been normalised. |
*! White space is normalised by stripping leading and trailing white space |
*! and replacing sequences of white space characters with a single space. |
*! |
*! @param whitespace |
*! Defines what is considered to be white space eligible for normalisation. |
*! It has a default value that starts with @expr{" \t\r\n\v\f"@} and in |
*! addition to that contains all whitespace characters part of Unicode. |
*! The first character denotes the character for replacing whitespace |
*! sequences. |
*! |
*! @note |
*! Trailing and leading whitespace around \r and \n characters |
*! is stripped as well (only useful if they're not in the @[whitespace] set). |
*! |
*! @note |
*! This function is a lot faster with just one argument (i.e. the builtin |
*! whitespace set has an optimised code path). |
*/ |
PMOD_EXPORT |
PIKEFUN string string_normalize_space (string s, string|void whitespace) |
errname normalize_space; |
optflags OPT_TRY_OPTIMIZE; |
{ |
size_t len = s->len, wlen; |
const void *src = s->str; |
unsigned shift = s->size_shift, replspace; |
const void *ws; |
void *wstemp = 0; |
struct string_builder sb; |
unsigned foundspace = 0; |
|
wlen = replspace = 0; /* useless, but suppresses silly compiler warning */ |
|
{ |
unsigned bshift = shift, wshift; |
if(whitespace) { |
if(!(wlen = whitespace->len)) { |
REF_RETURN s; |
} else { |
ws = whitespace->str; wshift = whitespace->size_shift; |
replspace = index_shared_string(whitespace, 0); |
if(replspace > 0xffff) |
bshift = 2; |
else if(replspace > 0xff && !bshift) |
bshift = 1; |
if(wshift!=shift) { /* convert whitespace to shift of input */ |
PCHARP pcnws; |
wstemp = xalloc(wlen<<shift); |
pcnws = MKPCHARP(wstemp, shift); |
if(wshift>shift) { |
PCHARP pcows = MKPCHARP_STR(whitespace); |
size_t clen = wlen, i; |
i = wlen = 0; |
do { |
unsigned chr = INDEX_PCHARP(pcows, i++); |
if (chr<=0xff || (chr<=0xffff && shift)) /* shift is 0 or 1 */ |
SET_INDEX_PCHARP(pcnws, wlen++, chr); |
} while(--clen); |
} else |
pike_string_cpy(pcnws, whitespace); |
ws = wstemp; |
} |
} |
} else |
ws = 0; |
|
init_string_builder_alloc (&sb, len, bshift); |
if(bshift == shift) |
sb.known_shift = bshift; |
} |
|
switch (shift) { |
#define NORMALISE_TIGHT_LOOP(TYPE,CASE) \ |
{ \ |
const TYPE *start = src, *end = start+len; \ |
if (!ws) { \ |
TYPE *dst = (void*)sb.s->str; \ |
for (; start < end; start++) { \ |
switch(*start) { \ |
CASE \ |
continue; \ |
} \ |
break; \ |
} \ |
for (; start < end; start++) { \ |
if(*start<=' ' || *start>=0x85) /* optimise common case */ \ |
switch(*start) { \ |
CASE \ |
if (!foundspace) \ |
*dst++ = ' ', foundspace=1; \ |
continue; \ |
default:goto found##TYPE; \ |
} \ |
else \ |
found##TYPE: \ |
foundspace=0; \ |
*dst++ = *start; \ |
} \ |
sb.s->len = dst - (TYPE*)sb.s->str; \ |
} else { \ |
const TYPE*ps = (const TYPE*)ws+wlen; \ |
for (; start < end; start++) { \ |
size_t clen = wlen; \ |
do { \ |
if (ps[0-clen] == *start) \ |
goto lead##TYPE; \ |
} while(--clen); \ |
break; \ |
lead##TYPE:; \ |
} \ |
for (; start < end; start++) { \ |
TYPE chr = *start; \ |
size_t clen = wlen; \ |
do \ |
if (ps[0-clen] == chr) { \ |
if (!foundspace) \ |
string_builder_putchar(&sb, replspace), foundspace=1; \ |
goto skip##TYPE; \ |
} \ |
while(--clen); \ |
if (foundspace && (chr=='\n' || chr=='\r')) { \ |
sb.s->len--; string_builder_putchar(&sb, chr); \ |
foundspace=0; \ |
goto lead##TYPE; \ |
} \ |
string_builder_putchar(&sb, chr); foundspace=0; \ |
skip##TYPE:; \ |
} \ |
} \ |
} |
case 0: NORMALISE_TIGHT_LOOP (p_wchar0,SPACECASE8); break; |
case 1: NORMALISE_TIGHT_LOOP (p_wchar1,SPACECASE16); break; |
case 2: NORMALISE_TIGHT_LOOP (p_wchar2,SPACECASE16); break; |
#undef NORMALISE_TIGHT_LOOP |
} |
if (wstemp) |
free(wstemp); |
if (foundspace) |
sb.s->len--; |
RETURN finish_string_builder (&sb); |
} |
|
/*! @decl string trim (string s) |
*! @belongs String |
*! |
*! Trim leading and trailing white spaces characters (space, tab, |
*! newline, carriage return, form feed, vertical tab and all the |
*! white spaces defined in Unicode) from the string @[s]. |
*/ |
PMOD_EXPORT |
PIKEFUN string string_trim (string s) |
errname trim; |
optflags OPT_TRY_OPTIMIZE; |
{ |
ptrdiff_t start = 0, end = s->len; |
int chr; |
switch (s->size_shift) { |
|
#define DO_IT(TYPE,CASE) \ |
{ \ |
for (; start < end; start++) { \ |
chr = ((TYPE *) s->str)[start]; \ |
switch(chr) { \ |
CASE \ |
continue; \ |
} \ |
break; \ |
} \ |
while (--end > start) { \ |
chr = ((TYPE *) s->str)[end]; \ |
switch(chr) { \ |
CASE \ |
continue; \ |
} \ |
break; \ |
} \ |
} |
case 0: DO_IT (p_wchar0,SPACECASE8); break; |
case 1: DO_IT (p_wchar1,SPACECASE16); break; |
case 2: DO_IT (p_wchar2,SPACECASE16); break; |
#undef DO_IT |
} |
RETURN string_slice (s, start, end + 1 - start); |
} |
|
/*! @decl string status(int verbose) |
*! @belongs String |
*! |
*! Get string table statistics. |
*! |
*! @returns |
*! Returns a string with an ASCII table containing |
*! the current string table statistics. |
*! |
*! @note |
*! Currently returns the empty string (@expr{""@}) |
*! if @[verbose] is zero. |
*! |
*! @note |
*! The formatting and contents of the result |
*! may vary between different versions of Pike. |
*/ |
PIKEFUN string string_status(int verbose) |
errname status; |
{ |
RETURN add_string_status(verbose); |
} |
|
/*! @decl int implements(program prog, program api) |
*! @belongs Program |
*! |
*! Returns 1 if @[prog] implements @[api]. |
*/ |
PMOD_EXPORT |
PIKEFUN int program_implements(program prog, program api) |
errname implements; |
optflags OPT_TRY_OPTIMIZE; |
{ |
RETURN implements(prog, api); |
} |
|
/*! @decl int inherits(program|object child, program parent) |
*! @belongs Program |
*! |
*! Returns 1 if @[child] has inherited @[parent]. |
*/ |
PMOD_EXPORT |
PIKEFUN int program_inherits(program|object child, program parent) |
errname inherits; |
optflags OPT_TRY_OPTIMIZE; |
{ |
struct program *p = program_from_svalue(child); |
|
if (!p) |
SIMPLE_ARG_TYPE_ERROR("inherits", 1, "program|object"); |
RETURN low_get_storage(p, parent) != -1; |
} |
|
/*! @decl string defined(program p) |
*! @belongs Program |
*! |
*! Returns a string with filename and linenumber describing where |
*! the program @[p] was defined. |
*! |
*! The returned string is of the format @expr{"filename:linenumber"@}. |
*! |
*! If it cannot be determined where the program was defined, @expr{0@} |
*! (zero) will be returned. |
*/ |
PMOD_EXPORT |
PIKEFUN string program_defined(program p) |
errname defined; |
optflags OPT_TRY_OPTIMIZE; |
{ |
INT_TYPE line; |
struct pike_string *tmp = low_get_program_line(p, &line); |
|
pop_n_elems(args); |
|
if (tmp) { |
push_string(tmp); |
if(line >= 1) |
{ |
push_static_text(":"); |
push_int(line); |
f_add(3); |
} |
} |
else |
push_int(0); |
} |
|
static void f_proxy_create(INT32 args) |
{ |
struct object *o; |
struct program *p; |
struct program *expected; |
if (args != 1) { |
wrong_number_of_args_error("create", args, 1); |
} |
if (TYPEOF(Pike_sp[-1]) != PIKE_T_OBJECT) { |
SIMPLE_ARG_TYPE_ERROR("create", 1, "object"); |
} |
expected = Pike_fp->context->prog->constants[1].sval.u.program; |
o = Pike_sp[-1].u.object; |
p = o->prog; |
if (!p) { |
Pike_error("Destructed object.\n"); |
} |
p = p->inherits[SUBTYPEOF(Pike_sp[-1])].prog; |
if (p != expected) { |
int e; |
int found = 0; |
for (e = 1; e < p->num_inherits; e++) { |
if (p->inherits[e].prog == expected) { |
/* Found */ |
if ((found) && |
(p->inherits[found].inherit_level <= p->inherits[e].inherit_level)) |
continue; |
found = e; |
} |
} |
if (found) { |
SET_SVAL_SUBTYPE(Pike_sp[-1], SUBTYPEOF(Pike_sp[-1]) + found); |
} |
} |
object_low_set_index(Pike_fp->current_object, |
Pike_fp->context->identifier_level + 0, |
Pike_sp-1); |
} |
|
/** |
* Look up the reference in the wrapped object |
* corresponding to the current function. |
*/ |
static int low_proxy_ref(void) |
{ |
/* Note that we index on the identifier offset, |
* NOT the reference number, as more references |
* may get added on demand via eg local::, etc. |
*/ |
struct reference *ref = |
PTR_FROM_INT(Pike_fp->context->prog, |
Pike_fp->fun - Pike_fp->context->identifier_level); |
struct array *a = Pike_fp->context->prog->constants[0].sval.u.array; |
return ITEM(a)[ref->identifier_offset].u.integer; |
} |
|
static void f_proxy_getter(INT32 args) |
{ |
struct program *expected = |
Pike_fp->context->prog->constants[1].sval.u.program; |
int proxy_ref = low_proxy_ref(); |
/* NB: We only have a single variable i storage. */ |
struct svalue *storage = (struct svalue *)Pike_fp->current_storage; |
struct object *obj; |
struct inherit *inh; |
struct identifier *id; |
|
if (args) { |
wrong_number_of_args_error(NULL, args, 0); |
} |
|
if (TYPEOF(*storage) != PIKE_T_OBJECT) { |
Pike_error("Proxy for %O not initialized.\n", |
Pike_fp->context->prog->constants + 1); |
} |
|
obj = storage->u.object; |
if (!obj->prog) { |
Pike_error("Proxied object has been destructed.\n"); |
} |
inh = obj->prog->inherits + SUBTYPEOF(*storage); |
|
if (inh->prog == expected) { |
low_object_index_no_free(Pike_sp, obj, inh->identifier_level + proxy_ref); |
Pike_sp++; |
return; |
} |
|
/* Attempt to index the object anyway. */ |
id = ID_FROM_INT(expected, proxy_ref); |
ref_push_string(id->name); |
object_index_no_free(Pike_sp, obj, SUBTYPEOF(*storage), Pike_sp-1); |
Pike_sp++; |
} |
|
static void f_proxy_setter(INT32 args) |
{ |
struct program *expected = |
Pike_fp->context->prog->constants[1].sval.u.program; |
int proxy_ref = low_proxy_ref(); |
/* NB: We only have a single variable i storage. */ |
struct svalue *storage = (struct svalue *)Pike_fp->current_storage; |
struct object *obj; |
struct inherit *inh; |
struct identifier *id; |
|
if (args != 1) { |
wrong_number_of_args_error(NULL, args, 1); |
} |
|
if (TYPEOF(*storage) != PIKE_T_OBJECT) { |
Pike_error("Proxy for %O not initialized.\n", |
&Pike_fp->context->prog->constants[1].sval); |
} |
|
obj = storage->u.object; |
if (!obj->prog) { |
Pike_error("Proxied object has been destructed.\n"); |
} |
inh = obj->prog->inherits + SUBTYPEOF(*storage); |
|
if (inh->prog == expected) { |
object_low_set_index(obj, inh->identifier_level + proxy_ref, Pike_sp-args); |
return; |
} |
|
/* Attempt to set the index in the object anyway. */ |
id = ID_FROM_INT(expected, proxy_ref); |
ref_push_string(id->name); |
object_set_index(obj, SUBTYPEOF(*storage), Pike_sp-1, Pike_sp-(args+1)); |
} |
|
static void f_proxy_fun(INT32 args) |
{ |
struct program *expected = |
Pike_fp->context->prog->constants[1].sval.u.program; |
int proxy_ref = low_proxy_ref(); |
/* NB: We only have a single variable i storage. */ |
struct svalue *storage = (struct svalue *)Pike_fp->current_storage; |
struct object *obj; |
struct inherit *inh; |
struct identifier *id; |
|
if (TYPEOF(*storage) != PIKE_T_OBJECT) { |
Pike_error("Proxy for %O not initialized.\n", |
Pike_fp->context->prog->constants + 1); |
} |
|
obj = storage->u.object; |
if (!obj->prog) { |
Pike_error("Proxied object has been destructed.\n"); |
} |
inh = obj->prog->inherits + SUBTYPEOF(*storage); |
|
if (inh->prog == expected) { |
apply_low(obj, inh->identifier_level + proxy_ref, args); |
return; |
} |
|
/* Attempt to apply the symbol in the object anyway. */ |
/* FIXME: Doesn't care about the object subtype! */ |
id = ID_FROM_INT(expected, proxy_ref); |
apply_shared(obj, id->name, args); |
} |
|
static void f_proxy__sprintf(INT32 args) |
{ |
struct program *expected = |
Pike_fp->context->prog->constants[1].sval.u.program; |
int proxy_ref = low_proxy_ref(); |
/* NB: We only have a single variable in storage. */ |
struct svalue *storage = (struct svalue *)Pike_fp->current_storage; |
struct object *obj; |
struct inherit *inh; |
struct identifier *id; |
|
if (TYPEOF(*storage) != PIKE_T_OBJECT) { |
Pike_error("Proxy for %O not initialized.\n", |
Pike_fp->context->prog->constants + 1); |
} |
|
obj = storage->u.object; |
if (!obj->prog) { |
Pike_error("Proxied object has been destructed.\n"); |
} |
|
if (args && (TYPEOF(Pike_sp[-args]) == T_INT) && |
(Pike_sp[-args].u.integer == 'O')) { |
/* Special case for debug mode. */ |
push_constant_text("%O(%O)"); |
ref_push_program(Pike_fp->current_object->prog); |
push_svalue(storage); |
f_sprintf(3); |
return; |
} |
|
if (proxy_ref < 0) { |
push_undefined(); |
return; |
} |
|
apply_lfun(obj, LFUN__SPRINTF, args); |
} |
|
static struct mapping *proxy_lookup = NULL; |
/*! @decl program ProxyFactory(program p) |
*! |
*! Create a class that acts as a proxy for the specified program. |
*! |
*! @param p |
*! Program to generate a proxy for. |
*! |
*! The generated class will have the same symbols (public and |
*! private) with the same types as in @[p], where accesses to |
*! these will proxy the access to the same symbol in the proxied |
*! (aka wrapped) object. With the following exceptions: |
*! |
*! @dl |
*! @item @expr{protected void create(object(p) obj)@} |
*! Initialize the object to act as a proxy for @expr{obj@}. |
*! |
*! @item @expr{_sprintf(int c, mapping|void params)@} |
*! Special case for @expr{c@} == @expr{'O'@}, where |
*! it will return @expr{sprintf("%O(%O)", this_program, obj)@}, |
*! and otherwise proxy the call. |
*! |
*! @item @expr{void _destruct()@}/@expr{void destroy()@} |
*! These lfuns will not be present as the act of them |
*! being proxied would likely confuse the proxied object. |
*! @enddl |
*! |
*! @note |
*! The same proxy class will be returned if this function |
*! is called with the same program multiple times. |
*/ |
PIKEFUN program ProxyFactory(program p) |
optflags OPT_TRY_OPTIMIZE; |
rawtype tFunc(tSetvar(0, tPrg(tObj)), tVar(0)); |
{ |
struct array *a; |
int i = 0; |
struct pike_type *fun_type; |
union idptr ptr; |
int j; |
struct svalue *cached; |
|
ref_push_program(p); |
if (proxy_lookup && |
(cached = low_mapping_lookup(proxy_lookup, Pike_sp-1))) { |
push_svalue(cached); |
return; |
} |
/* NB: Keep the program on the stack, we will use it |
* later when we update the proxy_lookup cache. |
*/ |
|
enter_compiler(NULL, 0); |
start_new_program(); |
|
/* Constant #0: The mapping from local reference # to external d:o. */ |
/* NB: Over allocation. */ |
/* NB: For the lookup to work as intended all functions that use |
* this array *need* to be declared ID_LOCAL to avoid being |
* renumbered when overloaded. |
*/ |
push_int((p->num_identifier_references+1) * 3); |
push_int(-1); |
f_allocate(2); |
a = Pike_sp[-1].u.array; |
store_constant(Pike_sp-1, 0, NULL); |
|
/* Constant #1: The program that we intend to wrap. */ |
ref_push_program(p); |
store_constant(Pike_sp-1, 0, NULL); |
|
/* Reference #0: The variable that we store the proxied object in. */ |
/* NB: Use a name that will not clash. */ |
push_string(make_shared_static_string("proxied_obj\0", 12, eightbit)); |
type_stack_mark(); |
push_object_type(0, p->id); |
fun_type = pop_unfinished_type(); |
define_variable(Pike_sp[-1].u.string, fun_type, |
ID_LOCAL|ID_PROTECTED|ID_PRIVATE|ID_HIDDEN); |
free_type(fun_type); |
pop_stack(); |
|
/* Add proxy functions for the references. */ |
for (i = 0; i < p->num_identifier_references; i++) { |
struct reference *ref = PTR_FROM_INT(p, i); |
struct identifier *id; |
if (ref->id_flags & ID_HIDDEN) continue; |
id = ID_FROM_PTR(p, ref); |
if (IDENTIFIER_IS_ALIAS(id->identifier_flags)) { |
/* Handle in second pass. */ |
yyerror("Aliases not supported (yet)."); |
} else if (IDENTIFIER_IS_VARIABLE(id->identifier_flags) || |
IDENTIFIER_IS_CONSTANT(id->identifier_flags)) { |
struct pike_string *fun_name; |
|
/* Getter. */ |
type_stack_mark(); |
push_finished_type(id->type); |
push_type(T_VOID); |
push_type(T_MANY); |
fun_type = pop_unfinished_type(); |
push_static_text("`"); |
ref_push_string(id->name); |
f_add(2); |
ptr.c_fun = f_proxy_getter; |
j = define_function(Pike_sp[-1].u.string, fun_type, |
ref->id_flags | ID_LOCAL, |
IDENTIFIER_C_FUNCTION, &ptr, 0); |
SET_SVAL(ITEM(a)[PTR_FROM_INT(Pike_compiler->new_program, j)-> |
identifier_offset], T_INT, NUMBER_NUMBER, integer, i); |
free_type(fun_type); |
|
if (IDENTIFIER_IS_VARIABLE(id->identifier_flags)) { |
/* Setter. */ |
type_stack_mark(); |
push_type(T_VOID); |
push_type(T_VOID); |
push_type(T_MANY); |
push_finished_type(id->type); |
push_type(T_FUNCTION); |
fun_type = pop_unfinished_type(); |
push_static_text("="); |
f_add(2); |
ptr.c_fun = f_proxy_setter; |
j = define_function(Pike_sp[-1].u.string, fun_type, |
ref->id_flags | ID_LOCAL, |
IDENTIFIER_C_FUNCTION, &ptr, 0); |
SET_SVAL(ITEM(a)[PTR_FROM_INT(Pike_compiler->new_program, j)-> |
identifier_offset], T_INT, NUMBER_NUMBER, integer, i); |
free_type(fun_type); |
} |
|
pop_stack(); |
} else { |
/* Check if getter or setter and skip, as they are handled above. */ |
if (id->name->len && (index_shared_string(id->name, 0) == '`') && |
!is_lfun_name(id->name)) { |
/* Name starting with `, but not an lfun. |
* This is thus probably a getter or setter. |
*/ |
continue; |
} |
if ((id->name == lfun_strings[LFUN_CREATE]) || |
(id->name == lfun_strings[LFUN__DESTRUCT]) || |
(id->name == compat_lfun_destroy_string) || |
(id->name == lfun_strings[LFUN__SPRINTF])) { |
/* Skip. We add a custom create() and _sprintf() below. */ |
continue; |
} |
ptr.c_fun = f_proxy_fun; |
j = define_function(id->name, id->type, ref->id_flags, |
IDENTIFIER_C_FUNCTION | ID_LOCAL, &ptr, 0); |
SET_SVAL(ITEM(a)[PTR_FROM_INT(Pike_compiler->new_program, j)-> |
identifier_offset], T_INT, NUMBER_NUMBER, integer, i); |
} |
} |
|
/* Add a suitable _sprintf(). */ |
/* FIXME! */ |
{ |
int id_flags = ID_PROTECTED; |
i = FIND_LFUN(p, LFUN__SPRINTF); |
if (i >= 0) { |
struct reference *ref = PTR_FROM_INT(p, i); |
struct identifier *id = ID_FROM_PTR(p, ref); |
id_flags = ref->id_flags; |
fun_type = id->type; |
} else { |
type_stack_mark(); |
push_int_type(MIN_INT32, MAX_INT32); |
push_type(T_STRING); |
push_type(T_VOID); |
push_type(T_MANY); |
push_int_type(MIN_INT32, MAX_INT32); |
push_type(T_STRING); |
push_int_type(MIN_INT32, MAX_INT32); |
push_type(T_MAPPING); |
push_type(T_VOID); |
push_type(T_OR); |
push_type(T_FUNCTION); |
push_int_type(MIN_INT32, MAX_INT32); |
push_type(T_FUNCTION); |
fun_type = pop_unfinished_type(); |
simple_describe_type(fun_type); |
} |
ptr.c_fun = f_proxy__sprintf; |
j = define_function(lfun_strings[LFUN__SPRINTF], fun_type, |
id_flags | ID_LOCAL, |
IDENTIFIER_C_FUNCTION, &ptr, 0); |
SET_SVAL(ITEM(a)[PTR_FROM_INT(Pike_compiler->new_program, j)-> |
identifier_offset], T_INT, NUMBER_NUMBER, integer, i); |
if (i < 0) { |
free_type(fun_type); |
} |
} |
|
/* Add a suitable create(). */ |
type_stack_mark(); |
push_type(T_VOID); |
push_type(T_VOID); |
push_type(T_MANY); |
push_object_type(0, p->id); |
push_type(T_FUNCTION); |
fun_type = pop_unfinished_type(); |
ptr.c_fun = f_proxy_create; |
j = define_function(lfun_strings[LFUN_CREATE], fun_type, ID_PROTECTED, |
IDENTIFIER_C_FUNCTION, &ptr, 0); |
free_type(fun_type); |
|
p = end_program(); |
exit_compiler(); |
|
if (p) { |
push_program(p); |
|
if (!proxy_lookup) { |
proxy_lookup = allocate_mapping(10); |
mapping_set_flags(proxy_lookup, MAPPING_WEAK_INDICES); |
} |
|
mapping_insert(proxy_lookup, Pike_sp-2, Pike_sp-1); |
} else { |
push_undefined(); |
} |
} |
|
/*! @decl int(8..8)|int(16..16)|int(32..32) width(string s) |
*! @belongs String |
*! |
*! Returns the width of a string. |
*! |
*! @returns |
*! Three return values are currently possible: |
*! @int |
*! @value 8 |
*! The string @[s] only contains characters <= 255. |
*! @value 16 |
*! The string @[s] only contains characters <= 65535. |
*! @value 32 |
*! The string @[s] contains characters >= 65536. |
*! @endint |
*! |
*! @note |
*! It is possible that a future version of Pike may return |
*! further values. In particular the width @expr{7@} seems |
*! like it could be useful. |
*/ |
PMOD_EXPORT |
PIKEFUN int(8 .. 8)|int(16 .. 16)|int(32 .. 32) string_width(string s) |
errname width; |
optflags OPT_TRY_OPTIMIZE; |
{ |
RETURN 8 * (1 << s->size_shift); |
} |
|
/*! @decl mixed m_delete(object|mapping|multiset map, mixed index) |
*! |
*! If @[map] is an object that implements @[lfun::_m_delete()], |
*! that function will be called with @[index] as its single argument. |
*! |
*! Otherwise if @[map] is a mapping or multiset the entry with |
*! index @[index] will be removed from @[map] destructively. |
*! |
*! If the mapping or multiset does not have an entry with |
*! index @[index], nothing is done. |
*! |
*! @returns |
*! The value that was removed will be returned, |
*! and @expr{UNDEFINED@} otherwise. |
*! |
*! @note |
*! Note that @[m_delete()] changes @[map] destructively. |
*! |
*! @seealso |
*! @[mappingp()] |
*/ |
PMOD_EXPORT |
PIKEFUN mixed m_delete(object|mapping|multiset map, mixed index) |
efun; |
optflags OPT_SIDE_EFFECT; |
rawtype tOr3(tFunc(tMap(tSetvar(0,tMix),tSetvar(1,tMix)) tVar(0),tVar(1)), |
tFunc(tSet(tSetvar(0,tMix)) tVar(0),tInt01), |
tFunc(tObj tMix,tMix)); |
{ |
struct program *p; |
if( TYPEOF(*map) == T_MAPPING ) |
{ |
struct svalue s; |
map_delete_no_free(map->u.mapping, index, &s); |
pop_n_elems(args); |
*Pike_sp=s; |
Pike_sp++; |
dmalloc_touch_svalue(Pike_sp-1); |
} |
else if (TYPEOF(*map) == T_OBJECT && (p = map->u.object->prog)) |
{ |
int id = FIND_LFUN(p->inherits[SUBTYPEOF(*map)].prog, LFUN__M_DELETE); |
|
if( id == -1 ) |
SIMPLE_ARG_TYPE_ERROR("m_delete", 1, "object containing the _m_delete method"); |
|
apply_low(map->u.object, |
id + p->inherits[SUBTYPEOF(*map)].identifier_level, 1); |
stack_swap(); |
pop_stack(); |
} else if (TYPEOF(*map) == T_MULTISET) { |
struct svalue s; |
multiset_delete_2(map->u.multiset, index, &s); |
pop_n_elems(args); |
*Pike_sp = s; |
Pike_sp++; |
dmalloc_touch_svalue(Pike_sp-1); |
} else { |
SIMPLE_ARG_TYPE_ERROR("m_delete", 1, "object|mapping|multiset"); |
} |
} |
|
/*! @decl void m_clear(mapping|multiset|object map) |
*! |
*! Clear the contents of a mapping or multiset. |
*! |
*! This function clears the content of the mapping |
*! or multiset @[map] so that it becomes empty. |
*! This is an atomic operation. |
*! |
*! If @[map] is an object @[lfun::_m_clear()] will be called |
*! in it. |
*! |
*! @seealso |
*! @[m_delete()] |
*/ |
PMOD_EXPORT |
PIKEFUN void m_clear(mapping|multiset|object map) |
efun; |
optflags OPT_SIDE_EFFECT; |
{ |
struct program *p; |
if (TYPEOF(*map) == PIKE_T_MAPPING) { |
clear_mapping(map->u.mapping); |
} else if (TYPEOF(*map) == PIKE_T_MULTISET) { |
clear_multiset(map->u.multiset); |
} else if ((TYPEOF(*map) == PIKE_T_OBJECT) && (p = map->u.object->prog)) { |
int id = FIND_LFUN(p->inherits[SUBTYPEOF(*map)].prog, LFUN__M_CLEAR); |
if( id == -1 ) { |
SIMPLE_ARG_TYPE_ERROR("m_clear", 1, "object with lfun::_m_clear()"); |
} |
apply_low(map->u.object, |
id + p->inherits[SUBTYPEOF(*map)].identifier_level, 0); |
pop_stack(); |
} else { |
SIMPLE_ARG_TYPE_ERROR("m_clear", 1, "mapping|multiset"); |
} |
pop_n_elems(args); |
} |
|
/*! @decl void m_add(multiset|object l, mixed val) |
*! |
*! Add a member to a multiset. |
*! |
*! @seealso |
*! @[m_delete()] |
*/ |
PMOD_EXPORT |
PIKEFUN void m_add(multiset|object l, mixed val) |
efun; |
optflags OPT_SIDE_EFFECT; |
rawtype tFunc(tSet(tSetvar(0, tMix)) tVar(0), tVoid); |
{ |
struct program *p; |
if (TYPEOF(*l) == PIKE_T_MULTISET) { |
multiset_add(l->u.multiset, val); |
} else if ((TYPEOF(*l) == PIKE_T_OBJECT) && (p = l->u.object->prog)) { |
int id = FIND_LFUN(p->inherits[SUBTYPEOF(*l)].prog, LFUN__M_ADD); |
if( id == -1 ) { |
SIMPLE_ARG_TYPE_ERROR("m_add", 1, "object with lfun::_m_add()"); |
} |
apply_low(l->u.object, |
id + p->inherits[SUBTYPEOF(*l)].identifier_level, 1); |
} else { |
SIMPLE_ARG_TYPE_ERROR("m_add", 1, "multiset|object"); |
} |
pop_n_elems(args); |
} |
|
/*! @decl int get_weak_flag(array|mapping|multiset m) |
*! |
*! Returns the weak flag settings for @[m]. It's a combination of |
*! @[Pike.WEAK_INDICES] and @[Pike.WEAK_VALUES]. |
*/ |
PMOD_EXPORT |
PIKEFUN int get_weak_flag(array m) |
efun; |
optflags OPT_EXTERNAL_DEPEND; |
{ |
RETURN (m->flags & ARRAY_WEAK_FLAG) ? PIKE_WEAK_VALUES : 0; |
} |
|
PMOD_EXPORT |
PIKEFUN int get_weak_flag(mapping m) |
{ |
RETURN mapping_get_flags(m) & MAPPING_WEAK; |
} |
|
PMOD_EXPORT |
PIKEFUN int get_weak_flag(multiset m) |
{ |
RETURN multiset_get_flags(m) & MULTISET_WEAK; |
} |
|
/*! @decl program __empty_program(int|void line, string|void file) |
*/ |
PIKEFUN program __empty_program(int|zero|void line, string|void file) |
efun; |
optflags OPT_EXTERNAL_DEPEND; |
{ |
struct program *prog = low_allocate_program(); |
if (file) ext_store_program_line (prog, line, file); |
RETURN prog; |
} |
|
/* Cut the string at the first NUL. */ |
static struct pike_string *delambda(struct pike_string *str) |
{ |
PCHARP pcharp = MKPCHARP_STR(str); |
ptrdiff_t len = pcharp_strlen(pcharp); |
if (len == str->len) { |
/* Common case. */ |
add_ref(str); |
return str; |
} |
return make_shared_binary_pcharp(pcharp, len); |
} |
|
/*! @decl string function_name(function|program f) |
*! |
*! Return the name of the function or program @[f]. |
*! |
*! If @[f] is a global function defined in the runtime @expr{0@} |
*! (zero) will be returned. |
*! |
*! @seealso |
*! @[function_object()] |
*/ |
PMOD_EXPORT |
PIKEFUN string function_name(program|function func) |
efun; |
optflags OPT_TRY_OPTIMIZE; |
{ |
int f = -1; |
struct program *p = NULL; |
|
switch(TYPEOF(*func)) |
{ |
default: |
SIMPLE_ARG_TYPE_ERROR("function_name", 1, "function|program"); |
UNREACHABLE(return); |
|
case PIKE_T_PROGRAM: |
{ |
p = func->u.program; |
|
if(p->parent) |
{ |
int e; |
p=p->parent; |
/* search constants in parent for this |
* program... |
*/ |
|
for(e = p->num_identifier_references; e--; ) |
{ |
struct identifier *id; |
if (p->identifier_references[e].id_flags & ID_HIDDEN) |
continue; |
|
id = ID_FROM_INT(p, e); |
if (IDENTIFIER_IS_CONSTANT(id->identifier_flags) && |
(id->func.const_info.offset >= 0) && |
is_eq( & PROG_FROM_INT(p, e)->constants[id->func.const_info.offset].sval, |
func)) |
REF_RETURN id->name; |
} |
#ifdef PIKE_DEBUG |
if (d_flag>5) { |
fprintf(stderr, |
"Failed to find symbol for program %p\n" |
"Parent program info:\n", |
func->u.program); |
dump_program_tables(func->u.program->parent, 0); |
} |
#endif |
} |
break; |
} |
|
case PIKE_T_FUNCTION: |
if((f = SUBTYPEOF(*func)) == FUNCTION_BUILTIN) break; |
if(!(p = func->u.object->prog)) |
bad_arg_error("function_name", args, 1, |
"function", Pike_sp-args, |
"Destructed object.\n"); |
if(p == pike_trampoline_program) |
{ |
struct pike_trampoline *t; |
t=((struct pike_trampoline *)func->u.object->storage); |
|
if(t->frame->current_object->prog) { |
p = t->frame->current_object->prog; |
f = t->func; |
} |
} |
|
#ifdef PIKE_DEBUG |
if(f >= p->num_identifier_references) |
Pike_fatal("Function without reference.\n"); |
#endif |
RETURN delambda(ID_FROM_INT(p, f)->name); |
} |
pop_n_elems(args); |
push_int(0); |
} |
|
/*! @decl object function_object(function f) |
*! |
*! Return the object the function @[f] is in. |
*! |
*! If @[f] is a global function defined in the runtime @expr{0@} |
*! (zero) will be returned. |
*! |
*! Zero will also be returned if @[f] is a constant in the |
*! parent class. In that case @[function_program()] can be |
*! used to get the parent program. |
*! |
*! @seealso |
*! @[function_name()], @[function_program()] |
*/ |
PMOD_EXPORT |
PIKEFUN object function_object(function|program func) |
efun; |
optflags OPT_TRY_OPTIMIZE; |
type function(function:object); |
{ |
switch(TYPEOF(*func)) |
{ |
case PIKE_T_PROGRAM: |
break; |
|
case PIKE_T_FUNCTION: |
if(SUBTYPEOF(*func) == FUNCTION_BUILTIN) break; |
if(func->u.object->prog == pike_trampoline_program) |
{ |
struct object *o; |
o=((struct pike_trampoline *)func->u.object->storage)->frame->current_object; |
add_ref(o); |
pop_n_elems(args); |
push_object(o); |
return; |
} |
SET_SVAL(*func, T_OBJECT, 0, object, func->u.object); |
return; |
|
|
default: |
SIMPLE_ARG_TYPE_ERROR("function_object",1,"function"); |
} |
pop_n_elems(args); |
push_int(0); |
} |
|
/*! @decl program function_program(function|program f) |
*! |
*! Return the program the function @[f] is in. |
*! |
*! If @[f] is a global function defined in the runtime @expr{0@} |
*! (zero) will be returned. |
*! |
*! @seealso |
*! @[function_name()], @[function_object()] |
*/ |
PMOD_EXPORT |
PIKEFUN program function_program(program|function func) |
efun; |
optflags OPT_TRY_OPTIMIZE; |
{ |
switch(TYPEOF(*func)) |
{ |
case PIKE_T_PROGRAM: |
{ |
struct program *p; |
if(!(p=func->u.program->parent)) break; |
add_ref(p); |
free_program(func->u.program); |
func->u.program=p; |
return; |
} |
|
case PIKE_T_FUNCTION: |
{ |
struct program *p; |
if(SUBTYPEOF(*func) == FUNCTION_BUILTIN) |
p = func->u.efun->prog; |
else |
p = func->u.object->prog; |
if(p == pike_trampoline_program) |
{ |
p = ((struct pike_trampoline *)func->u.object->storage)-> |
frame->current_object->prog; |
} |
if (p) { |
ref_push_program(p); |
stack_pop_n_elems_keep_top(args); |
return; |
} |
} |
break; |
|
default: |
SIMPLE_ARG_TYPE_ERROR("function_program", 1, "function"); |
} |
pop_n_elems(args); |
push_int(0); |
} |
|
/*! @class RandomInterface |
*/ |
PIKECLASS RandomInterface |
{ |
CVAR UINT64 int_buffer; |
CVAR int buffer_bits; |
|
#ifdef PIKE_NULL_IS_SPECIAL |
INIT |
{ |
THIS->int_buffer = 0; |
THIS->buffer_bits = 0; |
} |
#endif |
|
/*! @decl string(8bit) random_string(int(0..)) |
*! |
*! Prototype for the randomness generating function. |
*! |
*! Override this symbol to implement a usable class. |
*/ |
PIKEFUN string(8bit) random_string(int(0..)) |
prototype; |
{} |
|
static void fill_int_buffer() |
{ |
push_int(8); |
apply_current(f_RandomInterface_random_string_fun_num, 1); |
if( TYPEOF(Pike_sp[-1]) != T_STRING || |
Pike_sp[-1].u.string->len != 8 ) |
Pike_error("Illegal return value from random_string.\n"); |
#if PIKE_BYTEORDER == 1234 |
/* Little endian. */ |
THIS->int_buffer = ((INT64 *)Pike_sp[-1].u.string->str)[0]; |
#else |
/* Big endian. */ |
{ |
int i; |
for (i = 0; i < 8; i++) { |
((p_wchar0 *)&THIS->int_buffer)[i] = STR0(Pike_sp[-1].u.string)[7-i]; |
} |
} |
#endif |
THIS->buffer_bits = 64; |
pop_stack(); |
} |
|
/* Generates a number 0<=c<limit from random bits taken from the |
int_buffer. Follows the NIST SP800-90A method for converting bit |
sequences into bound numbers, described in section B.5.1.1, and |
summarized as "throw away attempts that are too large". */ |
static INT_TYPE read_int(INT_TYPE limit) |
{ |
int bits; |
UINT64 mask; |
int i; |
|
if(limit <= 1) return 0; |
|
bits = my_log2(limit-1)+1; |
mask = (((UINT64)1)<<bits)-1; |
for(i=0; i<1000; i++) |
{ |
INT_TYPE ret; |
if(THIS->buffer_bits < bits) |
fill_int_buffer(); |
ret = THIS->int_buffer & mask; |
THIS->int_buffer >>= bits; |
THIS->buffer_bits -= bits; |
if( ret < limit ) |
return ret; |
} |
Pike_error("Failed to generate random data.\n"); |
} |
|
/*! @decl int random(int max) |
*! |
*! This function returns a random number in the range |
*! @expr{0 .. @[max]-1@}. |
*! |
*! @seealso |
*! @[Random] |
*/ |
PIKEFUN int(0..) random(int limit) |
{ |
RETURN read_int(limit); |
} |
|
/*! @decl float random(float max) |
*! |
*! This function returns a random number in the range |
*! @expr{0 .. @[max]-É›@}. |
*! |
*! @seealso |
*! @[Random] |
*/ |
PIKEFUN float random(float f) |
{ |
UINT64 value; |
if(f<=0.0) RETURN 0.0; |
if (THIS->buffer_bits < 64) |
fill_int_buffer(); |
value = THIS->int_buffer; |
THIS->buffer_bits = 0; |
|
RETURN (FLOAT_TYPE)ldexp((double)f * value, -64); |
} |
|
/*! @decl mixed random(array|multiset x) |
*! Returns a random element from @[x]. |
*! |
*! @throws |
*! Throws an exception if the array or multiset is empty. |
*/ |
|
PIKEFUN mixed random(array a) |
rawtype tFunc(tArr(tSetvar(0,tMix)),tVar(0)); |
{ |
if(!a->size) |
SIMPLE_ARG_TYPE_ERROR("random", 1, "array with elements in it"); |
push_svalue(a->item + (read_int(a->size))); |
} |
|
PIKEFUN mixed random(multiset m) |
rawtype tFunc(tSet(tSetvar(1,tMix)),tVar(1)); |
{ |
if(multiset_is_empty (m)) |
SIMPLE_ARG_TYPE_ERROR("random", 1, "multiset with elements in it"); |
push_multiset_index (m, multiset_get_nth (m, read_int(multiset_sizeof (m)))); |
sub_msnode_ref (m); |
} |
|
/*! @decl array random(mapping m) |
*! Returns a random index-value pair from the mapping. |
*! |
*! @array |
*! @elem mixed 0 |
*! The index of the mapping entry. |
*! @elem mixed 1 |
*! The value f the mapping entry. |
*! @endarray |
*! |
*! @throws |
*! Throws an exception if the mapping is empty. |
*/ |
PIKEFUN array random(mapping m) |
{ |
struct keypair *k; |
int e, count; |
|
if(!m_sizeof(m)) |
SIMPLE_ARG_TYPE_ERROR("random", 1, "mapping with elements in it"); |
|
count = read_int( m_sizeof(m) ); |
|
NEW_MAPPING_LOOP(m->data) |
{ |
if(count-- < 1) |
{ |
push_svalue(&k->ind); |
push_svalue(&k->val); |
f_aggregate(2); |
return; |
} |
} |
|
UNREACHABLE(return); |
} |
|
/*! @decl mixed random(object o) |
*! If random is called with an object, @[lfun::_random] will be |
*! called in the object. |
*! |
*! @throws |
*! Throws an exception if the object doesn't have a _random method. |
*! |
*! @seealso |
*! @[lfun::_random()] |
*/ |
PIKEFUN mixed random(object o) |
{ |
int f = low_find_lfun(o->prog, LFUN__RANDOM); |
struct object *co; |
|
if (f < 0) |
Pike_error("Calling undefined lfun::%s.\n", lfun_names[LFUN__RANDOM]); |
|
co = Pike_fp->current_object; |
ref_push_function(co, f_RandomInterface_random_string_fun_num); |
/* FIXME: precompiler doesn't generate usable fun_nums for variant |
symbols. */ |
ref_push_function(co, find_shared_string_identifier(MK_STRING("random"), |
co->prog)); |
apply_low(o, f, 2); |
} |
} |
/*! @endclass |
*/ |
|
/** |
* Generates a random string of length len, using the current |
* random_string() function, and pushes it on the stack. |
*/ |
PMOD_EXPORT void push_random_string(unsigned len) |
{ |
struct svalue *random = |
simple_mapping_string_lookup(get_builtin_constants(), "random_string"); |
if(!random || (TYPEOF(*random) != T_FUNCTION)) |
Pike_error("Unable to resolve random function.\n"); |
push_int(len); |
apply_svalue(random, 1); |
if(TYPEOF(Pike_sp[-1])!=T_STRING || Pike_sp[-1].u.string->len != (ptrdiff_t)len || |
Pike_sp[-1].u.string->size_shift != 0) |
Pike_error("Couldn't generate random string.\n"); |
} |
|
#ifdef __NT__ |
#include <wincrypt.h> |
static HCRYPTPROV crypto_handle; |
#else |
static int random_fd = -1; |
#endif |
/*! @class RandomSystem |
*/ |
PIKECLASS RandomSystem |
{ |
/*! @decl inherit RandomInterface |
*/ |
INHERIT RandomInterface; |
|
/*! @decl string(8bit) random_string(int(0..) len) |
*! |
*! Return a string of random data from the system randomness pool. |
*! |
*! On POSIX platforms this reads random data from @tt{/dev/urandom@} |
*! on other platforms it may use other methods. |
*! |
*! @throws |
*! May throw errors on unexpected state. |
*/ |
PIKEFUN string(8bit) random_string(int(0..) len) |
{ |
struct pike_string *ret; |
char *str; |
|
if( !len ) |
REF_RETURN empty_pike_string; |
if( len<0 ) |
Pike_error("Bad argument 1 to random_string(). Expected int(0..).\n"); |
|
#ifdef __NT__ |
if(!crypto_handle) |
{ |
if( !CryptAcquireContext(&crypto_handle, 0, 0, PROV_RSA_FULL, |
CRYPT_VERIFYCONTEXT|CRYPT_SILENT) ) |
Pike_error("Failed to set up Crypto Service.\n"); |
} |
|
ret = begin_shared_string(len); |
str = ret->str; |
if( !CryptGenRandom(crypto_handle, len, (BYTE*)str) ) |
{ |
do_free_unlinked_pike_string (ret); |
Pike_error("Failed to create random data.\n"); |
} |
#else /* !__NT__ */ |
while( random_fd == -1 ) |
{ |
random_fd = open("/dev/urandom", O_RDONLY); |
if( random_fd==-1 ) { |
if (errno == EINTR) continue; /* Retry */ |
Pike_error("Failed to open /dev/urandom.\n"); |
} |
} |
|
ret = begin_shared_string(len); |
str = ret->str; |
while( len ) |
{ |
int sz = read(random_fd, str, len); |
if (sz < 0) { |
if (errno == EINTR) continue; /* Retry */ |
|
free_string(ret); |
|
/* Attempt to recover on next call. */ |
close(random_fd); |
random_fd = -1; |
|
Pike_error("Failed to read %d bytes from /dev/urandom.\n", len); |
} |
str += sz; |
len -= sz; |
} |
#endif /* !__NT__ */ |
|
RETURN end_shared_string(ret); |
} |
} |
/*! @endclass |
*/ |
|
#if defined(HAVE_SETENV) && defined(HAVE_UNSETENV) |
#define USE_SETENV |
#else |
/* Used to hold refs to the strings that we feed to putenv. Indexed on |
* variable names, values are the "name=value" strings. |
* |
* This is not needed when using {,un}setenv(), since they maintain |
* their own corresponding table. */ |
static struct mapping *env_allocs = NULL; |
#endif |
|
/* Works exactly like the getenv efun defined in the master, but only |
* accesses the real environment. Everyone should use the caching |
* version in the master instead. */ |
PIKEFUN string|mapping _getenv (void|string var) |
rawtype tOr(tFunc(tStr, tString), tFunc(tVoid, tMap (tStr, tStr))); |
{ |
/* FIXME: Perhaps add the amigaos4 stuff from pike_push_env here too. */ |
|
if (var) { |
if (var->size_shift) |
SIMPLE_ARG_TYPE_ERROR ("getenv", 1, "void|string(0..255)"); |
|
if (string_has_null (var)) { |
/* Won't find a variable name like this. */ |
pop_stack(); |
push_int (0); |
} |
|
else { |
char *entry = getenv (var->str); |
pop_stack(); |
if (!entry) |
push_int (0); |
else { |
char *eq = strchr (entry, '='); |
/* There should always be a '=' in the entry, but you never know.. */ |
push_string (make_shared_string (eq ? eq + 1 : entry)); |
} |
} |
} |
|
else { |
#ifdef DECLARE_ENVIRON |
extern char **environ; |
#endif |
struct mapping *m, *new_env_allocs; |
int n; |
|
/* Iterate the environment backwards below so that earlier |
* variables will override later ones in case the same variable |
* occur multiple times (which it shouldn't). That makes the |
* result similar to what getenv(3) commonly returns (at least the |
* one in gnu libc). */ |
for (n = 0; environ[n]; n++) {} |
|
m = allocate_mapping (n); |
#ifndef USE_SETENV |
if (env_allocs) |
new_env_allocs = allocate_mapping (m_sizeof (env_allocs)); |
#endif /* !USE_SETENV */ |
|
while (--n >= 0) { |
char *entry = environ[n], *eq = strchr (entry, '='); |
if (eq) { /* gnu libc getenv ignores variables without '='. */ |
struct pike_string *var = make_shared_binary_string (entry, eq - entry); |
struct pike_string *val = make_shared_string (eq + 1); |
mapping_string_insert_string (m, var, val); |
|
#ifndef USE_SETENV |
/* Populate new_env_allocs with the env_allocs entries that |
* are still in use. */ |
if (env_allocs) { |
struct svalue *ea_val = low_mapping_string_lookup (env_allocs, var); |
if (ea_val && ea_val->u.string->str == entry) |
mapping_string_insert (new_env_allocs, var, ea_val); |
} |
#endif /* !USE_SETENV */ |
|
free_string (var); |
free_string (val); |
} |
} |
|
#ifndef USE_SETENV |
if (env_allocs) { |
free_mapping (env_allocs); |
env_allocs = new_env_allocs; |
} |
#endif /* !USE_SETENV */ |
|
push_mapping (m); |
} |
} |
|
/* Works exactly like the putenv efun defined in the master, but only |
* updates the real environment. Everyone should use the version in |
* the master instead so that the cache doesn't get stale. */ |
PIKEFUN void _putenv (string var, void|string val) |
{ |
#ifndef USE_SETENV |
struct pike_string *putenv_str, *env_alloc_var; |
#endif |
|
if (var->size_shift) |
SIMPLE_ARG_TYPE_ERROR ("putenv", 1, "string(0..255)"); |
if (string_has_null (var) || strchr (var->str, '=')) |
SIMPLE_ARG_ERROR ("putenv", 1, "Variable name cannot contain '=' or NUL."); |
|
if (val) { |
#ifndef USE_SETENV |
struct string_builder sb; |
#endif |
|
if (val->size_shift) |
SIMPLE_ARG_TYPE_ERROR ("putenv", 2, "void|string(0..255)"); |
if (string_has_null (val)) |
SIMPLE_ARG_ERROR ("putenv", 2, "Variable value cannot contain NUL."); |
|
#ifdef USE_SETENV |
if (setenv(var->str, val->str, 1)) { |
if (errno == ENOMEM) |
SIMPLE_OUT_OF_MEMORY_ERROR ("putenv", 0); |
else |
Pike_error ("Error from setenv(3): %s\n", strerror (errno)); |
} |
#else /* !USE_SETENV */ |
init_string_builder (&sb, 0); |
string_builder_shared_strcat (&sb, var); |
string_builder_putchar (&sb, '='); |
string_builder_shared_strcat (&sb, val); |
putenv_str = finish_string_builder (&sb); |
push_string (putenv_str); /* Let mega_apply pop. */ |
#endif /* USE_SETENV */ |
} |
else { |
#ifdef USE_SETENV |
/* Note: Some versions of glibc have a unsetenv(3) that returns void, |
* thus no checking of the return value here. |
*/ |
unsetenv(var->str); |
#else /* !USE_SETENV */ |
#ifdef PUTENV_ALWAYS_REQUIRES_EQUAL |
/* Windows can never get things quite right.. :P */ |
struct string_builder sb; |
init_string_builder (&sb, 0); |
string_builder_shared_strcat (&sb, var); |
string_builder_putchar (&sb, '='); |
putenv_str = finish_string_builder (&sb); |
push_string (putenv_str); /* Let mega_apply pop. */ |
#else |
putenv_str = var; |
#endif |
#endif /* USE_SETENV */ |
} |
|
#ifndef USE_SETENV |
if (putenv (putenv_str->str)) { |
if (errno == ENOMEM) |
SIMPLE_OUT_OF_MEMORY_ERROR ("putenv", 0); |
else |
Pike_error ("Error from putenv(3): %s\n", strerror (errno)); |
} |
|
#ifdef __NT__ |
ref_push_string (var); |
f_lower_case (1); |
assert (TYPEOF(Pike_sp[-1]) == T_STRING); |
env_alloc_var = Pike_sp[-1].u.string; |
/* Let mega_apply pop. */ |
#else |
env_alloc_var = var; |
#endif |
|
if (!env_allocs) env_allocs = allocate_mapping (4); |
|
if (val) |
/* Must keep the string passed to putenv allocated (and we |
* assume no other entities are naughty enough to modify it). */ |
mapping_string_insert_string (env_allocs, env_alloc_var, putenv_str); |
else { |
struct svalue key; |
SET_SVAL(key, T_STRING, 0, string, env_alloc_var); |
map_delete (env_allocs, &key); |
} |
#endif /* !USE_SETENV */ |
} |
|
/* |
* Backtrace handling. |
*/ |
|
/*! @module Pike |
*/ |
|
/*! @class InhibitDestruct |
*! |
*! This is a class that implements a way to temporarily |
*! inhibit destruction by explicit calls of @[destruct()]. |
*! |
*! This is mostly useful as a mix-in for modules |
*! implemented in C or similar. |
*! |
*! All symbols in the class are either @expr{protected@} |
*! or @expr{private@} in order to affect users minimally. |
*/ |
PIKECLASS InhibitDestruct |
{ |
CVAR ptrdiff_t lock_count; |
|
/*! @decl void inhibit_destruct() |
*! |
*! Inhibit explicit destruction of this object. |
*! |
*! @seealso |
*! @[permit_destruct()], @[_destruct()], |
*! @[destruct()], @[lfun::_destruct()] |
*/ |
PIKEFUN void inhibit_destruct() |
flags ID_PROTECTED|ID_LOCAL; |
{ |
if (UNLIKELY(THIS->lock_count < 0)) { |
THIS->lock_count--; |
} else { |
THIS->lock_count++; |
} |
} |
|
/** |
* Inhibit explicit destruction of this object. |
* |
* @param inh Inherit level for the inherit of InhibitDestruct |
* in the current program. |
*/ |
PMOD_EXPORT void inhibit_destruct(int inh) |
{ |
apply_current(Pike_fp->current_program->inherits[inh].identifier_level + |
f_InhibitDestruct_inhibit_destruct_fun_num, 0); |
pop_stack(); |
} |
|
/*! @decl void permit_destruct() |
*! |
*! Allow explicit destruction of this object again. |
*! |
*! @seealso |
*! @[inhibit_destruct()], @[_destruct()], |
*! @[destruct()], @[lfun::_destruct()] |
*/ |
PIKEFUN void permit_destruct() |
flags ID_PROTECTED|ID_LOCAL; |
{ |
if (!THIS->lock_count) { |
Pike_error("permit_destruct() without inhibit.\n"); |
} |
if (UNLIKELY(THIS->lock_count < 0)) { |
/* destruct() has been called on the object. */ |
if (UNLIKELY(!++THIS->lock_count)) { |
/* No inhibits remaining. |
* |
* Time to die. |
*/ |
destruct(Pike_fp->current_object); |
} |
} else { |
THIS->lock_count--; |
} |
} |
|
/** |
* Allow explicit destruction of this object again. |
* |
* @param inh Inherit level for the inherit of InhibitDestruct |
* in the current program. |
*/ |
PMOD_EXPORT void permit_destruct(int inh) |
{ |
apply_current(Pike_fp->current_program->inherits[inh].identifier_level + |
f_InhibitDestruct_permit_destruct_fun_num, 0); |
pop_stack(); |
} |
|
/*! @decl int(0..1) _destruct(int|void reason) |
*! |
*! Returns @expr{1@} when @[inhibit_destruct()] has been |
*! called more times than @[permit_destruct()]. |
*! |
*! @seealso |
*! @[inhibit_destruct()], @[permit_destruct()], |
*! @[destruct()], @[lfun::_destruct()] |
*/ |
PIKEFUN int(0..1) _destruct(int|void reason) |
flags ID_PROTECTED|ID_LOCAL; |
{ |
if (THIS->lock_count > 0) { |
THIS->lock_count = -THIS->lock_count; |
} |
push_int(!!THIS->lock_count); |
} |
}; |
|
/*! @endclass |
*/ |
|
/*! @class FakeObject |
*! |
*! Used as a place holder in eg backtraces for objects that |
*! are unsuitable to have references to in backtraces. |
*! |
*! Examples of such objects are instances of @[Thread.MutexKey], |
*! and @[Nettle.Cipher.State]. |
*! |
*! @seealso |
*! @[backtrace()] |
*/ |
PIKECLASS FakeObject |
program_flags PROGRAM_CONSTANT; |
{ |
PIKEVAR program prog flags ID_PRIVATE|ID_PROTECTED|ID_HIDDEN; |
|
PIKEFUN void create(program|function|void prog) |
flags ID_PROTECTED; |
{ |
struct program *p = prog ? program_from_svalue(prog):NULL; |
do_free_program(THIS->prog); |
THIS->prog = p; |
if (p) add_ref(p); |
} |
|
PIKEFUN string _sprintf(int c, mapping|void ignored) |
flags ID_PROTECTED; |
{ |
push_text("%O()"); |
if (THIS->prog) { |
ref_push_program(THIS->prog); |
} else { |
ref_push_program(Pike_fp->current_program); |
} |
f_sprintf(2); |
} |
} |
|
/*! @endclass |
*/ |
|
static struct object *clone_fake_object(struct program *p) |
{ |
if (p) ref_push_program(p); |
else push_undefined(); |
return clone_object(FakeObject_program, 1); |
} |
|
/*! @class BacktraceFrame |
*/ |
|
PIKECLASS backtrace_frame |
{ |
PIKEVAR mixed _fun flags ID_PROTECTED|ID_PRIVATE; |
#ifdef PIKE_DEBUG |
PIKEVAR program oprog flags ID_PROTECTED|ID_PRIVATE; |
#endif |
PIKEVAR array args; |
|
/* These are cleared when filename and lineno have been initialized |
* from them. */ |
PIKEVAR program prog flags ID_PROTECTED|ID_PRIVATE; |
CVAR PIKE_OPCODE_T *pc; |
|
/* These two are considered to be uninitialized from prog, pc and |
* fun as long as lineno == 0. */ |
CVAR struct pike_string *filename; |
CVAR INT_TYPE lineno; |
|
#ifdef PIKE_NULL_IS_SPECIAL |
INIT |
{ |
THIS->pc = NULL; |
THIS->lineno = 0; |
THIS->filename = NULL; |
} |
#endif |
|
EXIT |
gc_trivial; |
{ |
if (THIS->filename) { |
free_string(THIS->filename); |
THIS->filename = NULL; |
} |
THIS->pc = NULL; |
THIS->lineno = 0; |
} |
|
/* NOTE: Use old-style getter/setter syntax for compatibility with |
* old Parser.Pike.split() used by precompile.pike. |
*/ |
|
PIKEFUN mixed `->fun() |
{ |
push_svalue(&THIS->_fun); |
} |
|
PIKEFUN void `->fun=(mixed val) |
{ |
/* FIXME: Should we allow this at all? |
* Linenumber info etc won't match. |
*/ |
#ifdef PIKE_DEBUG |
if ((TYPEOF(*val) == T_FUNCTION) && (SUBTYPEOF(*val) != FUNCTION_BUILTIN)) { |
assign_short_svalue((union anything *)&THIS->oprog, |
(union anything *)&val->u.object->prog, T_PROGRAM); |
} |
#endif |
assign_svalue(&THIS->_fun, val); |
} |
|
/*! @decl int(0..1) _is_type(string t) |
*! This object claims to be an array for backward compatibility. |
*/ |
PIKEFUN int(0..1) _is_type(string t) |
{ |
RETURN (t == literal_array_string); |
} |
|
static void fill_in_file_and_line(struct backtrace_frame_struct *this, |
struct local_variable_info *vars) |
{ |
struct pike_string *file = NULL; |
assert (this->lineno == 0); |
|
if (vars) vars->num_local = 0; |
if (this->pc && this->prog) { |
file = low_get_line(this->pc, this->prog, &this->lineno, vars); |
this->pc = NULL; |
} |
else if (TYPEOF(this->_fun) == PIKE_T_FUNCTION) { |
#ifdef PIKE_DEBUG |
if (this->_fun.u.object->prog && |
this->_fun.u.object->prog != this->oprog) { |
struct identifier *id = ID_FROM_INT(this->oprog, SUBTYPEOF(this->_fun)); |
/* FIXME: Dump dmalloc info for the object? */ |
Pike_fatal("Lost track of function pointer! Function name was %s.\n", |
id->name?id->name->str:"<no name>"); |
} |
#endif |
file = low_get_function_line (this->_fun.u.object, SUBTYPEOF(this->_fun), |
&this->lineno); |
} |
else if (this->prog) { |
file = low_get_program_line (this->prog, &this->lineno); |
} |
|
if (file) { |
if (!this->filename) this->filename = file; |
else free_string (file); |
} |
|
if (this->prog) { |
free_program(this->prog); |
this->prog = NULL; |
} |
} |
|
PIKEFUN void fill_in_file_and_line() |
flags ID_PROTECTED; |
{ |
if (THIS->lineno) return; |
fill_in_file_and_line(THIS, NULL); |
} |
|
PIKEFUN string `filename() |
{ |
apply_current(f_backtrace_frame_fill_in_file_and_line_fun_num, 0); |
pop_stack(); |
if (THIS->filename) { |
ref_push_string(THIS->filename); |
return; |
} |
push_undefined(); |
} |
|
PIKEFUN string `line() |
{ |
apply_current(f_backtrace_frame_fill_in_file_and_line_fun_num, 0); |
pop_stack(); |
push_int(THIS->lineno); |
} |
|
/*! @decl string _sprintf(int c, mapping|void opts) |
*/ |
PIKEFUN string _sprintf(int c, mapping|void opts) |
flags ID_PROTECTED; |
{ |
pop_n_elems(args); |
|
if (c != 'O') { |
push_undefined (); |
return; |
} |
|
push_static_text("backtrace_frame("); |
|
apply_current(f_backtrace_frame_fill_in_file_and_line_fun_num, 0); |
pop_stack(); |
|
if (THIS->filename) { |
ref_push_string(THIS->filename); |
push_static_text(":"); |
push_int(THIS->lineno); |
push_static_text(", "); |
f_add(4); |
} else { |
push_static_text("Unknown file, "); |
} |
if (TYPEOF(THIS->_fun) == PIKE_T_FUNCTION) { |
if (THIS->_fun.u.object->prog) { |
#ifdef PIKE_DEBUG |
if (THIS->_fun.u.object->prog != THIS->oprog) { |
struct identifier *id = |
ID_FROM_INT(THIS->oprog, SUBTYPEOF(THIS->_fun)); |
/* FIXME: Dump dmalloc info for the object? */ |
Pike_fatal("Lost track of function pointer! Function name was %s.\n", |
id->name?id->name->str:"<no name>"); |
} |
#endif |
push_svalue(&THIS->_fun); |
f_function_name(1); |
push_static_text("(), "); |
f_add(2); |
} else { |
free_svalue(&THIS->_fun); |
SET_SVAL(THIS->_fun, PIKE_T_INT, NUMBER_DESTRUCTED, integer, 0); |
push_static_text("destructed_function(), "); |
} |
} else if (TYPEOF(THIS->_fun) == PIKE_T_PROGRAM) { |
/* FIXME: Use the master? */ |
push_static_text("program(), "); |
} else if (TYPEOF(THIS->_fun) == PIKE_T_STRING) { |
push_svalue(&THIS->_fun); |
push_static_text("(), "); |
f_add(2); |
} else { |
push_static_text("destructed_function(), "); |
} |
|
if (THIS->args) { |
push_static_text("Args: "); |
push_int(THIS->args->size); |
f_add(2); |
} else { |
push_static_text("No args"); |
} |
push_static_text(")"); |
f_add(5); |
} |
|
/*! @decl int(3..) _sizeof() |
*/ |
PIKEFUN int(3..) _sizeof() |
flags ID_PROTECTED; |
{ |
if (THIS->args) { |
push_int(THIS->args->size + 3); |
} else { |
push_int(3); |
} |
} |
|
/*! @decl mixed `[](int index, int|void end_or_none) |
*! The BacktraceFrame object can be indexed as an array. |
*/ |
PIKEFUN mixed `[](int index, int|void end_or_none) |
flags ID_PROTECTED; |
{ |
INT_TYPE end = index; |
INT32 numargs = 3; |
INT32 i; |
|
if (THIS->args) |
numargs += THIS->args->size; |
|
if (!end_or_none) { |
if (index < 0) |
index_error("pike_frame->`[]", args, NULL, Pike_sp-args, |
"Indexing with negative index (%"PRINTPIKEINT"d)\n", index); |
else if (index >= numargs) |
index_error("pike_frame->`[]", args, NULL, Pike_sp-args, |
"Indexing with too large index (%"PRINTPIKEINT"d)\n", index); |
} else |
end = end_or_none->u.integer; |
|
pop_n_elems(args); |
|
if (end_or_none) { |
if ((end < 0) || (end < index) || (index >= numargs)) { |
f_aggregate(0); |
return; |
} |
|
if (end >= numargs) |
end = numargs-1; |
} |
|
apply_current(f_backtrace_frame_fill_in_file_and_line_fun_num, 0); |
pop_stack(); |
|
for (i = index; i <= end; i++) { |
switch(i) { |
case 0: /* Filename */ |
if (THIS->filename) |
ref_push_string(THIS->filename); |
else |
push_int(0); |
break; |
case 1: /* Linenumber */ |
push_int(THIS->lineno); |
break; |
case 2: /* Function */ |
push_svalue(&THIS->_fun); |
break; |
default: /* Arguments */ |
{ |
if ((i > 2) && (THIS->args) && (i-3 < THIS->args->size)) { |
push_svalue(THIS->args->item + (i - 3)); |
break; |
} |
bad_arg_error("`[]", args, 1, |
"int(0..)", Pike_sp-args, |
"Bad argument 1 to backtrace_frame->`[](): " |
"Expected int(0..%d)\n", |
numargs + 2); |
} |
UNREACHABLE(break); |
} |
} |
if (end_or_none) |
f_aggregate(1 + end - index); |
} |
|
/*! @decl mixed `[]=(int index, mixed value) |
*/ |
PIKEFUN mixed `[]=(int index, mixed value) |
flags ID_PROTECTED; |
{ |
INT32 numargs = 3; |
|
if (THIS->args) |
numargs += THIS->args->size; |
|
if ((index < -numargs) || (index >= numargs)) |
index_error("pike_frame->`[]=", args, NULL, Pike_sp-args, |
"Index %"PRINTPIKEINT"d is out of array range 0..%d,\n", |
index, numargs-1); |
else if (index < 0) |
index += numargs; |
|
if (args > 2) { |
pop_n_elems(args - 2); |
args = 2; |
} |
|
apply_current(f_backtrace_frame_fill_in_file_and_line_fun_num, 0); |
pop_stack(); |
|
switch(index) { |
case 0: /* Filename */ |
if (TYPEOF(*value) != PIKE_T_STRING) { |
if ((TYPEOF(*value) != PIKE_T_INT) || |
(value->u.integer)) { |
SIMPLE_ARG_TYPE_ERROR("`[]=", 2, "string|int(0..0)"); |
} |
if (THIS->filename) { |
free_string(THIS->filename); |
THIS->filename = NULL; |
} |
} else { |
if (THIS->filename) { |
free_string(THIS->filename); |
THIS->filename = NULL; |
} |
copy_shared_string(THIS->filename, value->u.string); |
} |
break; |
|
case 1: /* Linenumber */ |
if (TYPEOF(*value) != PIKE_T_INT) |
SIMPLE_ARG_TYPE_ERROR("`[]=", 2, "int(1..)"); |
THIS->lineno = value->u.integer; |
break; |
|
case 2: /* Function */ |
assign_svalue(&THIS->_fun, value); |
break; |
default: /* Arguments */ |
assign_svalue(THIS->args->item + index - 3, value); |
break; |
} |
stack_swap(); |
pop_stack(); |
} |
|
}; |
|
/*! @endclass |
*/ |
|
/*! @class LiveBacktraceFrame |
*! |
*! A @[BacktraceFrame] which retains access to the running code. |
*! |
*! This means that unless the corresponding thread running the code |
*! is halted (or similar), the values in the fields contained in |
*! this object may change at any time. |
*! |
*! On the other hand it allows for inspection and altering of |
*! the local variables (if any) belonging to the frame. Typical |
*! use of this would be for debugging. |
*! |
*! @seealso |
*! @[BacktraceFrame] |
*/ |
PIKECLASS LiveBacktraceFrame |
{ |
/*! @decl @Annotations.Implements(BacktraceFrame) |
*/ |
IMPLEMENTS backtrace_frame; |
|
/* The live frame to inspect. */ |
CVAR struct pike_frame *fp; |
|
/* FIXME: Keep track of the corresponding thread? */ |
|
/* fp->pc when low_get_line() was last called. */ |
CVAR PIKE_OPCODE_T *pc; |
|
/* Cached result of low_get_line(). lineno == 0 indicates |
* that the values are stale. |
*/ |
CVAR struct pike_string *filename; |
CVAR INT_TYPE lineno; |
CVAR struct local_variable_info locals; |
|
EXIT |
gc_trivial; |
{ |
if (THIS->filename) { |
free_string(THIS->filename); |
THIS->filename = NULL; |
} |
if (THIS->fp) { |
free_pike_frame(THIS->fp); |
THIS->fp = NULL; |
} |
} |
|
GC_CHECK |
{ |
if (THIS->fp && !debug_gc_check(THIS->fp, " as live backtrace frame")) |
gc_check_frame(THIS->fp); |
} |
|
GC_RECURSE |
{ |
if (THIS->fp) gc_recurse_frame(THIS->fp); |
} |
|
PIKEFUN void fill_in_file_and_line() |
flags ID_PROTECTED; |
{ |
if (THIS->lineno) { |
if (THIS->fp && (THIS->pc == THIS->fp->pc)) return; |
THIS->lineno = 0; |
} |
if (THIS->filename) { |
free_string(THIS->filename); |
THIS->filename = NULL; |
} |
THIS->locals.num_local = 0; |
if (!THIS->fp) return; |
|
THIS->filename = |
low_get_line((THIS->pc = THIS->fp->pc), THIS->fp->current_program, |
&THIS->lineno, &THIS->locals); |
} |
|
static void f_LiveBacktraceFrame_get_fun(struct pike_frame *fp) |
{ |
if (!fp) { |
push_undefined(); |
return; |
} |
|
if (!fp->context) { |
if (fp->pc == (void *)do_gc) { |
push_text("gc"); |
} else { |
push_undefined(); |
} |
return; |
} |
|
if (fp->current_object && fp->current_object->prog) { |
if (fp->fun == FUNCTION_BUILTIN) { |
/* Unusual case. The frame is from call_c_initializers(), gc() |
* or similar. cf [bug 6156]. /grubba |
* |
* Masquerade as the program. |
* |
* FIXME: Ought to keep parent-pointers. |
*/ |
ref_push_program(fp->current_object->prog); |
} else { |
ref_push_function(fp->current_object, fp->fun); |
} |
return; |
} |
|
push_undefined(); |
} |
|
PIKEFUN mixed `fun() |
{ |
f_LiveBacktraceFrame_get_fun(THIS->fp); |
} |
|
/*! @decl int(0..1) _is_type(string t) |
*! This object claims to be an array for backward compatibility. |
*/ |
PIKEFUN int(0..1) _is_type(string t) |
{ |
RETURN (t == literal_array_string); |
} |
|
PIKEFUN array `args() |
{ |
struct pike_frame *fp = THIS->fp; |
struct array *res; |
|
if (!fp) { |
push_undefined(); |
return; |
} |
|
push_array(res = allocate_array_no_init(fp->num_args, 0)); |
res->type_field = |
assign_svalues_no_free(res->item, fp->locals, fp->num_args, BIT_MIXED); |
|
if (fp->context && fp->current_object && fp->current_object->prog && |
(fp->fun != FUNCTION_BUILTIN)) { |
struct identifier *func = |
ID_FROM_INT(fp->current_object->prog, fp->fun); |
if ((func->identifier_flags & IDENTIFIER_VARARGS) && |
(fp->num_args < fp->num_locals) && |
TYPEOF(fp->locals[fp->num_args]) == T_ARRAY) { |
ref_push_array(fp->locals[fp->num_args].u.array); |
f_add(2); |
res = Pike_sp[-1].u.array; |
} |
} |
|
if (res->type_field & BIT_OBJECT) { |
ptrdiff_t i; |
for (i = 0; i < res->size; i++) { |
struct svalue *s = ITEM(res) + i; |
if ((TYPEOF(*s) == T_OBJECT) && |
s->u.object->prog && |
(s->u.object->prog->flags & |
(PROGRAM_DESTRUCT_IMMEDIATE|PROGRAM_CLEAR_STORAGE))) { |
/* It is typically a bad idea to have extra references |
* to objects with these flags. The flags are usually |
* used by stuff like mutex keys and encryption keys |
* respectively. |
*/ |
struct object *o = clone_fake_object(s->u.object->prog); |
free_object(s->u.object); |
SET_SVAL(*s, T_OBJECT, 0, object, o); |
} |
} |
} |
} |
|
PIKEFUN string `filename() |
{ |
apply_current(f_LiveBacktraceFrame_fill_in_file_and_line_fun_num, 0); |
pop_stack(); |
if (THIS->filename) { |
ref_push_string(THIS->filename); |
return; |
} |
push_undefined(); |
} |
|
PIKEFUN string `line() |
{ |
apply_current(f_LiveBacktraceFrame_fill_in_file_and_line_fun_num, 0); |
pop_stack(); |
push_int(THIS->lineno); |
} |
|
/*! @decl int(3..) _sizeof() |
*/ |
PIKEFUN int(3..) _sizeof() |
flags ID_PROTECTED; |
{ |
struct pike_frame *fp = THIS->fp; |
int sz = 3; |
|
if (fp) { |
sz += fp->num_args; |
|
if (fp->context && fp->current_object && fp->current_object->prog && |
(fp->fun != FUNCTION_BUILTIN)) { |
struct identifier *func = |
ID_FROM_INT(fp->current_object->prog, fp->fun); |
if ((func->identifier_flags & IDENTIFIER_VARARGS) && |
(fp->num_args < fp->num_locals) && |
TYPEOF(fp->locals[fp->num_args]) == T_ARRAY) { |
sz += fp->locals[fp->num_args].u.array->size; |
} |
} |
} |
|
push_int(sz); |
} |
|
/*! @decl mixed `[](int index, int|void end_or_none) |
*! The BacktraceFrame object can be indexed as an array. |
*/ |
PIKEFUN mixed `[](int index, int|void end_or_none) |
flags ID_PROTECTED; |
{ |
INT_TYPE end = index; |
INT32 numargs = 3; |
INT32 i; |
struct pike_frame *fp = THIS->fp; |
struct array *extra = NULL; |
|
if (THIS->fp) { |
numargs += THIS->fp->num_args; |
|
if (fp->context && fp->current_object && fp->current_object->prog && |
(fp->fun != FUNCTION_BUILTIN)) { |
struct identifier *func = |
ID_FROM_INT(fp->current_object->prog, fp->fun); |
if ((func->identifier_flags & IDENTIFIER_VARARGS) && |
(fp->num_args < fp->num_locals) && |
TYPEOF(fp->locals[fp->num_args]) == T_ARRAY) { |
extra = fp->locals[fp->num_args].u.array; |
numargs += extra->size; |
} |
} |
} |
|
if (!end_or_none) { |
if (index < 0) |
index_error("pike_frame->`[]", args, NULL, Pike_sp-args, |
"Indexing with negative index (%"PRINTPIKEINT"d)\n", index); |
else if (index >= numargs) |
index_error("pike_frame->`[]", args, NULL, Pike_sp-args, |
"Indexing with too large index (%"PRINTPIKEINT"d)\n", index); |
} else |
end = end_or_none->u.integer; |
|
pop_n_elems(args); |
|
if (end_or_none) { |
if ((end < 0) || (end < index) || (index >= numargs)) { |
f_aggregate(0); |
return; |
} |
|
if (end >= numargs) |
end = numargs-1; |
} |
|
apply_current(f_LiveBacktraceFrame_fill_in_file_and_line_fun_num, 0); |
pop_stack(); |
|
for (i = index; i <= end; i++) { |
switch(i) { |
case 0: /* Filename */ |
if (THIS->filename) |
ref_push_string(THIS->filename); |
else |
push_int(0); |
break; |
case 1: /* Linenumber */ |
push_int(THIS->lineno); |
break; |
case 2: /* Function */ |
f_LiveBacktraceFrame_get_fun(THIS->fp); |
break; |
default: /* Arguments */ |
{ |
if (i > 2) { |
int j = i - 3; |
if (j < fp->num_args) { |
push_svalue(fp->locals + j); |
break; |
} |
j -= fp->num_args; |
if (extra && (j < extra->size)) { |
push_svalue(ITEM(extra) + j); |
break; |
} |
} |
bad_arg_error("`[]", args, 1, |
"int(0..)", Pike_sp-args, |
"Bad argument 1 to backtrace_frame->`[](): " |
"Expected int(0..%d)\n", |
numargs + 2); |
} |
UNREACHABLE(break); |
} |
} |
if (end_or_none) |
f_aggregate(1 + end - index); |
} |
|
PIKEFUN int(0..) `num_locals() |
{ |
struct pike_frame *fp = THIS->fp; |
if (!fp) { |
push_undefined(); |
} else { |
push_int(fp->num_locals); |
} |
} |
|
PIKEFUN mixed get_local(int(0..) l) |
{ |
struct pike_frame *fp = THIS->fp; |
if (!fp || (l >= fp->num_locals)) { |
Pike_error("No such local: #%d\n", l); |
} |
push_svalue(fp->locals + l); |
} |
|
PIKEFUN mixed set_local(int(0..) l, mixed val) |
{ |
struct pike_frame *fp = THIS->fp; |
if (!fp || (l >= fp->num_locals)) { |
Pike_error("No such local: #%d\n", l); |
} |
assign_svalue(fp->locals + l, val); |
} |
|
PIKEFUN string get_local_name(int(0..) l) |
{ |
struct pike_frame *fp = THIS->fp; |
if (!fp || (l >= fp->num_locals)) { |
Pike_error("No such local: #%d\n", l); |
} |
apply_current(f_LiveBacktraceFrame_fill_in_file_and_line_fun_num, 0); |
pop_stack(); |
if ((l < THIS->locals.num_local) && fp->current_object && |
fp->current_object->prog) { |
int strno = THIS->locals.names[l]; |
if (strno < THIS->fp->current_object->prog->num_strings) { |
ref_push_string(THIS->fp->current_object->prog->strings[strno]); |
return; |
} |
} |
push_undefined(); |
} |
|
PIKEFUN type get_local_type(int(0..) l) |
{ |
struct pike_frame *fp = THIS->fp; |
if (!fp || (l >= fp->num_locals)) { |
Pike_error("No such local: #%d\n", l); |
} |
apply_current(f_LiveBacktraceFrame_fill_in_file_and_line_fun_num, 0); |
pop_stack(); |
if ((l < THIS->locals.num_local) && fp->current_object && |
fp->current_object->prog) { |
int constno = THIS->locals.types[l]; |
if (constno < THIS->fp->current_object->prog->num_constants) { |
push_svalue(&THIS->fp->current_object->prog->constants[constno].sval); |
return; |
} |
} |
push_undefined(); |
} |
}; |
|
/*! @endclass |
*/ |
|
/*! @decl mapping(string:int|string) get_runtime_info() |
*! |
*! Get information about the Pike runtime. |
*! |
*! @returns |
*! Returns a mapping with the following content: |
*! @mapping |
*! @member string "bytecode_method" |
*! A string describing the bytecode method used by |
*! the Pike interpreter. |
*! @member int "abi" |
*! The number of bits in the ABI. Usually @expr{32@} or @expr{64@}. |
*! @member int "native_byteorder" |
*! The byte order used by the native cpu. |
*! Usually @expr{1234@} (aka little endian) or |
*! @expr{4321@} (aka bigendian). |
*! @member int "int_size" |
*! The number of bits in the native integer type. |
*! Usually @expr{32@} or @expr{64@}. |
*! @member int "time_size" |
*! The number of bits in the native @tt{time_t@} type. |
*! This is typically the same value as @expr{"int_size"@}. |
*! @member int "float_size" |
*! The number of bits in the native floating point type. |
*! Usually @expr{32@} or @expr{64@}. |
*! @member int(1..1) "auto_bignum" |
*! Integers larger than the native size are now always |
*! automatically converted into bignums. |
*! @endmapping |
*/ |
PIKEFUN mapping(string:int|string) get_runtime_info() |
optflags OPT_TRY_OPTIMIZE; |
{ |
pop_n_elems(args); |
push_static_text("bytecode_method"); |
push_text(PIKE_BYTECODE_METHOD_NAME); |
push_static_text("abi"); |
push_int(sizeof(void *) * 8); |
push_static_text("native_byteorder"); |
push_int(PIKE_BYTEORDER); |
push_static_text("int_size"); |
push_int(sizeof(INT_TYPE) * 8); |
push_static_text("time_size"); |
push_int(sizeof(time_t) * 8); |
push_static_text("float_size"); |
push_int(sizeof(FLOAT_TYPE) * 8); |
push_static_text("auto_bignum"); |
push_int(1); |
f_aggregate_mapping(7*2); |
} |
|
/*! @endmodule |
*/ |
|
void low_backtrace(struct Pike_interpreter_struct *i, int flags) |
{ |
struct svalue *stack_top = i->stack_pointer; |
struct pike_frame *f, *of = 0; |
int size = 0; |
struct array *res = NULL; |
|
for (f = i->frame_pointer; f; f = f->next) { |
size++; |
} |
|
res = allocate_array_no_init(size, 0); |
push_array(res); |
|
for (f = i->frame_pointer; f && size; f = (of = f)->next) { |
struct object *o; |
struct backtrace_frame_struct *bf; |
struct identifier *function = NULL; |
|
size--; |
|
if (flags & 1) { |
struct LiveBacktraceFrame_struct *bf; |
|
o = fast_clone_object(LiveBacktraceFrame_program); |
|
bf = OBJ2_LIVEBACKTRACEFRAME(o); |
|
SET_SVAL(res->item[size], PIKE_T_OBJECT, 0, object, o); |
|
add_ref(bf->fp = f); |
|
/* FIXME: Keep track of the corresponding thread? */ |
|
continue; |
} |
|
o = fast_clone_object(backtrace_frame_program); |
|
SET_SVAL(res->item[size], PIKE_T_OBJECT, 0, object, o); |
|
bf = OBJ2_BACKTRACE_FRAME(o); |
|
SET_SVAL(bf->_fun, PIKE_T_INT, NUMBER_DESTRUCTED, integer, 0); |
|
if (!f->context) { |
if (f->pc == (void *)do_gc) { |
SET_SVAL(bf->_fun, PIKE_T_STRING, 0, string, make_shared_string("gc")); |
} else if (f->pc == (void *)pike_do_exit) { |
SET_SVAL(bf->_fun, PIKE_T_STRING, 0, string, |
make_shared_string("exit")); |
} |
continue; |
} |
|
if ((bf->prog = f->context->prog)) { |
add_ref(bf->prog); |
bf->pc = f->pc; |
} |
|
if (f->current_object && f->current_object->prog) { |
if (f->fun == FUNCTION_BUILTIN) { |
/* Unusual case. The frame is from call_c_initializers(), gc() |
* or similar. cf [bug 6156]. /grubba |
* |
* Masquerade as the program. |
* |
* FIXME: Ought to keep parent-pointers. |
*/ |
SET_SVAL(bf->_fun, PIKE_T_PROGRAM, 0, |
program, f->current_object->prog); |
add_ref(f->current_object->prog); |
} else { |
SET_SVAL(bf->_fun, PIKE_T_FUNCTION, |
CHECK_IDREF_RANGE(f->fun, f->current_object->prog), |
object, f->current_object); |
add_ref(f->current_object); |
function = ID_FROM_INT(f->current_object->prog, f->fun); |
#ifdef PIKE_DEBUG |
add_ref(bf->oprog = bf->_fun.u.object->prog); |
#endif |
} |
} |
|
if (f->locals) { |
INT32 numargs = (INT32) MINIMUM(f->num_args, |
stack_top - f->locals); |
INT32 varargs = 0; |
|
if(of && of->locals) { |
/* f->num_args can be too large, so this is necessary for some |
* reason. I don't know why. /mast |
* |
* possibly because f->num_args was uninitialized for c_initializers |
* /arne |
* */ |
|
numargs = (INT32)MINIMUM(f->num_args,of->locals - f->locals); |
} |
|
numargs = MAXIMUM(numargs, 0); |
|
/* Handle varargs... */ |
if (function && (function->identifier_flags & IDENTIFIER_VARARGS) && |
(f->locals + numargs < stack_top) && |
(TYPEOF(f->locals[numargs]) == T_ARRAY)) { |
varargs = f->locals[numargs].u.array->size; |
} |
|
if (numargs + varargs) { |
bf->args = allocate_array_no_init(numargs + varargs, 0); |
bf->args->type_field = |
assign_svalues_no_free(bf->args->item, f->locals, numargs, BIT_MIXED); |
if (varargs) { |
bf->args->type_field |= |
assign_svalues_no_free(bf->args->item + numargs, |
f->locals[numargs].u.array->item, |
varargs, BIT_MIXED); |
} |
if (bf->args->type_field & BIT_OBJECT) { |
ptrdiff_t i; |
for (i = 0; i < bf->args->size; i++) { |
struct svalue *s = ITEM(bf->args) + i; |
if ((TYPEOF(*s) == T_OBJECT) && |
s->u.object->prog && |
(s->u.object->prog->flags & |
(PROGRAM_DESTRUCT_IMMEDIATE|PROGRAM_CLEAR_STORAGE))) { |
/* It is typically a bad idea to have extra references |
* to objects with these flags. The flags are usually |
* used by stuff like mutex keys and encryption keys |
* respectively. |
*/ |
struct object *o = clone_fake_object(s->u.object->prog); |
free_object(s->u.object); |
SET_SVAL(*s, T_OBJECT, 0, object, o); |
} |
} |
} |
} |
} |
} |
res->type_field = BIT_OBJECT; |
/* NOTE: res has already been pushed on the stack. */ |
} |
|
/*! @decl array(Pike.BacktraceFrame) backtrace(int|void flags) |
*! |
*! Get a description of the current call stack. |
*! |
*! @param flags |
*! |
*! A bit mask of flags affecting generation of the backtrace. |
*! |
*! Currently a single flag is defined: |
*! @int |
*! @value 1 |
*! Return @[LiveBacktraceFrame]s. This flag causes the frame |
*! objects to track changes (as long as they are in use), and |
*! makes eg local variables for functions available for |
*! inspection or change. |
*! |
*! Note that since these values are "live", they may change or |
*! dissapear at any time unless the corresponding thread has |
*! been halted or similar. |
*! @endint |
*! |
*! @returns |
*! The description is returned as an array with one entry for each call |
*! frame on the stack. |
*! |
*! The entries are represented by @[Pike.BacktraceFrame] objects. |
*! |
*! The current call frame will be last in the array. |
*! |
*! @note |
*! Please note that the frame order may be reversed in a later version |
*! of Pike to accommodate for deferred backtraces. |
*! |
*! @note |
*! Note that the arguments reported in the backtrace are the current |
*! values of the variables, and not the ones that were at call-time. |
*! This can be used to hide sensitive information from backtraces |
*! (eg passwords). |
*! |
*! @note |
*! In old versions of Pike the entries used to be represented |
*! by arrays of the following format: |
*! @array |
*! @elem string file |
*! A string with the filename if known, else zero. |
*! @elem int line |
*! An integer containing the linenumber if known, else zero. |
*! @elem function fun |
*! The function that was called at this level. |
*! @elem mixed|void ... args |
*! The arguments that the function was called with. |
*! @endarray |
*! The above format is still supported by eg @[describe_backtrace()]. |
*! |
*! @seealso |
*! @[catch()], @[throw()] |
*/ |
PMOD_EXPORT |
PIKEFUN array(mixed) backtrace(int|void local_vars) |
efun; |
optflags OPT_EXTERNAL_DEPEND; |
{ |
low_backtrace(& Pike_interpreter, local_vars?local_vars->u.integer:0); |
stack_pop_n_elems_keep_top(args); |
} |
|
/*! @class Replace |
*! |
*! This is a "compiled" version of the @[replace] function applied on |
*! a string, with more than one replace string. The replace strings |
*! are given to the create method as a @i{from@} and @i{to@} array |
*! and are then analyzed. The @expr{`()@} is then called with a |
*! string and the replace rules in the Replace object will be |
*! applied. The Replace object is used internally by the Pike |
*! optimizer and need not be used manually. |
*/ |
PIKECLASS multi_string_replace |
{ |
CVAR struct replace_many_context ctx; |
/* NOTE: from and to are only kept for _encode()'s use. */ |
PIKEVAR array from flags ID_PROTECTED; |
PIKEVAR array to flags ID_PROTECTED; |
|
PIKEFUN int _size_object() |
{ |
int res = 0, i; |
if( THIS->ctx.v ) |
{ |
struct svalue tmp; |
SET_SVAL_TYPE(tmp, PIKE_T_STRING); |
for( i=0; i<THIS->ctx.num; i++ ) |
{ |
res += sizeof(struct replace_many_tupel); |
tmp.u.string = THIS->ctx.v[i].ind; |
res += rec_size_svalue( &tmp, NULL ); |
tmp.u.string = THIS->ctx.v[i].val; |
res += rec_size_svalue( &tmp, NULL ); |
} |
} |
|
RETURN res; |
} |
|
/*! @decl void create() |
*! @decl void create(mapping(string:string)) |
*! @decl void create(array(string) from, array(string)|string to) |
*/ |
PIKEFUN void create(array(string)|mapping(string:string)|void from_arg, |
array(string)|string|void to_arg) |
{ |
if (THIS->from) { |
free_array(THIS->from); |
THIS->from = NULL; |
} |
if (THIS->to) { |
free_array(THIS->to); |
THIS->to = NULL; |
} |
if (THIS->ctx.v) |
free_replace_many_context(&THIS->ctx); |
|
if (to_arg) { |
if (!from_arg || (TYPEOF(*from_arg) != T_ARRAY)) { |
SIMPLE_ARG_TYPE_ERROR("replace", 1, |
"array(string)|mapping(string:string)"); |
} |
if (TYPEOF(*to_arg) == T_STRING) { |
push_int(from_arg->u.array->size); |
stack_swap(); |
f_allocate(2); |
to_arg = Pike_sp - 1; |
} |
else if (TYPEOF(*to_arg) != T_ARRAY) { |
SIMPLE_ARG_TYPE_ERROR("replace", 2, "array(string)|string"); |
} |
else if (from_arg->u.array->size != to_arg->u.array->size) { |
Pike_error("Replace must have equal-sized from and to arrays.\n"); |
} |
add_ref(THIS->from = from_arg->u.array); |
add_ref(THIS->to = to_arg->u.array); |
} else if (from_arg) { |
if (TYPEOF(*from_arg) != T_MAPPING) |
Pike_error("Illegal arguments to create().\n"); |
THIS->from = mapping_indices(from_arg->u.mapping); |
THIS->to = mapping_values(from_arg->u.mapping); |
} else { |
pop_n_elems(args); |
return; |
} |
|
if (!THIS->from->size) { |
/* Enter no-op mode. */ |
pop_n_elems(args); |
return; |
} |
|
if( (THIS->from->type_field & ~BIT_STRING) && |
(array_fix_type_field(THIS->from) & ~BIT_STRING) ) |
SIMPLE_ARG_TYPE_ERROR("replace", 1, |
"array(string)|mapping(string:string)"); |
|
if( (THIS->to->type_field & ~BIT_STRING) && |
(array_fix_type_field(THIS->to) & ~BIT_STRING) ) |
SIMPLE_ARG_TYPE_ERROR("replace", 2, "array(string)|string"); |
|
compile_replace_many(&THIS->ctx, THIS->from, THIS->to, 1); |
pop_n_elems(args); |
} |
|
/*! @decl string `()(string str) |
*/ |
PIKEFUN string `()(string str) |
{ |
if (!THIS->ctx.v) { |
/* The result is already on the stack in the correct place... */ |
return; |
} |
|
RETURN execute_replace_many(&THIS->ctx, str); |
} |
|
/*! @decl array(array(string)) _encode() |
*/ |
PIKEFUN array(array(string)) _encode() |
{ |
if (THIS->from) { |
ref_push_array(THIS->from); |
} else { |
push_undefined(); |
} |
if (THIS->to) { |
ref_push_array(THIS->to); |
} else { |
push_undefined(); |
} |
f_aggregate(2); |
} |
|
/*! @decl void _decode(array(array(string)) encoded) |
*/ |
PIKEFUN void _decode(array(array(string)) encoded) |
{ |
INT32 i; |
for (i=0; i < encoded->size; i++) |
push_svalue(encoded->item + i); |
|
f_multi_string_replace_create(i); |
} |
|
#ifdef PIKE_NULL_IS_SPECIAL |
INIT |
{ |
memset(&THIS->ctx, 0, sizeof(struct replace_many_context)); |
} |
#endif |
|
EXIT |
gc_trivial; |
{ |
free_replace_many_context(&THIS->ctx); |
} |
} |
|
/*! @endclass |
*/ |
|
/*! @class SingleReplace |
*! |
*! This is a "compiled" version of the @[replace] function applied on |
*! a string, with just one replace string. The replace strings are |
*! given to the create method as a @i{from@} and @i{tom@} string and |
*! are then analyzed. The @expr{`()@} is then called with a string |
*! and the replace rule in the Replace object will be applied. The |
*! Replace object is used internally by the Pike optimizer and need |
*! not be used manually. |
*/ |
PIKECLASS single_string_replace |
{ |
CVAR SearchMojt mojt; |
PIKEVAR string del flags ID_PROTECTED|ID_PRIVATE; |
PIKEVAR string to flags ID_PROTECTED|ID_PRIVATE; |
|
EXTRA |
{ |
PIKE_MAP_VARIABLE ("o", |
single_string_replace_storage_offset + |
OFFSETOF (single_string_replace_struct, mojt.container), |
tObj, T_OBJECT, ID_PROTECTED|ID_PRIVATE); |
} |
|
/*! @decl void create(string|void from, string|void to) |
*! |
*! @note |
*! May be called with either zero or two arguments. |
*/ |
PIKEFUN void create(string|void del, string|void to) |
{ |
if (THIS->del) { |
free_string(THIS->del); |
THIS->del = NULL; |
} |
if (THIS->to) { |
free_string(THIS->to); |
THIS->to = NULL; |
} |
|
if (!del) { |
pop_n_elems(args); |
return; |
} |
|
if (!to) { |
SIMPLE_ARG_TYPE_ERROR("replace", 2, "string"); |
} |
|
if (del == to) { |
/* No-op... */ |
pop_n_elems(args); |
return; |
} |
|
copy_shared_string(THIS->del, del); |
copy_shared_string(THIS->to, to); |
|
if (del->len) { |
THIS->mojt = simple_compile_memsearcher(del); |
} |
pop_n_elems(args); |
} |
|
/*** replace function ***/ |
typedef char *(* replace_searchfunc)(void *,void *,size_t); |
|
/*! @decl string `()(string str) |
*/ |
PIKEFUN string `()(string str) |
{ |
int shift; |
struct pike_string *del = THIS->del; |
struct pike_string *to = THIS->to; |
struct pike_string *ret = NULL; |
|
if (!str->len || !del || !to) { |
/* The result is already on the stack in the correct place... */ |
return; |
} |
|
shift = MAXIMUM(str->size_shift, to->size_shift); |
|
if (!del->len) { |
int e, pos; |
ret = begin_wide_shared_string(str->len + to->len * (str->len-1), |
shift); |
low_set_index(ret, 0, index_shared_string(str, 0)); |
for(pos=e=1;e<str->len;e++) |
{ |
pike_string_cpy(MKPCHARP_STR_OFF(ret,pos), to); |
pos+=to->len; |
low_set_index(ret,pos++,index_shared_string(str,e)); |
} |
} else { |
char *s, *end, *tmp; |
replace_searchfunc f = (replace_searchfunc)0; |
void *mojt_data = THIS->mojt.data; |
PCHARP r; |
|
end = str->str+(str->len<<str->size_shift); |
|
switch(str->size_shift) |
{ |
case eightbit: f = (replace_searchfunc)THIS->mojt.vtab->func0; break; |
case sixteenbit: f = (replace_searchfunc)THIS->mojt.vtab->func1; break; |
case thirtytwobit: f = (replace_searchfunc)THIS->mojt.vtab->func2; break; |
default: UNREACHABLE(break); |
} |
|
if(del->len == to->len) |
{ |
ret = begin_wide_shared_string(str->len, shift); |
} else { |
INT32 delimiters = 0; |
|
s = str->str; |
|
while((s = f(mojt_data, s, (end-s)>>str->size_shift))) |
{ |
delimiters++; |
s += del->len << str->size_shift; |
} |
|
if (!delimiters) { |
/* The result is already on the stack in the correct place... */ |
return; |
} |
|
ret = begin_wide_shared_string(str->len + |
(to->len-del->len)*delimiters, shift); |
} |
|
s = str->str; |
r = MKPCHARP_STR(ret); |
|
while((tmp = f(mojt_data, s, (end-s)>>str->size_shift))) |
{ |
#ifdef PIKE_DEBUG |
if(tmp + (del->len << str->size_shift) > end) |
Pike_fatal("SearchMojt found a match beyond end of string!\n"); |
#endif |
generic_memcpy(r,MKPCHARP(s,str->size_shift),(tmp-s)>>str->size_shift); |
INC_PCHARP(r,(tmp-s)>>str->size_shift); |
pike_string_cpy(r,to); |
INC_PCHARP(r,to->len); |
s=tmp+(del->len << str->size_shift); |
} |
generic_memcpy(r,MKPCHARP(s,str->size_shift),(end-s)>>str->size_shift); |
} |
RETURN end_shared_string(ret); |
} |
|
/*! @decl array(string) _encode() |
*/ |
PIKEFUN array(string) _encode() |
{ |
if (THIS->del) { |
ref_push_string(THIS->del); |
ref_push_string(THIS->to); |
f_aggregate(2); |
} else { |
push_int(0); |
} |
} |
|
/*! @decl void _decode(array(string)|int(0..0) encoded) |
*/ |
PIKEFUN void _decode(array(string)|int(0..0) encoded_) |
{ |
INT32 i = 0; |
if (TYPEOF(*encoded_) == PIKE_T_ARRAY) { |
struct array *encoded = encoded_->u.array; |
|
for (i=0; i < encoded->size; i++) |
push_svalue(encoded->item + i); |
} |
|
f_single_string_replace_create(i); |
} |
} |
|
/*! @endclass |
*/ |
|
/*! @class Bootstring |
*! |
*! This class implements the "Bootstring" string transcoder described in |
*! @url{ftp://ftp.rfc-editor.org/in-notes/rfc3492.txt@}. |
*/ |
PIKECLASS bootstring |
{ |
CVAR INT_TYPE base, tmin, tmax, skew, damp; |
CVAR INT_TYPE initial_bias, initial_n; |
CVAR p_wchar2 delim; |
PIKEVAR string digits flags ID_PROTECTED|ID_PRIVATE; |
|
static INT_TYPE bootstring_cp_to_digit(p_wchar2 ch) |
{ |
ptrdiff_t digit = THIS->digits->len; |
PCHARP digits = MKPCHARP_STR( THIS->digits ); |
while (digit>=0) |
if (INDEX_PCHARP( digits, digit ) == ch) |
return digit; |
else |
--digit; |
return -1; |
} |
|
static INT_TYPE bootstring_adapt(INT_TYPE delta, INT_TYPE numpoints, |
int firsttime) |
{ |
struct bootstring_struct *bs = THIS; |
INT_TYPE k = 0, b = bs->base; |
INT_TYPE a = b - bs->tmin; |
INT_TYPE limit = (a * bs->tmax) >> 1; |
if (firsttime) |
delta /= bs->damp; |
else |
delta >>= 1; |
delta += delta / numpoints; |
while (delta > limit) { |
delta /= a; |
k += b; |
} |
return k + (a + 1)*delta / (delta + bs->skew); |
} |
|
/*! @decl string decode(string s) |
*! |
*! Decodes a Bootstring encoded string of "basic" code points back |
*! to the original string space. |
*/ |
PIKEFUN string decode(string s) |
{ |
struct bootstring_struct *bs = THIS; |
INT_TYPE n = bs->initial_n; |
INT_TYPE i = 0; |
INT_TYPE bias = bs->initial_bias; |
ptrdiff_t pos, input_left; |
PCHARP input; |
struct string_builder output; |
init_string_builder( &output,0 ); |
input = MKPCHARP_STR( s ); |
input_left = s->len; |
for (pos = input_left-1; pos > 0; --pos) |
if (INDEX_PCHARP( input, pos ) == bs->delim) { |
string_builder_append( &output, input, pos ); |
INC_PCHARP( input, pos+1 ); |
input_left -= pos+1; |
break; |
} |
|
while (input_left > 0) { |
INT_TYPE oldi = i; |
INT_TYPE w = 1; |
INT_TYPE k; |
for (k=bs->base; ; k+=bs->base) { |
INT_TYPE digit, t; |
if (input_left < 1 || |
(digit = bootstring_cp_to_digit( EXTRACT_PCHARP( input ) )) < 0) { |
free_string_builder( &output ); |
Pike_error( "Invalid variable-length integer.\n" ); |
} |
INC_PCHARP( input, 1 ); |
--input_left; |
i += digit * w; /* fail on overflow... */ |
if (k <= bias + bs->tmin) |
t = bs->tmin; |
else if (k >= bias + bs->tmax) |
t = bs->tmax; |
else |
t = k - bias; |
if (digit < t) break; |
w *= (bs->base - t); |
} |
bias = bootstring_adapt( i - oldi, output.s->len+1, !oldi ); |
n += i / (output.s->len+1); |
i %= output.s->len+1; |
string_builder_putchar( &output, n ); |
if (i != output.s->len-1) |
switch (output.s->size_shift) { |
case 0: |
{ |
p_wchar0 *s = STR0(output.s); |
INT_TYPE p = output.s->len; |
while (--p>i) |
s[p] = s[p-1]; |
s[p] = (p_wchar0) n; |
} |
break; |
case 1: |
{ |
p_wchar1 *s = STR1(output.s); |
INT_TYPE p = output.s->len; |
while (--p>i) |
s[p] = s[p-1]; |
s[p] = (p_wchar1) n; |
} |
break; |
case 2: |
{ |
p_wchar2 *s = STR2(output.s); |
INT_TYPE p = output.s->len; |
while (--p>i) |
s[p] = s[p-1]; |
s[p] = (p_wchar2) n; |
} |
break; |
} |
i++; |
} |
|
RETURN finish_string_builder( &output ); |
} |
|
/*! @decl string encode(string s) |
*! |
*! Encodes a string using Bootstring encoding into a string constisting |
*! only of "basic" code points (< initial_n). |
*/ |
PIKEFUN string encode(string s) |
{ |
struct bootstring_struct *bs = THIS; |
INT_TYPE n = bs->initial_n; |
INT_TYPE delta = 0; |
INT_TYPE bias = bs->initial_bias; |
INT_TYPE c, h, b = 0; |
ptrdiff_t pos, input_left; |
PCHARP input; |
struct string_builder output; |
init_string_builder( &output,0 ); |
input = MKPCHARP_STR( s ); |
input_left = s->len; |
for (pos=0; pos<input_left; pos++) |
if ((c = INDEX_PCHARP( input, pos )) < n) { |
string_builder_putchar( &output, c ); |
b++; |
} |
if ((h = b)) |
string_builder_putchar( &output, bs->delim ); |
while (h < input_left) { |
INT_TYPE m = -1; |
for (pos=0; pos<input_left; pos++) |
if ((c = INDEX_PCHARP( input, pos )) >= n && |
(m < 0 || c < m)) |
m = c; |
delta = delta + (m - n) * (h + 1); /* fail on overflow... */ |
n = m; |
for (pos=0; pos<input_left; pos++) |
if ((c = INDEX_PCHARP( input, pos )) < n) |
delta++; |
else if (c == n) { |
INT_TYPE k, q = delta; |
for (k=bs->base; ; k+=bs->base) { |
INT_TYPE t, bt; |
if (k <= bias + bs->tmin) |
t = bs->tmin; |
else if(k >= bias + bs->tmax) |
t = bs->tmax; |
else |
t = k-bias; |
if (q < t) |
break; |
bt = bs->base - t; |
string_builder_putchar( &output, |
index_shared_string( bs->digits, |
t + (q-t)%bt ) ); |
q = (q-t) / bt; |
} |
string_builder_putchar( &output, |
index_shared_string( bs->digits, q ) ); |
bias = bootstring_adapt( delta, h+1, h==b ); |
delta = 0; |
h++; |
} |
delta++; |
n++; |
} |
|
RETURN finish_string_builder( &output ); |
} |
|
/*! @decl void create(int base, int tmin, int tmax, int skew, @ |
*! int damp, int initial_bias, int initial_n, @ |
*! int delim, string digits) |
*! |
*! Creates a Bootstring transcoder instance using the specified parameters. |
*! |
*! @param base |
*! The base used by the variable-length integers. |
*! @param tmin |
*! The minimum threshold digit value for the variable-length integers. |
*! Must be >=0 and <= tmax. |
*! @param tmax |
*! The maximum threshold digit value for the variable-length integers. |
*! Must be <= base-1. |
*! @param skew |
*! The skew term for the bias adapation. Must be >= 1. |
*! @param damp |
*! The damping factor for the bias adaption. Must be >= 2. |
*! @param initial_bias |
*! The initial bias for the variable-length integer thresholding. |
*! initial_bias % base must be <= base - tmin. |
*! @param initial_n |
*! The first code point outside the "basic" set of code points. |
*! @param delim |
*! The "basic" code point used as the delimiter. |
*! @param digits |
*! The "basic" code points used as digits. The length of the string |
*! should be the same as the base parameter. |
*/ |
PIKEFUN void create( int base, int tmin, int tmax, |
int skew, int damp, |
int initial_bias, int initial_n, |
int delim, string digits ) |
flags ID_PROTECTED; |
{ |
struct bootstring_struct *bs = THIS; |
if (base<2) |
Pike_error("Bogus base\n"); |
if (tmin<0 || tmax<tmin || base-1<tmax) |
Pike_error("Parameters violate 0 <= tmin <= tmax <= base-1\n"); |
if (skew < 1) |
Pike_error("Parameters violate skew >= 1\n"); |
if (damp < 2) |
Pike_error("Parameters violate damp >= 2\n"); |
if (initial_bias%base > base-tmin) |
Pike_error("Parameters violate initial_bias%%base <= base-tmin\n"); |
if (digits->len != base) |
Pike_error("Length of digits string does not match base.\n"); |
bs->base = base; bs->tmin = tmin; bs->tmax = tmax; |
bs->skew = skew; bs->damp = damp; |
bs->initial_bias = initial_bias; bs->initial_n = initial_n; |
bs->delim = delim; |
if (bs->digits) { |
free_string( bs->digits ); |
bs->digits = NULL; |
} |
copy_shared_string( bs->digits, digits ); |
} |
|
} |
|
/*! @endclass |
*/ |
|
/*! @endmodule |
*/ |
|
/*! @module System |
*/ |
|
/*! @class Time |
*! |
*! The current time as a structure containing a sec and a usec |
*! member. |
*/ |
PIKECLASS Time |
{ |
CVAR int hard_update; |
|
/*! @decl int sec |
*! @decl int usec |
*! |
*! The number of seconds and microseconds since the epoch and the |
*! last whole second, respectively. (See also @[predef::time()]) |
*! |
*! @note |
*! Please note that these variables will continually update when |
*! they are requested, there is no need to create new Time() |
*! objects. |
*/ |
|
PIKEFUN int `sec() |
{ |
struct timeval now; |
|
if( THIS->hard_update ) |
ACCURATE_GETTIMEOFDAY( &now ); |
else |
INACCURATE_GETTIMEOFDAY( &now ); |
|
RETURN now.tv_sec; |
} |
|
PIKEFUN int `usec() |
{ |
struct timeval now; |
|
if( THIS->hard_update ) |
ACCURATE_GETTIMEOFDAY( &now ); |
else |
INACCURATE_GETTIMEOFDAY( &now ); |
|
RETURN now.tv_usec; |
} |
|
/*! @decl int usec_full |
*! |
*! The number of microseconds since the epoch. Please note that |
*! pike needs to have been compiled with bignum support for this |
*! variable to contain sensible values. |
*/ |
|
PIKEFUN int `usec_full() |
{ |
struct timeval now; |
|
if( THIS->hard_update ) |
ACCURATE_GETTIMEOFDAY( &now ); |
else |
INACCURATE_GETTIMEOFDAY( &now ); |
|
push_int( now.tv_sec ); |
push_int( 1000000 ); |
f_multiply( 2 ); |
push_int( now.tv_usec ); |
f_add( 2 ); |
return; |
} |
|
/*! @decl protected void create( int fast ); |
*! |
*! If @[fast] is true, do not request a new time from the system, |
*! instead use the global current time variable. |
*! |
*! This will only work in callbacks, but can save significant amounts |
*! of CPU. |
*/ |
PIKEFUN void create( int|zero|void fast ) |
flags ID_PROTECTED; |
{ |
THIS->hard_update = !fast; |
} |
} |
|
/*! @endclass |
*/ |
|
/*! @class Timer |
*/ |
PIKECLASS Timer |
{ |
CVAR struct timeval last_time; |
CVAR int hard_update; |
|
/*! @decl float peek() |
*! Return the time in seconds since the last time @[get] was called. |
*/ |
PIKEFUN float peek( ) |
{ |
struct timeval now; |
FLOAT_TYPE res; |
if( THIS->hard_update ) |
ACCURATE_GETTIMEOFDAY( &now ); |
else |
INACCURATE_GETTIMEOFDAY( &now ); |
res = now.tv_sec-THIS->last_time.tv_sec + |
(now.tv_usec-THIS->last_time.tv_usec)/(FLOAT_TYPE) 1000000.0; |
RETURN res; |
} |
|
/*! @decl float get() |
*! Return the time in seconds since the last time get was called. |
*! The first time this method is called the time since the object |
*! was created is returned instead. |
*/ |
PIKEFUN float get( ) |
{ |
f_Timer_peek( 0 ); |
INACCURATE_GETTIMEOFDAY(&THIS->last_time); |
return; |
} |
|
/*! @decl protected void create( int|void fast ) |
*! Create a new timer object. The timer keeps track of relative time |
*! with sub-second precision. |
*! |
*! If @[fast] is specified, the timer will not do system calls to get |
*! the current time but instead use the one maintained by pike. This |
*! will result in faster but more or less inexact timekeeping. |
*! The pike maintained time is only updated when a @[Pike.Backend] |
*! object stops waiting and starts executing code. |
*/ |
PIKEFUN void create( int|zero|void fast ) |
flags ID_PROTECTED; |
{ |
THIS->hard_update = !fast; |
if( THIS->hard_update ) |
ACCURATE_GETTIMEOFDAY( &THIS->last_time ); |
else |
INACCURATE_GETTIMEOFDAY( &THIS->last_time ); |
} |
} |
|
/*! @endclass |
*/ |
|
/*! @endmodule |
*/ |
|
|
/*! @module Builtin |
*/ |
|
/*! @class automap_marker |
*! |
*! This is an internal class used by @[__automap__()]. |
*! |
*! It may show up during module dumping or in backtraces |
*! and the like. |
*! |
*! It should in normal circumstances never be used directly. |
*! |
*! @seealso |
*! @[__automap__()], @[map()] |
*/ |
PIKECLASS automap_marker |
{ |
PIKEVAR array arg; |
PIKEVAR int depth; |
|
/*! @decl void create(array arr, int depth) |
*! |
*! @param arr |
*! Array that @[__automap__()] is to loop over. |
*! |
*! @param depth |
*! Recursion depth of @[arr] where the loop will be. |
*/ |
PIKEFUN void create(array a, int d) |
{ |
if(THIS->arg) free_array(THIS->arg); |
add_ref(THIS->arg=a); |
THIS->depth=d; |
pop_n_elems(2); |
} |
|
PIKEFUN string _sprintf(int mode, mapping flags) |
{ |
pop_n_elems(args); |
if (mode != 'O') { |
push_undefined (); |
return; |
} |
push_static_text("%O%*'[*]'n"); |
if(THIS->arg) |
ref_push_array(THIS->arg); |
else |
push_int(0); |
push_int(THIS->depth*3); |
f_sprintf(3); |
} |
} |
|
/*! @endclass |
*/ |
|
/*! @endmodule |
*/ |
|
static void low_automap(int d, |
int depth, |
struct svalue *fun, |
struct svalue *real_args, |
INT32 args) |
{ |
INT32 x,e,tmp,size=0x7fffffff; |
struct svalue *tmpargs=Pike_sp - args; |
struct array *ret; |
TYPE_FIELD types; |
|
for(e=0;e<args;e++) |
{ |
if(TYPEOF(real_args[e]) == T_OBJECT && |
real_args[e].u.object->prog == automap_marker_program && |
OBJ2_AUTOMAP_MARKER(real_args[e].u.object)->depth >= d) |
{ |
if(TYPEOF(tmpargs[e]) != T_ARRAY) |
index_error("__automap__", |
args, |
tmpargs, |
NULL, |
"Automap on non-array.\n"); |
tmp=tmpargs[e].u.array->size; |
if(tmp < size) |
size=tmp; |
} |
} |
|
if(size == 0x7fffffff) |
Pike_error("No automap markers found in __automap__\n"); |
|
push_array(ret=allocate_array(size)); |
types = 0; |
|
for(x=0;x<size;x++) |
{ |
for(e=0;e<args;e++) |
{ |
if(TYPEOF(real_args[e]) == T_OBJECT && |
real_args[e].u.object->prog == automap_marker_program && |
OBJ2_AUTOMAP_MARKER(real_args[e].u.object)->depth >= d) |
{ |
#ifdef PIKE_DEBUG |
if(x >= tmpargs[e].u.array->size) |
Pike_fatal("low_automap failed to determine size!\n"); |
#endif |
push_svalue(ITEM(tmpargs[e].u.array)+x); |
}else{ |
push_svalue(tmpargs+e); |
} |
} |
|
if(d == depth) |
apply_svalue(fun,args); |
else |
low_automap(d+1,depth,fun,real_args,args); |
stack_pop_to_no_free (ITEM(ret) + x); |
types |= 1 << TYPEOF(ITEM(ret)[x]); |
} |
ret->type_field = types; |
stack_unlink(args); |
} |
|
/*! @decl array __automap__(function fun, mixed ... args) |
*! |
*! Automap execution function. |
*! |
*! @param fun |
*! Function to call for each of the mapped arguments. |
*! |
*! @param args |
*! Arguments for @[fun]. Either |
*! @mixed |
*! @type Builtin.automap_marker |
*! Wrapper for an array to loop over. All of the |
*! arrays will be looped over in parallel. |
*! @type mixed |
*! All other arguments will be held constant during |
*! the automap, and sent as is to @[fun]. |
*! @endmixed |
*! |
*! @note |
*! This function is used by the compiler to implement the |
*! automap syntax, and should in normal circumstances never |
*! be used directly. |
*! |
*! It may however show up during module dumping and in |
*! backtraces. |
*! |
*! @note |
*! It is an error not to have any @[Builtin.automap_marker]s |
*! in @[args]. |
*! |
*! @seealso |
*! @[Builtin.automap_marker], @[map()] |
*/ |
PIKEFUN array __automap__(mixed fun, mixed ... tmpargs) |
efun; |
{ |
int e,depth=-1; |
check_stack(args); |
|
for(e=0;e<args-1;e++) |
{ |
if(TYPEOF(tmpargs[e]) == T_OBJECT && |
tmpargs[e].u.object->prog == automap_marker_program) |
{ |
int tmp=OBJ2_AUTOMAP_MARKER(tmpargs[e].u.object)->depth; |
if(tmp > depth) depth=tmp; |
ref_push_array(OBJ2_AUTOMAP_MARKER(tmpargs[e].u.object)->arg); |
}else{ |
push_svalue(tmpargs+e); |
} |
} |
check_stack(depth * (args+1)); |
low_automap(1,depth,fun,tmpargs,args-1); |
stack_unlink(args); |
} |
|
/*! @module Builtin |
*/ |
|
/*! @class Setter |
*! |
*! Internal class for implementing setters. |
*! |
*! This class is used by @[_get_setter()]. |
*! |
*! @seealso |
*! @[_get_setter()] |
*/ |
PIKECLASS Setter |
{ |
PIKEVAR object o |
flags ID_PROTECTED|ID_PRIVATE|ID_LOCAL; |
CVAR int f; |
|
/*! @decl void `()(mixed val) |
*! |
*! Set the variable for the setter to @[val]. |
*! |
*! This is the function returned by @[_get_setter()]. |
*/ |
PIKEFUN void `()(mixed val) |
flags ID_PROTECTED; |
{ |
if (!THIS->o) { |
Pike_error("Uninitialized Setter!\n"); |
} |
object_low_set_index(THIS->o, THIS->f, Pike_sp-1); |
} |
|
PIKEFUN string _sprintf(int c, mapping|void opts) |
flags ID_PROTECTED; |
{ |
struct program *prog; |
if (!THIS->o) { |
push_static_text("Setter()"); |
} else if ((prog = THIS->o->prog)) { |
push_static_text("%O->`%s="); |
ref_push_object(THIS->o); |
ref_push_string(ID_FROM_INT(prog, THIS->f)->name); |
f_sprintf(3); |
} else { |
push_static_text("Setter(destructed object)"); |
} |
stack_pop_n_elems_keep_top(args); |
} |
} |
|
/*! @endclass |
*/ |
|
PMOD_EXPORT struct object *get_setter(struct object *o, int f) |
{ |
struct object *res = clone_object(Setter_program, 0); |
struct Setter_struct *setter = OBJ2_SETTER(res); |
add_ref(setter->o = o); |
setter->f = f; |
return res; |
} |
|
/*! @decl function(mixed_void) _get_setter(object o, string varname) |
*! |
*! Get a setter for the variable named @[varname] in object @[o]. |
*! |
*! @returns |
*! Returns a @[Setter()->`()()] for the variable if it exists, |
*! and @expr{UNDEFINED@} otherwise. |
*! |
*! @seealso |
*! @[object_variablep()] |
*/ |
PIKEFUN function(mixed:void) _get_setter(object o, string s) |
{ |
struct program *p; |
struct inherit *inh; |
int f; |
if (!(p = o->prog)) { |
Pike_error("Indexing a destructed object.\n"); |
} |
inh = p->inherits + SUBTYPEOF(Pike_sp[-args]); |
p = inh->prog; |
f = find_shared_string_identifier(s, p); |
if ((f >= 0) && |
IDENTIFIER_IS_VARIABLE(ID_FROM_INT(p, f)->identifier_flags)) { |
f += inh->identifier_level; |
push_function(get_setter(o, f), f_Setter_cq__backtick_28_29_fun_num); |
} else { |
push_undefined(); |
} |
stack_pop_n_elems_keep_top(args); |
} |
|
/*! @class Null |
*! |
*! This class is used to implement the low-level aspects of @[Val.Null]. |
*! |
*! @note |
*! This class should typically not be used directly. Use |
*! @[Val.Null] instead. |
*! |
*! @note |
*! This class was previously available as @[Sql.Null]. Any such use |
*! should be replaced with @[Val.Null]. |
*! |
*! @deprecated Val.Null |
*! |
*! @seealso |
*! @[Val.Null], @[Val.null] |
*/ |
PIKECLASS Null |
{ |
EXTRA { |
/*! @decl constant is_val_null = 1 |
*! |
*! Nonzero recognition constant. |
*/ |
add_integer_constant("is_val_null", 1, 0); |
|
/*! @decl constant is_sql_null = 1 |
*! |
*! SQL Null marker. |
*! |
*! @deprecated is_val_null |
*/ |
add_integer_constant("is_sql_null", 1, 0); |
} |
|
PIKEFUN int `!() |
flags ID_PROTECTED; |
{ |
RETURN 1; |
} |
|
PIKEFUN string _sprintf(int fmt, mixed ... extras) |
flags ID_PROTECTED; |
{ |
pop_n_elems(args); |
if (fmt == 'O') { |
push_static_text("Val.null"); |
} else { |
push_undefined(); |
} |
} |
|
PIKEFUN int __hash() |
flags ID_PROTECTED; |
{ |
RETURN 17; |
} |
|
PIKEFUN int `==(mixed other) |
flags ID_PROTECTED; |
{ |
if (TYPEOF(*other) != T_OBJECT) |
RETURN 0; |
|
/* Look for the is_val_null constant directly in the program of |
* other, without going through its `[]. When this is called in a |
* codec, other can be a completely arbitrary object which may not |
* have a `[] that works in that context. */ |
push_int (0); |
ref_push_program (other->u.object->prog); |
push_constant_text("is_val_null"); |
if (program_index_no_free (Pike_sp - 3, Pike_sp - 2, Pike_sp - 1) && |
TYPEOF(Pike_sp[-3]) == T_INT && Pike_sp[-3].u.integer) { |
pop_n_elems (4); |
push_int (1); |
} |
else { |
pop_n_elems (4); |
push_int (0); |
} |
} |
|
/*! @decl string encode_json() |
*! |
*! Defined for use with @[Standards.JSON.encode], so that it |
*! formats NULL as @expr{null@}. |
*/ |
PIKEFUN string encode_json(...) |
{ |
pop_n_elems(args); |
push_constant_text ("null"); |
} |
} |
|
/*! @endclass |
*/ |
|
/*! @class DestructImmediate |
*! An empty class that can be intherited to get the PROGRAM_DESTRUCT_IMMEDIATE |
*! flag set. |
*/ |
|
PIKECLASS DestructImmediate |
program_flags PROGRAM_DESTRUCT_IMMEDIATE; |
{ |
} |
|
/*! @endclass |
*/ |
|
PMOD_EXPORT |
PIKEFUN int levenshtein_distance(string a, string b) |
{ |
int i, j, n, *lev_i, *lev_p; |
|
/* Simple cases: strings are equal or one of them is empty: */ |
if (a == b) RETURN 0; |
if (a->len == 0) RETURN b->len; |
if (b->len == 0) RETURN a->len; |
|
/* Return -1 if any of the strings is wider than 8 bits: */ |
if (a->size_shift || b->size_shift) RETURN -1; |
|
/* Allocate two rows on the stack: */ |
n = b->len+1; |
lev_i = alloca(n*sizeof(int)); |
lev_p = alloca(n*sizeof(int)); |
if (!lev_i || !lev_p) RETURN -1; |
|
/* Initialise the first row */ |
for (j = 0; j < n; j++) lev_i[j] = j; |
|
for (i = 0; i < a->len; i++) |
{ |
/* lev_p = row for i, lev_i = row for i+1: */ |
memcpy(lev_p, lev_i, n*sizeof(int)); |
lev_i[0] = i + 1; |
for (j = 0; j < b->len; j++) |
{ |
int cost = (a->str[i] == b->str[j]) ? 0 : 1; |
int test, min_val = lev_i[j]+1; |
if ((test = lev_p[j+1]+1) < min_val) min_val = test; |
if ((test = lev_p[j]+cost) < min_val) min_val = test; |
lev_i[j+1] = min_val; |
} |
} |
RETURN lev_i[b->len]; |
} |
|
/*! @endmodule |
*/ |
|
/*! @module Serializer |
*/ |
|
/*! @class Serializable |
*! |
*! The base class for serializable objects. |
*! |
*! Inherit this class in classes that need to be serializable. |
*! |
*! @seealso |
*! @[Serializer.serialize()], @[Serializer.deserialize()] |
*/ |
PIKECLASS Serializable |
{ |
/* Loop over all true variables, and call fun_num in the current object. */ |
static void low_serialize(int i, struct svalue *fun, |
int use_setter, int fun_num) |
{ |
struct inherit *inh; |
struct program *p = Pike_fp->current_object->prog; |
struct svalue *save_sp = Pike_sp; |
|
inh = p->inherits + i; |
p = inh->prog; |
|
for (i = 0; i < p->num_identifier_references; i++) { |
struct reference *ref = PTR_FROM_INT(p, i); |
struct identifier *id; |
if ((ref->id_flags & ID_HIDDEN) || |
((ref->id_flags & (ID_PRIVATE|ID_INHERITED)) == |
(ID_PRIVATE|ID_INHERITED))) { |
continue; |
} |
id = ID_FROM_PTR(p, ref); |
if (!IDENTIFIER_IS_VARIABLE(id->identifier_flags) || |
(id->run_time_type == PIKE_T_GET_SET)) { |
continue; |
} |
push_svalue(fun); |
if (use_setter) { |
push_function(get_setter(Pike_fp->current_object, |
i + inh->identifier_level), |
f_Setter_cq__backtick_28_29_fun_num); |
} else { |
low_object_index_no_free(Pike_sp, Pike_fp->current_object, |
i + inh->identifier_level); |
Pike_sp++; |
} |
ref_push_string(id->name); |
ref_push_type_value(id->type); |
apply_current(fun_num, 4); |
pop_stack(); |
} |
if (Pike_sp != save_sp) { |
/* Not likely, but... */ |
pop_n_elems(Pike_sp - save_sp); |
} |
} |
|
/*! @decl protected void _serialize_variable( @ |
*! function(mixed, string, type:void) serializer, @ |
*! mixed value, @ |
*! string symbol, @ |
*! type symbol_type) |
*! |
*! Default serialization function for variables. |
*! |
*! @param serializer |
*! Function to be called in turn. |
*! |
*! @param value |
*! Value of the variable. |
*! |
*! @param symbol |
*! Variable name. |
*! |
*! @param symbol_type |
*! Type of the variable. |
*! |
*! This function is typically called from @[_serialize()], and just does |
*! @code |
*! serializer(value, symbol, symbol_type); |
*! @endcode |
*! |
*! It is provided for overloading for eg filtering or validation purposes. |
*! |
*! @seealso |
*! @[_serialize()], @[_deserialize_variable()] |
*/ |
PIKEFUN void _serialize_variable(function(mixed, string, type:void) |
serializer, mixed value, |
string symbol, type symbol_type) |
flags ID_PROTECTED; |
rawtype tFunc(tFunc(tMix tStr tType(tMix), tVoid) |
tMix tStr tType(tMix), tVoid); |
{ |
f_call_function(args); |
} |
|
/*! @decl protected void _serialize(object o, @ |
*! function(mixed, string, type:void) serializer) |
*! |
*! Dispatch function for serialization. |
*! |
*! @param o |
*! Object to serialize. Always a context of the current object. |
*! |
*! @param serializer |
*! Function to typically be called once for every variable |
*! in the inheriting class. |
*! |
*! This function calls @[_serialize_variable()] once |
*! for every variable in the inheriting class, which |
*! in turn will call @[serializer] with the arguments: |
*! @dl |
*! @item Argument 1 |
*! The value of the variable. |
*! @item Argument 2 |
*! The name of the variable. |
*! @item Argument 3 |
*! The declared type of the variable. |
*! @enddl |
*! |
*! @note |
*! The symbols will be listed in the order they were defined |
*! in the class. |
*! |
*! @note |
*! This function is typically called via @[Serializer.serialize()]. |
*! |
*! @seealso |
*! @[Serializer.serialize()], @[_serialize_variable()], |
*! @[_deserialize()] |
*/ |
PIKEFUN void _serialize(object o, |
function(mixed, string, type:void) serializer) |
flags ID_PROTECTED; |
rawtype tFunc(tObj tFunc(tMix tStr tType(tMix), tVoid), tVoid); |
{ |
if (o != Pike_fp->current_object) { |
SIMPLE_ARG_TYPE_ERROR("_serialize", 1, "this"); |
} |
low_serialize(SUBTYPEOF(Pike_sp[-args]), serializer, 0, |
f_Serializable_cq__serialize_variable_fun_num); |
} |
|
static void *find_program_from_object_type_cb(struct pike_type *t) |
{ |
struct program *p; |
if ((t->type != PIKE_T_OBJECT) || !t->cdr) return NULL; |
p = id_to_program(CDR_TO_INT(t)); |
if (!p || (p->flags & PROGRAM_NEEDS_PARENT) || |
(low_find_lfun(p, LFUN__DESERIALIZE) == -1)) return NULL; |
return p; |
} |
|
DEFAULT_CMOD_STORAGE void f_deserialize(INT32 args); |
|
/*! @decl protected void _deserialize_variable( @ |
*! function(function(mixed:void), @ |
*! string, type: void) deserializer, @ |
*! function(mixed:void) setter, @ |
*! string symbol, @ |
*! type symbol_type) |
*! |
*! Default deserialization function for variables. |
*! |
*! @param deserializer |
*! Function to be called in turn. |
*! |
*! @param setter |
*! Function that sets the value of the variable. |
*! |
*! @param symbol |
*! Variable name. |
*! |
*! @param symbol_type |
*! Type of the variable. |
*! |
*! This function is typically called from @[_deserialize()], and does |
*! something like: |
*! @code |
*! if (object_typep(symbol_type)) { |
*! program p = program_from_type(symbol_type); |
*! if (p && !needs_parent(p) && is_deserializable(p)) { |
*! object value = p(); |
*! setter(value); |
*! Serializer.deserialize(value, deserializer); |
*! return; |
*! } |
*! } |
*! deserializer(setter, symbol, symbol_type); |
*! @endcode |
*! |
*! @note |
*! The above takes care of the most common cases, but |
*! @ul |
*! @item |
*! Does not support anonymous object types. |
*! @item |
*! Does not support objects needing a parent. |
*! @item |
*! Does not support non-serializable objects. |
*! @item |
*! Selects one of the object types in case of a complex |
*! @[symbol_type]. The selected type is NOT deterministic |
*! in case there are multiple choices that satisfy the above. |
*! @item |
*! Is likely to throw errors if @tt{p()@} requires arguments. |
*! @endul |
*! |
*! These issues can all be solved by overloading this function. |
*! |
*! @seealso |
*! @[_deserialize()], @[_serialize_variable()], @[Builtin.Setter] |
*/ |
PIKEFUN void _deserialize_variable(function(function(mixed:void), |
string, type: void) |
deserializer, function(mixed:void) setter, |
string symbol, |
type symbol_type) |
flags ID_PROTECTED; |
rawtype tFunc(tFunc(tFunc(tMix, tVoid) tStr tType(tMix), tVoid) |
tFunc(tMix, tVoid) tStr tType(tMix), tVoid); |
{ |
struct program *p = find_type(symbol_type, |
find_program_from_object_type_cb); |
if (p) { |
struct object *o = clone_object(p, 0); |
push_object(o); /* Protection against errors and arg to deserialize. */ |
ref_push_object(o); |
apply_svalue(setter, 1); |
pop_stack(); |
push_svalue(deserializer); |
f_deserialize(2); |
return; |
} |
f_call_function(args); |
} |
|
/*! @decl protected void _deserialize(object o, @ |
*! function(function(mixed:void), @ |
*! string, type: void) deserializer) |
*! |
*! Dispatch function for deserialization. |
*! |
*! @param o |
*! Object to serialize. Always a context of the current object. |
*! |
*! @param deserializer |
*! Function to typically be called once for every variable |
*! in the inheriting class. |
*! |
*! This function calls @[_deserialize_variable()] once |
|