diff --git a/lib/modules/String.pmod/testsuite.in b/lib/modules/String.pmod/testsuite.in index e07447bc7c899efcd706eba4edfadfc971f0430f..5082b60cfadb723c59f3d7cfaa020d81f5bd204a 100644 --- a/lib/modules/String.pmod/testsuite.in +++ b/lib/modules/String.pmod/testsuite.in @@ -3,25 +3,25 @@ START_MARKER test_eq([[ String.Buffer()->add("xxx") ]], 3) test_any([[ String.Buffer s=String.Buffer(); - s->add("xxx"); - s->add("a", "b"); + s->append("xxx"); + s->append("a", "b"); s->putchar(65); - s->add("B"); + s->append("B"); s->putchar(67); return (string)s; ]], "xxxabABC") test_any([[ String.Buffer s=String.Buffer(2); - s->add("a"); - s->add("b"); - s->add("c"); + s->append("a"); + s->append("b"); + s->append("c"); return (string)s; ]], "abc") test_any([[ String.Buffer s=String.Buffer(2); - s->add("abcdefg"); - s->add(""); - s->add("hij"); + s->append("abcdefg"); + s->append(""); + s->append("hij"); return s->get(); ]], "abcdefghij") test_any([[ @@ -30,23 +30,23 @@ test_any([[ ]], "") test_any([[ String.Buffer s=String.Buffer(); - s->add("xxx"); + s->append("xxx"); string t=s->get(); - s->add("yyy"); + s->append("yyy"); string u=(string)s; - s->add("zzz"); + s->append("zzz"); return t+u+(string)s; ]], "xxxyyyyyyzzz") test_any([[ String.Buffer s=String.Buffer(); - s->add("xxx"); + s->append("xxx"); string t=s->get_copy(); - s->add("yyy"); + s->append("yyy"); return t+(string)s; ]], "xxxxxxyyy") test_any([[ String.Buffer s=String.Buffer(); - s->add("abcde"); + s->append("abcde"); return sizeof(s); ]], 5) test_any([[ @@ -61,17 +61,17 @@ test_any([[ ]], 1) test_any([[ String.Buffer b = String.Buffer(); - b->add("abc"); - b->add("\400\500"); + b->append("abc"); + b->append("\400\500"); b->putchar(256); - b->add("x"); + b->append("x"); return (string)b; ]], "abc\400\500\400x") test_any([[ String.Buffer b = String.Buffer(); - b->add("x"); + b->append("x"); b->sprintf("%x",1178); - b->add("y"); + b->append("y"); return (string)b; ]], "x49ay") test_any([[ @@ -88,7 +88,7 @@ test_any([[ test_compile_error([[ String.Buffer()->sprintf("%d"); ]]) test_eval_error([[ function f=String.Buffer()->sprintf; f("%d"); ]]) -test_eval_error([[ String.Buffer()->add("x",0); ]]) +test_eval_error([[ String.Buffer()->append("x",([])); ]]) test_do([[ String.Buffer b = String.Buffer(); @@ -111,194 +111,593 @@ test_do([[ test_any([[ String.Buffer b = String.Buffer(); - b->add("abc"); + b->append("abc"); return b[1]; ]], 98) test_any([[ String.Buffer b = String.Buffer(); - b->add("abc"); + b->append("abc"); return b[-1]; ]], 99) test_runtime_error([[ String.Buffer b = String.Buffer(); - b->add("abc"); + b->append("abc"); return b[3]; ]]) test_any([[ String.Buffer b = String.Buffer(); - b->add("a\u0100a"); + b->append("a\u0100a"); return b[1]; ]], 256) test_any([[ String.Buffer b = String.Buffer(); - b->add("a\U00010000a"); + b->append("a\U00010000a"); return b[1]; ]], 65536) test_any([[ String.Buffer b = String.Buffer(); - b->add("aaa"); + b->append("aaa"); return b[1]='b'; ]], 98) test_any([[ String.Buffer b = String.Buffer(); - b->add("aaa"); + b->append("aaa"); b[1]='b'; return (string)b; ]], "aba") test_any([[ String.Buffer b = String.Buffer(); - b->add("aaa"); + b->append("aaa"); b[1]=256; return (string)b; ]], "a\u0100a") test_any([[ String.Buffer b = String.Buffer(); - b->add("aaa"); + b->append("aaa"); b[1]=65536; return (string)b; ]], "a\U00010000a") test_any([[ String.Buffer b = String.Buffer(); - b->add("1234"); + b->append("1234"); b = b[..1]; - b->add("x"); + b->append("x"); return (string)b; ]], "12x") test_any([[ String.Buffer b = String.Buffer(); - b->add("1234"); + b->append("1234"); return (string)b[1..]; ]], "234") test_any([[ String.Buffer b = String.Buffer(); - b->add("1234"); + b->append("1234"); return (string)b[1..2]; ]], "23") test_any([[ String.Buffer b = String.Buffer(); - b->add("1234"); + b->append("1234"); return (string)b[..<1]; ]], "123") test_any([[ String.Buffer b = String.Buffer(); - b->add("ab\u0100\u0101\U00010000\U00010001"); + b->append("ab\u0100\u0101\U00010000\U00010001"); return (string)b[0..1]; ]], "ab") test_any([[ String.Buffer b = String.Buffer(); - b->add("ab\u0100\u0101\U00010000\U00010001"); + b->append("ab\u0100\u0101\U00010000\U00010001"); return (string)b[2..3]; ]], "\u0100\u0101") test_any([[ String.Buffer b = String.Buffer(); - b->add("ab\u0100\u0101\U00010000\U00010001"); + b->append("ab\u0100\u0101\U00010000\U00010001"); return (string)b[4..5]; ]], "\U00010000\U00010001") test_any([[ String.Buffer b = String.Buffer(); - b->add("ab\u0100\u0101\U00010000\U00010001cd"); + b->append("ab\u0100\u0101\U00010000\U00010001cd"); b->cut(4,5,1); return (string)b; ]], "ab\u0100\u0101cd") test_any([[ String.Buffer b = String.Buffer(); - b->add("ab\u0100\u0101\U00010000\U00010001cd"); + b->append("ab\u0100\u0101\U00010000\U00010001cd"); b->cut(2,5,1); return (string)b; ]], "abcd") test_any([[ - String.Buffer b = String.Buffer(); - b->add("ab\u0100\u0101\U00010000\U00010001cd"); - return (string)b->cut(4,5) + "_" + (string)b; + String.Buffer b = String.Buffer(),c; + b->append("ab\u0100\u0101\U00010000\U00010001cd"); + c=b->cut(4,5); + return (string)(c + "_" + b); ]], "\U00010000\U00010001_ab\u0100\u0101cd") test_any([[ - String.Buffer b = String.Buffer(); - b->add("ab\u0100\u0101\U00010000\U00010001cd"); - return (string)b->cut(2,5) + "_" + (string)b; + String.Buffer b = String.Buffer(),c; + b->append("ab\u0100\u0101\U00010000\U00010001cd"); + c=b->cut(2,5); + return (string)c + "_" + (string)b; ]], "\u0100\u0101\U00010000\U00010001_abcd") test_any([[ String.Buffer b = String.Buffer(), a = String.Buffer(); - b->add("abcd");a->add("abcd"); + b->append("abcd");a->append("abcd"); return b=="abcd" && b==a; ]], 1) test_any([[ String.Buffer b = String.Buffer(), a = String.Buffer(); - b->add("abcd");a->add("abcd"); + b->append("abcd");a->append("abcd"); return b>="abcd" && b>=a; ]], 1) test_any([[ String.Buffer b = String.Buffer(), a = String.Buffer(); - b->add("abcd");a->add("abcd"); + b->append("abcd");a->append("abcd"); return b<="abcd" && b<=a; ]], 1) test_any([[ String.Buffer b = String.Buffer(), a = String.Buffer(); - b->add("abce");a->add("abcd"); + b->append("abce");a->append("abcd"); return b>="abcd" && b>=a; ]], 1) test_any([[ String.Buffer b = String.Buffer(), a = String.Buffer(); - b->add("abce");a->add("abcd"); + b->append("abce");a->append("abcd"); return b<="abcd" || b<=a; ]], 0) test_any([[ String.Buffer b = String.Buffer(), a = String.Buffer(); - b->add("abce");a->add("abcd"); + b->append("abce");a->append("abcd"); return b>"abcd" && b>a; ]], 1) test_any([[ String.Buffer b = String.Buffer(), a = String.Buffer(); - b->add("abce");a->add("abcd"); + b->append("abce");a->append("abcd"); return b<"abcd" || b<a; ]], 0) test_any([[ String.Buffer b = String.Buffer(), a = String.Buffer(); - b->add("abcb");a->add("abcd"); + b->append("abcb");a->append("abcd"); return b=="abcd" || b==a; ]], 0) test_any([[ String.Buffer b = String.Buffer(), a = String.Buffer(); - b->add("abcb");a->add("abcd"); + b->append("abcb");a->append("abcd"); return b>="abcd" || b>=a; ]], 0) test_any([[ String.Buffer b = String.Buffer(), a = String.Buffer(); - b->add("abcb");a->add("abcd"); + b->append("abcb");a->append("abcd"); return b<="abcd" && b<=a; ]], 1) test_any([[ String.Buffer b = String.Buffer(), a = String.Buffer(); - b->add("abcb");a->add("abcd"); + b->append("abcb");a->append("abcd"); return b>"abcd" || b>a; ]], 0) test_any([[ String.Buffer b = String.Buffer(), a = String.Buffer(); - b->add("abcb");a->add("abcd"); + b->append("abcb");a->append("abcd"); return b<"abcd" || b<a; ]], 1) test_any([[ String.Buffer a=String.Buffer(),b=String.Buffer(); - a->add("Testing"); - a->addat(0, "Is that sentence"); + a->append("Testing"); + a->appendat(0, "Is that sentence"); a->putchar(','); - a->add(" the concluding three ", "words"); - a->add(" `were left out'"); - a->addat(3,"th","is"); + a->append(" the concluding three ", "words"); + a->append(" `were left out'"); + a->appendat(3,"th","is"); a[1]=a[10]; - b->add("Test me harder"); - b->add(a); + b->append("Test me harder"); + b->append(a); if(b->cut(5,6) != "me") return 0; b->cut(0,11,1); - return a[3..6] == "this" && a->get_copy() ==(string)b && a->get() - == "In this sentence, the concluding three words `were left out'"; + if(a[3..6] != "this" || a->get_copy() !=b) + return "X"+(string)b; + if(a->get()!="In this sentence, the concluding three words `were left out'") + return "X"+a->get(); + return 1; ]],1) +test_equal([[String.Buffer("hej")->read(1)]], "h") + +dnl sscanf + +test_equal([[String.Buffer("hej")->sscanf("%3c")]], ({6841706})) +test_equal([[String.Buffer("hej")->match("%3c")]], 6841706) + +test_any([[ + int i; + String.Buffer b=String.Buffer("hej hej hej"); + + while( array a = b->sscanf( "%*[ ]%[^ ]") ) + i+= sizeof(a); + return i; +]], 3) + +dnl sizeof() +test_equal( sizeof(String.Buffer("hej")), sizeof("hej")) +test_equal( sizeof(String.Buffer("ej")->append("alpha")), 7) +test_equal( sizeof(String.Buffer()->sprintf("%4H","hej")), 7) + +dnl create(int) +test_any([[ + String.Buffer b = String.Buffer(1024*1024); + if( b->_size_object() != 1024*1024 ) + return -1; + for( int i = 0; i<1024/4*1024; i++ ) + b->append("test"); + if( b->_size_object() != 1024*1024 ) + return -2; + + return 1; +]], 1 ) + +dnl create/add( system.memory ) +test_any([[ + System.Memory a = System.Memory(__FILE__); + System.Memory b = System.Memory(100); + + String.Buffer buf = String.Buffer(a); + + if( buf->_size_object() != 0 ) + return -1; + + buf->append( b ); + + if( buf->_size_object() < sizeof(a)+sizeof(b) ) + return -2; + if( !has_suffix((string)buf,"\0"*100) ) + return -3; + return 1; +]], 1) + +dnl create/add( iobuffer ) +test_any([[ + String.Buffer a = String.Buffer("buffer 1"); + String.Buffer b = String.Buffer("buffer 2"); + + String.Buffer buf = String.Buffer(a); + + buf->append( b ); + + if( buf->_size_object() < sizeof(a)+sizeof(b) ) + return -2; + if( (string)buf != "buffer 1buffer 2") + return -3; + return 1; +]], 1) + +dnl add( char ) +test_any([[ + string a = ("buffer 1"); + + String.Buffer buf = String.Buffer(a); + + if( buf->_size_object() >= sizeof(a) ) + return -1; + + buf->append( 'b','u','f','f','e','r',' ','2' ); + + if( buf->_size_object() < sizeof(a)+8 ) + return -2; + if( (string)buf != "buffer 1buffer 2") + return -3; + return 1; +]], 1) + +dnl add(array( mix )) +test_any([[ + System.Memory tst = System.Memory(2); + tst[0] = ' '; + tst[1] = '2'; + String.Buffer er = String.Buffer(); + mixed z = ({ + 'b','u','f',String.Buffer("f"), + er, + tst + }); + er->append("er"); + + String.Buffer buf = String.Buffer("buffer 1"); + + buf->append( z, 'b','u','f','f',({'e','r',({' '}),'3'}) ); + + if( buf->_size_object() < 24 ) + return -2; + if( (string)buf != "buffer 1buffer 2buffer 3") + return -3; + + return 1; +]], 1) + +dnl multi add( combo, also sort of a speed test ) +dnl will use on average 3Mb RAM +test_any([[ + String.Buffer two = String.Buffer(); + array tst = ({ + System.Memory(1), + two, + '3', + "4", + String.Buffer("5"), + ({ "6", ({'7',"","",""}) }), + }); + + two->append("2"); + + int l = 0; + String.Buffer res = String.Buffer(); + for( int i = 0; i<102*1024; i++ ) + { + array args = allocate( random(50) ); + for( int j=0; j<sizeof( args ); j++ ) + { + mixed e = random( tst ); + if( intp( e ) ) + l +=1; + else + l += sizeof( e ); + args[j] = e; + } + res->append(@args); + if( sizeof( res ) != l ) + return ([i:args, "l":l,"sizeof":sizeof(res)]); + } + + return 1; +]], 1) + +dnl basic zero-copy check + +test_any( [[ + String.Buffer i = String.Buffer("zero copy"); + + i->append(" data goes here"); + if( i->_size_object() == 0 ) return -4; + + i->read(); + if( i->_size_object() != 0 ) return -5; + + i->append("zero copy"); + if( i->_size_object() == 0 ) return -6; + + i->append(" indeed"); /* note: shorter than first string. */ + if( i->_size_object() == 0 ) return -7; + + /* + crete - no move + add - realloc + move + read - no move (buffer now empty) + add - point to str + add - move to malloc arena + add - no move + */ + return 1; +]], 1) + +dnl add_byte() add_int8() + +test_any( [[ + String.Buffer b = String.Buffer(); + + for( int i=0; i<255; i++ ) + b->add_byte( i )->add_int8(~i); + + for( int i=0; i<255; i++ ) + if( b[i*2] != i || b[i*2+1] != (~i&255)) + return -i; + return 1; +]], 1 ) + +dnl add_short() add_int16() + + test_any( [[ + string ref = ""; + String.Buffer b = String.Buffer(); + + for( int i=0; i<255; i++ ) + { + ref += sprintf("%2c%2c", i, (~i)&0xffff ); + b->add_short( i )->add_int16(~i); + if( (string)b != ref ) return ([ + "i":i, + "ref":ref, + "b":(string)b + ]); + } + return 1; + ]], 1 ) + +dnl add_int32() + test_any( [[ + string ref = ""; + String.Buffer b = String.Buffer(); + + for( int i=0; i<255; i++ ) + { + ref += sprintf("%4c%4c", i, (~i)&0xffffffff ); + b->add_int32( i )->add_int32(~i); + if( (string)b != ref ) return ([ + "i":i, + "ref":ref, + "b":(string)b + ]); + } + return 1; + ]], 1 ) + + +dnl add_int( x, bits); + + test_any( [[ + string ref = ""; + String.Buffer b = String.Buffer(); + + for( int j=1; j<255; j++ ) + { + string fmt = "%"+j+"c"; + for( int i=0; i<255; i+=random(10) ) + { + ref += sprintf(fmt, i ); + b->add_int( i, j ); + } + if( (string)b != ref ) return ([ + "width":j, + "ref":ref, + "b":(string)b + ]); + b->clear(); + ref = ""; + } + return 1; + ]], 1 ) + + +dnl add_int( bignum, bits); + +test_any( [[ + string ref = ""; + String.Buffer b = String.Buffer(); + + for( int j=1; j<255; j++ ) + { + int i = random((1<<(j*8))-1); + ref += sprintf("%"+j+"c", i ); + b->add_int( i, j ); + if( (string)b != ref ) return ([ + "i":i, "j":j, + "ref":ref, + "b":(string)b + ]); + } + return 1; + ]], 1 ) + + +dnl add_hstring( str, bytes ) + +test_error( [[ + String.Buffer b = String.Buffer(); + b->add_hstring("testing", 0 ); + ]], 1 ) + +test_error( [[ + String.Buffer b = String.Buffer(); + b->add_hstring(" "*256, 1 ); +]], 1 ) + +test_any( [[ + array(String.Buffer) b = allocate(10,String.Buffer)(); + for( int i = 0; i<1000; i++ ) + { + string chunk = " "*random(255); + + for( int w = 0; w<9; w++ ) + b[w]->add_hstring( chunk, w+1 ); + } + + for( int i = 0; i<1000; i++ ) + { + string chunk = b[0]->read_hstring(1); + for( int w = 1; w<9; w++ ) + { + if( b[w]->read_hstring( w+1 ) != chunk ) + return ({w+1,strlen(chunk)}); + } + } + return 1; +]], 1); + +test_any( [[ + String.Buffer b = String.Buffer(); + b->append( "\0\1" ); + if( b->read_hstring( 2 ) ) + return -1; + if( sizeof(b) != 2 ) + return -2; + b->append("x"); + if( b->read_hstring( 2 ) != "x" ) + return -3; + return 1; +]], 1); + +dnl add_hstring( obj*, bits ) + +test_any( [[ + array(String.Buffer) b = allocate(10,String.Buffer)(); + System.Memory chunk = System.Memory(255); + for( int i = 0; i<1000; i++ ) + { + for( int w = 0; w<9; w++ ) + b[w]->add_hstring( chunk, w+1 ); + } + + for( int i = 0; i<1000; i++ ) + { + string chunk = b[0]->read_hstring(1); + for( int w = 1; w<9; w++ ) + { + if( b[w]->read_hstring( w+1 ) != chunk ) + return ({w+1,strlen(chunk)}); + } + } + return 1; +]], 1); + +dnl add_ints + +test_any( [[ + array q = allocate(100000,random)(8438439834983948938439849834983498349834983498); + String.Buffer i = String.Buffer(); + i->add_ints( q, 20 ); + return 1; +]], 1); + +test_error( [[ + array q = ({8438439834983948938439849834983498349834983498}); + String.Buffer i = String.Buffer(); + i->add_ints( q, 7 ); + return 1; +]], 1); + + +test_any( [[ + array q = ({10,20,30}); + String.Buffer i = String.Buffer(); + i->add_ints( q, 7 ); + return 1; +]], 1); +test_error( [[ + array q = ({10,"20",30}); + String.Buffer i = String.Buffer(); + i->add_ints( q, 7 ); + return 1; +]], 1); + +test_any( [[ + array q = ({10,"20",30}); + String.Buffer i = String.Buffer(); + catch{i->add_ints( q, 7 );}; + if( sizeof(i) ) + return -1; + return 1; +]], 1); + +dnl lock + +dnl cast + +dnl indexing + +dnl set_error_mode + +dnl _sprintf test_eq([[ String.count( "", "a" ) ]], 0) test_eq([[ String.count( "aaa", "a" ) ]], 3) diff --git a/src/builtin.cmod b/src/builtin.cmod index f257f6b57ac7cb7299f41413d21a6e8737cd6526..fb05bc33993dc47e4364a9741df4c5676a17bc1f 100644 --- a/src/builtin.cmod +++ b/src/builtin.cmod @@ -31,6 +31,10 @@ #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> @@ -2971,19 +2975,497 @@ PIKEFUN array(mixed) backtrace() */ PIKECLASS Buffer { + CVAR char* buffer; + CVAR size_t offset; + CVAR size_t len; + CVAR unsigned shift; CVAR struct string_builder str; - CVAR int initial; + CVAR unsigned readonly; + CVAR size_t initial; + + 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() { - RETURN THIS->str.malloced; + 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; } - void f_Buffer_get_copy( INT32 args ); - void f_Buffer_get( INT32 args ); - void f_Buffer_add( INT32 args ); + /*! @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. *! @@ -2992,11 +3474,58 @@ PIKECLASS Buffer *! the operation of @[add()] (slightly) by passing the size to this *! function. */ - PIKEFUN void create( int|void size ) + PIKEFUN void create( string|object|int|void value ) { struct Buffer_struct *str = THIS; - init_string_builder_alloc(&str->str, - str->initial = size ? MINIMUM(size->u.integer, 1 ) : 256 , 0); + 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 ) @@ -3005,37 +3534,52 @@ PIKECLASS Buffer */ PIKEFUN string _sprintf( int flag, mapping flags ) { + pop_n_elems( args ); switch( flag ) { case 'O': - { - struct pike_string *res; + { struct Buffer_struct *str = THIS; - push_text( "Buffer(%d /* %d */)" ); - push_int(str->str.s->len); + push_text( "Buffer(%d-%d /* %d */)" ); + push_int(str->len); + push_int(str->offset); push_int(str->str.malloced); - f_sprintf( 3 ); - dmalloc_touch_svalue(Pike_sp-1); - res = Pike_sp[-1].u.string; - Pike_sp--; - RETURN res; - } - + f_sprintf( 4 ); + break; + } case 's': - { - pop_n_elems( args ); - if( Pike_fp->current_object->refs != 1 ) - f_Buffer_get_copy( 0 ); - else - f_Buffer_get( 0 ); - } - return; - + 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': - RETURN make_shared_binary_string("Buffer",6); + push_string(make_shared_binary_string("Buffer",6)); + break; + + default: + push_undefined(); } - pop_n_elems( args ); - 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 ) @@ -3048,10 +3592,11 @@ PIKECLASS Buffer if( type == literal_string_string ) { pop_stack(); - if( Pike_fp->current_object->refs != 1 ) + if( Pike_fp->current_object->refs != 1 + || THIS->str.s->refs != 1) f_Buffer_get_copy( 0 ); else - f_Buffer_get( 0 ); + f_Buffer_read( 0 ); return; } @@ -3059,10 +3604,11 @@ PIKECLASS Buffer { struct Buffer_struct *str = THIS; pop_stack(); - if( Pike_fp->current_object->refs != 1 ) + if( Pike_fp->current_object->refs != 1 + || THIS->str.s->refs != 1) f_Buffer_get_copy( 0 ); else - f_Buffer_get( 0 ); + f_Buffer_read( 0 ); o_cast_to_int( ); return; } @@ -3070,13 +3616,13 @@ PIKECLASS Buffer pop_stack(); push_undefined(); } - + /*! @decl int `[](int index) */ PIKEFUN int `[](int index) { - struct pike_string *s = THIS->str.s; - unsigned len = s->len; + struct Buffer_struct *str = THIS; + unsigned len = str->len; if (index<0) index += len; @@ -3084,7 +3630,7 @@ PIKECLASS Buffer 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(s->str, s->size_shift, index); + RETURN generic_extract(str->buffer, str->shift, index); } /*! @decl Buffer `[..](int start, int start_type, int end, int end_type) @@ -3092,10 +3638,9 @@ PIKECLASS Buffer PIKEFUN Buffer `[..](int start, int start_type, int end, int end_type) { struct Buffer_struct *str = THIS; - struct pike_string *s = str->str.s; - unsigned len = s->len, shift = s->size_shift; - char *p; - + size_t len = str->len; + unsigned shift = str->shift; + struct pike_string *s; struct object *res; struct Buffer_struct *str2; @@ -3110,15 +3655,19 @@ PIKECLASS Buffer start = 0; if (end >= len) end = len-1; - str2->initial = end -= start-1; - init_string_builder_alloc (&str2->str, end, shift); - p = s->str; - memcpy ((s = str2->str.s)->str, p+(start<<shift), end<<shift); - s->str[end<<shift] = 0; // Ensure NUL-termination - s->len = end; + + 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); + res->flags |= OBJECT_CLEAR_ON_EXIT; + push_object(res); } /*! @decl int `[]=(int index, int ch) @@ -3126,8 +3675,8 @@ PIKECLASS Buffer PIKEFUN int `[]=(int index, int ch) { struct Buffer_struct *str = THIS; - struct pike_string *s = str->str.s; - unsigned len = s->len; + struct pike_string *s; + size_t len = str->len; pop_n_elems(args); @@ -3137,8 +3686,9 @@ PIKECLASS Buffer 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(s->size_shift) { + switch(str->shift) { case 0: if ((unsigned)ch < 65536) len = 1; @@ -3149,50 +3699,127 @@ PIKECLASS Buffer else break; } - string_build_mkspace(&str->str, 0, str->str.known_shift = len); + str->str.known_shift = len; } - low_set_index(str->str.s,index,ch); + s = buffer_mkspace(0, len); + low_set_index(s, index+str->offset, ch); push_int(ch); } - /*! @decl Buffer `+( string|Buffer what ) + /*! @decl Buffer `+=( string|Buffer ... what ) */ - PIKEFUN Buffer `+( string|Buffer what ) + PIKEFUN Buffer `+=( string|Buffer ... what ) rawtype tFunc(tOr(tString, tObjIs_BUFFER), tObjIs_BUFFER); { - struct Buffer_struct *str = THIS, *str2; - struct object *res = fast_clone_object( Buffer_program ); - str2 = OBJ2_BUFFER( res ); - str2->initial = str->initial; - init_string_builder_copy (&str2->str, &str->str); - if( (Pike_fp->current_object->flags & OBJECT_CLEAR_ON_EXIT) ) - res->flags |= OBJECT_CLEAR_ON_EXIT; - apply( res, "add", 1 ); - RETURN res; + f_Buffer_add( args ); + pop_stack(); + ref_push_object(Pike_fp->current_object); } - /*! @decl Buffer `+=( string|Buffer what ) + /*! @decl Buffer `+( string|Buffer ... what ) */ - PIKEFUN Buffer `+=( string|Buffer what ) + PIKEFUN Buffer `+( string|Buffer ... what ) rawtype tFunc(tOr(tString, tObjIs_BUFFER), tObjIs_BUFFER); { - f_Buffer_add( 1 ); - REF_RETURN Pike_fp->current_object; + 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); } - static struct pike_string*getotherstr(struct svalue*other,unsigned stricttype) + /*! @decl Buffer ``+( string ... what ) + */ + PIKEFUN Buffer ``+( string ... what ) + rawtype tFunc(tString, tObjIs_BUFFER); { - switch(TYPEOF(*other)) - { + 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: - return other->u.string; + b = MKPCHARP_STR(s2 = other->u.string); + blen = s2->len; + goto runcmp; case PIKE_T_OBJECT: - if (other->u.object->prog == Buffer_program) - return OBJ2_BUFFER(other->u.object)->str.s; + 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 0; + return 1; } /*! @decl int(0..1) `==( mixed other ) @@ -3202,8 +3829,7 @@ PIKECLASS Buffer PIKEFUN int(0..1) `==( mixed other ) rawtype tFunc(tMixed, tInt01); { - struct pike_string *a; - RETURN ((a = getotherstr(other,0)) ? !my_quick_strcmp(THIS->str.s, a) : 0); + RETURN !buffer_cmp(other, 0); } /*! @decl int(0..1) `<( mixed other ) @@ -3213,7 +3839,7 @@ PIKECLASS Buffer PIKEFUN int(0..1) `<( mixed other ) rawtype tFunc(tMixed, tInt01); { - RETURN my_quick_strcmp(THIS->str.s, getotherstr(other,1))<0; + RETURN buffer_cmp(other, 1)<0; } /*! @decl int(0..1) `>( mixed other ) @@ -3223,187 +3849,292 @@ PIKECLASS Buffer PIKEFUN int(0..1) `>( mixed other ) rawtype tFunc(tMixed, tInt01); { - RETURN my_quick_strcmp(THIS->str.s, getotherstr(other,1))>0; + 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 int addat(int(0..) pos, string|Buffer ... data) + /*! @decl Buffer putchar( int char ) + *! @decl Buffer add_byte( int(0..255) i ) + *! @decl Buffer add_int8( int(-128..127) i ) *! - *! 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. + *! Appends a single character at the end of the string. *! *! @returns - *! Returns the size of the buffer. - *! - *! @note - *! If the starting position @[pos] extends beyond the end of the current - *! buffer content, the gap will be filled with NUL-characters. + *! Returns the buffer itself. *! *! @seealso *! @[add()] */ - PIKEFUN int addat(int(0..) pos, string|Buffer ... arg1 ) - rawtype tFuncV(tIntPos, tOr(tString, tObjIs_BUFFER), tIntPos); - { + 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 sum = 0; - int j,shift = 0; + ptrdiff_t len; - if (pos < 0) - SIMPLE_BAD_ARG_ERROR("addat", 1, "int(0..)"); + 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); - for (j=1; j < args; j++) { - struct pike_string *a; - if (TYPEOF(Pike_sp[j-args]) == PIKE_T_STRING) - a = Pike_sp[j-args].u.string; - else if ((TYPEOF(Pike_sp[j-args]) != PIKE_T_OBJECT) || - (Pike_sp[j-args].u.object->prog != Buffer_program)) - SIMPLE_BAD_ARG_ERROR("addat", j+1, "string|String.Buffer"); - else if(!(a = OBJ2_BUFFER(Pike_sp[j-args].u.object)->str.s)) - continue; - sum += a->len; - shift |= a->size_shift; - } + pop_n_elems(args); + ref_push_object(Pike_fp->current_object); + } - s = str->str.s; - sum += pos; - shift |= str->str.known_shift; - shift = shift & ~(shift >> 1); - j = sum - s->len; - if (j>0) { - if (str->initial > sum) - j = str->initial-s->len; - string_build_mkspace(&str->str, j, shift); - } - else if (shift != str->str.known_shift) - string_build_mkspace(&str->str, 0, shift); - s = str->str.s; - /* We know it will be a string that really is this wide. */ - str->str.known_shift = shift; + static void buffer_add_bignum( struct object *i, int width ) + { + struct Buffer_struct *str = THIS; + struct pike_string *snum; - if (s->len < pos) // Clear the padding - memset(s->str + (s->len << s->size_shift), - 0, (pos - s->len) << s->size_shift); + push_int(256); + apply( i, "digits", 1 ); - for(j = 1; j<args; j++) { - struct pike_string *a; - if (TYPEOF(Pike_sp[j-args]) == PIKE_T_STRING) - a = Pike_sp[j-args].u.string; - else if(!(a = OBJ2_BUFFER(Pike_sp[j-args].u.object)->str.s)) - continue; - pike_string_cpy(MKPCHARP_STR_OFF(s, pos), a); - pos += a->len; - } + snum = Pike_sp[-1].u.string; - if (s->len < pos) { - s->len = pos; - s->str[s->len << s->size_shift] = 0; // Ensure NUL-termination - } + /* 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 ); + } - RETURN s->len; + static void buffer_end_rewind_on_error( ONERROR *x ) + { + UNSET_ONERROR( (*x) ); } - /*! @decl int add(string|Buffer ... data) + /*! @decl Buffer add_ints( array(int) integers, int(0..255) len ) *! - *! Adds @[data] to the buffer. + *! 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 size of the buffer. + *! Returns the buffer itself. *! *! @seealso - *! @[addat()] + *! @[add()],@[add_byte()],@[add_int8()] */ - PIKEFUN int add( string|Buffer ... arg1 ) - rawtype tFuncV(tNone, tOr(tString, tObjIs_BUFFER), tIntPos); - { - struct Buffer_struct *str = THIS; - push_int(str->str.s->len); - stack_revroll(++args); - f_Buffer_addat(args); + PIKEFUN Buffer add_int16( int(-32768..32767) i ) { + push_int(2); + f_Buffer_add_int(2); } - /*! @decl void putchar(int ch) - *! Appends the character @[ch] at the end of the string. - */ - PIKEFUN void putchar(int ch) { - struct Buffer_struct *str = THIS; - string_builder_putchar(&str->str, ch); + PIKEFUN Buffer add_short( int(0..65535) i ) { + f_Buffer_add_int16(1); } - /*! @decl int sprintf(strict_sprintf_format format, sprintf_args ... args) - *! Appends the output from @[sprintf] at the end of the string. - *! Returns the resulting size of the String.Buffer. + /*! @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 int sprintf(mixed ... arguments) - rawtype tFuncV(tAttr("strict_sprintf_format", tOr(tStr, tObj)), - tAttr("sprintf_args", tMix), tStr); + PIKEFUN Buffer add_int32( int(-2147483648..2147483647) i ) { + push_int(4); + f_Buffer_add_int(2); + } - { - // FIXME: Reset length on exception? - struct Buffer_struct *str = THIS; - low_f_sprintf(args, 0, &str->str); - RETURN str->str.s->len; + PIKEFUN Buffer add_long( int(0..4294967295) i ) { + f_Buffer_add_int32(1); } - /*! @decl string get_copy() + /*! @decl Buffer add_hstring( string|Buffer|System.Memory data, int size_size ) *! - *! Get the data from the buffer. Significantly slower than @[get], - *! but does not clear the buffer. + *! 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 - *! @[get()] + *! @[add()],@[sprintf()] */ - PIKEFUN string get_copy() - { - struct pike_string *str = THIS->str.s; - ptrdiff_t len; - if((len = str->len) > 0 ) - { - char *d = (char *)str->str; - switch( str->size_shift ) + 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: { - case 0: - str=make_shared_binary_string0((p_wchar0 *)d,len); + struct sysmem *sm; + if (str->u.object->prog == Buffer_program) { + len = OBJ2_BUFFER(str->u.object)->len; break; - case 1: - str=make_shared_binary_string1((p_wchar1 *)d,len); - break; - default: - str=make_shared_binary_string2((p_wchar2 *)d,len); + } + if (sm = system_memory(str->u.object)) { + len = sm->size; break; + } } - if( Pike_fp->current_object->flags & OBJECT_CLEAR_ON_EXIT ) - str->flags |= STRING_CLEAR_ON_EXIT; - RETURN str; + default: + Pike_error("Bad argument 1 to add_hstring()," + " expected string|Buffer|System.Memory.\n"); } - push_empty_string(); + /* 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 string get() - *! - *! Get the data from the buffer. - *! - *! @note - *! This will clear the data in the buffer - *! - *! @seealso - *! @[get_copy()], @[clear()] + /*! @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 string get( ) + 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 = finish_string_builder( &str->str ); - if( Pike_fp->current_object->flags & OBJECT_CLEAR_ON_EXIT ) - s->flags |= STRING_CLEAR_ON_EXIT; - push_string(s); - // Pick a smaller size to minimise fragmentation - // addat() will expand if it notices reuse of - // the Buffer - init_string_builder_alloc(&str->str, sizeof(struct svalue), 0); + 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 discard) + /*! @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. @@ -3411,46 +4142,56 @@ PIKECLASS Buffer *! @seealso *! @[get()], @[get_copy()], @[clear()] */ - PIKEFUN Buffer|void cut(int index, int|void end_or_none,void|int discard) + 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; - struct pike_string *s = str->str.s; - unsigned len = s->len,shift = s->size_shift; - char *p = s->str; - INT_TYPE end,vdiscard; + 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-1; - - p = (char*)p+(index<<shift); - index = end-index+1; - end = len-end; - len = index<<shift; - if (!vdiscard) - { - struct pike_string *s2; + 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 ); - init_string_builder_alloc(&str2->str, str2->initial = index, shift); - memcpy((s2=str2->str.s)->str, p, len); - s2->str[len] = 0; // Ensure NUL-termination - s2->len = index; + 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; + } + } } - memmove(p, p+len, end<<shift); // Copy NUL-termination - if( s->flags & STRING_CLEAR_ON_EXIT) - guaranteed_memset(p+len+(end<<shift) , 0, len ); - s->len -= index; + 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(!vdiscard) { if( (Pike_fp->current_object->flags & OBJECT_CLEAR_ON_EXIT) ) res->flags |= OBJECT_CLEAR_ON_EXIT; push_object(res); @@ -3459,16 +4200,452 @@ PIKECLASS Buffer 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()] + *! @[get()], @[cut()], @[trim()] */ PIKEFUN void clear() { struct Buffer_struct *str = THIS; + str->len = str->offset = 0; reset_string_builder(&str->str); } @@ -3478,8 +4655,7 @@ PIKECLASS Buffer */ PIKEFUN int _sizeof() { - struct Buffer_struct *str = THIS; - RETURN str->str.s->len; + RETURN THIS->len; } INIT @@ -3492,9 +4668,14 @@ PIKECLASS Buffer gc_trivial; { struct Buffer_struct *str = THIS; - if( Pike_fp->flags & OBJECT_CLEAR_ON_EXIT ) - guaranteed_memset( str->str.s->str, 0, str->str.s->len ); - free_string_builder( &str->str ); + 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 @@ -3502,6 +4683,142 @@ PIKECLASS Buffer 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 @@ -6179,4 +7496,5 @@ void exit_builtin(void) #ifndef USE_SETENV if (env_allocs) free_mapping (env_allocs); #endif + if( shm_program ) free_program( shm_program ); } diff --git a/src/stralloc.h b/src/stralloc.h index c98a89f5b24be448d98f23e933df49d0b11bfd12..4c8d009707c3f18d9461a49b81afa4ccb82c237c 100644 --- a/src/stralloc.h +++ b/src/stralloc.h @@ -335,6 +335,17 @@ ptrdiff_t generic_quick_binary_strcmp(const char *a, ptrdiff_t alen, int asize, const char *b, ptrdiff_t blen, int bsize) ATTRIBUTE((pure)); + +/* Does not take locale into account */ + +static INLINE ptrdiff_t __attribute__((unused)) + my_quick_pcharpcmp(const PCHARP a, ptrdiff_t alen, + const PCHARP b, ptrdiff_t blen) +{ + return generic_quick_binary_strcmp(a.ptr, alen, a.shift, + b.ptr, blen, b.shift); +} + ptrdiff_t generic_find_binary_prefix(const char *a, ptrdiff_t alen, int asize, const char *b,