This file is indexed.

/usr/lib/python3/dist-packages/pyorick/pyorick.i0 is in python3-pyorick 1.4-2~deb9u1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
/* pyorick.i0
 * run yorick as a subprocess of python
 * Copyright (c) 2014, David H. Munro and John E. Field
 * All rights reserved.
 * This is Open Source software, released under the BSD 2-clause license,
 * see http://opensource.org/licenses/BSD-2-Clause for details.
 */

/* This script normally runs from the command line:
 *   yorick -i pyorick.i0 RFD WFD ...
 *   yorick -batch pyorick.i0 RFD WFD ...
 * where
 *   RFD is the read file descriptor number and
 *   WFD is the write file descriptor number
 * Binary messages from python to yorick flow through RFD pipe, while
 * binary messages from yorick to python flow through the WFD pipe.
 * You may also set _pyorick_rfd and _pyorick_wfd by hand before invoking
 * this script for debugging purposes.
 */
local pyorick_dir;
/* DOCUMENT pyorick_dir
 *   Directory containing pyorick.i0, the pyorick installation directory.
 */
pyorick_dir = current_include();
pyorick_dir = strpart(pyorick_dir, strgrep(".*/", pyorick_dir));

func pyorick(mode)
/* DOCUMENT pyorick
 *          pyorick, mode
 *   With no argument, receives active message from python-to-yorick
 *   pipe, performs the requested action, and sends the response to
 *   the yorick-to-python pipe.
 *   With the MODE argument, switches from one-shot mode to idler mode.
 *   MODE=0 is idler mode (the initial state if this file included from
 *   the command line), the only communication channel is through the
 *   python-yorick pipes; yorick ignores stdin, and installs an idler
 *   which continues the request-response cycle indefinitely, until
 *   the mode is set to one-shot.
 *   MODE=1 is one-shot mode, in which only a single request-response
 *   cycle executes.  (This is the intial state if this file is not
 *   included from the command line.)
 *   In idler mode, yorick spends the time between request-response
 *   cycles blocked waiting for the next request to come through the
 *   python-to-yorick pipe.
 *   In one-shot mode, the time between cycles is spent blocked waiting
 *   for input from stdin, as if this were an ordinary interactive session.
 *   If you want yorick to make screen plots, you must use one-shot mode,
 *   because screen plots must process windowing system events, which only
 *   happens waiting for input from stdin.
 *   On the other hand, if you are running in unattended batch mode,
 *   yorick quits instead of waiting for stdin, so you must use idler mode.
 * SEE ALSO: pydebug
 */
{
  extern _pyorick_mode, after_error;
  if (is_void(mode)) {
    local __, ___;
    _pyorick_hold = 0;  /* hold result, set in wait/action/unparse */
    if (_pyorick_wait() && _pyorick_sending && !is_void(___)) {
      if (pydebug) { write, "Y>pyorick request is:"; print, ___; }
      _pydebug = pydebug;
      include, grow("___=[];" , ___), 1;
      swap, pydebug, _pydebug;
      if (pydebug) { write, "Y>pyorick response is:"; info, ___; }
      msg = _pyorick_encode(___, 1);
      _pyorick_put, msg;
      _pyorick_sending = 0;
      swap, pydebug, _pydebug;
      if (!_pyorick_hold && is_obj(msg) && allof(msg(hdr) == [_ID_EOL,2]))
        save, _pyorick_refs, 3, ___;  /* prepare for auto-reference */
    }
  } else if (mode < 0) {
    _pyorick_mode = 1n;
    set_idler, , 0;
    after_error = [];
    if (pydebug) write, "Y>yorick has entered terminal mode";
  } else {
    _pyorick_mode = (mode != 0);
    if (_pyorick_mode) set_idler, , 4;
    else set_idler, _pyorick_idler, 4;
    after_error = _pyorick_err_handler;
    if (pydebug) write, format=" Y>yorick mode = %d\n", _pyorick_mode;
  }
}

