@@ -48,6 +48,7 @@ const enum Op {
4848 APPEND = 0x61 , // a
4949 EMPTY_DICT = 0x7d , // }
5050 SETITEM = 0x73 , // s
51+ MARK = 0x28 , // ( (mark stack position)
5152
5253 // Memo / frame machinery
5354 BINPUT = 0x71 , // q idx(1)
@@ -56,6 +57,8 @@ const enum Op {
5657 LONG_BINGET = 0x6a , // j idx(4)
5758 MEMOIZE = 0x94 , // \x94 (≥4)
5859 FRAME = 0x95 , // \x95 size(8) (proto‑5)
60+ APPENDS = 0x65 , // e
61+ SETITEMS = 0x75 , // u
5962}
6063
6164// ─── Binary helpers ─────────────────────────────────────────
@@ -118,6 +121,15 @@ class Reader {
118121 const hi = this . uint32LE ( ) >>> 0 ;
119122 return hi * 2 ** 32 + lo ;
120123 }
124+ int32LE ( ) {
125+ const v = new DataView (
126+ this . buf . buffer ,
127+ this . buf . byteOffset + this . pos ,
128+ 4 ,
129+ ) . getInt32 ( 0 , true ) ;
130+ this . pos += 4 ;
131+ return v ;
132+ }
121133 float64BE ( ) {
122134 const v = new DataView (
123135 this . buf . buffer ,
@@ -276,6 +288,9 @@ export function loads(buf: Uint8Array): any {
276288 void size ; // silence tsclint
277289 }
278290
291+ // Unique marker for stack operations (cannot be confused with user data)
292+ const MARK = Symbol ( "pickle-mark" ) ;
293+
279294 while ( ! r . eof ( ) ) {
280295 const op = r . byte ( ) ;
281296 switch ( op ) {
@@ -302,8 +317,7 @@ export function loads(buf: Uint8Array): any {
302317 break ;
303318 }
304319 case Op . BININT4 : {
305- const n = r . uint32LE ( ) ;
306- push ( n >>> 31 ? n - 0x1_0000_0000 : n ) ;
320+ push ( r . int32LE ( ) ) ;
307321 break ;
308322 }
309323 case Op . BINFLOAT :
@@ -386,6 +400,51 @@ export function loads(buf: Uint8Array): any {
386400 /* ignore */ break ;
387401 }
388402
403+ case Op . MARK :
404+ push ( MARK ) ;
405+ break ;
406+
407+ case Op . APPENDS : {
408+ // Pops all items after the last MARK and appends them to the list below the MARK
409+ // Find the last MARK
410+ const markIndex = stack . lastIndexOf ( MARK ) ;
411+ if ( markIndex === - 1 ) {
412+ throw new PickleError ( "APPENDS without MARK" ) ;
413+ }
414+ const lst = stack [ markIndex - 1 ] ;
415+ if ( ! Array . isArray ( lst ) ) {
416+ throw new PickleError ( "APPENDS expects a list below MARK" ) ;
417+ }
418+ const items = stack . slice ( markIndex + 1 ) ;
419+ lst . push ( ...items ) ;
420+ stack . length = markIndex - 1 ; // Remove everything after the list
421+ push ( lst ) ;
422+ break ;
423+ }
424+
425+ case Op . SETITEMS : {
426+ // Sets multiple key-value pairs in a dict after the last MARK
427+ // Find the last MARK
428+ const markIndex = stack . lastIndexOf ( MARK ) ;
429+ if ( markIndex === - 1 ) {
430+ throw new PickleError ( "SETITEMS without MARK" ) ;
431+ }
432+ const d = stack [ markIndex - 1 ] ;
433+ if ( typeof d !== "object" || d === null || Array . isArray ( d ) ) {
434+ throw new PickleError ( "SETITEMS expects a dict below MARK" ) ;
435+ }
436+ const items = stack . slice ( markIndex + 1 ) ;
437+ // Set key-value pairs (items come in pairs: key, value, key, value, ...)
438+ for ( let i = 0 ; i < items . length ; i += 2 ) {
439+ if ( i + 1 < items . length ) {
440+ d [ items [ i ] ] = items [ i + 1 ] ;
441+ }
442+ }
443+ stack . length = markIndex - 1 ; // Remove everything after the dict
444+ push ( d ) ;
445+ break ;
446+ }
447+
389448 default :
390449 throw new PickleError ( `Unsupported opcode 0x${ op . toString ( 16 ) } ` ) ;
391450 }
0 commit comments