xref: /dragonfly/contrib/tcsh-6/ed.xmap.c (revision 5fb3968e)
1 /*
2  * ed.xmap.c: This module contains the procedures for maintaining
3  *	      the extended-key map.
4  *
5  * 	      An extended-key (Xkey) is a sequence of keystrokes
6  *	      introduced with an sequence introducer and consisting
7  *	      of an arbitrary number of characters.  This module maintains
8  *	      a map (the Xmap) to convert these extended-key sequences
9  * 	      into input strings (XK_STR), editor functions (XK_CMD), or
10  *	      unix commands (XK_EXE). It contains the
11  *	      following externally visible functions.
12  *
13  *		int GetXkey(ch,val);
14  *		CStr *ch;
15  *		XmapVal *val;
16  *
17  *	      Looks up *ch in map and then reads characters until a
18  *	      complete match is found or a mismatch occurs. Returns the
19  *	      type of the match found (XK_STR, XK_CMD, or XK_EXE).
20  *	      Returns NULL in val.str and XK_STR for no match.
21  *	      The last character read is returned in *ch.
22  *
23  *		void AddXkey(Xkey, val, ntype);
24  *		CStr *Xkey;
25  *		XmapVal *val;
26  *		int ntype;
27  *
28  *	      Adds Xkey to the Xmap and associates the value in val with it.
29  *	      If Xkey is already is in Xmap, the new code is applied to the
30  *	      existing Xkey. Ntype specifies if code is a command, an
31  *	      out string or a unix command.
32  *
33  *	        int DeleteXkey(Xkey);
34  *	        CStr *Xkey;
35  *
36  *	      Delete the Xkey and all longer Xkeys staring with Xkey, if
37  *	      they exists.
38  *
39  *	      Warning:
40  *		If Xkey is a substring of some other Xkeys, then the longer
41  *		Xkeys are lost!!  That is, if the Xkeys "abcd" and "abcef"
42  *		are in Xmap, adding the key "abc" will cause the first two
43  *		definitions to be lost.
44  *
45  *		void ResetXmap();
46  *
47  *	      Removes all entries from Xmap and resets the defaults.
48  *
49  *		void PrintXkey(Xkey);
50  *		CStr *Xkey;
51  *
52  *	      Prints all extended keys prefixed by Xkey and their associated
53  *	      commands.
54  *
55  *	      Restrictions:
56  *	      -------------
57  *	        1) It is not possible to have one Xkey that is a
58  *		   substring of another.
59  */
60 /*-
61  * Copyright (c) 1980, 1991 The Regents of the University of California.
62  * All rights reserved.
63  *
64  * Redistribution and use in source and binary forms, with or without
65  * modification, are permitted provided that the following conditions
66  * are met:
67  * 1. Redistributions of source code must retain the above copyright
68  *    notice, this list of conditions and the following disclaimer.
69  * 2. Redistributions in binary form must reproduce the above copyright
70  *    notice, this list of conditions and the following disclaimer in the
71  *    documentation and/or other materials provided with the distribution.
72  * 3. Neither the name of the University nor the names of its contributors
73  *    may be used to endorse or promote products derived from this software
74  *    without specific prior written permission.
75  *
76  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
77  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
78  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
79  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
80  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
81  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
82  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
83  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
84  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
85  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
86  * SUCH DAMAGE.
87  */
88 #include "sh.h"
89 #include "ed.h"
90 #include "ed.defns.h"
91 
92 #ifndef NULL
93 #define NULL 0
94 #endif
95 
96 /* Internal Data types and declarations */
97 
98 /* The Nodes of the Xmap.  The Xmap is a linked list of these node
99  * elements
100  */
101 typedef struct Xmapnode {
102     Char    ch;			/* single character of Xkey */
103     int     type;
104     XmapVal val; 		/* command code or pointer to string, if this
105 				 * is a leaf */
106     struct Xmapnode *next;	/* ptr to next char of this Xkey */
107     struct Xmapnode *sibling;	/* ptr to another Xkey with same prefix */
108 } XmapNode;
109 
110 static XmapNode *Xmap = NULL;	/* the current Xmap */
111 
112 
113 /* Some declarations of procedures */
114 static	int       TraverseMap	(XmapNode *, CStr *, XmapVal *);
115 static	int       TryNode	(XmapNode *, CStr *, XmapVal *, int);
116 static	XmapNode *GetFreeNode	(CStr *);
117 static	void	  PutFreeNode	(XmapNode *);
118 static	int	  TryDeleteNode	(XmapNode **, CStr *);
119 static	int	  Lookup	(struct Strbuf *, const CStr *,
120 				 const XmapNode *);
121 static	void	  Enumerate	(struct Strbuf *, const XmapNode *);
122 static	void	  unparsech	(struct Strbuf *, Char);
123 
124 
125 XmapVal *
126 XmapCmd(int cmd)
127 {
128     static XmapVal xm;
129     xm.cmd = (KEYCMD) cmd;
130     return &xm;
131 }
132 
133 XmapVal *
134 XmapStr(CStr *str)
135 {
136     static XmapVal xm;
137     xm.str.len = str->len;
138     xm.str.buf = str->buf;
139     return &xm;
140 }
141 
142 /* ResetXmap():
143  *	Takes all nodes on Xmap and puts them on free list.  Then
144  *	initializes Xmap with arrow keys
145  */
146 void
147 ResetXmap(void)
148 {
149     PutFreeNode(Xmap);
150     Xmap = NULL;
151 
152     DefaultArrowKeys();
153     return;
154 }
155 
156 
157 /* GetXkey():
158  *	Calls the recursive function with entry point Xmap
159  */
160 int
161 GetXkey(CStr *ch, XmapVal *val)
162 {
163     return (TraverseMap(Xmap, ch, val));
164 }
165 
166 /* TraverseMap():
167  *	recursively traverses node in tree until match or mismatch is
168  * 	found.  May read in more characters.
169  */
170 static int
171 TraverseMap(XmapNode *ptr, CStr *ch, XmapVal *val)
172 {
173     Char    tch;
174 
175     if (ptr->ch == *(ch->buf)) {
176 	/* match found */
177 	if (ptr->next) {
178 	    /* Xkey not complete so get next char */
179 	    if (GetNextChar(&tch) != 1) {	/* if EOF or error */
180 		val->cmd = F_SEND_EOF;
181 		return XK_CMD;/* PWP: Pretend we just read an end-of-file */
182 	    }
183 	    *(ch->buf) = tch;
184 	    return (TraverseMap(ptr->next, ch, val));
185 	}
186 	else {
187 	    *val = ptr->val;
188 	    if (ptr->type != XK_CMD)
189 		*(ch->buf) = '\0';
190 	    return ptr->type;
191 	}
192     }
193     else {
194 	/* no match found here */
195 	if (ptr->sibling) {
196 	    /* try next sibling */
197 	    return (TraverseMap(ptr->sibling, ch, val));
198 	}
199 	else {
200 	    /* no next sibling -- mismatch */
201 	    val->str.buf = NULL;
202 	    val->str.len = 0;
203 	    return XK_STR;
204 	}
205     }
206 }
207 
208 void
209 AddXkey(const CStr *Xkey, XmapVal *val, int ntype)
210 {
211     CStr cs;
212     cs.buf = Xkey->buf;
213     cs.len = Xkey->len;
214     if (Xkey->len == 0) {
215 	xprintf("%s", CGETS(9, 1, "AddXkey: Null extended-key not allowed.\n"));
216 	return;
217     }
218 
219     if (ntype == XK_CMD && val->cmd == F_XKEY) {
220 	xprintf("%s",
221 	    CGETS(9, 2, "AddXkey: sequence-lead-in command not allowed\n"));
222 	return;
223     }
224 
225     if (Xmap == NULL)
226 	/* tree is initially empty.  Set up new node to match Xkey[0] */
227 	Xmap = GetFreeNode(&cs);	/* it is properly initialized */
228 
229     /* Now recurse through Xmap */
230     (void) TryNode(Xmap, &cs, val, ntype);
231     return;
232 }
233 
234 static int
235 TryNode(XmapNode *ptr, CStr *str, XmapVal *val, int ntype)
236 {
237     /*
238      * Find a node that matches *string or allocate a new one
239      */
240     if (ptr->ch != *(str->buf)) {
241 	XmapNode *xm;
242 
243 	for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
244 	    if (xm->sibling->ch == *(str->buf))
245 		break;
246 	if (xm->sibling == NULL)
247 	    xm->sibling = GetFreeNode(str);	/* setup new node */
248 	ptr = xm->sibling;
249     }
250 
251     str->buf++;
252     str->len--;
253     if (str->len == 0) {
254 	size_t len;
255 
256 	/* we're there */
257 	if (ptr->next != NULL) {
258 	    PutFreeNode(ptr->next);	/* lose longer Xkeys with this prefix */
259 	    ptr->next = NULL;
260 	}
261 
262 	switch (ptr->type) {
263 	case XK_STR:
264 	case XK_EXE:
265 	    xfree(ptr->val.str.buf);
266 	    ptr->val.str.len = 0;
267 	    break;
268 	case XK_NOD:
269 	case XK_CMD:
270 	    break;
271 	default:
272 	    abort();
273 	    break;
274 	}
275 
276 	switch (ptr->type = ntype) {
277 	case XK_CMD:
278 	    ptr->val = *val;
279 	    break;
280 	case XK_STR:
281 	case XK_EXE:
282 	    ptr->val.str.len = val->str.len;
283 	    len = (val->str.len + 1) * sizeof(*ptr->val.str.buf);
284 	    ptr->val.str.buf = xmalloc(len);
285 	    (void) memcpy(ptr->val.str.buf, val->str.buf, len);
286 	    break;
287 	default:
288 	    abort();
289 	    break;
290 	}
291     }
292     else {
293 	/* still more chars to go */
294 	if (ptr->next == NULL)
295 	    ptr->next = GetFreeNode(str);	/* setup new node */
296 	(void) TryNode(ptr->next, str, val, ntype);
297     }
298     return (0);
299 }
300 
301 void
302 ClearXkey(KEYCMD *map, const CStr *in)
303 {
304     unsigned char c = (unsigned char) *(in->buf);
305     if ((map[c] == F_XKEY) &&
306 	((map == CcKeyMap && CcAltMap[c] != F_XKEY) ||
307 	 (map == CcAltMap && CcKeyMap[c] != F_XKEY)))
308 	(void) DeleteXkey(in);
309 }
310 
311 int
312 DeleteXkey(const CStr *Xkey)
313 {
314     CStr s;
315 
316     s = *Xkey;
317     if (s.len == 0) {
318 	xprintf("%s",
319 	        CGETS(9, 3, "DeleteXkey: Null extended-key not allowed.\n"));
320 	return (-1);
321     }
322 
323     if (Xmap == NULL)
324 	return (0);
325 
326     (void) TryDeleteNode(&Xmap, &s);
327     return (0);
328 }
329 
330 /* Destroys str */
331 static int
332 TryDeleteNode(XmapNode **inptr, CStr *str)
333 {
334     XmapNode *ptr;
335 
336     ptr = *inptr;
337     /*
338      * Find a node that matches *string or allocate a new one
339      */
340     if (ptr->ch != *(str->buf)) {
341 	XmapNode *xm;
342 
343 	for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
344 	    if (xm->sibling->ch == *(str->buf))
345 		break;
346 	if (xm->sibling == NULL)
347 	    return (0);
348 	inptr = &xm->sibling;
349 	ptr = xm->sibling;
350     }
351 
352     str->buf++;
353     str->len--;
354 
355     if (str->len == 0) {
356 	/* we're there */
357 	*inptr = ptr->sibling;
358 	ptr->sibling = NULL;
359 	PutFreeNode(ptr);
360 	return (1);
361     }
362     else if (ptr->next != NULL && TryDeleteNode(&ptr->next, str) == 1) {
363 	if (ptr->next != NULL)
364 	    return (0);
365 	*inptr = ptr->sibling;
366 	ptr->sibling = NULL;
367 	PutFreeNode(ptr);
368 	return (1);
369     }
370     else {
371 	return (0);
372     }
373 }
374 
375 /* PutFreeNode():
376  *	Puts a tree of nodes onto free list using free(3).
377  */
378 static void
379 PutFreeNode(XmapNode *ptr)
380 {
381     if (ptr == NULL)
382 	return;
383 
384     if (ptr->next != NULL) {
385 	PutFreeNode(ptr->next);
386 	ptr->next = NULL;
387     }
388 
389     PutFreeNode(ptr->sibling);
390 
391     switch (ptr->type) {
392     case XK_CMD:
393     case XK_NOD:
394 	break;
395     case XK_EXE:
396     case XK_STR:
397 	xfree(ptr->val.str.buf);
398 	break;
399     default:
400 	abort();
401 	break;
402     }
403     xfree(ptr);
404 }
405 
406 
407 /* GetFreeNode():
408  *	Returns pointer to an XmapNode for ch.
409  */
410 static XmapNode *
411 GetFreeNode(CStr *ch)
412 {
413     XmapNode *ptr;
414 
415     ptr = xmalloc(sizeof(XmapNode));
416     ptr->ch = ch->buf[0];
417     ptr->type = XK_NOD;
418     ptr->val.str.buf = NULL;
419     ptr->val.str.len = 0;
420     ptr->next = NULL;
421     ptr->sibling = NULL;
422     return (ptr);
423 }
424 
425 
426 /* PrintXKey():
427  *	Print the binding associated with Xkey key.
428  *	Print entire Xmap if null
429  */
430 void
431 PrintXkey(const CStr *key)
432 {
433     struct Strbuf buf = Strbuf_INIT;
434     CStr cs;
435 
436     if (key) {
437 	cs.buf = key->buf;
438 	cs.len = key->len;
439     }
440     else {
441 	cs.buf = STRNULL;
442 	cs.len = 0;
443     }
444     /* do nothing if Xmap is empty and null key specified */
445     if (Xmap == NULL && cs.len == 0)
446 	return;
447 
448     Strbuf_append1(&buf, '"');
449     cleanup_push(&buf, Strbuf_cleanup);
450     if (Lookup(&buf, &cs, Xmap) <= -1)
451 	/* key is not bound */
452 	xprintf(CGETS(9, 4, "Unbound extended key \"%S\"\n"), cs.buf);
453     cleanup_until(&buf);
454 }
455 
456 /* Lookup():
457  *	look for the string starting at node ptr.
458  *	Print if last node
459  */
460 static int
461 Lookup(struct Strbuf *buf, const CStr *str, const XmapNode *ptr)
462 {
463     if (ptr == NULL)
464 	return (-1);		/* cannot have null ptr */
465 
466     if (str->len == 0) {
467 	/* no more chars in string.  Enumerate from here. */
468 	Enumerate(buf, ptr);
469 	return (0);
470     }
471     else {
472 	/* If match put this char into buf.  Recurse */
473 	if (ptr->ch == *(str->buf)) {
474 	    /* match found */
475 	    unparsech(buf, ptr->ch);
476 	    if (ptr->next != NULL) {
477 		/* not yet at leaf */
478 		CStr tstr;
479 		tstr.buf = str->buf + 1;
480 		tstr.len = str->len - 1;
481 		return (Lookup(buf, &tstr, ptr->next));
482 	    }
483 	    else {
484 		/* next node is null so key should be complete */
485 		if (str->len == 1) {
486 		    Strbuf_append1(buf, '"');
487 		    Strbuf_terminate(buf);
488 		    printOne(buf->s, &ptr->val, ptr->type);
489 		    return (0);
490 		}
491 		else
492 		    return (-1);/* mismatch -- string still has chars */
493 	    }
494 	}
495 	else {
496 	    /* no match found try sibling */
497 	    if (ptr->sibling)
498 		return (Lookup(buf, str, ptr->sibling));
499 	    else
500 		return (-1);
501 	}
502     }
503 }
504 
505 static void
506 Enumerate(struct Strbuf *buf, const XmapNode *ptr)
507 {
508     size_t old_len;
509 
510     if (ptr == NULL) {
511 #ifdef DEBUG_EDIT
512 	xprintf(CGETS(9, 6, "Enumerate: BUG!! Null ptr passed\n!"));
513 #endif
514 	return;
515     }
516 
517     old_len = buf->len;
518     unparsech(buf, ptr->ch); /* put this char at end of string */
519     if (ptr->next == NULL) {
520 	/* print this Xkey and function */
521 	Strbuf_append1(buf, '"');
522 	Strbuf_terminate(buf);
523 	printOne(buf->s, &ptr->val, ptr->type);
524     }
525     else
526 	Enumerate(buf, ptr->next);
527 
528     /* go to sibling if there is one */
529     if (ptr->sibling) {
530 	buf->len = old_len;
531 	Enumerate(buf, ptr->sibling);
532     }
533 }
534 
535 
536 /* PrintOne():
537  *	Print the specified key and its associated
538  *	function specified by val
539  */
540 void
541 printOne(const Char *key, const XmapVal *val, int ntype)
542 {
543     struct KeyFuncs *fp;
544     static const char *fmt = "%s\n";
545 
546     xprintf("%-15S-> ", key);
547     if (val != NULL)
548 	switch (ntype) {
549 	case XK_STR:
550 	case XK_EXE: {
551 	    unsigned char *p;
552 
553 	    p = unparsestring(&val->str, ntype == XK_STR ? STRQQ : STRBB);
554 	    cleanup_push(p, xfree);
555 	    xprintf(fmt, p);
556 	    cleanup_until(p);
557 	    break;
558 	}
559 	case XK_CMD:
560 	    for (fp = FuncNames; fp->name; fp++)
561 		if (val->cmd == fp->func)
562 		    xprintf(fmt, fp->name);
563 		break;
564 	default:
565 	    abort();
566 	    break;
567 	}
568     else
569 	xprintf(fmt, CGETS(9, 7, "no input"));
570 }
571 
572 static void
573 unparsech(struct Strbuf *buf, Char ch)
574 {
575     if (ch == 0) {
576 	Strbuf_append1(buf, '^');
577 	Strbuf_append1(buf, '@');
578     }
579     else if (Iscntrl(ch)) {
580 	Strbuf_append1(buf, '^');
581 	if (ch == CTL_ESC('\177'))
582 	    Strbuf_append1(buf, '?');
583 	else
584 #ifdef IS_ASCII
585 	    Strbuf_append1(buf, ch | 0100);
586 #else
587 	    Strbuf_append1(buf, _toebcdic[_toascii[ch]|0100]);
588 #endif
589     }
590     else if (ch == '^') {
591 	Strbuf_append1(buf, '\\');
592 	Strbuf_append1(buf, '^');
593     } else if (ch == '\\') {
594 	Strbuf_append1(buf, '\\');
595 	Strbuf_append1(buf, '\\');
596     } else if (ch == ' ' || (Isprint(ch) && !Isspace(ch))) {
597 	Strbuf_append1(buf, ch);
598     }
599     else {
600 	Strbuf_append1(buf, '\\');
601 	Strbuf_append1(buf, ((ch >> 6) & 7) + '0');
602 	Strbuf_append1(buf, ((ch >> 3) & 7) + '0');
603 	Strbuf_append1(buf, (ch & 7) + '0');
604     }
605 }
606 
607 eChar
608 parseescape(const Char **ptr)
609 {
610     const Char *p;
611     Char c;
612 
613     p = *ptr;
614 
615     if ((p[1] & CHAR) == 0) {
616 	xprintf(CGETS(9, 8, "Something must follow: %c\n"), (char)*p);
617 	return CHAR_ERR;
618     }
619     if ((*p & CHAR) == '\\') {
620 	p++;
621 	switch (*p & CHAR) {
622 	case 'a':
623 	    c = CTL_ESC('\007');         /* Bell */
624 	    break;
625 	case 'b':
626 	    c = CTL_ESC('\010');         /* Backspace */
627 	    break;
628 	case 'e':
629 	    c = CTL_ESC('\033');         /* Escape */
630 	    break;
631 	case 'f':
632 	    c = CTL_ESC('\014');         /* Form Feed */
633 	    break;
634 	case 'n':
635 	    c = CTL_ESC('\012');         /* New Line */
636 	    break;
637 	case 'r':
638 	    c = CTL_ESC('\015');         /* Carriage Return */
639 	    break;
640 	case 't':
641 	    c = CTL_ESC('\011');         /* Horizontal Tab */
642 	    break;
643 	case 'v':
644 	    c = CTL_ESC('\013');         /* Vertical Tab */
645 	    break;
646 	case '\\':
647 	    c = '\\';
648 	    break;
649 	case '0':
650 	case '1':
651 	case '2':
652 	case '3':
653 	case '4':
654 	case '5':
655 	case '6':
656 	case '7':
657 	    {
658 		int cnt, val;
659 		Char ch;
660 
661 		for (cnt = 0, val = 0; cnt < 3; cnt++) {
662 		    ch = *p++ & CHAR;
663 		    if (ch < '0' || ch > '7') {
664 			p--;
665 			break;
666 		    }
667 		    val = (val << 3) | (ch - '0');
668 		}
669 		if ((val & ~0xff) != 0) {
670 		    xprintf("%s", CGETS(9, 9,
671 			    "Octal constant does not fit in a char.\n"));
672 		    return 0;
673 		}
674 #ifndef IS_ASCII
675 		if (CTL_ESC(val) != val && adrof(STRwarnebcdic))
676 		    xprintf(/*CGETS(9, 9, no NLS-String yet!*/
677 			    "Warning: Octal constant \\%3.3o is interpreted as EBCDIC value.\n", val/*)*/);
678 #endif
679 		c = (Char) val;
680 		--p;
681 	    }
682 	    break;
683 	default:
684 	    c = *p;
685 	    break;
686 	}
687     }
688     else if ((*p & CHAR) == '^' && (Isalpha(p[1] & CHAR) ||
689 				    strchr("@^_?\\|[{]}", p[1] & CHAR))) {
690 	p++;
691 #ifdef IS_ASCII
692 	c = ((*p & CHAR) == '?') ? CTL_ESC('\177') : ((*p & CHAR) & 0237);
693 #else
694 	c = ((*p & CHAR) == '?') ? CTL_ESC('\177') : _toebcdic[_toascii[*p & CHAR] & 0237];
695 	if (adrof(STRwarnebcdic))
696 	    xprintf(/*CGETS(9, 9, no NLS-String yet!*/
697 		"Warning: Control character ^%c may be interpreted differently in EBCDIC.\n", *p & CHAR /*)*/);
698 #endif
699     }
700     else
701 	c = *p;
702     *ptr = p;
703     return (c);
704 }
705 
706 
707 unsigned char *
708 unparsestring(const CStr *str, const Char *sep)
709 {
710     unsigned char *buf, *b;
711     Char   p;
712     int l;
713 
714     /* Worst-case is "\uuu" or result of wctomb() for each char from str */
715     buf = xmalloc((str->len + 1) * max(4, MB_LEN_MAX));
716     b = buf;
717     if (sep[0])
718 #ifndef WINNT_NATIVE
719 	*b++ = sep[0];
720 #else /* WINNT_NATIVE */
721 	*b++ = CHAR & sep[0];
722 #endif /* !WINNT_NATIVE */
723 
724     for (l = 0; l < str->len; l++) {
725 	p = str->buf[l];
726 	if (Iscntrl(p)) {
727 	    *b++ = '^';
728 	    if (p == CTL_ESC('\177'))
729 		*b++ = '?';
730 	    else
731 #ifdef IS_ASCII
732 		*b++ = (unsigned char) (p | 0100);
733 #else
734 		*b++ = _toebcdic[_toascii[p]|0100];
735 #endif
736 	}
737 	else if (p == '^' || p == '\\') {
738 	    *b++ = '\\';
739 	    *b++ = (unsigned char) p;
740 	}
741 	else if (p == ' ' || (Isprint(p) && !Isspace(p)))
742 	    b += one_wctomb((char *)b, p);
743 	else {
744 	    *b++ = '\\';
745 	    *b++ = ((p >> 6) & 7) + '0';
746 	    *b++ = ((p >> 3) & 7) + '0';
747 	    *b++ = (p & 7) + '0';
748 	}
749     }
750     if (sep[0] && sep[1])
751 #ifndef WINNT_NATIVE
752 	*b++ = sep[1];
753 #else /* WINNT_NATIVE */
754 	*b++ = CHAR & sep[1];
755 #endif /* !WINNT_NATIVE */
756     *b++ = 0;
757     return buf;			/* should check for overflow */
758 }
759