local pydebug;
/* DOCUMENT pydebug = 1
 *   Causes debugging messages to be printed to stdout during each
 *   request-response cycle.  Set pydebug to 0 or [] to turn off
 *   debugging messages.
 */

func py(arglist)
/* DOCUMENT py                    exit pyorick terminal mode
 *       or py, "code", arglist   execute python code
 *       or py("expr", arglist)   return value of python expression
 *            with arglist, invoke "code" or "expr" as python function
 *       or py("vexpr:")          reference python variable "vexpr" in arglist
 *       or py("vexpr:", indexlist)         return python "vexpr" slice
 *       or py, "vexpr=", value             set python "vexpr" to value
 *       or py, "vexpr:", indexlist, value  set python "vexpr" slice to value
 *            indexlist order and meaning as in python
 *
 *   Yorick API for sending active messages to python.  The postfix ":" and
 *   postfix "=" are interchangable in the last four forms.
 *
 * SEE ALSO: pyorick
 */
{
  command = arglist(1);
  if (is_void(command)) {  /* exit yorick terminal mode */
    _pyorick_send, [_ID_EOL, 0];  /* tell python to exit terminal mode */
    /* write, format="%s", "ExitTerminalMode> "; */
    /* pause for python to acknowledge with _ID_NIL message */
    if (!is_void(_pyorick_decode(_pyorick_get())))
      _pyorick_panic, "bad acknowledgement exiting yorick terminal mode";
    _pyorick_mode = 1n;
    set_idler, , 4;
    after_error = _pyorick_err_handler;
    if (pydebug) write, "Y>py exiting yorick terminal mode";
    dbexit, 0;  /* return, making sure we are out of dbug> mode */

  } else {
    nargs = arglist(0) - 1;
    keys = arglist(-);
    command = strchar(command)(1:-1);
    isvar = anyof(command(0) == [':','=']);
    if (isvar) command = command(1:-1);
    list = where(!command);
    if (numberof(list)) command(list) = '\n';
    idx = hasval = 0;
    if (!isvar) {
      if (nargs || numberof(keys)) {
        id = am_subroutine()? _ID_SUBCALL : _ID_FUNCALL;
      } else {
        id = am_subroutine()? _ID_EXEC : _ID_EVAL;
        idx = 1;
      }
    } else {
      if (numberof(keys))
        error, "python slice cannot accept keyword as index";
      if (am_subroutine()) {
        if (!nargs)
          error, "set python variable requires a value";
        id = (nargs>1)? _ID_SETSLICE : _ID_SETVAR;
        nargs -= 1;  /* final argument is value, not list */
        hasval = 1;
      } else {
        id = nargs? _ID_GETSLICE : _ID_GETVAR;
      }
    }
    if (pydebug)
      write, format=" Y>py id=%ld, nargs=%ld, hasval=%ld\n", id, nargs, hasval;
    hdr = [id, numberof(command)];
    if (idx) msg = save(_pyorick_id=id, hdr, value=command);
    else msg = save(_pyorick_id=id, hdr, name=command);
    if (id == _ID_GETVAR) return msg;  /* reference to python variable */
    if (nargs>0 || numberof(keys)) {
      args = save();
      for (i=1 ; i<=nargs ; ++i)
        save, args, string(0), _pyorick_encode(arglist(1+i), 2);
      for (i=1 ; i<=numberof(keys) ; ++i) {
        name = strchar(keys(i))(1:-1);
        key = save(_pyorick_id=_ID_SETVAR, hdr=[_ID_SETVAR,numberof(name)],
                   name, value=_pyorick_encode(arglist(keys(i)), 2));
        save, args, string(0), key;
      }
      save, msg, args;
    }
    if (hasval) save, msg, value=_pyorick_encode(arglist(2+nargs), 2);

    if (pydebug) write, "Y>py sending active request";
    _pyorick_put, msg;
    v = _pyorick_decode(_pyorick_get());
    if (pydebug) write, "Y>py decoded reply to active request";
    if (is_obj(v, _pyorick_id, 1))
      return v;  /* ordinary passive value */
    if (allof(v(hdr) == [_ID_EOL,1]))
      error, "python reported error";
    error, "python reply undecodable";
  }
}
wrap_args, py;

