commit 5abc0b66057a361eb68c241cfcba5aa6b63d73c3
parent 16308d53306b0dbb774d9443d72663f167766b74
Author: Alexander Burger <abu@software-lab.de>
Date:   Wed,  9 Mar 2011 18:09:14 +0100
Added 'err' (stderr redirection) function
Diffstat:
20 files changed, 277 insertions(+), 93 deletions(-)
diff --git a/CHANGES b/CHANGES
@@ -1,4 +1,5 @@
 * XXmar11 picoLisp-3.0.6
+   'err' function
    Removed 'rpc' function
    man pages for 'picolisp' and 'pil'
    'version' also for 32-bit
diff --git a/ReleaseNotes b/ReleaseNotes
@@ -39,7 +39,7 @@
 
 5. The name of the GUI function 'err' in "lib/form.l" was changed to 'error'
    (and also that of the corresponding default CSS type in "lib.css"). This was
-   done to reserve the name "err" for future standard error redirection.
+   done to reserve the name "err" for standard error redirection (point 8).
 
 6. The "opt/" directory (until now containing only a single "pilog.l" file) was
    removed from the base distribution. It is now tracked separately. To install
@@ -51,3 +51,6 @@
 
 7. The 'rpc' function was removed from the release. It is seldom used, and also
    redundant: (rpc 'foo ''arg) is equivalent to (pr '(foo 'arg))
+
+8. A new function 'err' for standard error redirection is now available. It is
+   analog to 'out', but accepts only a symbolic argument (like 'ctl').
diff --git a/doc/ref.html b/doc/ref.html
@@ -2164,11 +2164,12 @@ abbreviations:
 <dd><code>
    <a href="refP.html#path">path</a>
    <a href="refI.html#in">in</a>
-   <a href="refI.html#ipid">ipid</a>
    <a href="refO.html#out">out</a>
+   <a href="refE.html#err">err</a>
+   <a href="refC.html#ctl">ctl</a>
+   <a href="refI.html#ipid">ipid</a>
    <a href="refO.html#opid">opid</a>
    <a href="refP.html#pipe">pipe</a>
-   <a href="refC.html#ctl">ctl</a>
    <a href="refA.html#any">any</a>
    <a href="refS.html#sym">sym</a>
    <a href="refS.html#str">str</a>
diff --git a/doc/refC.html b/doc/refC.html
@@ -619,8 +619,9 @@ files does not exist, it will be created. When <code>sym</code> is
 <code>NIL</code>, a shared lock is tried on the current innermost I/O channel,
 and when it is <code>T</code>, an exclusive lock is tried instead. See also
 <code><a href="refI.html#in">in</a></code>, <code><a
-href="refP.html#pipe">pipe</a></code> and <code><a
-href="refO.html#out">out</a></code>.
+href="refO.html#out">out</a></code>, <code><a
+href="refE.html#err">err</a></code> and <code><a
+href="refP.html#pipe">pipe</a></code>.
 
 <pre><code>
 $ echo 9 >count                           # Write '9' to file "count"
diff --git a/doc/refE.html b/doc/refE.html
@@ -267,6 +267,22 @@ href="refM.html#member/2">member/2</a></code>.
 -> NIL
 </code></pre>
 
+<dt><a name="err"><code>(err 'sym . prg) -> any</code></a>
+<dd>Redirects the standard error stream to <code>sym</code> during the execution
+of <code>prg</code>. The current standard error stream will be saved and
+restored appropriately. If the argument is <code>NIL</code>, the current output
+stream will be used. Otherwise, <code>sym</code> is taken as a file name (opened
+in "append" mode if the first character is "+"), where standard error is to be
+written to. See also <code><a href="refI.html#in">in</a></code>, <code><a
+href="refO.html#out">out</a></code> and <code><a
+href="refC.html#ctl">ctl</a></code>.
+
+<pre><code>
+: (err "/dev/null"             # Suppress error messages
+   (call 'ls 'noSuchFile) )
+-> NIL
+</code></pre>
+
 <dt><a name="errno"><code>(errno) -> cnt</code></a>
 <dd>(64-bit version only) Returns the value of the standard I/O 'errno'
 variable.
diff --git a/doc/refI.html b/doc/refI.html
@@ -168,6 +168,7 @@ href="refC.html#call">call</a></code>, <code><a
 href="refL.html#load">load</a></code>, <code><a
 href="refF.html#file">file</a></code>, <code><a
 href="refO.html#out">out</a></code>, <code><a
+href="refE.html#err">err</a></code>, <code><a
 href="refP.html#poll">poll</a></code>, <code><a
 href="refP.html#pipe">pipe</a></code> and <code><a
 href="refC.html#ctl">ctl</a></code>.
diff --git a/doc/refO.html b/doc/refO.html
@@ -254,9 +254,10 @@ list), it is taken as a command with arguments, and a pipe is opened for output.
 See also <code><a href="refO.html#opid">opid</a></code>, <code> <a
 href="refC.html#call">call</a></code>, <code><a
 href="refI.html#in">in</a></code>, <code> <a
-href="refP.html#poll">poll</a></code>, <code> <a
-href="refP.html#pipe">pipe</a></code>, <code> <a
+href="refE.html#err">err</a></code>, <code> <a
 href="refC.html#ctl">ctl</a></code>, <code><a
+href="refP.html#pipe">pipe</a></code>, <code> <a
+href="refP.html#poll">poll</a></code>, <code> <a
 href="refC.html#close">close</a></code> and <code><a
 href="refL.html#load">load</a></code>.
 
diff --git a/doc64/structures b/doc64/structures
@@ -1,4 +1,4 @@
-# 03feb11abu
+# 09mar11abu
 # (c) Software Lab. Alexander Burger
 
 
@@ -208,7 +208,7 @@
          <III> put/get  |
          <II>  pid      |
          <I>   fd       |
-               LINK ----+  <-- inFrames, outFrames, ctlFrames
+               LINK ----+  <-- inFrames, outFrames, errFrames, ctlFrames
 
 
       Coroutine frame:
diff --git a/ersatz/picolisp.jar b/ersatz/picolisp.jar
Binary files differ.
diff --git a/lib/tags b/lib/tags
@@ -29,7 +29,7 @@ adr (603 . "@src64/main.l")
 alarm (480 . "@src64/main.l")
 all (772 . "@src64/sym.l")
 and (1621 . "@src64/flow.l")
-any (3879 . "@src64/io.l")
+any (3933 . "@src64/io.l")
 append (1338 . "@src64/subr.l")
 apply (713 . "@src64/apply.l")
 arg (2270 . "@src64/main.l")
@@ -82,12 +82,12 @@ cdddr (245 . "@src64/subr.l")
 cddr (79 . "@src64/subr.l")
 cdr (17 . "@src64/subr.l")
 chain (1141 . "@src64/subr.l")
-char (3361 . "@src64/io.l")
+char (3415 . "@src64/io.l")
 chop (1093 . "@src64/sym.l")
 circ (816 . "@src64/subr.l")
 circ? (2398 . "@src64/subr.l")
 clip (1795 . "@src64/subr.l")
-close (4267 . "@src64/io.l")
+close (4339 . "@src64/io.l")
 cmd (2873 . "@src64/main.l")
 cnt (1413 . "@src64/apply.l")
 co (2546 . "@src64/flow.l")
@@ -98,7 +98,7 @@ cond (1916 . "@src64/flow.l")
 connect (201 . "@src64/net.l")
 cons (747 . "@src64/subr.l")
 copy (1225 . "@src64/subr.l")
-ctl (4207 . "@src64/io.l")
+ctl (4217 . "@src64/io.l")
 ctty (2671 . "@src64/main.l")
 cut (1797 . "@src64/sym.l")
 date (2385 . "@src64/main.l")
@@ -115,13 +115,14 @@ dir (2804 . "@src64/main.l")
 dm (543 . "@src64/flow.l")
 do (2138 . "@src64/flow.l")
 e (2920 . "@src64/flow.l")
-echo (4298 . "@src64/io.l")
+echo (4370 . "@src64/io.l")
 env (615 . "@src64/main.l")
-eof (3438 . "@src64/io.l")
-eol (3429 . "@src64/io.l")
+eof (3492 . "@src64/io.l")
+eol (3483 . "@src64/io.l")
+err (4197 . "@src64/io.l")
 errno (1381 . "@src64/main.l")
 eval (182 . "@src64/flow.l")
-ext (5028 . "@src64/io.l")
+ext (5100 . "@src64/io.l")
 ext? (1034 . "@src64/sym.l")
 extern (900 . "@src64/sym.l")
 extra (1263 . "@src64/flow.l")
@@ -136,13 +137,13 @@ find (1322 . "@src64/apply.l")
 fish (1613 . "@src64/apply.l")
 flg? (2441 . "@src64/subr.l")
 flip (1695 . "@src64/subr.l")
-flush (5003 . "@src64/io.l")
+flush (5075 . "@src64/io.l")
 fold (3343 . "@src64/sym.l")
 for (2227 . "@src64/flow.l")
 fork (3264 . "@src64/flow.l")
 format (2089 . "@src64/big.l")
 free (2047 . "@src64/db.l")
-from (3457 . "@src64/io.l")
+from (3511 . "@src64/io.l")
 full (1075 . "@src64/subr.l")
 fun? (734 . "@src64/sym.l")
 gc (432 . "@src64/gc.l")
@@ -154,14 +155,14 @@ glue (1234 . "@src64/sym.l")
 gt0 (2716 . "@src64/big.l")
 head (1816 . "@src64/subr.l")
 heap (535 . "@src64/main.l")
-hear (3142 . "@src64/io.l")
+hear (3196 . "@src64/io.l")
 host (184 . "@src64/net.l")
 id (1027 . "@src64/db.l")
 idx (2037 . "@src64/sym.l")
 if (1802 . "@src64/flow.l")
 if2 (1821 . "@src64/flow.l")
 ifn (1862 . "@src64/flow.l")
-in (4103 . "@src64/io.l")
+in (4157 . "@src64/io.l")
 inc (2256 . "@src64/big.l")
 index (2633 . "@src64/subr.l")
 info (2708 . "@src64/main.l")
@@ -170,7 +171,7 @@ ipid (3209 . "@src64/flow.l")
 isa (959 . "@src64/flow.l")
 job (1426 . "@src64/flow.l")
 journal (970 . "@src64/db.l")
-key (3290 . "@src64/io.l")
+key (3344 . "@src64/io.l")
 kill (3241 . "@src64/flow.l")
 last (2040 . "@src64/subr.l")
 le0 (2691 . "@src64/big.l")
@@ -178,14 +179,14 @@ length (2737 . "@src64/subr.l")
 let (1476 . "@src64/flow.l")
 let? (1537 . "@src64/flow.l")
 lieu (1156 . "@src64/db.l")
-line (3613 . "@src64/io.l")
-lines (3766 . "@src64/io.l")
+line (3667 . "@src64/io.l")
+lines (3820 . "@src64/io.l")
 link (1172 . "@src64/subr.l")
 lisp (1948 . "@src64/main.l")
 list (887 . "@src64/subr.l")
 listen (151 . "@src64/net.l")
 lit (157 . "@src64/flow.l")
-load (4080 . "@src64/io.l")
+load (4134 . "@src64/io.l")
 lock (1184 . "@src64/db.l")
 loop (2170 . "@src64/flow.l")
 low? (3215 . "@src64/sym.l")
@@ -235,30 +236,30 @@ offset (2673 . "@src64/subr.l")
 on (1583 . "@src64/sym.l")
 onOff (1613 . "@src64/sym.l")
 one (1646 . "@src64/sym.l")
-open (4229 . "@src64/io.l")
+open (4301 . "@src64/io.l")
 opid (3225 . "@src64/flow.l")
 opt (2994 . "@src64/main.l")
 or (1637 . "@src64/flow.l")
-out (4123 . "@src64/io.l")
+out (4177 . "@src64/io.l")
 pack (1144 . "@src64/sym.l")
 pair (2390 . "@src64/subr.l")
 pass (754 . "@src64/apply.l")
 pat? (720 . "@src64/sym.l")
 path (1238 . "@src64/io.l")
-peek (3345 . "@src64/io.l")
+peek (3399 . "@src64/io.l")
 pick (1369 . "@src64/apply.l")
-pipe (4144 . "@src64/io.l")
-poll (3234 . "@src64/io.l")
+pipe (4238 . "@src64/io.l")
+poll (3288 . "@src64/io.l")
 pool (648 . "@src64/db.l")
 pop (1773 . "@src64/sym.l")
 port (5 . "@src64/net.l")
-pr (5111 . "@src64/io.l")
+pr (5183 . "@src64/io.l")
 pre? (1411 . "@src64/sym.l")
-prin (4927 . "@src64/io.l")
-prinl (4941 . "@src64/io.l")
-print (4967 . "@src64/io.l")
-println (4998 . "@src64/io.l")
-printsp (4983 . "@src64/io.l")
+prin (4999 . "@src64/io.l")
+prinl (5013 . "@src64/io.l")
+print (5039 . "@src64/io.l")
+println (5070 . "@src64/io.l")
+printsp (5055 . "@src64/io.l")
 prior (2709 . "@src64/subr.l")
 prog (1757 . "@src64/flow.l")
 prog1 (1765 . "@src64/flow.l")
@@ -278,12 +279,12 @@ rand (2973 . "@src64/big.l")
 range (997 . "@src64/subr.l")
 rank (3029 . "@src64/subr.l")
 raw (458 . "@src64/main.l")
-rd (5045 . "@src64/io.l")
-read (2573 . "@src64/io.l")
+rd (5117 . "@src64/io.l")
+read (2627 . "@src64/io.l")
 replace (1499 . "@src64/subr.l")
 rest (2299 . "@src64/main.l")
 reverse (1674 . "@src64/subr.l")
-rewind (5011 . "@src64/io.l")
+rewind (5083 . "@src64/io.l")
 rollback (1890 . "@src64/db.l")
 rot (848 . "@src64/subr.l")
 run (313 . "@src64/flow.l")
@@ -296,31 +297,31 @@ set (1482 . "@src64/sym.l")
 setq (1515 . "@src64/sym.l")
 sigio (496 . "@src64/main.l")
 size (2802 . "@src64/subr.l")
-skip (3415 . "@src64/io.l")
+skip (3469 . "@src64/io.l")
 sort (3958 . "@src64/subr.l")
 sp? (711 . "@src64/sym.l")
-space (4945 . "@src64/io.l")
+space (5017 . "@src64/io.l")
 split (1588 . "@src64/subr.l")
 stack (564 . "@src64/main.l")
 state (2006 . "@src64/flow.l")
 stem (1985 . "@src64/subr.l")
-str (3933 . "@src64/io.l")
+str (3987 . "@src64/io.l")
 str? (1013 . "@src64/sym.l")
 strip (1572 . "@src64/subr.l")
 sub? (1444 . "@src64/sym.l")
 sum (1460 . "@src64/apply.l")
 super (1218 . "@src64/flow.l")
-sym (3919 . "@src64/io.l")
+sym (3973 . "@src64/io.l")
 sym? (2430 . "@src64/subr.l")
-sync (3102 . "@src64/io.l")
+sync (3156 . "@src64/io.l")
 sys (3061 . "@src64/flow.l")
 t (1748 . "@src64/flow.l")
 tail (1907 . "@src64/subr.l")
-tell (3174 . "@src64/io.l")
+tell (3228 . "@src64/io.l")
 text (1272 . "@src64/sym.l")
 throw (2490 . "@src64/flow.l")
 tick (3177 . "@src64/flow.l")
-till (3524 . "@src64/io.l")
+till (3578 . "@src64/io.l")
 time (2518 . "@src64/main.l")
 touch (1049 . "@src64/sym.l")
 trim (1755 . "@src64/subr.l")
@@ -337,12 +338,12 @@ use (1570 . "@src64/flow.l")
 usec (2623 . "@src64/main.l")
 val (1463 . "@src64/sym.l")
 version (3008 . "@src64/main.l")
-wait (3064 . "@src64/io.l")
+wait (3118 . "@src64/io.l")
 when (1881 . "@src64/flow.l")
 while (2058 . "@src64/flow.l")
 wipe (3090 . "@src64/sym.l")
 with (1327 . "@src64/flow.l")
-wr (5128 . "@src64/io.l")
+wr (5200 . "@src64/io.l")
 xchg (1538 . "@src64/sym.l")
 xor (1698 . "@src64/flow.l")
 x| (2885 . "@src64/big.l")
diff --git a/src/io.c b/src/io.c
@@ -795,6 +795,38 @@ void wrOpen(any ex, any x, outFrame *f) {
    }
 }
 
+void erOpen(any ex, any x, errFrame *f) {
+   int fd;
+
+   NeedSym(ex,x);
+   f->fd = dup(STDERR_FILENO);
+   if (isNil(x))
+      fd = dup(OutFile->fd);
+   else {
+      char nm[pathSize(x)];
+
+      pathString(x,nm);
+      if (nm[0] == '+') {
+         while ((fd = open(nm+1, O_APPEND|O_CREAT|O_WRONLY, 0666)) < 0) {
+            if (errno != EINTR)
+               openErr(ex, nm);
+            if (*Signal)
+               sighandler(ex);
+         }
+      }
+      else {
+         while ((fd = open(nm, O_CREAT|O_TRUNC|O_WRONLY, 0666)) < 0) {
+            if (errno != EINTR)
+               openErr(ex, nm);
+            if (*Signal)
+               sighandler(ex);
+         }
+      }
+      closeOnExec(ex, fd);
+   }
+   dup2(fd, STDERR_FILENO),  close(fd);
+}
+
 void ctOpen(any ex, any x, ctlFrame *f) {
    NeedSym(ex,x);
    if (isNil(x)) {
@@ -882,6 +914,10 @@ void pushOutFiles(outFrame *f) {
    f->link = Env.outFrames,  Env.outFrames = f;
 }
 
+void pushErrFiles(errFrame *f) {
+   f->link = Env.errFrames,  Env.errFrames = f;
+}
+
 void pushCtlFiles(ctlFrame *f) {
    f->link = Env.ctlFrames,  Env.ctlFrames = f;
 }
@@ -921,6 +957,12 @@ void popOutFiles(void) {
    OutFile = OutFiles[(Env.outFrames = Env.outFrames->link)? Env.outFrames->fd : STDOUT_FILENO];
 }
 
+void popErrFiles(void) {
+   dup2(Env.errFrames->fd, STDERR_FILENO);
+   close(Env.errFrames->fd);
+   Env.errFrames = Env.errFrames->link;
+}
+
 void popCtlFiles(void) {
    if (Env.ctlFrames->fd >= 0)
       close(Env.ctlFrames->fd);
@@ -2064,6 +2106,32 @@ any doOut(any ex) {
    return x;
 }
 
+// (err 'sym . prg) -> any
+any doErr(any ex) {
+   any x;
+   errFrame f;
+
+   x = cdr(ex),  x = EVAL(car(x));
+   erOpen(ex,x,&f);
+   pushErrFiles(&f);
+   x = prog(cddr(ex));
+   popErrFiles();
+   return x;
+}
+
+// (ctl 'sym . prg) -> any
+any doCtl(any ex) {
+   any x;
+   ctlFrame f;
+
+   x = cdr(ex),  x = EVAL(car(x));
+   ctOpen(ex,x,&f);
+   pushCtlFiles(&f);
+   x = prog(cddr(ex));
+   popCtlFiles();
+   return x;
+}
+
 // (pipe exe) -> cnt
 // (pipe exe . prg) -> any
 any doPipe(any ex) {
@@ -2101,19 +2169,6 @@ any doPipe(any ex) {
    return x;
 }
 
-// (ctl 'sym . prg) -> any
-any doCtl(any ex) {
-   any x;
-   ctlFrame f;
-
-   x = cdr(ex),  x = EVAL(car(x));
-   ctOpen(ex,x,&f);
-   pushCtlFiles(&f);
-   x = prog(cddr(ex));
-   popCtlFiles();
-   return x;
-}
-
 // (open 'any) -> cnt | NIL
 any doOpen(any ex) {
    any x = evSym(cdr(ex));
diff --git a/src/main.c b/src/main.c
@@ -1,4 +1,4 @@
-/* 07mar11abu
+/* 09mar11abu
  * (c) Software Lab. Alexander Burger
  */
 
@@ -616,6 +616,8 @@ void unwind(catchFrame *catch) {
          popInFiles();
       while (Env.outFrames != q->env.outFrames)
          popOutFiles();
+      while (Env.errFrames != q->env.errFrames)
+         popErrFiles();
       while (Env.ctlFrames != q->env.ctlFrames)
          popCtlFiles();
       Env = q->env;
@@ -634,6 +636,8 @@ void unwind(catchFrame *catch) {
       popInFiles();
    while (Env.outFrames)
       popOutFiles();
+   while (Env.errFrames)
+      popErrFiles();
    while (Env.ctlFrames)
       popCtlFiles();
 }
diff --git a/src/pico.h b/src/pico.h
@@ -102,6 +102,11 @@ typedef struct outFrame {
    int fd;
 } outFrame;
 
+typedef struct errFrame {
+   struct errFrame *link;
+   int fd;
+} errFrame;
+
 typedef struct ctlFrame {
    struct ctlFrame *link;
    int fd;
@@ -119,6 +124,7 @@ typedef struct stkEnv {
    any cls, key, task, *make, *yoke;
    inFrame *inFrames;
    outFrame *outFrames;
+   errFrame *errFrames;
    ctlFrame *ctlFrames;
    parseFrame *parser;
    void (*get)(void);
@@ -298,6 +304,7 @@ unsigned long ehash(any);
 any endString(void);
 bool eol(void);
 bool equal(any,any);
+void erOpen(any,any,errFrame*);
 void err(any,any,char*,...) __attribute__ ((noreturn));
 any evExpr(any,any);
 long evCnt(any,any);
@@ -354,6 +361,7 @@ void pathString(any,char*);
 void pipeError(any,char*);
 void popCtlFiles(void);
 void popInFiles(void);
+void popErrFiles(void);
 void popOutFiles(void);
 void pr(int,any);
 void prin(any);
@@ -362,9 +370,10 @@ void print(any);
 void print1(any);
 void prn(long);
 void protError(any,any) __attribute__ ((noreturn));
+void pushCtlFiles(ctlFrame*);
 void pushInFiles(inFrame*);
+void pushErrFiles(errFrame*);
 void pushOutFiles(outFrame*);
-void pushCtlFiles(ctlFrame*);
 void put(any,any,any);
 void putStdout(int);
 void rdOpen(any,any,inFrame*);
@@ -500,6 +509,7 @@ any doEq(any);
 any doEq0(any);
 any doEqT(any);
 any doEqual(any);
+any doErr(any);
 any doEval(any);
 any doExt(any);
 any doExtern(any);
diff --git a/src/tab.c b/src/tab.c
@@ -114,6 +114,7 @@ static symInit Symbols[] = {
    {doEq0, "=0"},
    {doEqT, "=T"},
    {doEqual, "="},
+   {doErr, "err"},
    {doEval, "eval"},
    {doExt, "ext"},
    {doExtern, "extern"},
diff --git a/src/vers.h b/src/vers.h
@@ -1 +1 @@
-static byte Version[4] = {3,0,5,21};
+static byte Version[4] = {3,0,5,22};
diff --git a/src64/err.l b/src64/err.l
@@ -1,4 +1,4 @@
-# 01mar11abu
+# 09mar11abu
 # (c) Software Lab. Alexander Burger
 
 # Debug print routine
@@ -241,7 +241,12 @@
          call popOutFiles  # Clean up
       loop
       do
-         cmp (EnvCtlFrames) (X VI)  # Open control frames?
+         cmp (EnvErrFrames) (X VI)  # Open error frames?
+      while ne  # Yes
+         call popErrFiles  # Clean up
+      loop
+      do
+         cmp (EnvCtlFrames) (X VII)  # Open control frames?
       while ne  # Yes
          call popCtlFiles  # Clean up
       loop
@@ -304,6 +309,11 @@
       call popOutFiles  # Clean up
    loop
    do
+      null (EnvErrFrames)  # Open error frames?
+   while nz  # Yes
+      call popErrFiles  # Clean up
+   loop
+   do
       null (EnvCtlFrames)  # Open control frames?
    while nz  # Yes
       call popCtlFiles  # Clean up
diff --git a/src64/glob.l b/src64/glob.l
@@ -505,8 +505,9 @@
    initFun NIL       "load"      doLoad
    initFun NIL       "in"        doIn
    initFun NIL       "out"       doOut
-   initFun NIL       "pipe"      doPipe
+   initFun NIL       "err"       doErr
    initFun NIL       "ctl"       doCtl
+   initFun NIL       "pipe"      doPipe
    initFun NIL       "open"      doOpen
    initFun NIL       "close"     doClose
    initFun NIL       "echo"      doEcho
@@ -581,7 +582,8 @@
 : EnvBind      word  0        # <III> Bind frames (first item in Env)
 : EnvInFrames  word  0        # <IV> Input frames
 : EnvOutFrames word  0        # <V> Output frames
-: EnvCtlFrames word  0        # <VI> Control frames
+: EnvErrFrames word  0        # <VI> Error frames
+: EnvCtlFrames word  0        # <VII> Control frames
 : EnvArgs      word  0        # Varargs frame
 : EnvNext      word  0        # Next vararg
 : EnvCls       word  0        # Method class
diff --git a/src64/io.l b/src64/io.l
@@ -1617,6 +1617,48 @@
    end
    ret
 
+(code 'erOpenEXY)
+   num E  # Need symbol
+   jnz symErrEX
+   sym E
+   jz symErrEX
+   cc dup(2)  # Duplicate current stderr
+   ld (Y I) A  # Save it
+   cmp E Nil  # Use current output channel?
+   if eq  # Yes
+      cc dup(((OutFile)))  # Duplicate 'fd'
+      ld C A  # Keep in C
+   else
+      push Z
+      call pathStringE_SZ  # File name
+      do
+         ld B (S)  # First char
+         cmp B (char "+")  # Plus?
+         if eq  # Yes
+            cc open(&(S 1) (| O_APPEND O_CREAT O_WRONLY) (oct "0666"))
+         else
+            cc open(S (| O_CREAT O_TRUNC O_WRONLY) (oct "0666"))
+         end
+         nul4  # OK?
+      while s  # No
+         call errno_A
+         cmp A EINTR  # Interrupted?
+         jne openErrEX  # No
+         null (Signal)  # Signal?
+         if nz  # Yes
+            call sighandlerX
+         end
+      loop
+      ld S Z  # Drop buffer
+      pop Z
+      ld C A  # Keep 'fd' in C
+      call closeOnExecAX
+   end
+   cc dup2(C 2)  # Dup 'fd' to STDERR_FILENO
+   ld A C
+   call closeAX
+   ret
+
 (code 'ctOpenEXY)
    num E  # Need symbol
    jnz symErrEX
@@ -1800,6 +1842,11 @@
    ld (EnvOutFrames) Y  # Link frame
    ret
 
+(code 'pushErrFilesY)
+   ld (Y) (EnvErrFrames)  # Set link
+   ld (EnvErrFrames) Y  # Link frame
+   ret
+
 (code 'pushCtlFilesY)
    ld (Y) (EnvCtlFrames)  # Set link
    ld (EnvCtlFrames) Y  # Link frame
@@ -1896,6 +1943,13 @@
    ld (OutFile) A  # Set OutFile
    ret
 
+(code 'popErrFiles)  # C
+   ld C (EnvErrFrames)  # Get ErrFrames
+   cc dup2((C I) 2)  # Restore stderr
+   cc close((C I))  # Close 'fd'
+   ld (EnvErrFrames) ((EnvErrFrames))  # Restore ErrFrames
+   ret
+
 (code 'popCtlFiles)  # C
    ld C (EnvCtlFrames)  # Get CtlFrames
    null (C I)  # 'fd' >= 0?
@@ -4139,6 +4193,46 @@
    pop X
    ret
 
+# (err 'sym . prg) -> any
+(code 'doErr 2)
+   push X
+   push Y
+   ld X E  # Expression in X
+   ld E (E CDR)
+   ld E (E)  # Eval 'any'
+   eval
+   sub S II  # ErrFrame
+   ld Y S
+   call erOpenEXY
+   call pushErrFilesY
+   ld X ((X CDR) CDR)  # Get 'prg'
+   prog X
+   call popErrFiles
+   add S II  # Drop ErrFrame
+   pop Y
+   pop X
+   ret
+
+# (ctl 'sym . prg) -> any
+(code 'doCtl 2)
+   push X
+   push Y
+   ld X E  # Expression in X
+   ld E (E CDR)
+   ld E (E)  # Eval 'any'
+   eval
+   sub S II  # CtlFrame
+   ld Y S
+   call ctOpenEXY
+   call pushCtlFilesY
+   ld X ((X CDR) CDR)  # Get 'prg'
+   prog X
+   call popCtlFiles
+   add S II  # Drop CtlFrame
+   pop Y
+   pop X
+   ret
+
 # (pipe exe) -> cnt
 # (pipe exe . prg) -> any
 (code 'doPipe 2)
@@ -4203,28 +4297,6 @@
    pop X
    ret
 
-# (ctl 'sym . prg) -> any
-(code 'doCtl 2)
-   push X
-   push Y
-   ld X E  # Expression in X
-   ld E (E CDR)
-   ld E (E)  # Eval 'any'
-   eval
-   push A  # CtlFrame
-   push A
-   ld Y S
-   call ctOpenEXY
-   call pushCtlFilesY
-   ld X ((X CDR) CDR)  # Get 'prg'
-   prog X
-   call popCtlFiles
-   pop A  # Drop CtlFrame
-   pop A
-   pop Y
-   pop X
-   ret
-
 # (open 'sym) -> cnt | NIL
 (code 'doOpen 2)
    push X
diff --git a/src64/version.l b/src64/version.l
@@ -1,6 +1,6 @@
-# 07mar11abu
+# 09mar11abu
 # (c) Software Lab. Alexander Burger
 
-(de *Version 3 0 5 21)
+(de *Version 3 0 5 22)
 
 # vi:et:ts=3:sw=3
diff --git a/test/src/io.l b/test/src/io.l
@@ -112,7 +112,7 @@
 (test 6 (load "-* 1 2 3"))
 
 
-### in out ###
+### in out err ###
 (out (tmp "file")
    (println 123)
    (println 'abc)
@@ -124,6 +124,11 @@
       (test 'abc (in -1 (read))) )
    (test '(d e f) (read)) )
 
+(let Err (tmp "err")
+   (test 1 (err Err (msg 1)))
+   (test 2 (err (pack "+" Err) (msg 2)))
+   (test "1^J2^J" (in Err (till NIL T))) )
+
 
 ### pipe ###
 (test 123 (pipe (println 123) (read)))