xref: /dragonfly/contrib/tcsh-6/ed.xmap.c (revision b97fef05)
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("%-15" TCSH_S "-> ", 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 static Char
608 parse_hex_range(const Char **cp, size_t l)
609 {
610     size_t ui = 0;
611     Char r = 0, c;
612 
613     if (l > 8)
614 	abort();
615 
616     for (; (c = (**cp & CHAR)) != '\0' && ui < l && Isxdigit(c); (*cp)++, ui++) {
617 	Char x = Isdigit(c) ? '0' : ((Isupper(c) ? 'A' : 'a') - 10);
618 #ifndef IS_ASCII
619 	c = _toascii(c);
620 #endif
621 	r <<= 4;
622 	r |= c - x;
623     }
624     (*cp)--;
625 #ifndef IS_ASCII
626     return _toebcdic[r & 0xff];
627 #else
628     return r;
629 #endif
630 }
631 
632 /*
633  * The e parameter is false when we are doing echo and true when we are
634  * using it to parse other escaped strings. For echo, we don't print errors
635  * and we don't unescape backslashes that we don't understand. Perhaps we
636  * always not unescape backslashes we don't understand...
637  */
638 eChar
639 parseescape(const Char **ptr, int e)
640 {
641     const Char *p;
642     Char c;
643 
644     p = *ptr;
645 
646     if ((p[1] & CHAR) == 0) {
647 	if (e)
648 	    xprintf(CGETS(9, 8, "Something must follow: %c\n"), (char)*p);
649 	return CHAR_ERR;
650     }
651     if ((*p & CHAR) == '\\') {
652 	p++;
653 	switch (*p & CHAR) {
654 	case 'a':
655 	    c = CTL_ESC('\007');         /* Bell */
656 	    break;
657 	case 'b':
658 	    c = CTL_ESC('\010');         /* Backspace */
659 	    break;
660 	case 'c':
661 	    p++;
662 	    if ((*p & CHAR) == '\\') {
663 		p++;
664 		if ((*p & CHAR) != '\\') {
665 		    *ptr = p;
666 		    if (e)
667 			xprintf(/*CGETS(9, 9, */
668 			    "Invalid escape sequence: %s%c\n"/*)*/, "\\c\\", (char)*p);
669 		    return CHAR_ERR;
670 		}
671 		c = (*p & CHAR) & 0237;
672 	    } else if ((Isalpha(*p & CHAR) || strchr("@^_?\\|[{]}", *p & CHAR))) {
673 		/* XXX: Duplicate code from ^ below */
674 #ifdef IS_ASCII
675 		c = ((*p & CHAR) == '?') ? CTL_ESC('\177') : ((*p & CHAR) & 0237);
676 #else
677 		c = ((*p & CHAR) == '?') ? CTL_ESC('\177') : _toebcdic[_toascii[*p & CHAR] & 0237];
678 		if (adrof(STRwarnebcdic))
679 		    xprintf(/*CGETS(9, 9, no NLS-String yet!*/
680 			"Warning: Control character %s%c may be interpreted differently in EBCDIC.\n", "\\c", *p & CHAR /*)*/);
681 #endif
682 	    } else { /* backward compat */
683 		c = '\\';
684 		p -= 2;
685 	    }
686 	    break;
687 	case 'e':
688 	    c = CTL_ESC('\033');         /* Escape */
689 	    break;
690 	case 'f':
691 	    c = CTL_ESC('\014');         /* Form Feed */
692 	    break;
693 	case 'n':
694 	    c = CTL_ESC('\012');         /* New Line */
695 	    break;
696 	case 'r':
697 	    c = CTL_ESC('\015');         /* Carriage Return */
698 	    break;
699 	case 't':
700 	    c = CTL_ESC('\011');         /* Horizontal Tab */
701 	    break;
702 	case 'v':
703 	    c = CTL_ESC('\013');         /* Vertical Tab */
704 	    break;
705 	case '\\':
706 	    c = '\\';
707 	    break;
708 	case 'x':
709 	    p++;
710 	    if ((*p & CHAR) == '{' && Isxdigit(*(p + 1) & CHAR)) { /* \x{20ac} */
711 		const Char *q = p - 2;
712 		p++;
713 		c = parse_hex_range(&p, 8);
714 		if ((p[1] & CHAR) != '}') {
715 		    if (e)
716 			xprintf("%s", /* CGETS(9, 9, */
717 			    "Missing closing brace");
718 		    p = q;
719 		    c = '\\';
720 		    break;
721 		}
722 		p++;
723 	    } else if (Isxdigit(*p & CHAR)) {	/* \x9f */
724 		c = parse_hex_range(&p, 2);
725 	    } else { /* backward compat */
726 		c = '\\';
727 		p -= 2;
728 	    }
729 	    break;
730 	case 'u':
731 	case 'U':
732 	    {
733 		size_t limit = (*p & CHAR) == 'u' ? 4 : 8;
734 		p++;
735 		if (Isxdigit(*p & CHAR)) {	/* \u20ac or \U000020ac */
736 		    c = parse_hex_range(&p, limit);
737 		} else { /* backward compat */
738 		    c = '\\';
739 		    p -= 2;
740 		}
741 	    }
742 	    break;
743 	case '0':
744 	case '1':
745 	case '2':
746 	case '3':
747 	case '4':
748 	case '5':
749 	case '6':
750 	case '7':
751 	    {
752 		int cnt, val;
753 		Char ch;
754 
755 		for (cnt = 0, val = 0; cnt < 3; cnt++) {
756 		    ch = *p++ & CHAR;
757 		    if (ch < '0' || ch > '7') {
758 			p--;
759 			break;
760 		    }
761 		    val = (val << 3) | (ch - '0');
762 		}
763 		if ((val & ~0xff) != 0) {
764 		    if (e)
765 			xprintf("%s", CGETS(9, 9,
766 			    "Octal constant does not fit in a char.\n"));
767 		    *ptr = p;
768 		    return CHAR_ERR;
769 		}
770 #ifndef IS_ASCII
771 		if (CTL_ESC(val) != val && adrof(STRwarnebcdic))
772 		    xprintf(/*CGETS(9, 9, no NLS-String yet!*/
773 			"Warning: Octal constant \\%3.3o is interpreted as "
774 			"EBCDIC value.\n", val/*)*/);
775 #endif
776 		c = (Char) val;
777 		--p;
778 	    }
779 	    break;
780 	default:
781 	    if (!e)
782 		--p;
783 	    c = *p;
784 	    break;
785 	}
786     }
787     else if ((*p & CHAR) == '^' && (Isalpha(p[1] & CHAR) ||
788 				    strchr("@^_?\\|[{]}", p[1] & CHAR))) {
789 	p++;
790 #ifdef IS_ASCII
791 	c = ((*p & CHAR) == '?') ? CTL_ESC('\177') : ((*p & CHAR) & 0237);
792 #else
793 	c = ((*p & CHAR) == '?') ? CTL_ESC('\177') : _toebcdic[_toascii[*p & CHAR] & 0237];
794 	if (adrof(STRwarnebcdic))
795 	    xprintf(/*CGETS(9, 9, no NLS-String yet!*/
796 		"Warning: Control character %s%c may be interpreted differently in EBCDIC.\n", "^", *p & CHAR /*)*/);
797 #endif
798     }
799     else
800 	c = *p & CHAR;
801     *ptr = p;
802     return (c);
803 }
804 
805 
806 unsigned char *
807 unparsestring(const CStr *str, const Char *sep)
808 {
809     unsigned char *buf, *b;
810     Char   p;
811     int l;
812 
813     /* Worst-case is "\uuu" or result of wctomb() for each char from str */
814     buf = xmalloc((str->len + 1) * max(4, MB_LEN_MAX));
815     b = buf;
816     if (sep[0])
817 #ifndef WINNT_NATIVE
818 	*b++ = sep[0];
819 #else /* WINNT_NATIVE */
820 	*b++ = CHAR & sep[0];
821 #endif /* !WINNT_NATIVE */
822 
823     for (l = 0; l < str->len; l++) {
824 	p = str->buf[l];
825 	if (Iscntrl(p)) {
826 	    *b++ = '^';
827 	    if (p == CTL_ESC('\177'))
828 		*b++ = '?';
829 	    else
830 #ifdef IS_ASCII
831 		*b++ = (unsigned char) (p | 0100);
832 #else
833 		*b++ = _toebcdic[_toascii[p]|0100];
834 #endif
835 	}
836 	else if (p == '^' || p == '\\') {
837 	    *b++ = '\\';
838 	    *b++ = (unsigned char) p;
839 	}
840 	else if (p == ' ' || (Isprint(p) && !Isspace(p)))
841 	    b += one_wctomb((char *)b, p);
842 	else {
843 	    *b++ = '\\';
844 	    *b++ = ((p >> 6) & 7) + '0';
845 	    *b++ = ((p >> 3) & 7) + '0';
846 	    *b++ = (p & 7) + '0';
847 	}
848     }
849     if (sep[0] && sep[1])
850 #ifndef WINNT_NATIVE
851 	*b++ = sep[1];
852 #else /* WINNT_NATIVE */
853 	*b++ = CHAR & sep[1];
854 #endif /* !WINNT_NATIVE */
855     *b++ = 0;
856     return buf;			/* should check for overflow */
857 }
858