/* ------------------------------------------------------------------------ */
/* remainder is not part of user interface, supports pyorick.py */

/* message id 0-15 are numeric types:
 *    char short int long longlong   float double longdouble
 * followed by unsigned, complex variants
 * passive messages (response to request):
 */
_ID_STRING = 16;   // yorick assumes iso_8859_1, need separate id for utf-8?
_ID_SLICE = 17;
_ID_NIL = 18;
_ID_LST = 19;
_ID_DCT = 20;
_ID_EOL = 21;
/* active messages (passive response required): */
_ID_EVAL = 32;
_ID_EXEC = 33;
_ID_GETVAR = 34;
_ID_SETVAR = 35;
_ID_FUNCALL = 36;
_ID_SUBCALL = 37;
_ID_GETSLICE = 38;
_ID_SETSLICE = 39;
_ID_GETSHAPE = 40;

/* _ID_EOL flag values:
 * 0   end of list
 * 1   error sent by _pyorick_puterr
 * 2   after GETVAR request to indicate unencodable
 * -1  yorick has quit
 */

/* garbled messages are fatal, shut down the pipes and exit idler loop */
func _pyorick_panic(msg)
{
  extern _pyorick_mode;
  _pyorick_mode = 1n;
  msg = "PANIC: " + msg;
  if (pydebug) write, "Y>"+msg;
  r = _pyorick_rfd;
  w = _pyorick_wfd;
  _pyorick_rfd = _pyorick_wfd = [];
  if (is_array(r)) fd_close, r;
  if (is_array(w)) fd_close, w;
  error, msg;
}
errs2caller, _pyorick_panic;

_pyorick_type = save(char, short, int, long, longlong=1, float, double,
                     longdouble=2, uchar=char, ushort=short, uint=int,
                     ulong=long, ulonglong=1, fcomplex=3, complex, lcomplex=4);

/* make it easy to use alternative to fd_read/write pipe i/o channels */
func _pyorick_recv(x) { return fd_read(_pyorick_rfd, x); }
func _pyorick_send(x) { return fd_write(_pyorick_wfd, x); }

/* _pyorick_err_handler
 *    Installed as after_error function by _pyorick_idler and pyorick,mode.
 *    Reinstalls _pyorick_idler in idler mode.  Always calls dbexit.
 * _pyorick_idler
 *    Perpetual loop: Read a complete message from python, perform the
 *    requested action, and write the response message to python.
 *    If a python sends a passive message, raises an error (causing
 *    _pyorick_err_handler to generate an error-EOL message).
 * _pyorick_wait
 *    Called by _pyorick_idler: Wait for active message from python
 *    by calling _pyorick_get, then call _pyorick_action to interpret
 *    the message and generate the text of the yorick code to be
 *    executed back in _pyorick_idler.
 * _pyorick_get
 *    Read complete message from python with as little interpretation
 *    as possible.  This must be an atomic operation -- if it is
 *    interrupted, there is no way to resynchronize the pipe, and the
 *    whole connection must shut down.
 *    A complete message (called msg in the code) is an oxy object; it
 *    may contain a list of quoted messages in the case of the passive
 *    LST/DCT messages, or the active messages which have argument or
 *    index lists (FUNCALL, SUBCALL, GETSLICE, SETSLICE).
 * _pyorick_action
 *    Thin wrapper over _pyorick_unparse to interpret active message.
 * _pyorick_unparse
 *    For active messages, generate the text of the expression or statement
 *    which causes yorick to perform the requested action.  For quoted
 *    passive messages, add their value to the stack of arguments referenced
 *    by this text.
 * _pyorick_decode
 *    Decode passive messages from the uninterpreted form returned by
 *    _pyorick_get.  Most of the work is for string array and lst/dct objects.
 * _pyorick_encode
 *    Produce the passive message representing the yorick value which
 *    is the response to the python request.  The format is the same as
 *    the output returned by _pyorick_get.  Performs all the error checking
 *    and repackaging without actually sending the response.
 * _pyorick_put
 *    Send the response message to python.  This must be an atomic operation;
 *    there is no way to resynchronize the pipe, so any interruption is
 *    fatal and causes the connection to shut down.
 * _pyorick_puterr
 *    Send an error-EOL message to python, for when some error prevents
 *    the request from being completed.
 */

