/* -*- c -*- |
|| 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 "gc.h" |
#include "block_allocator.h" |
#include "pikecode.h" |
#include "opcodes.h" |
#include "whitespace.h" |
#include "fdlib.h" |
|
#include "modules/_Stdio/file_machine.h" |
#include "modules/_Stdio/file.h" |
|
#include <ctype.h> |
#include <errno.h> |
#include <math.h> |
#include <arpa/inet.h> |
|
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 pike_string *set_zone; |
|
#ifdef STRUCT_TM_HAS___TM_GMTOFF |
#define tm_zone __tm_zone |
#define tm_gmtoff __tm_gmtoff |
#define GET_GMTOFF(TM) ((TM)->tm_gmtoff) |
#define GET_ZONE(TM) ((TM)->tm_zone) |
#define SET_GMTOFF(TM, VAL) (((TM)->tm_gmtoff) = (VAL)) |
#define SET_ZONE(TM, VAL) (((TM)->tm_zone) = (VAL)) |
#elif defined(STRUCT_TM_HAS_GMTOFF) |
#define GET_GMTOFF(TM) ((TM)->tm_gmtoff) |
#define GET_ZONE(TM) ((TM)->tm_zone) |
#define SET_GMTOFF(TM, VAL) (((TM)->tm_gmtoff) = (VAL)) |
#define SET_ZONE(TM, VAL) (((TM)->tm_zone) = (VAL)) |
#else |
#define GET_GMTOFF(TM) 0 |
#define GET_ZONE(TM) ((char*)NULL) |
#define SET_GMTOFF(TM, VAL) (VAL) |
#define SET_ZONE(TM, VAL) (VAL) |
#endif |
|
#define strftime_zone strftime |
#define mktime_zone mktime |
#define strptime_zone strptime |
#define asctime_zone asctime |
#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){ \ |
THIS->unix_time = mktime_zone( &THIS->t ); \ |
THIS->modified = 0; \ |
} \ |
} while(0) |
|
#ifdef HAVE_STRPTIME |
/*! @decl int(0..1) strptime( string(1..255) format, string(1..255) data ) |
*! |
*! Parse the given @[data] using the format in @[format] as a date. |
*! |
*! @dl |
*! @item %% |
*! The % character. |
*! |
*! @item %a or %A |
*! The weekday name according to the C locale, in abbreviated |
*! form or the full name. |
*! |
*! @item %b or %B or %h |
*! The month name according to the C locale, in abbreviated form |
*! or the full name. |
*! |
*! @item %c |
*! The date and time representation for the C locale. |
*! |
*! @item %C |
*! The century number (0-99). |
*! |
*! @item %d or %e |
*! The day of month (1-31). |
*! |
*! @item %D |
*! Equivalent to %m/%d/%y. |
*! |
*! @item %H |
*! The hour (0-23). |
*! |
*! @item %I |
*! The hour on a 12-hour clock (1-12). |
*! |
*! @item %j |
*! The day number in the year (1-366). |
*! |
*! @item %m |
*! The month number (1-12). |
*! |
*! @item %M |
*! The minute (0-59). |
*! |
*! @item %n |
*! Arbitrary whitespace. |
*! |
*! @item %p |
*! The C locale's equivalent of AM or PM. |
*! |
*! @item %R |
*! Equivalent to %H:%M. |
*! |
*! @item %S |
*! The second (0-60; 60 may occur for leap seconds; |
*! earlier also 61 was allowed). |
*! |
*! @item %t |
*! Arbitrary whitespace. |
*! |
*! @item %T |
*! Equivalent to %H:%M:%S. |
*! |
*! @item %U |
*! The week number with Sunday the first day of the week (0-53). |
*! |
*! @item %w |
*! The weekday number (0-6) with Sunday = 0. |
*! |
*! @item %W |
*! The week number with Monday the first day of the week (0-53). |
*! |
*! @item %x |
*! The date, using the C locale's date format. |
*! |
*! @item %X |
*! The time, using the C locale's time format. |
*! |
*! @item %y |
*! The year within century (0-99). When a century is not |
*! otherwise specified, values in the range 69-99 refer to years |
*! in the twentieth century (1969-1999); values in the range |
*! 00-68 refer to years in the twenty-first century (2000-2068). |
*! |
*! @item %Y |
*! The year, including century (for example, 1991). |
*! @enddl |
*! |
*/ |
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 ) |
*! See also @[Gettext.setlocale] |
*! |
*! Convert the structure to a string. |
*! |
*! @dl |
*! @item %a |
*! The abbreviated weekday name according to the current locale |
*! |
*! @item %A |
*! The full weekday name according to the current locale. |
*! |
*! @item %b |
*! The abbreviated month name according to the current locale. |
*! |
*! @item %B |
*! The full month name according to the current locale. |
*! |
*! @item %c |
*! The preferred date and time representation for the current locale. |
*! |
*! @item %C |
*! The century number (year/100) as a 2-digit integer. |
*! |
*! @item %d |
*! The day of the month as a decimal number (range 01 to 31). |
*! |
*! @item %D |
*! Equivalent to @expr{%m/%d/%y@}. (for Americans only. |
*! Americans should note that in other countries @expr{%d/%m/%y@} |
*! is rather common. This means that in international context |
*! this format is ambiguous and should not be used.) |
*! |
*! @item %e |
*! Like @expr{%d@}, the day of the month as a decimal number, |
*! but a leading zero is replaced by a space. |
*! |
*! @item %E |
*! Modifier: use alternative format, see below. |
*! |
*! @item %F |
*! Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99) |
*! |
*! @item %G |
*! The ISO 8601 week-based year (see NOTES) with century as a |
*! decimal number. The 4-digit year corresponding to the ISO |
*! week number (see @expr{%V@}). This has the same format and |
*! value as @expr{%Y@}, except that if the ISO week number |
*! belongs to the previous or next year, that year is used instead. |
*! |
*! @item %g |
*! Like @expr{%G@}, but without century, that is, |
*! with a 2-digit year (00-99). (TZ) |
*! |
*! @item %h |
*! Equivalent to %b. |
*! |
*! @item %H |
*! The hour as a decimal number using a 24-hour clock (range 00 to 23). |
*! |
*! @item %I |
*! The hour as a decimal number using a 12-hour clock (range 01 to 12). |
*! |
*! @item %j |
*! The day of the year as a decimal number (range 001 to 366). |
*! |
*! @item %k |
*! The hour (24-hour clock) as a decimal number (range 0 to 23); |
*! single digits are preceded by a blank. (See also @expr{%H@}.) |
*! |
*! @item %l |
*! The hour (12-hour clock) as a decimal number (range 1 to 12); |
*! single digits are preceded by a blank. (See also @expr{%I@}.) |
*! |
*! @item %m |
*! The month as a decimal number (range 01 to 12). |
*! |
*! @item %M |
*! The minute as a decimal number (range 00 to 59). |
*! |
*! @item %n |
*! A newline character. (SU) |
*! |
*! @item %O |
*! Modifier: use alternative format, see below. (SU) |
*! |
*! @item %p |
*! Either @expr{"AM"@} or @expr{"PM"@} according to the given time |
*! value, or the corresponding strings for the current locale. |
*! Noon is treated as @expr{"PM"@} and midnight as @expr{"AM"@}. |
*! |
*! @item %P |
*! Like @expr{%p@} but in lowercase: @expr{"am"@} or @expr{"pm"@} |
*! or a corresponding string for the current locale. |
*! |
*! @item %r |
*! The time in a.m. or p.m. notation. In the POSIX locale this is |
*! equivalent to @expr{%I:%M:%S %p@}. |
*! |
*! @item %R |
*! The time in 24-hour notation (@expr{%H:%M@}). (SU) |
*! For a version including the seconds, see @expr{%T@} below. |
*! |
*! @item %s |
*! The number of seconds since the Epoch, |
*! 1970-01-01 00:00:00 +0000 (UTC). (TZ) |
*! |
*! @item %S |
*! The second as a decimal number (range 00 to 60). |
*! (The range is up to 60 to allow for occasional leap seconds.) |
*! |
*! @item %t |
*! A tab character. (SU) |
*! |
*! @item %T |
*! The time in 24-hour notation (@expr{%H:%M:%S@}). (SU) |
*! |
*! @item %u |
*! The day of the week as a decimal, range 1 to 7, Monday being 1. |
*! See also @expr{%w@}. (SU) |
*! |
*! @item %U |
*! The week number of the current year as a decimal number, |
*! range 00 to 53, starting with the first Sunday as the first |
*! day of week 01. See also @expr{%V@} and @expr{%W@}. |
*! |
*! @item %V |
*! The ISO 8601 week number of the current year as a decimal number, |
*! range 01 to 53, where week 1 is the first week that has at least |
*! 4 days in the new year. See also @expr{%U@} and @expr{%W@}. |
*! |
*! @item %w |
*! The day of the week as a decimal, range 0 to 6, Sunday being 0. |
*! See also @expr{%u@}. |
*! @enddl |
*/ |
PIKEFUN string strftime(string(1..255) format) |
{ |
char *buffer = xalloc( 8192 ); |
buffer[0] = 0; |
strftime_zone( buffer, 8192, 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 savings are 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(); |
{ |
char *tval = asctime_zone( &THIS->t ); |
if( tval ) |
push_text( tval ); |
else |
push_undefined(); |
} |
} |
|
PIKEFUN void _sprintf( int flag, mapping options ) |
{ |
int post_sum = 1; |
switch( flag ) |
{ |
case 'O': |
push_text("System.TM("); |
post_sum = 1; |
/* fallthrough */ |
case 's': |
f_TM_asctime(0); |
push_text("\n"); |
if( GET_ZONE(&(THIS->t)) ) |
{ |
push_text(" "); |
push_text( GET_ZONE(&(THIS->t)) ); |
f_add( 2 ); |
} |
else |
push_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_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->t)) ) |
push_text( GET_ZONE(&(THIS->t)) ); |
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)) ); |
} |
|
/* Setting the zone does not work, so.. */ |
|
/* PIKEFUN string `zone=(string x) { */ |
/* if( THIS->set_zone ) */ |
/* free_string( THIS->set_zone ); */ |
/* THIS->set_zone = x; */ |
/* MODIFY( tm_zone = x->str ); */ |
/* x->refs++; */ |
/* } */ |
|
/*! @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 ); |
|
/* These are supposedly correctly by localtime_zone. */ |
SET_GMTOFF(res, GET_GMTOFF(&(THIS->t))); |
SET_ZONE(res, GET_ZONE(&(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; |
THIS->modified = 1; |
RETURN 1; |
} |
|
/*! @decl void create(int 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 _t ) |
{ |
f_TM_gmtime( 1 ); |
if( Pike_sp[-1].u.integer == 0 ) |
Pike_error("time out of range\n"); |
} |
|
/*! @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->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; |
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 (THIS->set_zone) { |
free_string(THIS->set_zone); |
THIS->set_zone = NULL; |
} |
if( !timezone ) /* gmtime. */ |
SET_ZONE(t, "UTC"); |
else |
{ |
add_ref(timezone); |
THIS->set_zone = timezone; |
SET_ZONE(t, timezone->str); |
} |
THIS->unix_time = mktime_zone( t ); |
} |
|
INIT { |
THIS->set_zone = 0; |
THIS->modified = 0; |
} |
|
EXIT { |
if( THIS->set_zone ) |
free_string( THIS->set_zone ); |
} |
} |
/*! @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_BAD_ARG_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_text("svalue_ptr"); break; |
case T_OBJ_INDEX: push_text("obj_index"); break; |
case T_MAPPING_DATA: push_text("mapping_data"); break; |
case T_PIKE_FRAME: push_text("pike_frame"); break; |
case T_MULTISET_DATA: push_text("multiset_data"); break; |
default: push_text("unknown"); break; |
} |
} |
|
|
/*! @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_BAD_ARG_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_BAD_ARG_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) |
*! @appears String.string2hex |
*! |
*! Convert a string of binary data to a hexadecimal string. |
*! |
*! @seealso |
*! @[hex2string()] |
*/ |
|
static const char hexchar[] = { |
'0','1','2','3','4','5','6','7','8','9', |
'a','b','c','d','e','f' |
}; |
|
static const unsigned char hexdecode[256] = |
{ |
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
|
/* '0' - '9' */ |
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, |
|
0,0,0,0,0,0,0, |
|
/* 'A' - 'F' */ |
10, 11, 12, 13, 14, 15, |
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
/* 'a' - 'f' */ |
10, 11, 12, 13, 14, 15, |
}; |
|
PMOD_EXPORT |
PIKEFUN string(0..255) string2hex(string s) |
optflags OPT_TRY_OPTIMIZE; |
{ |
struct pike_string *hex; |
unsigned char *p,*st = (unsigned char *)s->str; |
int i, l; |
|
if (s->size_shift) |
Pike_error("Bad argument 1 to string2hex(), expected 8-bit string.\n"); |
|
hex = begin_shared_string(2 * s->len); |
p = (unsigned char *)hex->str; |
l = s->len; |
|
for (i=0; i<l; i++) { |
*p++ = hexchar[*st>>4]; |
*p++ = hexchar[*st&15]; |
st++; |
} |
|
RETURN end_shared_string(hex); |
} |
|
/*! @decl string hex2string(string hex) |
*! @appears String.hex2string |
*! |
*! Convert a string of hexadecimal digits to binary data. |
*! |
*! @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 = hex->len>>1; |
if(hex->size_shift) Pike_error("Only hex digits allowed.\n"); |
if(hex->len&1) Pike_error("Can't have odd number of digits.\n"); |
|
s = begin_shared_string(l); |
p = (unsigned char *)s->str; |
for (i=0; i<l; i++) |
{ |
tmp = hexdecode[*q++]; |
*p++ = (tmp<<4) | hexdecode[*q++]; |
} |
RETURN end_shared_string(s); |
} |
|
/*! @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", Pike_sp-args, 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_BAD_ARG_ERROR ("gc_parameters", 1, \ |
"float between 0.0 and 1.0 for " NAME); \ |
VAR = DO_NOT_WARN ((double) set->u.float_number); \ |
}, { \ |
SET_SVAL(get, T_FLOAT, 0, float_number, \ |
DO_NOT_WARN ((FLOAT_TYPE) VAR)); \ |
}); |
|
HANDLE_PARAM ("enabled", { |
if (TYPEOF(*set) != T_INT || set->u.integer < -1 || set->u.integer > 1) |
SIMPLE_BAD_ARG_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 |
*! @[time()], @[localtime()], @[mktime()], @[gmtime()] |
*/ |
PMOD_EXPORT |
PIKEFUN string ctime(longest timestamp) |
efun; |
optflags OPT_TRY_OPTIMIZE; |
{ |
time_t i; |
char *s; |
|
#if SIZEOF_TIME_T < SIZEOF_LONGEST |
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", Pike_sp-args, 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 DO_NOT_WARN((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_all_whites (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_all_whites (string s) |
errname trim_all_whites; |
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_text(":"); |
push_int(line); |
f_add(3); |
} |
} |
else |
push_int(0); |
} |
|
/*! @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 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 the entry with index @[index] |
*! will be removed from @[map] destructively. |
*! |
*! If the mapping does not have an entry with index @[index], nothing is done. |
*! |
*! @returns |
*! The value that was removed will be returned. |
*! |
*! @note |
*! Note that @[m_delete()] changes @[map] destructively. |
*! |
*! @seealso |
*! @[mappingp()] |
*/ |
PMOD_EXPORT |
PIKEFUN mixed m_delete(object|mapping map, mixed index) |
efun; |
optflags OPT_SIDE_EFFECT; |
rawtype tOr(tFunc(tMap(tSetvar(0,tMix),tSetvar(1,tMix)) tVar(0),tVar(1)),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_BAD_ARG_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 { |
SIMPLE_BAD_ARG_ERROR("m_delete", 1, "object|mapping"); |
} |
} |
|
/*! @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_BAD_ARG_ERROR("function_name", 1, "function|program"); |
return; /* NOTREACHED */ |
|
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", Pike_sp-args, 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_BAD_ARG_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_BAD_ARG_ERROR("function_program", 1, "function"); |
} |
pop_n_elems(args); |
push_int(0); |
} |
|
|
/*! @decl mixed random(object o) |
*! If random is called with an object, @[lfun::random] will be |
*! called in the object. |
*! |
*! @seealso |
*! @[lfun::_random()] |
*/ |
|
PMOD_EXPORT |
PIKEFUN mixed random(object o) |
efun; |
optflags OPT_TRY_OPTIMIZE|OPT_EXTERNAL_DEPEND; |
{ |
int f = low_find_lfun(o->prog, LFUN__RANDOM); |
if (f < 0) { |
Pike_error("Calling undefined lfun::%s.\n", lfun_names[LFUN__RANDOM]); |
} |
apply_low(o, f, 0); |
stack_swap(); |
pop_stack(); |
} |
|
/*! @decl int random(int max) |
*! @decl float random(float max) |
*! |
*! This function returns a random number in the range 0 - @[max]-1. |
*! |
*! @seealso |
*! @[random_seed()] |
*/ |
|
PMOD_EXPORT |
PIKEFUN int random(int i) |
{ |
if(i <= 0) RETURN 0; |
#if SIZEOF_INT_TYPE > 4 |
if(i >> 31) { |
unsigned INT_TYPE a = my_rand(); |
unsigned INT_TYPE b = my_rand(); |
RETURN (INT_TYPE)(((a<<32)|b) % i); |
} |
#endif |
RETURN my_rand() % i; |
} |
|
PMOD_EXPORT |
PIKEFUN float random(float f) |
{ |
if(f<=0.0) RETURN 0.0; |
#define N 1048576 |
RETURN f * (my_rand()%N/((float)N)) + |
f * (my_rand()%N/( ((float)N) * ((float)N) )); |
|
} |
|
/*! @decl mixed random(array|multiset x) |
*! Returns a random element from @[x]. |
*/ |
|
PMOD_EXPORT |
PIKEFUN mixed random(array a) |
rawtype tFunc(tArr(tSetvar(0,tMix)),tVar(0)); |
{ |
if(!a->size) |
SIMPLE_BAD_ARG_ERROR("random", 1, "array with elements in it"); |
push_svalue(a->item + (my_rand() % a->size)); |
stack_swap(); |
pop_stack(); |
} |
|
PMOD_EXPORT |
PIKEFUN mixed random(multiset m) |
rawtype tFunc(tSet(tSetvar(1,tMix)),tVar(1)); |
{ |
if(multiset_is_empty (m)) |
SIMPLE_BAD_ARG_ERROR("random", 1, "multiset with elements in it"); |
if (multiset_indval (m)) { |
ptrdiff_t nodepos = multiset_get_nth (m, my_rand() % multiset_sizeof (m)); |
push_multiset_index (m, nodepos); |
push_multiset_value (m, nodepos); |
sub_msnode_ref (m); |
f_aggregate (2); |
} |
else { |
push_multiset_index (m, multiset_get_nth (m, my_rand() % |
multiset_sizeof (m))); |
sub_msnode_ref (m); |
} |
stack_swap(); |
pop_stack(); |
} |
|
/*! @decl array random(mapping m) |
*! Returns a random index-value pair from the mapping. |
*/ |
|
PMOD_EXPORT |
PIKEFUN array random(mapping m) |
{ |
struct mapping_data *md=m->data; |
size_t bucket, count; |
struct keypair *k; |
|
if(!m_sizeof(m)) |
SIMPLE_BAD_ARG_ERROR("random", 1, "mapping with elements in it"); |
|
/* Find a random, nonempty bucket */ |
bucket=my_rand() % md->hashsize; |
while(! md->hash[bucket] ) |
if(++bucket > (size_t)md->hashsize) |
bucket=0; |
|
/* Count entries in bucket */ |
count=0; |
for(k=md->hash[bucket];k;k=k->next) count++; |
|
/* Select a random entry in this bucket */ |
count = my_rand() % count; |
k=md->hash[bucket]; |
while(count-- > 0) k=k->next; |
|
/* Push result and return */ |
push_svalue(&k->ind); |
push_svalue(&k->val); |
f_aggregate(2); |
stack_swap(); |
pop_stack(); |
} |
|
#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 */ |
} |
|
#if defined(PIKE_DEBUG) && defined(PIKE_PORTABLE_BYTECODE) |
|
/*! @decl void disassemble(function fun) |
*! @belongs Debug |
*! |
*! Disassemble a Pike function to @[Stdio.stderr]. |
*! |
*! @note |
*! This function is only available if the Pike runtime |
*! has been compiled with debug enabled. |
*/ |
PIKEFUN void _disassemble(function fun) |
{ |
if ((TYPEOF(*fun) != T_FUNCTION) || |
(SUBTYPEOF(*fun) == FUNCTION_BUILTIN)) { |
fprintf(stderr, |
"Disassembly only supported for functions implemented in Pike.\n"); |
} else if (!fun->u.object->prog) { |
fprintf(stderr, "Function in destructed object.\n"); |
} else { |
int f = SUBTYPEOF(*fun); |
struct reference *ptr = PTR_FROM_INT(fun->u.object->prog, f); |
struct program *p = PROG_FROM_PTR(fun->u.object->prog, ptr); |
struct identifier *id = p->identifiers + ptr->identifier_offset; |
if (id->func.offset >= 0) { |
struct pike_string *tripples = |
p->strings[read_program_data(p->program + id->func.offset, -1)]; |
switch(tripples->size_shift) { |
#define CASE(SHIFT) \ |
case SHIFT: \ |
{ \ |
PIKE_CONCAT(p_wchar, SHIFT) *str = \ |
PIKE_CONCAT(STR, SHIFT)(tripples); \ |
int i=0; \ |
while(i < tripples->len) { \ |
fprintf(stderr, "@@@ %d: %s, %d, %d\n", \ |
i/3, \ |
instrs[*str - F_OFFSET]. \ |
name, \ |
str[1], str[2]); \ |
str += 3; \ |
i += 3; \ |
} \ |
} \ |
break |
CASE(0); |
CASE(1); |
CASE(2); |
#undef CASE |
} |
} else { |
fprintf(stderr, "Prototype.\n"); |
} |
} |
pop_n_elems(args); |
push_int(0); |
} |
|
#endif /* PIKE_DEBUG && PIKE_PORTABLE_BYTECODE */ |
|
/* |
* Backtrace handling. |
*/ |
|
/*! @module Pike |
*/ |
|
/*! @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 == -1. */ |
CVAR struct pike_string *filename; |
CVAR INT_TYPE lineno; |
|
INIT |
{ |
THIS->pc = NULL; |
THIS->lineno = -1; |
THIS->filename = NULL; |
} |
|
EXIT |
gc_trivial; |
{ |
if (THIS->filename) { |
free_string(THIS->filename); |
THIS->filename = NULL; |
} |
THIS->pc = NULL; |
THIS->lineno = -1; |
} |
|
/* 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) |
{ |
INT_TYPE res = (t == findstring("array")); |
pop_n_elems(args); |
push_int(res); |
} |
|
static void fill_in_file_and_line() |
{ |
struct pike_string *file = NULL; |
assert (THIS->lineno == -1); |
|
if (THIS->pc && THIS->prog) { |
file = low_get_line(THIS->pc, THIS->prog, &THIS->lineno); |
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; |
} |
} |
|
/*! @decl string _sprintf(int c, mapping|void opts) |
*/ |
PIKEFUN string _sprintf(int c, mapping|void opts) |
{ |
pop_n_elems(args); |
|
if (c != 'O') { |
push_undefined (); |
return; |
} |
|
push_text("backtrace_frame("); |
|
if (THIS->lineno == -1) fill_in_file_and_line(); |
|
if (THIS->filename) { |
ref_push_string(THIS->filename); |
push_text(":"); |
push_int(THIS->lineno); |
push_text(", "); |
f_add(4); |
} else { |
push_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_text("(), "); |
f_add(2); |
} else { |
free_svalue(&THIS->_fun); |
SET_SVAL(THIS->_fun, PIKE_T_INT, NUMBER_DESTRUCTED, integer, 0); |
push_text("destructed_function(), "); |
} |
} else if (TYPEOF(THIS->_fun) == PIKE_T_PROGRAM) { |
/* FIXME: Use the master? */ |
push_text("program(), "); |
} else if (TYPEOF(THIS->_fun) == PIKE_T_STRING) { |
push_svalue(&THIS->_fun); |
push_text("(), "); |
f_add(2); |
} else { |
push_text("destructed_function(), "); |
} |
|
if (THIS->args) { |
push_text("Args: "); |
push_int(THIS->args->size); |
f_add(2); |
} else { |
push_text("No args"); |
} |
push_text(")"); |
f_add(5); |
} |
|
/*! @decl int(3..) _sizeof() |
*/ |
PIKEFUN int(3..) _sizeof() |
{ |
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) |
{ |
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->`[]", Pike_sp-args, args, NULL, Pike_sp-args, |
"Indexing with negative index (%"PRINTPIKEINT"d)\n", index); |
else if (index >= numargs) |
index_error("pike_frame->`[]", Pike_sp-args, 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; |
} |
|
for (i = index; i <= end; i++) { |
switch(i) { |
case 0: /* Filename */ |
if (THIS->lineno == -1) fill_in_file_and_line(); |
if (THIS->filename) |
ref_push_string(THIS->filename); |
else |
push_int(0); |
break; |
case 1: /* Linenumber */ |
if (THIS->lineno == -1) fill_in_file_and_line(); |
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("`[]", Pike_sp-args, args, 1, |
"int(0..)", Pike_sp-args, |
"Bad argument 1 to backtrace_frame->`[](): " |
"Expected int(0..%d)\n", |
numargs + 2); |
} |
/* NOT_REACHED */ |
break; |
} |
} |
if (end_or_none) |
f_aggregate(1 + end - index); |
} |
|
/*! @decl mixed `[]=(int index, mixed value) |
*/ |
PIKEFUN mixed `[]=(int index, mixed value) |
{ |
INT32 numargs = 3; |
|
if (THIS->args) |
numargs += THIS->args->size; |
|
if ((index < -numargs) || (index >= numargs)) |
index_error("pike_frame->`[]=", Pike_sp-args, 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; |
} |
|
switch(index) { |
case 0: /* Filename */ |
if (THIS->lineno == -1) fill_in_file_and_line(); |
if (TYPEOF(*value) != PIKE_T_STRING) { |
if ((TYPEOF(*value) != PIKE_T_INT) || |
(value->u.integer)) { |
SIMPLE_BAD_ARG_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 (THIS->lineno == -1) fill_in_file_and_line(); |
if (TYPEOF(*value) != PIKE_T_INT) |
SIMPLE_BAD_ARG_ERROR("`[]=", 2, "int(1..)"); |
THIS->lineno = value->u.integer; |
break; |
|
case 2: /* Function */ |
if (THIS->lineno == -1) fill_in_file_and_line(); |
assign_svalue(&THIS->_fun, value); |
break; |
default: /* Arguments */ |
assign_svalue(THIS->args->item + index - 3, value); |
break; |
} |
stack_swap(); |
pop_stack(); |
} |
|
}; |
|
/*! @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 "float_size" |
*! The number of bits in the native floating point type. |
*! Usually @expr{32@} or @expr{64@}. |
*! @member int(0..1) "auto_bignum" |
*! Present if integers larger than the native size are automatically |
*! converted into bignums. |
*! @endmapping |
*/ |
PIKEFUN mapping(string:int|string) get_runtime_info() |
optflags OPT_TRY_OPTIMIZE; |
{ |
pop_n_elems(args); |
push_text("bytecode_method"); |
push_text(PIKE_BYTECODE_METHOD_NAME); |
push_text("abi"); |
push_int(sizeof(void *) * 8); |
push_text("native_byteorder"); |
push_int(PIKE_BYTEORDER); |
push_text("int_size"); |
push_int(sizeof(INT_TYPE) * 8); |
push_text("float_size"); |
push_int(sizeof(FLOAT_TYPE) * 8); |
push_text("auto_bignum"); |
push_int(1); |
f_aggregate_mapping(6*2); |
} |
|
/*! @endmodule |
*/ |
|
void low_backtrace(struct Pike_interpreter_struct *i) |
{ |
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 = low_clone(backtrace_frame_program); |
struct backtrace_frame_struct *bf; |
struct identifier *function = NULL; |
|
call_c_initializers(o); |
|
size--; |
|
SET_SVAL(res->item[size], PIKE_T_OBJECT, 0, object, o); |
|
bf = OBJ2_BACKTRACE_FRAME(o); |
|
if ((bf->prog = f->context->prog)) { |
add_ref(bf->prog); |
bf->pc = f->pc; |
} |
|
SET_SVAL(bf->_fun, PIKE_T_INT, NUMBER_DESTRUCTED, integer, 0); |
|
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 = DO_NOT_WARN((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 = DO_NOT_WARN((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); |
} |
} |
} |
} |
res->type_field = BIT_OBJECT; |
/* NOTE: res has already been pushed on the stack. */ |
} |
|
/*! @decl array(Pike.BacktraceFrame) backtrace() |
*! |
*! FIXME: This documentation is not up to date! |
*! |
*! Get a description of the current call stack. |
*! |
*! The description is returned as an array with one entry for each call |
*! frame on the stack. |
*! |
*! Each entry has this 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 current call frame will be last in the array. |
*! |
*! @note |
*! Please note that the frame order may be reversed in a later version |
*! (than 7.1) of Pike to accommodate for deferred backtraces. |
*! |
*! 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). |
*! |
*! @seealso |
*! @[catch()], @[throw()] |
*/ |
PMOD_EXPORT |
PIKEFUN array(mixed) backtrace() |
efun; |
optflags OPT_EXTERNAL_DEPEND; |
{ |
low_backtrace(& Pike_interpreter); |
} |
|
/*! @module String |
*/ |
|
/*! @class Buffer |
*! A buffer, used for building strings. It's |
*! conceptually similar to a string, but speed-optimised for growing |
*! strings. |
*! |
*! You do not need to use this class unless you add very many |
*! strings together, or very large strings. |
*! |
*! @example |
*! For the fastest possible operation, write your code like this: |
*! |
*! @code |
*! String.Buffer b = String.Buffer( ); |
*! |
*! function add = b->add; |
*! |
*! .. call add several times in code ... |
*! |
*! string result = b->get(); // also clears the buffer |
*! @endcode |
*/ |
PIKECLASS Buffer |
{ |
CVAR struct string_builder str; /* Can be empty */ |
CVAR char* buffer; /* Current start of the buffer */ |
CVAR size_t offset; /* Characters consumed but still in the buffer */ |
CVAR size_t len; /* Number of characters available to be read */ |
CVAR unsigned shift; /* Current size_shift of the buffer */ |
CVAR unsigned readonly; /* If the buffer is marked readonly */ |
CVAR size_t initial; /* Initial reserved (unfilled) buffersize */ |
|
CVAR struct object *source; |
/* CVAR program *error_mode; */ |
CVAR unsigned error_mode; |
|
static INLINE unsigned min_magnitude(const unsigned c) |
{ |
return c<256 ? 0 : c<65536 ? 1 : 2; |
} |
|
static int buffer_range_error(int howmuch) |
{ |
if( THIS->error_mode ) |
Pike_error("Trying to read %d outside allowed range\n", howmuch); |
return 0; |
} |
|
static struct pike_string*buffer_mkspace(ptrdiff_t diff, unsigned shift) |
{ |
struct Buffer_struct *str = THIS; |
struct pike_string *s = str->str.s; |
|
if (str->readonly) |
Pike_error("Attempt to modify readonly buffer.\n"); |
|
if (!s || s->refs!=1) { |
shift |= str->shift; |
shift = shift & ~(shift >> 1); |
init_string_builder_alloc(&str->str, str->len+diff, shift); |
generic_memcpy(MKPCHARP(str->str.s->str, shift), |
MKPCHARP(str->buffer, str->shift), str->len); |
if (s) |
sub_ref(s); |
str->offset = 0; |
goto fixptr; |
} else { |
if (!(s->flags & STRING_NOT_SHARED)) |
unlink_pike_string (s); |
string_build_mkspace(&str->str, diff, shift); |
fixptr: |
s = str->str.s; |
str->buffer = s->str + (str->offset << (str->shift = s->size_shift)); |
} |
|
if( str->source ) { |
free_object( str->source ); |
str->source = 0; |
} |
|
return s; |
} |
|
static void buffer_lock() |
{ |
THIS->readonly++; |
} |
|
static void buffer_release() |
{ |
THIS->readonly--; |
} |
|
PIKEFUN int _size_object() |
{ |
struct Buffer_struct *str = THIS; |
unsigned retval = 0; |
|
if (str->str.s) { |
retval = str->str.s->refs; |
retval = (str->str.malloced+retval-1)/retval; |
} |
push_int( retval ); |
} |
|
/*! @decl void set_error_mode(int m) |
*! |
*! Set the error mode of this buffer to @[m]. |
*! |
*! If true operations that would normally return 0 |
*! (like trying to read too much) will instead thrown an error. |
*! |
*! This is useful when parsing received data, you do not have to |
*! verify that each and every read operation suceeds. |
*! |
*! However, the non-error mode is more useful when checking to see |
*! if a packet/segment/whatever has arrived. |
*! |
*! The thrown error object will have the constant buffer_error set |
*! to a non-false value. |
*! |
*! @example |
*! @code |
*! void read_callback(int i, string new_data) |
*! { |
*! inbuffer->add( new_data ); |
*! |
*! while( String.Buffer packet = inbuffer->read_hbuffer(2) ) |
*! { |
*! packet->set_error_mode(Buffer.THROW_ERROR); |
*! if( mixed e = catch( handle_packet( packet ) ) ) |
*! if( e->buffer_error ) |
*! protocol_error(); // illegal data in packet |
*! else |
*! throw(e); // the other code did something bad |
*! } |
*! } |
*! |
*! |
*! void handle_packet( String.Buffer pack ) |
*! { |
*! switch( pack->read_int8() ) |
*! { |
*! ... |
*! case HEADER_FRAME: |
*! int num_headers = pack->read_int32(); |
*! for( int i = 0; i<num_headers; i++ ) |
*! headers[pack->read_hstring(2)] = pack->read_hstring(2); |
*! ... |
*! } |
*! } |
*! @endcode |
*/ |
PIKEFUN void set_error_mode(int m) |
{ |
/* FIXME: buffer_error and Buffer.THROW_ERROR still need to |
be implemented */ |
THIS->error_mode = m; |
pop_stack(); |
} |
|
/*! @decl protected bool range_error( int howmuch ) |
*! |
*! This function is called when an attempt is made to read out of bounds. |
*! The default implementation simply returnns 0. |
*! |
*! Override this function to change the behaviour |
*! |
*! The argument @[howmuch] indicates how much data is needed: |
*! |
*! @dl |
*! @item int(1..) howmuch |
*! Need @[howmuch] bytes more |
*! @item int(0..0) howmuch |
*! The amount of data needed is not certain. |
*! This most often happens when @[sscanf] or @[read_json] is used |
*! @item int(..-1) howmuch=... |
*! Tried to @[unread] X bytes. There is usually no way to satisfy |
*! the requested range. |
*! |
*! Fill the @[unread()] buffer by passing data to it, then |
*! @[consume()] it again. |
*! @enddl |
*! |
*! @returns |
*! |
*! @[true] if the operation should be retried, @[false] otherwise. |
*! |
*! Do not return true unless you have added data to the buffer, |
*! doing so could result in an infinite loop (since no data is |
*! added, the range_error will be called again immediately). |
*/ |
PIKEFUN int(0..1) range_error( int howmuch ) |
flags ID_PROTECTED; |
{ |
/* Default: throw error if so desired, otherwise return 0. */ |
pop_n_elems(args); |
push_int(0); |
} |
|
struct sysmem { |
unsigned char *p; |
size_t size; |
}; |
|
static struct program *shm_program; |
static struct sysmem *system_memory(struct object *o) |
{ |
if( !shm_program ) { |
push_text("System.Memory"); |
SAFE_APPLY_MASTER("resolv", 1); |
shm_program = program_from_svalue(Pike_sp - 1); |
Pike_sp--; |
if (!shm_program) |
return 0; |
} |
return o->prog == shm_program ? get_storage( o, shm_program ) : 0; |
}; |
|
static size_t buffer_findsize(unsigned args, struct svalue*pargs, |
unsigned *pshift) |
{ |
unsigned j,shift = 0; |
size_t sum = 0; |
|
for( j=args ; j-- ; ++pargs) |
switch(TYPEOF(*pargs)) { |
case PIKE_T_STRING: |
{ |
struct pike_string *a = pargs->u.string; |
sum += a->len; |
shift |= a->size_shift; |
break; |
} |
case PIKE_T_INT: |
sum++; |
shift |= min_magnitude(pargs->u.integer); |
break; |
case PIKE_T_ARRAY: |
{ |
struct array *arp = pargs->u.array; |
DECLARE_CYCLIC(); |
|
if (BEGIN_CYCLIC(Pike_fp->current_object, arp)) |
Pike_error("Attempt to append a cyclic array to a buffer.\n"); |
|
sum += buffer_findsize(arp->size, arp->item, &shift); |
|
END_CYCLIC(); |
break; |
} |
case PIKE_T_OBJECT: |
{ |
struct sysmem *sm; |
if(pargs->u.object->prog == Buffer_program) { |
struct Buffer_struct *bstr = OBJ2_BUFFER(pargs->u.object); |
sum += bstr->len; |
shift |= bstr->shift; |
break; |
} else if (sm = system_memory(pargs->u.object)) { |
sum += sm->size; |
break; |
} |
} |
default: |
SIMPLE_BAD_ARG_ERROR("append", args-j, |
"string|int|String.Buffer|System.Memory" |
"|array(string|int|String.Buffer|System.Memory)"); |
} |
*pshift |= shift; |
return sum; |
} |
|
static char* buffer_memcpy(char*dest, unsigned shift, |
unsigned args, struct svalue*pargs) |
{ |
unsigned j; |
|
for( j=args ; j-- ; ++pargs) { |
PCHARP b; |
size_t blen; |
|
switch(TYPEOF(*pargs)) { |
case PIKE_T_STRING: |
{ |
struct pike_string *bs; |
b = MKPCHARP_STR(bs = pargs->u.string); |
blen = bs->len; |
break; |
} |
case PIKE_T_INT: |
{ |
unsigned ch = pargs->u.integer; |
switch( shift ) { |
case 0: *(p_wchar0*)dest = ch; break; |
case 1: *(p_wchar1*)dest = ch; break; |
default: *(p_wchar2*)dest = ch; break; |
} |
blen = 1; |
goto inc; |
} |
case PIKE_T_ARRAY: |
{ |
struct array *arp = pargs->u.array; |
dest = buffer_memcpy(dest, shift, arp->size, arp->item); |
continue; |
} |
default: |
{ |
struct sysmem *sm; |
if(pargs->u.object->prog == Buffer_program) { |
struct Buffer_struct *bstr = OBJ2_BUFFER(pargs->u.object); |
b = MKPCHARP(bstr->buffer, bstr->shift); |
blen = bstr->len; |
break; |
} else if (sm = system_memory(pargs->u.object)) { |
b = MKPCHARP(sm->p, 0); |
blen = sm->size; |
break; |
} |
} |
} |
generic_memcpy(MKPCHARP(dest, shift), b, blen); |
inc: dest += blen<<shift; |
} |
return dest; |
} |
|
/*! @decl Buffer appendat(int(0..) pos, |
*! string|int|Buffer|object|array(string|int|Buffer|object) ... data) |
*! |
*! Adds @[data] to the buffer, starting at position @[pos]. |
*! It overwrites existing content at that offset, never truncates the |
*! buffer, possibly extends the buffer if the new content does not fit. |
*! |
*! @returns |
*! Returns the buffer itself. |
*! |
*! @note |
*! If the starting position @[pos] extends beyond the end of the |
*! current buffer content, the gap will be filled with NUL-characters. |
*! |
*! @seealso |
*! @[append()], @[add()] |
*/ |
PIKEFUN Buffer appendat(int(0..) pos, |
string|int|Buffer|object|array(string|int|Buffer|object) ... data ) |
{ |
struct Buffer_struct *str = THIS; |
struct svalue*pargs; |
struct pike_string *s; |
ptrdiff_t sum; |
int j; |
unsigned shift = 0; |
|
if (pos < 0) |
SIMPLE_BAD_ARG_ERROR("appendat", 1, "int(0..)"); |
|
args--; |
sum = pos + buffer_findsize(args, Pike_sp-args, &shift); |
|
shift |= str->shift; |
shift = shift & ~(shift >> 1); |
j = sum - str->len; |
if (j>0) { |
if (str->initial > j && str->initial > str->str.malloced) |
j = str->initial; |
s = buffer_mkspace(j, shift); |
} |
else |
s = buffer_mkspace(0, shift); |
/* We know it will be a string that really is this wide. */ |
str->str.known_shift = shift; |
|
if (str->len < pos) /* Clear the padding */ |
MEMSET(str->buffer + (str->len << shift), 0, (pos - str->len) << shift); |
|
{ |
char*dest = buffer_memcpy(s->str+((pos+str->offset)<<shift), shift, |
args, Pike_sp-args); |
|
if (s->len < (pos = (dest - s->str)>>shift)) |
s->len = pos; |
if (str->len < (pos -= str->offset)) |
str->len = pos; |
} |
|
pop_n_elems( args+1 ); |
ref_push_object(Pike_fp->current_object); |
} |
|
/*! @decl Buffer append( |
*! string|int|Buffer|object|array(string|int|Buffer|object) ... data ) |
*! |
*! Adds @[data] to the buffer. |
*! |
*! @returns |
*! Returns the buffer itself. |
*! |
*! @seealso |
*! @[appendat()], @[add()] |
*/ |
PIKEFUN Buffer append( |
string|int|Buffer|object|array(string|int|Buffer|object) ... data ) |
{ |
push_int(THIS->len); |
stack_revroll(++args); |
f_Buffer_appendat(args); |
} |
|
/*! @decl int add( |
*! string|int|Buffer|object|array(string|int|Buffer|object) ... data ) |
*! |
*! Adds @[data] to the buffer. |
*! |
*! @returns |
*! Returns the size of the buffer. |
*! |
*! @seealso |
*! @[appendat()] |
*/ |
PIKEFUN int add( |
string|int|Buffer|object|array(string|int|Buffer|object) ... data ) |
{ |
f_Buffer_append(args); |
pop_stack(); |
push_int(THIS->len); |
} |
|
/*! @decl string get_copy() |
*! |
*! Get the data from the buffer. Significantly slower than @[get], |
*! but does not clear the buffer. |
*! |
*! @seealso |
*! @[get()] |
*/ |
PIKEFUN string get_copy() |
{ |
struct Buffer_struct *str = THIS; |
|
if(str->len) { |
struct pike_string *s; |
s = make_shared_binary_pcharp(MKPCHARP(str->buffer, str->shift), |
str->len); |
if( Pike_fp->current_object->flags & OBJECT_CLEAR_ON_EXIT ) |
s->flags |= STRING_CLEAR_ON_EXIT; |
push_string(s); |
} else |
push_empty_string(); |
} |
|
/*! @decl string|void read(void|int n) |
*! |
*! Read and remove @[n] characters of data from the beginning |
*! of the buffer. |
*! If there are less than @[n] characters available, nothing |
*! will be removed and the function will return 0. |
*! |
*! When called without arguments, this is equivalent to @[get()]. |
*! |
*! @seealso |
*! @[get()], @[clear()] |
*/ |
PIKEFUN string|void read( void|int(0..) n ) |
{ |
struct Buffer_struct *str = THIS; |
struct pike_string *s; |
if (args) { |
int want = n->u.integer; |
pop_stack(); |
while (want<0 || want>str->len) |
if (!buffer_range_error( want )) { |
push_undefined(); |
return; |
} |
if (want != str->len) { |
push_string(make_shared_binary_pcharp( |
MKPCHARP(str->buffer, str->shift), want)); |
str->buffer += want<<=str->shift; |
str->offset += want; |
str->len -= want; |
return; |
} |
} |
buffer_mkspace(0, 0); |
if (str->offset) { |
memmove( str->str.s->str, str->buffer, str->len<<str->shift); |
str->str.s->len = str->len; |
} |
s = finish_string_builder( &str->str ); |
if( Pike_fp->current_object->flags & OBJECT_CLEAR_ON_EXIT ) |
s->flags |= STRING_CLEAR_ON_EXIT; |
push_string(s); |
str->str.s = 0; |
str->len = 0; |
} |
|
/*! @decl string get() |
*! |
*! Get the data from the buffer. |
*! |
*! @note |
*! This will clear the data in the buffer |
*! |
*! @seealso |
*! @[get_copy()], @[clear()] |
*/ |
PIKEFUN string get( ) |
{ |
f_Buffer_read(0); |
} |
|
/*! @decl void create(void|int(1..) initial_size) |
*! @decl void create(string|Buffer|System.Memory value) |
*! |
*! Initializes a new buffer. |
*! |
*! If no @[initial_size] is specified, 256 is used. If you |
*! know approximately how big the buffer will be, you can optimize |
*! the operation of @[add()] (slightly) by passing the size to this |
*! function. |
*/ |
PIKEFUN void create( string|object|int|void value ) |
{ |
struct Buffer_struct *str = THIS; |
struct pike_string *s; |
struct sysmem *sm; |
|
if( str->buffer ) |
Pike_error("Can not initialise twice.\n"); |
str->initial = 256; |
if (!args) |
goto definit; |
switch(TYPEOF(*value)) { |
default: |
Pike_error("Unsupported object type\n"); |
case PIKE_T_INT: |
str->initial = MAX(value->u.integer, 1 ); |
definit: |
init_string_builder_alloc(&str->str, str->initial, 0); |
str->buffer = str->str.s->str; |
break; |
case PIKE_T_STRING: |
s = value->u.string; |
if (s->refs == 1) |
/* Unlink the string and use it as buffer directly. */ |
unlink_pike_string (s); |
else |
add_ref(s); |
str->str.s = s; |
str->len = str->str.malloced = s->len; |
str->shift = str->str.known_shift = s->size_shift; |
str->buffer = s->str; |
break; |
case PIKE_T_OBJECT: |
if (value->u.object->prog == Buffer_program) { |
struct Buffer_struct *str2 = OBJ2_BUFFER(value->u.object); |
str->buffer = str2->buffer; |
str->offset = str2->offset; |
str->len = str2->len; |
str->shift = str2->shift; |
if (str->source = str2->source) |
add_ref(str->source); |
str->str = str2->str; |
if (str->str.s) |
add_ref(str->str.s); |
} |
else if (sm = system_memory(value->u.object)) { |
add_ref(str->source = value->u.object); |
str->buffer = sm->p; |
str->len = sm->size; |
} |
break; |
} |
} |
|
/*! @decl string _sprintf( int flag, mapping flags ) |
*! It is possible to @[sprintf] a String.Buffer object |
*! as @tt{%s@} just as if it were a string. |
*/ |
PIKEFUN string _sprintf( int flag, mapping flags ) |
{ |
pop_n_elems( args ); |
switch( flag ) |
{ |
case 'O': |
{ |
struct Buffer_struct *str = THIS; |
push_text( "Buffer(%d-%d /* %d */)" ); |
push_int(str->len); |
push_int(str->offset); |
push_int(str->str.malloced); |
f_sprintf( 4 ); |
break; |
} |
case 's': |
if( Pike_fp->current_object->refs != 1 |
|| THIS->str.s->refs != 1) |
f_Buffer_get_copy( 0 ); |
else |
f_Buffer_read( 0 ); |
break; |
|
case 't': |
push_string(make_shared_binary_string("Buffer",6)); |
break; |
|
default: |
push_undefined(); |
} |
} |
|
/*! @decl string _encode() |
*! @decl void _decode(string x) |
*! |
*! Encode and decode @[String.Buffer] objects. |
*! Only the buffer data is kept, no other state is saved. |
*/ |
PIKEFUN string _encode() |
{ |
f_Buffer_read( 0 ); |
} |
|
PIKEFUN void _decode(string x) |
{ |
if( THIS->str.s->len ) |
Pike_error("Cannot initialise twice.\n"); |
f_Buffer_add( 1 ); |
} |
|
/*! @decl string|int cast( string type ) |
*! It is possible to cast a String.Buffer object to |
*! a @expr{string@} and an @expr{int@}. |
*/ |
PIKEFUN string|int cast( string type ) |
flags ID_PROTECTED; |
{ |
if( type == literal_string_string ) |
{ |
pop_stack(); |
if( Pike_fp->current_object->refs != 1 |
|| THIS->str.s->refs != 1) |
f_Buffer_get_copy( 0 ); |
else |
f_Buffer_read( 0 ); |
return; |
} |
|
if( type == literal_int_string ) |
{ |
struct Buffer_struct *str = THIS; |
pop_stack(); |
if( Pike_fp->current_object->refs != 1 |
|| THIS->str.s->refs != 1) |
f_Buffer_get_copy( 0 ); |
else |
f_Buffer_read( 0 ); |
o_cast_to_int( ); |
return; |
} |
|
pop_stack(); |
push_undefined(); |
} |
|
/*! @decl int `[](int index) |
*/ |
PIKEFUN int `[](int index) |
{ |
struct Buffer_struct *str = THIS; |
unsigned len = str->len; |
|
if (index<0) |
index += len; |
if (index<0 || index>=len) |
index_error("Buffer->`[]", Pike_sp, args, NULL, Pike_sp, |
"Index %"PRINTPIKEINT"d is out of array range 0..%d,\n", |
index, len-1); |
RETURN generic_extract(str->buffer, str->shift, index); |
} |
|
/*! @decl Buffer `[..](int start, int start_type, int end, int end_type) |
*/ |
PIKEFUN Buffer `[..](int start, int start_type, int end, int end_type) |
{ |
struct Buffer_struct *str = THIS; |
size_t len = str->len; |
unsigned shift = str->shift; |
struct pike_string *s; |
struct object *res; |
struct Buffer_struct *str2; |
|
pop_n_elems(args); |
if (start_type == INDEX_FROM_END) |
start = len-1-start; |
if (end_type != INDEX_FROM_BEG) |
end = len-1-end; |
res = fast_clone_object( Buffer_program ); |
str2 = OBJ2_BUFFER( res ); |
if (start < 0) |
start = 0; |
if (end >= len) |
end = len-1; |
|
if (str->str.s) { |
add_ref(str->str.s); |
str2->str = str->str; |
} |
str2->offset = str->offset+start; |
str2->len = end-start+1; |
str2->buffer = str->buffer+(start<<shift); |
str2->shift = str->shift; |
str2->initial = str->initial; |
if( (Pike_fp->current_object->flags & OBJECT_CLEAR_ON_EXIT) ) |
res->flags |= OBJECT_CLEAR_ON_EXIT; |
push_object(res); |
} |
|
/*! @decl int `[]=(int index, int ch) |
*/ |
PIKEFUN int `[]=(int index, int ch) |
{ |
struct Buffer_struct *str = THIS; |
struct pike_string *s; |
size_t len = str->len; |
|
pop_n_elems(args); |
|
if (index<0) |
index += len; |
if (index<0 || index>=len) |
index_error("Buffer->`[]=", Pike_sp, args, NULL, Pike_sp, |
"Index %"PRINTPIKEINT"d is out of array range 0..%d,\n", |
index, len-1); |
len = 0; |
if ((unsigned)ch >= 256) |
switch(str->shift) { |
case 0: |
if ((unsigned)ch < 65536) |
len = 1; |
else { |
case 1: |
if((unsigned)ch >= 65536) |
len = 2; |
else |
break; |
} |
str->str.known_shift = len; |
} |
s = buffer_mkspace(0, len); |
low_set_index(s, index+str->offset, ch); |
push_int(ch); |
} |
|
/*! @decl Buffer `+=( string|Buffer ... what ) |
*/ |
PIKEFUN Buffer `+=( string|Buffer ... what ) |
rawtype tFunc(tOr(tString, tObjIs_BUFFER), tObjIs_BUFFER); |
{ |
f_Buffer_add( args ); |
pop_stack(); |
ref_push_object(Pike_fp->current_object); |
} |
|
/*! @decl Buffer `+( string|Buffer ... what ) |
*/ |
PIKEFUN Buffer `+( string|Buffer ... what ) |
rawtype tFunc(tOr(tString, tObjIs_BUFFER), tObjIs_BUFFER); |
{ |
struct Buffer_struct *str = THIS, *str2; |
struct pike_string *s = str->str.s; |
|
if( Pike_fp->current_object->refs != 1 |
|| s->refs != 1) { |
struct Buffer_struct *str2; |
unsigned shift = str->shift; |
size_t len = str->len; |
struct object *res = fast_clone_object( Buffer_program ); |
str2 = OBJ2_BUFFER( res ); |
str2->initial = str->initial; |
str2->shift = shift; |
init_string_builder_alloc (&str2->str, MAX(len, str2->initial), shift); |
str2->str.s->len = str2->len = len; |
memcpy(str2->buffer = str2->str.s->str, str->buffer, len<<shift); |
if( (Pike_fp->current_object->flags & OBJECT_CLEAR_ON_EXIT) ) |
res->flags |= OBJECT_CLEAR_ON_EXIT; |
apply( res, "add", args ); |
pop_stack(); |
ref_push_object(res); |
} else |
f_Buffer_cq__backtick_add_eq(args); |
} |
|
/*! @decl Buffer ``+( string ... what ) |
*/ |
PIKEFUN Buffer ``+( string ... what ) |
rawtype tFunc(tString, tObjIs_BUFFER); |
{ |
struct Buffer_struct *str = THIS, *str2; |
|
if( Pike_fp->current_object->refs != 1 |
|| str->str.s->refs != 1) { |
struct Buffer_struct *str2; |
struct object *res = fast_clone_object( Buffer_program ); |
str2 = OBJ2_BUFFER( res ); |
str2->initial = str->initial; |
if( (Pike_fp->current_object->flags & OBJECT_CLEAR_ON_EXIT) ) |
res->flags |= OBJECT_CLEAR_ON_EXIT; |
ref_push_object(Pike_fp->current_object); |
apply( res, "add", args+1 ); |
} else { |
char*dest; |
struct svalue*pargs = Pike_sp-args; |
int j,shift = 0; |
size_t sum = 0, len; |
|
pargs = Pike_sp; |
j = args; |
do { |
struct pike_string *a = --pargs->u.string; |
sum += a->len; |
shift |= a->size_shift; |
} while(--j); |
|
shift |= str->shift; |
shift = shift & ~(shift >> 1); |
buffer_mkspace(sum - str->str.s->len + (len = str->len), shift); |
memmove(str->str.s->str+(sum<<shift), str->buffer, len<<shift); |
|
str->buffer = dest = str->str.s->str; |
str->offset = 0; |
str->str.s->len = str->len = len + sum; |
|
do { |
struct pike_string *bs = pargs++->u.string; |
size_t blen = bs->len; |
generic_memcpy(MKPCHARP(dest, shift), MKPCHARP_STR( bs ), blen); |
dest += blen<<shift; |
} while(--args); |
|
ref_push_object(Pike_fp->current_object); |
} |
} |
|
static int buffer_cmp(struct svalue*other, unsigned stricttype) |
{ |
struct Buffer_struct *str = THIS; |
struct pike_string *s2; |
PCHARP b; |
ptrdiff_t blen; |
|
switch(TYPEOF(*other)) { |
case PIKE_T_STRING: |
b = MKPCHARP_STR(s2 = other->u.string); |
blen = s2->len; |
goto runcmp; |
case PIKE_T_OBJECT: |
if (other->u.object->prog == Buffer_program) { |
struct Buffer_struct *str2 = OBJ2_BUFFER(other->u.object); |
b = MKPCHARP(str2->buffer, str2->shift); |
blen = str2->len; |
runcmp: return my_quick_pcharpcmp( |
MKPCHARP(str->buffer, str->shift), str->len, b, blen); |
} |
} |
if(stricttype) |
Pike_error("Unsupported types in comparison.\n"); |
return 1; |
} |
|
/*! @decl int(0..1) `==( mixed other ) |
*! |
*! Is able to compare directly with strings and other Buffers. |
*/ |
PIKEFUN int(0..1) `==( mixed other ) |
rawtype tFunc(tMixed, tInt01); |
{ |
RETURN !buffer_cmp(other, 0); |
} |
|
/*! @decl int(0..1) `<( mixed other ) |
*! |
*! Is able to compare directly with strings and other Buffers. |
*/ |
PIKEFUN int(0..1) `<( mixed other ) |
rawtype tFunc(tMixed, tInt01); |
{ |
RETURN buffer_cmp(other, 1)<0; |
} |
|
/*! @decl int(0..1) `>( mixed other ) |
*! |
*! Is able to compare directly with strings and other Buffers. |
*/ |
PIKEFUN int(0..1) `>( mixed other ) |
rawtype tFunc(tMixed, tInt01); |
{ |
RETURN buffer_cmp(other, 1)>0; |
} |
|
static void buffer_putchar(int ch, unsigned mag) { |
struct Buffer_struct *str = THIS; |
void*dest; |
unsigned shift; |
pop_stack(); |
buffer_mkspace(1, mag); |
shift = str->shift; |
dest = str->buffer+(str->len++<<shift); |
switch( shift ) { |
case 0: *(p_wchar0*)dest = ch; break; |
case 1: *(p_wchar1*)dest = ch; break; |
default: *(p_wchar2*)dest = ch; break; |
} |
str->str.s->len = str->offset+str->len; |
ref_push_object(Pike_fp->current_object); |
} |
|
/*! @decl Buffer putchar( int char ) |
*! @decl Buffer add_byte( int(0..255) i ) |
*! @decl Buffer add_int8( int(-128..127) i ) |
*! |
*! Appends a single character at the end of the string. |
*! |
*! @returns |
*! Returns the buffer itself. |
*! |
*! @seealso |
*! @[add()] |
*/ |
PIKEFUN Buffer putchar(int ch) { |
buffer_putchar(ch, min_magnitude(ch)); |
} |
|
PIKEFUN int add_byte( int(0..255) i ) { |
buffer_putchar(i&255, 0); |
} |
|
PIKEFUN int add_int8( int(-128..127) i ) { |
buffer_putchar(i&255, 0); |
} |
|
/*! @decl Buffer add_int( int i, int(0..) width ) |
*! |
*! Appends a generic integer to the buffer as an (width*8)bit |
*! network byteorder number. |
*! |
*! @returns |
*! Returns the buffer itself. |
*! |
*! @seealso |
*! @[add_int32()] |
*/ |
PIKEFUN Buffer add_int( int i, int width ) { |
struct Buffer_struct *str = THIS; |
struct pike_string *s; |
ptrdiff_t len; |
|
s = buffer_mkspace(width, 0); |
len = str->offset+str->len; |
if(width>=0) |
for(str->len += width, s->len = len+=width; width--; i>>=8) |
low_set_index(s, --len, i&0xff); |
else |
for(str->len -= width, s->len = len-width; width++; i>>=8) |
low_set_index(s, len++, i&0xff); |
|
pop_n_elems(args); |
ref_push_object(Pike_fp->current_object); |
} |
|
static void buffer_add_bignum( struct object *i, int width ) |
{ |
struct Buffer_struct *str = THIS; |
struct pike_string *snum; |
|
push_int(256); |
apply( i, "digits", 1 ); |
|
snum = Pike_sp[-1].u.string; |
|
/* FIXME: little endian */ |
|
if( snum->len > width ) |
Pike_error("Number too large to store in %d bits\n", width*8); |
|
push_int (str->len + width-snum->len); |
stack_swap(); |
f_Buffer_appendat( 2 ); |
} |
|
PIKEFUN Buffer add_int( object i, int width ) { |
pop_stack(); /* width */ |
convert_stack_top_to_bignum(); |
buffer_add_bignum ( Pike_sp[-1].u.object, width); |
stack_pop_keep_top(); /* i */ |
} |
|
static void buffer_do_rewind_on_error( void*arg ) |
{ |
struct Buffer_struct *str = THIS; |
str->str.s->len = str->offset+(str->len = arg-(void*)str->buffer); |
} |
|
static void buffer_begin_rewind_on_error( ONERROR *x ) |
{ |
struct Buffer_struct *str = THIS; |
SET_ONERROR( (*x), buffer_do_rewind_on_error, str->buffer+str->len ); |
} |
|
static void buffer_end_rewind_on_error( ONERROR *x ) |
{ |
UNSET_ONERROR( (*x) ); |
} |
|
/*! @decl Buffer add_ints( array(int) integers, int(0..255) len ) |
*! |
*! Add the integers in the specified array, @[len] bytes per int. |
*! Equivalent to add_int( integers[*], len ) but faster. |
*/ |
PIKEFUN Buffer add_ints( array(int) a, int bpi ) |
{ |
struct Buffer_struct *str = THIS; |
size_t i,l = a->size; |
struct svalue *it = a->item; |
|
if( bpi < 0 ) |
Pike_error("Invalid int width\n"); /* FIXME: little endian? */ |
|
if (bpi <= SIZEOF_INT_TYPE) { |
struct pike_string *s = buffer_mkspace(bpi*l, 0); |
size_t len = str->offset+str->len - bpi; |
for (i=l; i--; it++) { |
INT_TYPE si; |
unsigned j; |
|
if( TYPEOF(*it) == PIKE_T_INT ) |
si = it->u.integer; |
else if( is_bignum_object_in_svalue( it ) ) { |
INT64 i64; |
if( int64_from_bignum( &i64, it->u.object ) ) |
si = i64; |
else |
Pike_error("Too big bignum\n"); /* FIXME: support these anyway? */ |
} else |
Pike_error("Non integer in array\n"); |
|
for(len+=(j=bpi)<<1; j--; si>>=8) |
low_set_index(s, --len, si&0xff); |
} |
str->len = (s->len = len + bpi) - str->offset; |
} else { /* bignums. */ |
ONERROR e; |
buffer_begin_rewind_on_error( &e ); |
for (i=l; i--;) { |
push_svalue( it++ ); |
push_int( bpi ); |
f_Buffer_add_int(2); |
pop_stack(); |
} |
buffer_end_rewind_on_error( &e ); |
} |
pop_n_elems(args); |
ref_push_object(Pike_fp->current_object); |
} |
|
/*! @decl Buffer add_int16( int(-32768..32767) i ) |
*! @decl Buffer add_short( int(0..65535) i ) |
*! |
*! Appends an int16 at the end of the string. |
*! |
*! @returns |
*! Returns the buffer itself. |
*! |
*! @seealso |
*! @[add()],@[add_byte()],@[add_int8()] |
*/ |
PIKEFUN Buffer add_int16( int(-32768..32767) i ) { |
push_int(2); |
f_Buffer_add_int(2); |
} |
|
PIKEFUN Buffer add_short( int(0..65535) i ) { |
f_Buffer_add_int16(1); |
} |
|
/*! @decl Buffer add_int32( int(-2147483648..2147483647) i ) |
*! @decl Buffer add_long( int(0..4294967295) i ) |
*! |
*! Appends an int32 at the end of the string. |
*! |
*! @returns |
*! Returns the buffer itself. |
*! |
*! @seealso |
*! @[add()],@[add_byte()],@[add_int8()] |
*/ |
PIKEFUN Buffer add_int32( int(-2147483648..2147483647) i ) { |
push_int(4); |
f_Buffer_add_int(2); |
} |
|
PIKEFUN Buffer add_long( int(0..4294967295) i ) { |
f_Buffer_add_int32(1); |
} |
|
/*! @decl Buffer add_hstring( string|Buffer|System.Memory data, int size_size ) |
*! |
*! Adds length/data for @[data] to the buffer. |
*! |
*! This is identical to @[sprintf("%"+size_size+"H",data)] but |
*! significantly faster. |
*! |
*! @[size_size] must be less than Int.NATIVE_MAX. |
*! |
*! @returns |
*! Returns the buffer itself. |
*! |
*! @seealso |
*! @[add()],@[sprintf()] |
*/ |
PIKEFUN Buffer add_hstring( string|Buffer|object str, int size_size ) { |
size_t len; |
|
switch(TYPEOF(*str)) { |
case PIKE_T_STRING: |
len = str->u.string->len; |
break; |
case PIKE_T_OBJECT: |
{ |
struct sysmem *sm; |
if (str->u.object->prog == Buffer_program) { |
len = OBJ2_BUFFER(str->u.object)->len; |
break; |
} |
if (sm = system_memory(str->u.object)) { |
len = sm->size; |
break; |
} |
} |
default: |
Pike_error("Bad argument 1 to add_hstring()," |
" expected string|Buffer|System.Memory.\n"); |
} |
/* We know this is safe for size_size>=4, since pike strings are at |
* most 0x7ffffff bytes long. |
*/ |
if( size_size < 4 && |
len > (1<<(8*(size_size<0?-size_size:size_size)))-1 ) |
Pike_error("String too long\n"); |
pop_stack(); |
push_int(len); |
push_int(size_size); |
f_Buffer_add_int( 2 ); |
pop_stack(); |
f_Buffer_append( 1 ); |
} |
|
/*! @decl int sprintf(strict_sprintf_format format, sprintf_args ... args) |
*! Appends the output from @[sprintf] at the end of the string. |
*! Returns the buffer itself. |
*/ |
PIKEFUN Buffer sprintf(mixed ... arguments) |
rawtype tFuncV(tAttr("strict_sprintf_format", tOr(tStr, tObj)), |
tAttr("sprintf_args", tMix), tStr); |
|
{ |
struct Buffer_struct *str = THIS; |
struct pike_string *s; |
size_t oldlen; |
ONERROR e; |
buffer_mkspace(0, 0); |
oldlen = str->str.s->len; |
buffer_begin_rewind_on_error( &e ); |
low_f_sprintf(args, 0, &str->str); |
buffer_end_rewind_on_error( &e ); |
pop_n_elems(args); |
s = str->str.s; |
str->buffer = s->str + (str->offset<<(str->shift = s->size_shift)); |
str->len += s->len-oldlen; |
ref_push_object(Pike_fp->current_object); |
} |
|
/*! @decl Buffer|void cut(int start, int|void end, void|int(0..1) discard) |
*! |
*! Cut and delete the range of data from the current buffer. |
*! Returns a new buffer with the cut data, unless discard is true. |
*! |
*! @seealso |
*! @[get()], @[get_copy()], @[clear()] |
*/ |
PIKEFUN Buffer|void cut(int index, int|void end_or_none, |
void|int(0..1) discard) |
{ |
struct Buffer_struct *str = THIS, *str2; |
struct object *res; |
unsigned shift; |
size_t len; |
INT_TYPE end, vdiscard; |
|
len = str->len; shift = str->str.s->size_shift; |
end = args==1 ? len-1 : end_or_none->u.integer; |
vdiscard = args==3 ? discard->u.integer : 0; |
pop_n_elems(args); |
|
if (index < 0) |
index = 0; |
if (++end > len) |
end = len; |
|
str->len -= len = end-index; |
if (index) |
index += str->offset, end+=str->offset; |
if (!vdiscard) { |
res = fast_clone_object( Buffer_program ); |
str2 = OBJ2_BUFFER( res ); |
str2->shift = str->shift; |
str2->len = len; |
if (index) { |
init_string_builder_alloc(&str2->str, str2->initial = len, 0); |
string_builder_append(&str2->str, |
MKPCHARP_OFF(str->buffer, str->shift, index), len); |
str2->buffer = str2->str.s->str; |
} else { |
str2->offset = str->offset; |
str2->initial = str->initial; |
str2->buffer = str->buffer; |
if (str->str.s) { |
add_ref(str->str.s); |
str2->str = str->str; |
} |
} |
} |
if(index) { |
struct pike_string *s = buffer_mkspace(0, 0); |
memmove(s->str+(index<<shift), s->str+(end<<shift), (s->len-end)<<shift); |
s->len -= len; |
} else |
str->offset += end, str->buffer += end<<shift; |
|
if(!vdiscard) { |
if( (Pike_fp->current_object->flags & OBJECT_CLEAR_ON_EXIT) ) |
res->flags |= OBJECT_CLEAR_ON_EXIT; |
push_object(res); |
} |
else |
push_undefined(); |
} |
|
/*! @decl Buffer read_buffer(int n) |
*! |
*! Same as @[read()], but returns the result as a @[Buffer]. |
*! |
*! @seealso |
*! @[read()], @[get()], @[trim()] |
*/ |
PIKEFUN Buffer|void read_buffer(int n) |
{ |
struct Buffer_struct *str = THIS, *str2; |
struct pike_string *s = str->str.s; |
size_t len = str->len; |
struct object *res; |
|
while (n<0 || n>len) |
if (!buffer_range_error( n )) { |
pop_n_elems(args); |
push_undefined(); |
return; |
} |
pop_n_elems(args); |
res = fast_clone_object( Buffer_program ); |
str2 = OBJ2_BUFFER( res ); |
if (s) |
add_ref(s); |
str2->str = str->str; |
str2->buffer = str->buffer; |
str2->offset = str->offset; |
str2->shift = str->shift; |
str2->len = n; |
if( (Pike_fp->current_object->flags & OBJECT_CLEAR_ON_EXIT) ) |
res->flags |= OBJECT_CLEAR_ON_EXIT; |
push_object(res); |
} |
|
#define LONGEST_HIGH_BYTE (~(MAX_ULONGEST>>8)) |
|
static LONGEST buffer_read_number(size_t n) |
{ |
struct Buffer_struct *str = THIS; |
unsigned shift = str->shift; |
unsigned char*buf = str->buffer; |
LONGEST res = 0; |
|
if (n>=0) |
while (n--) { |
if (LONGEST_HIGH_BYTE&res) |
goto overflow; |
res<<=8; res|=*buf; buf+=1<<shift; |
} |
else |
for (buf+=n<<shift; n++; ) { |
if (LONGEST_HIGH_BYTE&res) |
goto overflow; |
res<<=8; buf-=1<<shift; res|=*buf; |
} |
if (res<0) |
overflow: |
Pike_error("Integer (%dbit) overflow.\n", SIZEOF_LONGEST*8); |
return res; |
} |
|
static int buffer_rc_number(size_t n) |
{ |
struct Buffer_struct *str = THIS; |
|
pop_stack(); |
for(;;) { |
LONGEST slen; |
ptrdiff_t len = str->len; |
|
if ((len -= n)<0) |
goto rangeerror; |
slen = buffer_read_number(n); |
if ((len -= slen)<0) { |
rangeerror: |
if( buffer_range_error( -len ) ) |
continue; |
push_undefined(); |
return 0; |
} |
str->len -= n; |
str->offset += n; |
str->buffer += n<<str->shift; |
push_int(slen); |
return 1; |
} |
} |
|
static int buffer_presscanf(const struct pike_string*format) |
{ |
struct Buffer_struct *str = THIS; |
ptrdiff_t num_used; |
|
goto jin; |
do { |
if (!buffer_range_error(0)) |
return 0; |
jin: low_sscanf_pcharp( MKPCHARP(str->buffer, str->shift), str->len, |
MKPCHARP_STR(format), format->len, &num_used, 0); |
} while (!num_used); |
|
str->len -= num_used; |
str->offset += num_used; |
str->buffer += num_used<<str->shift; |
return 1; |
} |
|
/*! @decl int read_int( int n ) |
*! |
*! Read a network (if n is positive) or little endian (if n is |
*! negative) byte order unsigned number of size n*8 bits, then |
*! return it. |
*! |
*! @returns |
*! Will return -1 if there is not enough buffer space available |
*! unless error mode is set to throw errors. |
*/ |
PIKEFUN int(0..)|int(-1..-1) read_int( int n ) |
{ |
struct Buffer_struct *str = THIS; |
|
while (str->len < n) |
if (!buffer_range_error( (int)str->len-n )) { |
Pike_sp[-1].u.integer = -1; |
return; |
} |
|
if( n < SIZEOF_INT_TYPE ) { /* will for sure fit. */ |
pop_stack(); |
push_int(buffer_read_number(n)); |
str->len -= n; |
str->offset += n; |
} else { |
f_Buffer_read( 1 ); |
push_int(256); |
push_object(clone_object( get_auto_bignum_program(), 2 )); |
reduce_stack_top_bignum(); |
} |
} |
|
/*! @decl string read_hstring( int(0..) n ) |
*! |
*! Identical in functionality to @[read](@[read_int](@[n])) but |
*! faster. |
*! |
*! Read a network byte order number of size n*8 bits, then return the |
*! indicated number of bytes as a string. |
*! @returns |
*! If there is not enough data available return 0. |
*! |
*! Note that a pike string can not be longer than 0x7fffffff bytes (~2Gb). |
*/ |
PIKEFUN string|void read_hstring( int n ) |
{ |
if(buffer_rc_number(n)) |
f_Buffer_read( 1 ); |
} |
|
/*! @decl Buffer read_hbuffer( int n ) |
*! |
*! Same as @[read_hstring], but returns the result as a Buffer. |
*! |
*/ |
PIKEFUN Buffer|void read_hbuffer( int n ) |
{ |
if(buffer_rc_number(n)) |
f_Buffer_read_buffer( 1 ); |
} |
|
/*! @decl array|void sscanf(string format) |
*! |
*! Reads data from the beginning of the buffer to match the |
*! specifed format, then return an array with the matches. |
*! |
*! The non-matching data will be left in the buffer. |
*! |
*! See @[array_sscanf] for more information. |
*/ |
PIKEFUN array|void sscanf( string format ) |
{ |
struct svalue *start = Pike_sp; |
unsigned matched, results; |
|
matched = buffer_presscanf(format); |
results = Pike_sp - start; |
|
if(matched) |
f_aggregate(results); |
else { |
pop_n_elems(results); |
push_undefined(); |
} |
} |
|
/*! @decl mixed|void match(string format) |
*! |
*! Reads data from the beginning of the buffer to match the |
*! specifed format, then return the match. |
*! |
*! The non-matching data will be left in the buffer. |
*! |
*! This function is very similar to @[sscanf], but the |
*! result is the sum of the matches. Most useful to match |
*! a single value. |
*! |
*! @example |
*! @code |
*! // get the next whitespace separated word from the buffer. |
*! buffer->match("%*[ \t\r\n]%*[^ \t\r\n]"); |
*! @endcode |
*/ |
PIKEFUN string|int|float|array|void match( string format ) |
{ |
struct svalue *start = Pike_sp; |
unsigned matched, results; |
|
matched = buffer_presscanf(format); |
results = Pike_sp - start; |
|
if(matched) |
f_add(results); |
else { |
pop_n_elems(results); |
push_undefined(); |
} |
} |
|
/*! @decl int(0..)|int(-1..-1) consume(int(0..) n) |
*! |
*! Discard the next @[n] bytes from the beginning of the buffer. |
*! |
*! @returns |
*! Returns the remaining size of the buffer, or -1 if you attempted |
*! to consume more than was available. |
*! |
*! @seealso |
*! @[cut()], @[unread()] |
*/ |
PIKEFUN int(-1..) consume( int(0..) n ) |
{ |
struct Buffer_struct *str = THIS; |
int left; |
|
while (n<0 || (left = str->len-n) < 0) |
if (!buffer_range_error( -left )) { |
left = -1; |
goto ret; |
} |
|
str->len -= n; |
str->offset += n; |
str->buffer += n<<str->shift; |
ret: |
Pike_sp[-1].u.integer = left; |
} |
|
/*! @decl int(0..)|int(-1..-1) unread(int(0..)|string|Buffer n) |
*! |
*! Rewind the buffer @[n] bytes. If passed a string of Buffer |
*! argument, it will push back that string at the front of the buffer. |
*! |
*! @returns |
*! Returns how many characters of buffer is available to rewind, |
*! or -1 if you attempted to rewind more than is available. |
*! |
*! @seealso |
*! @[consume()] |
*/ |
PIKEFUN int(-1..) unread( int(0..)|string|Buffer n ) |
{ |
struct Buffer_struct *str = THIS; |
int ret = Pike_sp[-1].u.integer; |
struct pike_string *s2; |
PCHARP b; |
ptrdiff_t blen; |
|
switch (TYPEOF(Pike_sp[-1])) { |
case PIKE_T_INT: |
ret = Pike_sp[-1].u.integer; |
while (ret<0 || ret>str->offset) |
if (!buffer_range_error( ret-str->offset )) { |
ret = -1; |
goto rangx1; |
} |
str->buffer -= ret<<str->shift; |
str->len += ret; |
ret = str->offset -= ret; |
rangx1: break; |
case PIKE_T_STRING: |
s2 = Pike_sp[-1].u.string; |
b = MKPCHARP_STR(s2); |
blen = s2->len; |
goto pastein; |
case PIKE_T_OBJECT: |
if (Pike_sp[-1].u.object->prog == Buffer_program) |
{ struct Buffer_struct *str2 = OBJ2_BUFFER(Pike_sp[-1].u.object); |
ptrdiff_t dif; |
|
b = MKPCHARP(str2->buffer, str2->shift); |
blen = str2->len; |
pastein: if ((dif = (ptrdiff_t)str->offset-blen) < 0) { |
struct pike_string *s = buffer_mkspace(-dif, b.shift); |
unsigned shift = s->size_shift; |
memmove(s->str+(blen<<shift), str->buffer, str->len<<shift); |
str->buffer = s->str; |
s->len = str->len += blen; |
ret = str->offset = 0; |
} |
else { |
buffer_mkspace(0, 0); |
str->buffer -= blen<<str->shift; |
str->len += blen; |
ret = str->offset = dif; |
} |
generic_memcpy(MKPCHARP(str->buffer, str->shift), b, blen); |
break; |
} |
default: |
SIMPLE_BAD_ARG_ERROR("unread", 1, "int(0..)|string|String.Buffer"); |
} |
pop_stack(); |
push_int(ret); |
} |
|
static int buffer_is_whitespace(struct Buffer_struct *str, size_t pos) |
{ |
switch( generic_extract(str->buffer, str->shift, pos) ) { |
SPACECASE8 |
return 1; |
} |
return 0; |
} |
|
/*! @decl mixed read_json(int|void require_whitespace_separator) |
*! |
*! Read a single JSON expression from the buffer and return it. |
*! |
*! If @[require_whitespace_separator] is true there must be a whitespace |
*! after each json value (as an example, newline or space). |
*! |
*! The JSON is assumed to be utf-8 encoded. |
*! |
*! @returns |
*! UNDEFINED if no data is available to read. |
*! The read value otherwise. |
*! |
*! @note |
*! Unless strwhitespaces are required this function only really work correctly |
*! with objects, arrays and strings. |
*! |
*! There is really no way to see where one value starts and the other ends |
*! for most other cases |
*/ |
PIKEFUN mixed read_json(int|void require_whitespace) |
{ |
struct Buffer_struct *str = THIS; |
unsigned whites = 0; |
static ptrdiff_t(*parse_json_pcharp)(PCHARP,size_t,int,char**); |
if( require_whitespace ) { |
whites = require_whitespace->u.integer; |
pop_stack(); |
} |
|
if( !parse_json_pcharp ) |
parse_json_pcharp |
= PIKE_MODULE_IMPORT(Standards.JSON, parse_json_pcharp ); |
for(;;) { |
char *err = NULL; |
ptrdiff_t stop = parse_json_pcharp( MKPCHARP(str->buffer,str->shift), |
str->len, 1|8, &err ); /* json_utf8 */ |
if( stop < 0 ) |
if( -stop == str->len || (err && !strncmp(err,"Unterminated",12))) { |
if (buffer_range_error( 0 )) |
continue; |
push_undefined(); |
} else /* FIXME: Use real json error? */ |
if( err ) |
Pike_error("Syntax error in json at offset %d: %s\n", -stop, err ); |
else |
Pike_error("Syntax error in json at offset %d\n", -stop ); |
else if( whites && |
(stop == str->len || !buffer_is_whitespace(str,stop)) |
&& !buffer_is_whitespace(str,stop-1)) |
if( stop == str->len ) { |
if (buffer_range_error( 0 )) |
continue; |
pop_stack(); |
push_undefined(); |
} else |
Pike_error("Missing whitespace between json values at offset %d\n", |
stop ); |
else { |
if( whites ) |
while( buffer_is_whitespace( str, stop ) ) |
stop++; |
push_int( stop ); |
f_Buffer_consume( 1 ); |
pop_stack(); |
} |
break; |
} |
} |
|
/*! @decl void trim() |
*! |
*! Compact buffer, free unused memory. |
*! When called on a subbuffer, it clears the subbuffer and releases |
*! the parent buffer to be modified again. |
*! |
*! @note |
*! Calling this function excessively might slow things down, |
*! since the data often has to be copied. |
*! |
*! @seealso |
*! @[clear()]] |
*/ |
PIKEFUN void trim() |
{ |
struct Buffer_struct *str = THIS; |
struct pike_string *s = str->str.s; |
unsigned offset = str->offset, shift = s->size_shift; |
|
if (str->readonly || !s || s->refs!=1) |
buffer_mkspace(0, 0); |
else { |
if (str->offset) |
memmove(s->str, str->buffer, str->len<<str->shift); |
if (s = realloc_unlinked_string(s, str->len)) |
str->str.s = s, str->str.malloced = str->len; |
str->buffer = s->str; |
str->offset = 0; |
} |
} |
|
/*! @decl void clear() |
*! |
*! Empty the buffer, and discard the old content. |
*! |
*! @seealso |
*! @[get()], @[cut()], @[trim()] |
*/ |
PIKEFUN void clear() |
{ |
struct Buffer_struct *str = THIS; |
str->len = str->offset = 0; |
reset_string_builder(&str->str); |
} |
|
/*! @decl int _sizeof() |
*! |
*! Returns the size of the buffer. |
*/ |
PIKEFUN int _sizeof() |
{ |
RETURN THIS->len; |
} |
|
INIT |
{ |
struct Buffer_struct *str = THIS; |
memset( str, 0, sizeof( *str ) ); |
} |
|
EXIT |
gc_trivial; |
{ |
struct Buffer_struct *str = THIS; |
struct pike_string *s; |
if (s = str->str.s) { |
if( Pike_fp->flags & OBJECT_CLEAR_ON_EXIT ) |
guaranteed_memset( s->str, 0, str->str.malloced ); |
free_string( s ); |
} |
if( str->source ) |
free_object( str->source ); |
} |
|
GC_RECURSE |
{ |
if (mc_count_bytes (Pike_fp->current_object)) |
mc_counted_bytes += THIS->str.malloced; |
} |
|
#define BUFFER_BLOCK_SIZE 4096 |
|
/*! @decl int input_from( Stdio.Stream f, void|int n ) |
*! |
*! Read data from @[f] into this buffer. |
*/ |
PIKEFUN int(0..) input_from( object f, void|int _n ) |
{ |
struct Buffer_struct *str = THIS; |
size_t bread = 0, n = (size_t)-1; |
struct my_file *fd; |
|
if( _n ) |
n = _n->u.integer; |
|
buffer_mkspace(0, 0); |
if( (fd = get_storage( f, file_program )) ) { |
struct pike_string *s; |
void *tbuf = 0; |
|
if (str->shift) { |
tbuf = malloc(MIN(n,BUFFER_BLOCK_SIZE)); |
if (!tbuf) |
Pike_error("Out of memory allocating %d buffer space.\n", |
BUFFER_BLOCK_SIZE); |
} |
do { |
int res; |
void *ptr; |
size_t getnow = n-bread; |
if (getnow < BUFFER_BLOCK_SIZE) |
getnow = BUFFER_BLOCK_SIZE; |
s = buffer_mkspace(getnow, 0); |
ptr = tbuf? tbuf: s->str+s->len; /* shift is 0 when no tbuf */ |
|
buffer_lock(); |
{ |
THREADS_ALLOW(); |
res = fd_read( fd->box.fd, ptr, getnow ); |
THREADS_DISALLOW(); |
} |
buffer_release(); |
|
if( res == -1 && errno == EINTR ) |
continue; |
|
if( res <= 0 ) |
break; |
|
if (tbuf) |
generic_memcpy(MKPCHARP_STR_OFF(s, s->len), MKPCHARP(tbuf,0), res); |
s->len += res; |
bread += res; |
if( res != getnow ) |
break; |
} while (n>bread); |
|
if (tbuf) |
free(tbuf); |
str->buffer = s->str + (str->offset<<str->shift); |
str->len += bread; |
} else /* some other object. Just call read */ |
for(;;) { |
push_int( BUFFER_BLOCK_SIZE ); |
safe_apply( f, "read", 1 ); |
if( TYPEOF(Pike_sp[-1]) != PIKE_T_STRING |
|| Pike_sp[-1].u.string->len == 0 ) |
break; |
bread += Pike_sp[-1].u.string->len; |
f_Buffer_add( 1 ); |
pop_stack(); |
} |
RETURN bread; |
} |
|
/*! @decl int output_to( Stdio.Stream f, void|int n ) |
*! |
*! Write data from the buffer to the indicated file. |
*! |
*! Will return the number of bytes that were successfully written. |
*/ |
PIKEFUN int(0..) output_to( object f, void|int _n ) |
{ |
struct Buffer_struct *str = THIS; |
size_t sz = str->len; |
size_t written = 0; |
struct my_file *fd; |
INT_TYPE *wr = &Pike_sp->u.integer; |
|
if( _n ) |
sz = MIN(_n->u.integer, sz); |
|
if( !str->shift && (fd = get_storage( f, file_program )) ) |
{ |
buffer_lock(); |
|
THREADS_ALLOW(); |
while( sz > written ) { |
int res = fd_write( fd->box.fd, str->buffer, sz-written ); |
if( res == -1 && errno == EINTR ) |
continue; |
if( res <= 0 ) |
break; |
str->buffer += res; |
written += res; |
} |
THREADS_DISALLOW(); |
str->len -= written; |
str->offset += written; |
buffer_release(); |
} else { |
/* some other object. Just call write */ |
while( sz > written ) { |
size_t rd = MIN(sz-written,BUFFER_BLOCK_SIZE); |
push_int(rd); |
f_Buffer_read( 1 ); |
if( TYPEOF(Pike_sp[-1]) != PIKE_T_STRING |
|| Pike_sp[-1].u.string->len == 0 ) |
break; |
/* when this throws we need to rewind the buffer correctly. */ |
safe_apply(f, "write", 1); |
if( *wr <= 0 ) |
goto rewind; |
written += *wr; |
if( 0 < (rd-=*wr) ) { |
rewind: str->buffer -= rd<<str->shift; |
str->offset -= rd; |
str->len += rd; |
break; |
} |
pop_stack(); |
} |
} |
RETURN written; |
} |
} |
|
/*! @endclass |
*/ |
|
/*! @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(array(string)|mapping(string:string)|void from, @ |
*! array(string)|string|void 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 (!args) { |
push_int(0); |
return; |
} |
if (from_arg && TYPEOF(*from_arg) == T_MAPPING) { |
if (to_arg) { |
Pike_error("Bad number of arguments to create().\n"); |
} |
THIS->from = mapping_indices(from_arg->u.mapping); |
THIS->to = mapping_values(from_arg->u.mapping); |
pop_n_elems(args); |
args = 0; |
} else { |
/* FIXME: Why is from declared |void, when it isn't allowed |
* to be void? |
* /grubba 2004-09-02 |
* |
* It probably has to do with the "if (!args)" above: It should |
* be possible to create an empty instance. /mast |
*/ |
if (!from_arg || !to_arg) { |
Pike_error("Bad number of arguments to create().\n"); |
} |
pop_n_elems(args-2); |
args = 2; |
if (TYPEOF(*from_arg) != T_ARRAY) { |
SIMPLE_BAD_ARG_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); |
} |
if (TYPEOF(*to_arg) != T_ARRAY) { |
SIMPLE_BAD_ARG_ERROR("replace", 2, "array(string)|string"); |
} |
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); |
} |
|
if (!THIS->from->size) { |
/* Enter no-op mode. */ |
pop_n_elems(args); |
push_int(0); |
return; |
} |
|
if( (THIS->from->type_field & ~BIT_STRING) && |
(array_fix_type_field(THIS->from) & ~BIT_STRING) ) |
SIMPLE_BAD_ARG_ERROR("replace", 1, |
"array(string)|mapping(string:string)"); |
|
if( (THIS->to->type_field & ~BIT_STRING) && |
(array_fix_type_field(THIS->to) & ~BIT_STRING) ) |
SIMPLE_BAD_ARG_ERROR("replace", 2, "array(string)|string"); |
|
compile_replace_many(&THIS->ctx, THIS->from, THIS->to, 1); |
|
pop_n_elems(args); |
push_int(0); |
} |
|
/*! @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); |
stack_swap(); |
} |
pop_stack(); |
|
f_multi_string_replace_create(i); |
} |
|
INIT |
{ |
memset(&THIS->ctx, 0, sizeof(struct replace_many_context)); |
} |
|
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 |
{ |
MAP_VARIABLE ("o", tObj, ID_PROTECTED|ID_PRIVATE, |
single_string_replace_storage_offset + |
OFFSETOF (single_string_replace_struct, mojt.container), |
T_OBJECT); |
} |
|
/*! @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) return; |
|
if (!to) { |
SIMPLE_BAD_ARG_ERROR("replace", 2, "string"); |
} |
|
if (del == to) { |
/* No-op... */ |
return; |
} |
|
copy_shared_string(THIS->del, del); |
copy_shared_string(THIS->to, to); |
|
if (del->len) { |
THIS->mojt = simple_compile_memsearcher(del); |
} |
} |
|
/*** 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 0: f = (replace_searchfunc)THIS->mojt.vtab->func0; break; |
case 1: f = (replace_searchfunc)THIS->mojt.vtab->func1; break; |
case 2: f = (replace_searchfunc)THIS->mojt.vtab->func2; break; |
#ifdef PIKE_DEBUG |
default: Pike_fatal("Illegal shift.\n"); |
#endif |
} |
|
if(del->len == to->len) |
{ |
ret = begin_wide_shared_string(str->len, shift); |
|