func _pyorick_err_handler
{
  extern _pyorick_errmsg;
  if (pydebug) write, "Y>_pyorick_err_handler called";
  if (_pyorick_looping > 32)
    _pyorick_panic, "caught in exception loop\n" +
      (_pyorick_errmsg? _pyorick_errmsg : "");
  if (_pyorick_looping < 2) _pyorick_errmsg = catch_message;
  if (!_pyorick_mode) set_idler, _pyorick_idler, 4;
  _pyorick_puterr;  /* noop unless _pyorick_sending */
  dbexit, 0;        /* get out of debug mode from set_idler,,4 */
}

_pyorick_looping = 0;
func _pyorick_idler
{
  _pyorick_looping += 1;
  if (pydebug) write, "Y>_pyorick_idler: enter";
  for (__=___=[] ; !_pyorick_mode ; __=___=[]) pyorick;
  if (pydebug) write, "Y>_pyorick_idler: exit";
  if (_pyorick_mode || _pyorick_looping>64) set_idler, , 4;
  else if (!is_void(_pyorick_rfd)) set_idler, _pyorick_idler;
  _pyorick_looping = 0;
}

func _pyorick_wait(void)
{
  if (!is_void(_pyorick_rfd)) {
    if (_pyorick_sending) {
      if (!_pyorick_errmsg)
        _pyorick_errmsg = "ERROR (_pyorick_wait) <lost error message>";
      _pyorick_puterr;
      _pyorick_sending = 0;
    }
    msg = _pyorick_get(1);  /* atomic read */
    if (pydebug) write, "Y>_pyorick_wait: got message "+print(msg(hdr))(1);
    if (!is_void(_pyorick_rfd)) {
      /* perhaps should call _pyorick_puterr if _pyorick_errmsg is set? */
      _pyorick_sending = 1;
      if (_pyorick_action(msg))
        error, "got passive message when expecting active message";
      return 1n;
    }
  }
  return 0;
}

func _pyorick_refs(n, value)
{
  if (!n) {
    save, _pyorick_refs, 3, value;
    extern ___;
    ___ = _pyorick_refs(1,1);
  } else if (am_subroutine()) {
    if (n>3 && n<=_pyorick_refs(*)) { /* add n to free list */
      save, _pyorick_refs, noop(n), _pyorick_refs(2);
      save, _pyorick_refs, 2, n;
    }
    save, _pyorick_refs, 3, [];
  } else if (n == 1) {
    n = _pyorick_refs(*);
    if (_pyorick_refs(2) > n) {
      save, _pyorick_refs, 2, n+1;  /* should be true without this */
      for (i=1 ; i<n ;) /* expand and initialize free list */
        save, _pyorick_refs, string(0), n+(++i);
    }
    n = _pyorick_refs(2);
    save, _pyorick_refs, 2, _pyorick_refs(noop(n)); /* set next free */
    save, _pyorick_refs, noop(n), _pyorick_refs(3);
    return n;
  }
}
_pyorick_refs = save(,_pyorick_refs, ,4, ,[]);
/* _pyorick_refs(#)   is held use of reference #, for #>3
 *   _pyorick_refs(1)  is above function
 *   _pyorick_refs(2)  is next free reference #
 *   _pyorick_refs(3)  is temporary value of previous result (if unencodable)
 * _pyorick_refs(1,1)  moves 3 to next free reference #, returning that #
 * _pyorick_refs,1,#   frees reference #, dropping use
 * _pyorick_refs, 1,, value
 *    sets next free reference # to value, and sets ___ to that #
 */

/* slurp complete message with no excess processing -- must be atomic op
 * panic (close connection) if dimensions or lengths make no sense,
 *   or if message (or quoted message) not recognized
 */
func _pyorick_get(ctx)
{
  if (pydebug) write, "Y>_pyorick_get: blocking...";
  hdr = _pyorick_recv([0, 0]);
  if (pydebug) write, "Y>_pyorick_get: got message "+print(hdr)(1);
  ctx = ctx && allof(hdr == [_ID_GETVAR,0]);
  if (!ctx) _pyorick_refs, 1, 3;  /* discard previous result, if any */ 
  id = hdr(1);
  msg = save(_pyorick_id=id, hdr);
  if (id>=0 && id<_ID_STRING) {
    rank = hdr(2);
    dims = [rank];
    if (rank>0) grow, dims, _pyorick_recv(array(0, rank));
    if (rank<0 || (rank && anyof(dims(1:)<=0)))
      _pyorick_panic, "illegal dimensions";
    type = _pyorick_type(1+id);
    if (type == 1) {
      if (sizeof(long) == 8) type = long;
      else _pyorick_panic, "longlong unsupported";
    } else if (type == 3) {
      type = float;
      dims = grow(dims(1:1)+1, 2, (dims(1)? dims(2:) : []));
    } else if (is_array(type)) {
      _pyorick_panic, "longdouble unsupported";
    }
    dims = _pyorick_safedims(dims);
    save, msg, value = _pyorick_recv(array(type, dims));
  } else if (id == _ID_STRING) {
    rank = hdr(2);
    dims = [rank];
    if (rank) grow, dims, _pyorick_recv(array(0, rank));
    if (rank<0 || (rank && anyof(dims(1:)<=0)))
      _pyorick_panic, "illegal string array dimensions";
    dims = _pyorick_safedims(dims);
    lens = _pyorick_recv(array(0, dims));
    if (anyof(lens<0)) _pyorick_panic, "negative string length";
    save, msg, lens;
    lens = sum(lens);
    save, msg, value = (lens? _pyorick_recv(array(char, lens)) : []);
  } else if (id == _ID_SLICE) {
    save, msg, flag = hdr(2), value = _pyorick_recv([0, 0, 0]);
  } else if (id == _ID_NIL) {
    save, msg, value = [];
  } else if (anyof(id == [_ID_LST, _ID_DCT])) {
    save, msg, value = _pyorick_getlist();
  } else if (id == _ID_EOL) {
    save, msg, flag = hdr(2);

  } else if (anyof(id == [_ID_EVAL, _ID_EXEC])) {
    save, msg, value = _pyorick_getname(hdr(2));
  } else if (anyof(id == [_ID_GETVAR, _ID_SETVAR, _ID_GETSHAPE])) {
    save, msg, name = (ctx? [] : _pyorick_getname(hdr(2)));
    if (id == _ID_SETVAR) save, msg, value = _pyorick_get();
  } else if (id>=_ID_FUNCALL && id<=_ID_SETSLICE) {
    save, msg, name = _pyorick_getname(hdr(2));
    save, msg, args = _pyorick_getlist();
    if (id == _ID_SETSLICE) save, msg, value = _pyorick_get();

  } else {
    _pyorick_panic, "unknown message id";
  }
  return msg;
}

func _pyorick_safedims(dims)
{
  rank = dims(1);
  while (rank > 10) {
    dims(1) = --rank;
    dims(1+rank) *= dims(2+rank);
  }
  if (numberof(dims) > 1+rank) dims = dims(1:1+rank);
  return dims;
}

func _pyorick_getname(len)
{
  if (len <= 0) _pyorick_panic, "zero length name";
  return _pyorick_recv(array(char, len));
}

func _pyorick_getlist(void)
{
  args = save();
  for (;;) {
    m = _pyorick_get();
    if (m._pyorick_id == _ID_EOL) break;
    save, args, string(0), m;
  }
  return args;
}

func _pyorick_action(msg)
{
  id = msg(_pyorick_id);
  if (id < _ID_EVAL) return 1n;
  extern ___, __;  /* command/expression string and arguments object */
  __ = save();
  ___ = _pyorick_unparse(msg);
  return 0n;
}

func _pyorick_unparse(msg, raw)
{
  local value, args;
  id = msg(_pyorick_id);
  pfx = "___=";
  if (id < _ID_EVAL) {
    if (!raw) error, "expecting active message";
    if (id == _ID_NIL) {
      txt = "[]";
    } else {
      save, __, string(0), _pyorick_decode(msg);
      txt = "__(" + totxt(__(*)) + ")";
    }

  } else if (id==_ID_EVAL || id==_ID_EXEC) {
    eq_nocopy, value, msg(value);
    isf = (id == _ID_EVAL);
    if (raw && !isf) error, "illegal value or argument";
    list = where((value == '\n') | (value == '\r'));
    if (numberof(list)) value(list) = '\0';
    if (!raw) {
      extern _pyorick_hold;
      _pyorick_hold = (value(1)=='\05' && numberof(value)>1);
      if (_pyorick_hold) {
        value = value(2:);
        pfx = "_pyorick_refs,1,,";
      }
    }
    txt = strchar(value);
    if (isf && !raw) txt(1) = pfx + txt(1);

  } else if (id == _ID_GETVAR) {
    name = msg(name);
    if (raw || numberof(name)) {
      name = _pyorick_name(name, raw, _pyorick_hold, pfx);
      txt = (raw? "" : pfx) + name;
    } else {
      txt = "___=_pyorick_refs(1,1)";
    }
  } else if (id == _ID_SETVAR) {
    if (raw == 3) error, "illegal keyword argument in index list";
    txt = _pyorick_name(msg(name)) + "=" + _pyorick_unparse(msg(value), 1);
  } else if (id == _ID_GETSHAPE) {
    txt = pfx + "_pyorick_shape(" + _pyorick_name(msg(name)) + ")";

  } else {
    if (raw && id!=_ID_FUNCALL) error, "illegal argument or index";
    args = msg(args);  /* args always an oxy object, no copy here */
    n = args(*);
    if (id==_ID_FUNCALL || id>=_ID_GETSLICE) {
      name = _pyorick_name(msg(name), raw, _pyorick_hold, pfx);
      txt = (raw? "" : pfx) + name + "(";
      off = raw? 0 : strlen(pfx);
    } else if (id == _ID_SUBCALL) {
      txt = _pyorick_name(msg(name)) + (n? "," : "");
    }
    for (i=1 ; i<=n ; ++i) {
      a = args(noop(i));  /* args is a msg object, no copy here */
      if (id>=_ID_GETSLICE && a(_pyorick_id)==_ID_STRING) {
        /* immediate string index to slice operation means member extract
         * name(        -->  get_member(name,"msg")(
         * name(index,  -->  get_member(name(index),"msg")(
         */
        s = ",\"" + _pyorick_decode(a) + "\")(";  /* assume no " in string */
        len = strlen(txt);
        if (strpart(txt, len:len) == ",") s = ")" + s;
        txt = streplace(txt, [off,off,len-1,len], ["get_member(", s]);
      } else {
        txt += _pyorick_unparse(a, 2+(id>=_ID_GETSLICE));
        if (i < n) txt += ",";
      }
    }
    if (id==_ID_FUNCALL) {
      txt += ")";
    } else if (id >= _ID_GETSLICE) {
      if (strpart(txt, -1:0) == ")(") txt = strpart(txt, 1:-1);
      else txt += ")";
      if (id == _ID_SETSLICE)
        txt += "=" + _pyorick_unparse(msg(value), 1);
    }
  }
  return txt;
}

func _pyorick_name(name, raw, &hold, &pfx)
{
  hold = (name(1)=='\05' && numberof(name)>1 && !raw);
  if (hold) {
    pfx = "_pyorick_refs,1,,";
    name = name(2:);
  }
  ref = (name(1)<='9' && name(1)>='1');
  name = strchar(name);
  if (ref) name = "_pyorick_refs(" + name + ")";
  return name;
}

func _pyorick_decode(msg)
{
  local lens, value, name;
  id = msg(_pyorick_id);
  if (id < _ID_STRING) {
    return msg(value);
  } else if (id == _ID_STRING) {
    eq_nocopy, lens, msg(lens);
    s = array(string, dimsof(lens));
    list = where(lens);
    if (numberof(list)) s(list) = strchar(msg(value));
    list = where(lens == 1);
    if (numberof(list)) s(list) = "";
    return s;
  } else if (id == _ID_SLICE) {
    return rangeof([msg(value,1), msg(value,2), msg(value,3), msg(hdr,2)])
  } else if (id == _ID_NIL) {
    return [];
  } else if (anyof(id == [_ID_LST, _ID_DCT])) {
    named = (id == _ID_DCT);
    m = msg(value);
    value = save();
    for (i=1,n=m(*) ; i<=n ; ++i) {
      mi = m(noop(i));  /* mi is a message object, no copy here */
      if (named) {
        if (mi._pyorick_id != _ID_SETVAR)
          error, "missing member name in dict object";
        name = strchar(mi(name));
        mi = mi(value);
      } else {
        name = string(0);
      }
      if (mi._pyorick_id > _ID_DCT)
        error, "illegal member in dict or list object";
      save, value, noop(name), _pyorick_decode(mi);
    }
    return value;
  } else {
    return msg;  /* other stuff is not decodable */
  }
}

func _pyorick_shape(a)
{
  if (structof(a) == string) id = 17
  else id = where(_pyorick_type(*,) == typeof(a));
  if (numberof(id)) {
    if (id(1) == 1) id = 9;
    return grow(id-1, dimsof(a));
  } else if (is_func(a)) {
    return [-1];
  } else if (is_obj(a)==3 && is_void(a._pyorick_id)) {
    names = a(*,);
    return [(noneof(names)? -2 : (allof(names)? -3 : -8))];
  } else if (is_range(a)) {
    return [-4];
  } else if (is_void(a)) {
    return [-5];
  } else if (is_stream(a)) {
    return [-6];
  } else if (typeof(g) == "text_stream") {
    return [-7];
  } else {
    return [-9];
  }
}

func _pyorick_encode(value, env)
{
  if (is_obj(value, _pyorick_id, 1)) {
    /* ordinary passive value */
    if (is_array(value)) {
      if (structof(value) == string) {
        id = 16;
        lens = strlen(value) + 1;
        dims = dimsof(lens);
        list = where(!value);
        if (numberof(list)) {
          if (dims(1)) lens(list) = 0;
          else lens = 0;
          value = value(where(value));
        }
      } else {
        id = where(_pyorick_type(*,) == typeof(value));
        if (numberof(id)) {
          id = id(1) - 1;
          if (!id) id = 8;
          dims = dimsof(value);
        }
      }
      if (numberof(id)) {
        msg = save(_pyorick_id=id, hdr=[id,dims(1)]);
        if (id != 16) save, msg, value;
        else save, msg, lens, value=(is_void(value)?[]:strchar(value));
      } else {
        msg = "unencodable array datatype " + typeof(value);
      }

    } else if (is_range(value)) {
      id = _ID_SLICE;
      r = rangeof(value);
      msg = save(_pyorick_id=id, hdr=[id,r(4)], value=r(1:3));

    } else if (is_void(value)) {
      msg = save(_pyorick_id=_ID_NIL, hdr=[_ID_NIL,0], value=[]);

    } else if (is_obj(value) == 3) {
      names = value(*,);
      named = allof(names);
      if (named || noneof(names)) {
        id = (named? _ID_DCT : _ID_LST);
        v = save();
        msg = save(_pyorick_id=id, hdr=[id, 0], value=v);
        n = numberof(names);
        for (i=1 ; i<=n ; ++i) {
          m = _pyorick_encode(value(noop(i)), 0);
          if (!is_obj(m))
            return m;
          if (named) {
            name = strchar(names(i));
            m = save(_pyorick_id=_ID_SETVAR, hdr=[_ID_SETVAR,numberof(name)],
                     name, value=m);
          }
          save, v, string(0), m;
        }
      } else {
        msg = "oxy object members neither all named nor all anonymous";
      }

    } else {  /* unencodable value, indicate variable reference */
      msg = "unencodable value";
    }
    if (env==1 && !is_obj(msg))
      msg = save(_pyorick_id=_ID_EOL, hdr=[_ID_EOL,2], value=[]);

  } else {
    /* special or active value */
    msg = "unexpected active message to python";
  }

  if (env && !is_obj(msg)) {
    error, msg;
  }
  return msg;
}

/* send complete message with no excess processing - must be atomic op
 * an unknown message or other error here is a bug in _pyorick_encode
 */
func _pyorick_put(msg)
{
  id = msg(_pyorick_id);
  if (pydebug) write, "Y>_pyorick_put: sending message "+print(msg(hdr))(1);
  _pyorick_send, msg(hdr);
  if (id < _ID_STRING) {
    dims = dimsof(msg(value));
    if (dims(1)) _pyorick_send, dims(2:);
    _pyorick_send, msg(value);
  } else if (id == _ID_STRING) {
    dims = dimsof(msg(lens));
    if (dims(1)) _pyorick_send, dims(2:);
    _pyorick_send, msg(lens);
    if (sum(msg(lens))) _pyorick_send, msg(value);
  } else if (id == _ID_SLICE) {
    _pyorick_send, msg(value);
  } else if (id == _ID_NIL) {
    /* hdr only */
  } else if (id == _ID_LST || id == _ID_DCT) {
    _pyorick_putlist, msg(value);
  } else if (id == _ID_EOL) {
    /* hdr only */
  } else if (id == _ID_EVAL || id == _ID_EXEC) {
    _pyorick_send, msg(value);
  } else if (id == _ID_GETVAR) {
    _pyorick_send, msg(name);
  } else if (id == _ID_SETVAR) {
    _pyorick_send, msg(name);
    _pyorick_put, msg(value);
  } else if (id>=_ID_FUNCALL && id<=_ID_SETSLICE) {
    _pyorick_send, msg(name);
    _pyorick_putlist, msg(args);
    if (id == _ID_SETSLICE) _pyorick_put, msg(value);
  } else if (id == _ID_GETSHAPE) {
    _pyorick_send, msg(name);
    _pyorick_put, msg(value);
  } else {
    _pyorick_panic, "unknown message id";
  }
  if (pydebug) write, "Y>_pyorick_put: sent message "+print(msg(hdr))(1);
}

func _pyorick_putlist(msg)
{
  n = msg(*);
  for (i=1 ; i<=n ; ++i) _pyorick_put, msg(noop(i));
  _pyorick_send, [_ID_EOL, 0];
}

func _pyorick_puterr
{
  if (_pyorick_sending) {
    if (!is_void(_pyorick_wfd)) _pyorick_send, [_ID_EOL, 1];
    _pyorick_sending = 0;
  }
}

/* parse command line to get rfd, wfd file descriptors */
_pyorick_mode = 1n;
if (is_void(_pyorick_rfd)) {
  if (is_void(command_line)) command_line = get_argv(); /* e.g. -batch */
  if (numberof(command_line) >= 2) {
    _pyorick_rfd = tonum(command_line(1:2));
    if (allof((_pyorick_rfd>=0) & (_pyorick_rfd<1e8) & !(_pyorick_rfd%1))) {
      _pyorick_wfd = long(_pyorick_rfd(2));
      _pyorick_rfd = long(_pyorick_rfd(1));
      command_line = (numberof(command_line)>2)? command_line(3:) : [];
      pyorick, 0;
    }
  }
}

if (is_void(_pyorick_quit)) _pyorick_quit = quit;
func quit(void)
{
  /* send to both stdout and wfd in case parent blocked on either */
  write, format="\n%s", "PYORICK-QUIT> ";
  if (!is_void(_pyorick_wfd)) _pyorick_send, [_ID_EOL, -1];
  fd_close, _pyorick_wfd;
  fd_close, _pyorick_rfd;
  _pyorick_quit;
}