string.c revision 5dfecf96
1/*
2 * Copyright (c) 2001 by The XFree86 Project, Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17 * THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
19 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 *
22 * Except as contained in this notice, the name of the XFree86 Project shall
23 * not be used in advertising or otherwise to promote the sale, use or other
24 * dealings in this Software without prior written authorization from the
25 * XFree86 Project.
26 *
27 * Author: Paulo César Pereira de Andrade
28 */
29
30/* $XdotOrg: app/xedit/lisp/string.c,v 1.3 2004/12/04 00:43:13 kuhn Exp $ */
31/* $XFree86: xc/programs/xedit/lisp/string.c,v 1.24tsi Exp $ */
32
33#include "lisp/helper.h"
34#include "lisp/read.h"
35#include "lisp/string.h"
36#include "lisp/private.h"
37#include <ctype.h>
38
39#define CHAR_LESS		1
40#define CHAR_LESS_EQUAL		2
41#define CHAR_EQUAL		3
42#define CHAR_GREATER_EQUAL	4
43#define CHAR_GREATER		5
44#define CHAR_NOT_EQUAL		6
45
46#define CHAR_ALPHAP		1
47#define CHAR_DOWNCASE		2
48#define CHAR_UPCASE		3
49#define CHAR_INT		4
50#define CHAR_BOTHP		5
51#define CHAR_UPPERP		6
52#define CHAR_LOWERP		7
53#define CHAR_GRAPHICP		8
54
55#ifndef MIN
56#define MIN(a, b)		((a) < (b) ? (a) : (b))
57#endif
58
59/*
60 * Prototypes
61 */
62static LispObj *LispCharCompare(LispBuiltin*, int, int);
63static LispObj *LispStringCompare(LispBuiltin*, int, int);
64static LispObj *LispCharOp(LispBuiltin*, int);
65static LispObj *LispStringTrim(LispBuiltin*, int, int, int);
66static LispObj *LispStringUpcase(LispBuiltin*, int);
67static LispObj *LispStringDowncase(LispBuiltin*, int);
68static LispObj *LispStringCapitalize(LispBuiltin*, int);
69
70/*
71 * Implementation
72 */
73static LispObj *
74LispCharCompare(LispBuiltin *builtin, int operation, int ignore_case)
75{
76    LispObj *object;
77    int cmp, value, next_value;
78
79    LispObj *character, *more_characters;
80
81    more_characters = ARGUMENT(1);
82    character = ARGUMENT(0);
83
84    CHECK_SCHAR(character);
85    value = SCHAR_VALUE(character);
86    if (ignore_case && islower(value))
87	value = toupper(value);
88
89    if (!CONSP(more_characters))
90	return (T);
91
92    /* First check if all parameters are characters */
93    for (object = more_characters; CONSP(object); object = CDR(object))
94	CHECK_SCHAR(CAR(object));
95
96    /* All characters in list must be different */
97    if (operation == CHAR_NOT_EQUAL) {
98	/* Compare all characters */
99	do {
100	    for (object = more_characters; CONSP(object); object = CDR(object)) {
101		character = CAR(object);
102		next_value = SCHAR_VALUE(character);
103		if (ignore_case && islower(next_value))
104		    next_value = toupper(next_value);
105		if (value == next_value)
106		    return (NIL);
107	    }
108	    value = SCHAR_VALUE(CAR(more_characters));
109	    if (ignore_case && islower(value))
110		value = toupper(value);
111	    more_characters = CDR(more_characters);
112	} while (CONSP(more_characters));
113
114	return (T);
115    }
116
117    /* Linearly compare characters */
118    for (; CONSP(more_characters); more_characters = CDR(more_characters)) {
119	character = CAR(more_characters);
120	next_value = SCHAR_VALUE(character);
121	if (ignore_case && islower(next_value))
122	    next_value = toupper(next_value);
123
124	switch (operation) {
125	    case CHAR_LESS:		cmp = value < next_value;	break;
126	    case CHAR_LESS_EQUAL:	cmp = value <= next_value;	break;
127	    case CHAR_EQUAL:		cmp = value == next_value;	break;
128	    case CHAR_GREATER_EQUAL:	cmp = value >= next_value;	break;
129	    case CHAR_GREATER:		cmp = value > next_value;	break;
130	    default:			cmp = 0;			break;
131	}
132
133	if (!cmp)
134	    return (NIL);
135	value = next_value;
136    }
137
138    return (T);
139}
140
141LispObj *
142Lisp_CharLess(LispBuiltin *builtin)
143/*
144 char< character &rest more-characters
145 */
146{
147    return (LispCharCompare(builtin, CHAR_LESS, 0));
148}
149
150LispObj *
151Lisp_CharLessEqual(LispBuiltin *builtin)
152/*
153 char<= character &rest more-characters
154 */
155{
156    return (LispCharCompare(builtin, CHAR_LESS_EQUAL, 0));
157}
158
159LispObj *
160Lisp_CharEqual_(LispBuiltin *builtin)
161/*
162 char= character &rest more-characters
163 */
164{
165    return (LispCharCompare(builtin, CHAR_EQUAL, 0));
166}
167
168LispObj *
169Lisp_CharGreater(LispBuiltin *builtin)
170/*
171 char> character &rest more-characters
172 */
173{
174    return (LispCharCompare(builtin, CHAR_GREATER, 0));
175}
176
177LispObj *
178Lisp_CharGreaterEqual(LispBuiltin *builtin)
179/*
180 char>= character &rest more-characters
181 */
182{
183    return (LispCharCompare(builtin, CHAR_GREATER_EQUAL, 0));
184}
185
186LispObj *
187Lisp_CharNotEqual_(LispBuiltin *builtin)
188/*
189 char/= character &rest more-characters
190 */
191{
192    return (LispCharCompare(builtin, CHAR_NOT_EQUAL, 0));
193}
194
195LispObj *
196Lisp_CharLessp(LispBuiltin *builtin)
197/*
198 char-lessp character &rest more-characters
199 */
200{
201    return (LispCharCompare(builtin, CHAR_LESS, 1));
202}
203
204LispObj *
205Lisp_CharNotGreaterp(LispBuiltin *builtin)
206/*
207 char-not-greaterp character &rest more-characters
208 */
209{
210    return (LispCharCompare(builtin, CHAR_LESS_EQUAL, 1));
211}
212
213LispObj *
214Lisp_CharEqual(LispBuiltin *builtin)
215/*
216 char-equalp character &rest more-characters
217 */
218{
219    return (LispCharCompare(builtin, CHAR_EQUAL, 1));
220}
221
222LispObj *
223Lisp_CharGreaterp(LispBuiltin *builtin)
224/*
225 char-greaterp character &rest more-characters
226 */
227{
228    return (LispCharCompare(builtin, CHAR_GREATER, 1));
229}
230
231LispObj *
232Lisp_CharNotLessp(LispBuiltin *builtin)
233/*
234 char-not-lessp &rest more-characters
235 */
236{
237    return (LispCharCompare(builtin, CHAR_GREATER_EQUAL, 1));
238}
239
240LispObj *
241Lisp_CharNotEqual(LispBuiltin *builtin)
242/*
243 char-not-equal character &rest more-characters
244 */
245{
246    return (LispCharCompare(builtin, CHAR_NOT_EQUAL, 1));
247}
248
249static LispObj *
250LispCharOp(LispBuiltin *builtin, int operation)
251{
252    int value;
253    LispObj *result, *character;
254
255    character = ARGUMENT(0);
256    CHECK_SCHAR(character);
257    value = (int)SCHAR_VALUE(character);
258
259    switch (operation) {
260	case CHAR_ALPHAP:
261	    result = isalpha(value) ? T : NIL;
262	    break;
263	case CHAR_DOWNCASE:
264	    result = SCHAR(tolower(value));
265	    break;
266	case CHAR_UPCASE:
267	    result = SCHAR(toupper(value));
268	    break;
269	case CHAR_INT:
270	    result = FIXNUM(value);
271	    break;
272	case CHAR_BOTHP:
273	    result = isupper(value) || islower(value) ? T : NIL;
274	    break;
275	case CHAR_UPPERP:
276	    result = isupper(value) ? T : NIL;
277	    break;
278	case CHAR_LOWERP:
279	    result = islower(value) ? T : NIL;
280	    break;
281	case CHAR_GRAPHICP:
282	    result = value == ' ' || isgraph(value) ? T : NIL;
283	    break;
284	default:
285	    result = NIL;
286	    break;
287    }
288
289    return (result);
290}
291
292LispObj *
293Lisp_AlphaCharP(LispBuiltin *builtin)
294/*
295 alpha-char-p char
296 */
297{
298    return (LispCharOp(builtin, CHAR_ALPHAP));
299}
300
301LispObj *
302Lisp_CharDowncase(LispBuiltin *builtin)
303/*
304 char-downcase character
305 */
306{
307    return (LispCharOp(builtin, CHAR_DOWNCASE));
308}
309
310LispObj *
311Lisp_CharInt(LispBuiltin *builtin)
312/*
313 char-int character
314 char-code character
315 */
316{
317    return (LispCharOp(builtin, CHAR_INT));
318}
319
320LispObj *
321Lisp_CharUpcase(LispBuiltin *builtin)
322/*
323 char-upcase character
324 */
325{
326    return (LispCharOp(builtin, CHAR_UPCASE));
327}
328
329LispObj *
330Lisp_BothCaseP(LispBuiltin *builtin)
331/*
332 both-case-p character
333 */
334{
335    return (LispCharOp(builtin, CHAR_BOTHP));
336}
337
338LispObj *
339Lisp_UpperCaseP(LispBuiltin *builtin)
340/*
341 upper-case-p character
342 */
343{
344    return (LispCharOp(builtin, CHAR_UPPERP));
345}
346
347LispObj *
348Lisp_LowerCaseP(LispBuiltin *builtin)
349/*
350 upper-case-p character
351 */
352{
353    return (LispCharOp(builtin, CHAR_LOWERP));
354}
355
356LispObj *
357Lisp_GraphicCharP(LispBuiltin *builtin)
358/*
359 graphic-char-p char
360 */
361{
362    return (LispCharOp(builtin, CHAR_GRAPHICP));
363}
364
365LispObj *
366Lisp_Char(LispBuiltin *builtin)
367/*
368 char string index
369 schar simple-string index
370 */
371{
372    unsigned char *string;
373    long offset, length;
374
375    LispObj *ostring, *oindex;
376
377    oindex = ARGUMENT(1);
378    ostring = ARGUMENT(0);
379
380    CHECK_STRING(ostring);
381    CHECK_INDEX(oindex);
382    offset = FIXNUM_VALUE(oindex);
383    string = (unsigned char*)THESTR(ostring);
384    length = STRLEN(ostring);
385
386    if (offset >= length)
387	LispDestroy("%s: index %ld too large for string length %ld",
388		    STRFUN(builtin), offset, length);
389
390    return (SCHAR(string[offset]));
391}
392
393/* helper function for setf
394 *	DONT explicitly call. Non standard function
395 */
396LispObj *
397Lisp_XeditCharStore(LispBuiltin *builtin)
398/*
399 xedit::char-store string index value
400 */
401{
402    int character;
403    long offset, length;
404    LispObj *ostring, *oindex, *ovalue;
405
406    ovalue = ARGUMENT(2);
407    oindex = ARGUMENT(1);
408    ostring = ARGUMENT(0);
409
410    CHECK_STRING(ostring);
411    CHECK_INDEX(oindex);
412    length = STRLEN(ostring);
413    offset = FIXNUM_VALUE(oindex);
414    if (offset >= length)
415	LispDestroy("%s: index %ld too large for string length %ld",
416		    STRFUN(builtin), offset, length);
417    CHECK_SCHAR(ovalue);
418    CHECK_STRING_WRITABLE(ostring);
419
420    character = SCHAR_VALUE(ovalue);
421
422    if (character < 0 || character > 255)
423	LispDestroy("%s: cannot represent character %d",
424		    STRFUN(builtin), character);
425
426    THESTR(ostring)[offset] = character;
427
428    return (ovalue);
429}
430
431LispObj *
432Lisp_Character(LispBuiltin *builtin)
433/*
434 character object
435 */
436{
437    LispObj *object;
438
439    object = ARGUMENT(0);
440
441    return (LispCharacterCoerce(builtin, object));
442}
443
444LispObj *
445Lisp_Characterp(LispBuiltin *builtin)
446/*
447 characterp object
448 */
449{
450    LispObj *object;
451
452    object = ARGUMENT(0);
453
454    return (SCHARP(object) ? T : NIL);
455}
456
457LispObj *
458Lisp_DigitChar(LispBuiltin *builtin)
459/*
460 digit-char weight &optional radix
461 */
462{
463    long radix = 10, weight;
464    LispObj *oweight, *oradix, *result = NIL;
465
466    oradix = ARGUMENT(1);
467    oweight = ARGUMENT(0);
468
469    CHECK_FIXNUM(oweight);
470    weight = FIXNUM_VALUE(oweight);
471
472    if (oradix != UNSPEC) {
473	CHECK_INDEX(oradix);
474	radix = FIXNUM_VALUE(oradix);
475    }
476    if (radix < 2 || radix > 36)
477	LispDestroy("%s: radix must be >= 2 and <= 36, not %ld",
478		    STRFUN(builtin), radix);
479
480    if (weight >= 0 && weight < radix) {
481	if (weight < 9)
482	    weight += '0';
483	else
484	    weight += 'A' - 10;
485	result = SCHAR(weight);
486    }
487
488    return (result);
489}
490
491LispObj *
492Lisp_DigitCharP(LispBuiltin *builtin)
493/*
494 digit-char-p character &optional radix
495 */
496{
497    long radix = 10, character;
498    LispObj *ochar, *oradix, *result = NIL;
499
500    oradix = ARGUMENT(1);
501    ochar = ARGUMENT(0);
502
503    CHECK_SCHAR(ochar);
504    character = SCHAR_VALUE(ochar);
505    if (oradix != UNSPEC) {
506	CHECK_INDEX(oradix);
507	radix = FIXNUM_VALUE(oradix);
508    }
509    if (radix < 2 || radix > 36)
510	LispDestroy("%s: radix must be >= 2 and <= 36, not %ld",
511		    STRFUN(builtin), radix);
512
513    if (character >= '0' && character <= '9')
514	character -= '0';
515    else if (character >= 'A' && character <= 'Z')
516	character -= 'A' - 10;
517    else if (character >= 'a' && character <= 'z')
518	character -= 'a' - 10;
519    if (character < radix)
520	result = FIXNUM(character);
521
522    return (result);
523}
524
525LispObj *
526Lisp_IntChar(LispBuiltin *builtin)
527/*
528 int-char integer
529 code-char integer
530 */
531{
532    long character = 0;
533    LispObj *integer;
534
535    integer = ARGUMENT(0);
536
537    CHECK_FIXNUM(integer);
538    character = FIXNUM_VALUE(integer);
539
540    return (character >= 0 && character < 0xff ? SCHAR(character) : NIL);
541}
542
543/* XXX ignoring element-type */
544LispObj *
545Lisp_MakeString(LispBuiltin *builtin)
546/*
547 make-string size &key initial-element element-type
548 */
549{
550    long length;
551    char *string, initial;
552
553    LispObj *size, *initial_element;
554
555    initial_element = ARGUMENT(1);
556    size = ARGUMENT(0);
557
558    CHECK_INDEX(size);
559    length = FIXNUM_VALUE(size);
560    if (initial_element != UNSPEC) {
561	CHECK_SCHAR(initial_element);
562	initial = SCHAR_VALUE(initial_element);
563    }
564    else
565	initial = 0;
566
567    string = LispMalloc(length + 1);
568    memset(string, initial, length);
569    string[length] = '\0';
570
571    return (LSTRING2(string, length));
572}
573
574LispObj *
575Lisp_ParseInteger(LispBuiltin *builtin)
576/*
577 parse-integer string &key start end radix junk-allowed
578 */
579{
580    GC_ENTER();
581    char *ptr, *string;
582    int character, junk, sign, overflow;
583    long i, start, end, radix, length, integer, check;
584    LispObj *result;
585
586    LispObj *ostring, *ostart, *oend, *oradix, *junk_allowed;
587
588    junk_allowed = ARGUMENT(4);
589    oradix = ARGUMENT(3);
590    oend = ARGUMENT(2);
591    ostart = ARGUMENT(1);
592    ostring = ARGUMENT(0);
593
594    start = end = radix = 0;
595    result = NIL;
596
597    CHECK_STRING(ostring);
598    LispCheckSequenceStartEnd(builtin, ostring, ostart, oend,
599			      &start, &end, &length);
600    string = THESTR(ostring);
601    if (oradix == UNSPEC)
602	radix = 10;
603    else {
604	CHECK_INDEX(oradix);
605	radix = FIXNUM_VALUE(oradix);
606    }
607    if (radix < 2 || radix > 36)
608	LispDestroy("%s: :RADIX %ld must be in the range 2 to 36",
609		    STRFUN(builtin), radix);
610
611    integer = check = 0;
612    ptr = string + start;
613    sign = overflow = 0;
614
615    /* Skip leading white spaces */
616    for (i = start; i < end && *ptr && isspace(*ptr); ptr++, i++)
617	;
618
619    /* Check for sign specification */
620    if (i < end && (*ptr == '-' || *ptr == '+')) {
621	sign = *ptr == '-';
622	++ptr;
623	++i;
624    }
625
626    for (junk = 0; i < end; i++, ptr++) {
627	character = *ptr;
628	if (islower(character))
629	    character = toupper(character);
630	if (character >= '0' && character <= '9') {
631	    if (character - '0' >= radix)
632		junk = 1;
633	    else {
634		check = integer;
635		integer = integer * radix + character - '0';
636	    }
637	}
638	else if (character >= 'A' && character <= 'Z') {
639	    if (character - 'A' + 10 >= radix)
640		junk = 1;
641	    else {
642		check = integer;
643		integer = integer * radix + character - 'A' + 10;
644	    }
645	}
646	else {
647	    if (isspace(character))
648		break;
649	    junk = 1;
650	}
651
652	if (junk)
653	    break;
654
655	if (!overflow && check > integer)
656	    overflow = 1;
657	/* keep looping just to count read bytes */
658    }
659
660    if (!junk)
661	/* Skip white spaces */
662	for (; i < end && *ptr && isspace(*ptr); ptr++, i++)
663	    ;
664
665    if ((junk || ptr == string) &&
666	(junk_allowed == UNSPEC || junk_allowed == NIL))
667	LispDestroy("%s: %s has a bad integer representation",
668		    STRFUN(builtin), STROBJ(ostring));
669    else if (ptr == string)
670	result = NIL;
671    else if (overflow) {
672	mpi *bigi = LispMalloc(sizeof(mpi));
673	char *str;
674
675	length = end - start + sign;
676	str = LispMalloc(length + 1);
677
678	strncpy(str, string - sign, length + sign);
679	str[length + sign] = '\0';
680	mpi_init(bigi);
681	mpi_setstr(bigi, str, radix);
682	LispFree(str);
683	result = BIGNUM(bigi);
684    }
685    else
686	result = INTEGER(sign ? -integer : integer);
687
688    GC_PROTECT(result);
689    RETURN(0) = FIXNUM(i);
690    RETURN_COUNT = 1;
691    GC_LEAVE();
692
693    return (result);
694}
695
696LispObj *
697Lisp_String(LispBuiltin *builtin)
698/*
699 string object
700 */
701{
702    LispObj *object;
703
704    object = ARGUMENT(0);
705
706    return (LispStringCoerce(builtin, object));
707}
708
709LispObj *
710Lisp_Stringp(LispBuiltin *builtin)
711/*
712 stringp object
713 */
714{
715    LispObj *object;
716
717    object = ARGUMENT(0);
718
719    return (STRINGP(object) ? T : NIL);
720}
721
722/* XXX preserve-whitespace is being ignored */
723LispObj *
724Lisp_ReadFromString(LispBuiltin *builtin)
725/*
726 read-from-string string &optional eof-error-p eof-value &key start end preserve-whitespace
727 */
728{
729    GC_ENTER();
730    char *string;
731    LispObj *stream, *result;
732    long length, start, end, bytes_read;
733
734    LispObj *ostring, *eof_error_p, *eof_value, *ostart, *oend;
735
736    oend = ARGUMENT(4);
737    ostart = ARGUMENT(3);
738    eof_value = ARGUMENT(2);
739    eof_error_p = ARGUMENT(1);
740    ostring = ARGUMENT(0);
741
742    CHECK_STRING(ostring);
743    string = THESTR(ostring);
744    LispCheckSequenceStartEnd(builtin, ostring, ostart, oend,
745			      &start, &end, &length);
746
747    if (start > 0 || end < length)
748	length = end - start;
749    stream = LSTRINGSTREAM(string + start, STREAM_READ, length);
750
751    if (eof_value == UNSPEC)
752	eof_value = NIL;
753
754    LispPushInput(stream);
755    result = LispRead();
756    /* stream->data.stream.source.string->input is
757     * the offset of the last byte read in string */
758    bytes_read = stream->data.stream.source.string->input;
759    LispPopInput(stream);
760
761    if (result == NULL) {
762	if (eof_error_p == NIL)
763	    result = eof_value;
764	else
765	    LispDestroy("%s: unexpected end of input", STRFUN(builtin));
766    }
767
768    GC_PROTECT(result);
769    RETURN(0) = FIXNUM(start + bytes_read);
770    RETURN_COUNT = 1;
771    GC_LEAVE();
772
773    return (result);
774}
775
776static LispObj *
777LispStringTrim(LispBuiltin *builtin, int left, int right, int inplace)
778/*
779 string-{,left-,right-}trim character-bag string
780*/
781{
782    unsigned char *string;
783    long start, end, length;
784
785    LispObj *ochars, *ostring;
786
787    ostring = ARGUMENT(1);
788    ochars = ARGUMENT(0);
789
790    if (!POINTERP(ochars) || !(XSTRINGP(ochars) || XCONSP(ochars))) {
791	if (ARRAYP(ochars) && ochars->data.array.rank == 1)
792	    ochars = ochars->data.array.list;
793	else
794	    LispDestroy("%s: %s is not a sequence",
795			STRFUN(builtin), STROBJ(ochars));
796    }
797    CHECK_STRING(ostring);
798
799    string = (unsigned char*)THESTR(ostring);
800    length = STRLEN(ostring);
801
802    start = 0;
803    end = length;
804
805    if (XSTRINGP(ochars)) {
806	unsigned char *chars = (unsigned char*)THESTR(ochars);
807	long i, clength = STRLEN(ochars);
808
809	if (left) {
810	    for (; start < end; start++) {
811		for (i = 0; i < clength; i++)
812		    if (string[start] == chars[i])
813			break;
814		if (i >= clength)
815		    break;
816	    }
817	}
818	if (right) {
819	    for (--end; end >= 0; end--) {
820		for (i = 0; i < clength; i++)
821		    if (string[end] == chars[i])
822			break;
823		if (i >= clength)
824		    break;
825	    }
826	    ++end;
827	}
828    }
829    else {
830	LispObj *ochar, *list;
831
832	if (left) {
833	    for (; start < end; start++) {
834		for (list = ochars; CONSP(list); list = CDR(list)) {
835		    ochar = CAR(list);
836		    if (SCHARP(ochar) && string[start] == SCHAR_VALUE(ochar))
837			break;
838		}
839		if (!CONSP(list))
840		    break;
841	    }
842	}
843	if (right) {
844	    for (--end; end >= 0; end--) {
845		for (list = ochars; CONSP(list); list = CDR(list)) {
846		    ochar = CAR(list);
847		    if (SCHARP(ochar) && string[end] == SCHAR_VALUE(ochar))
848			break;
849		}
850		if (!CONSP(list))
851		    break;
852	    }
853	    ++end;
854	}
855    }
856
857    if (start == 0 && end == length)
858	return (ostring);
859
860    length = end - start;
861
862    if (inplace) {
863	CHECK_STRING_WRITABLE(ostring);
864	memmove(string, string + start, length);
865	string[length] = '\0';
866	STRLEN(ostring) = length;
867    }
868    else {
869	string = LispMalloc(length + 1);
870	memcpy(string, THESTR(ostring) + start, length);
871	string[length] = '\0';
872	ostring = LSTRING2((char*)string, length);
873    }
874
875    return (ostring);
876}
877
878LispObj *
879Lisp_StringTrim(LispBuiltin *builtin)
880/*
881 string-trim character-bag string
882 */
883{
884    return (LispStringTrim(builtin, 1, 1, 0));
885}
886
887LispObj *
888Lisp_NstringTrim(LispBuiltin *builtin)
889/*
890 ext::nstring-trim character-bag string
891 */
892{
893    return (LispStringTrim(builtin, 1, 1, 1));
894}
895
896LispObj *
897Lisp_StringLeftTrim(LispBuiltin *builtin)
898/*
899 string-left-trim character-bag string
900 */
901{
902    return (LispStringTrim(builtin, 1, 0, 0));
903}
904
905LispObj *
906Lisp_NstringLeftTrim(LispBuiltin *builtin)
907/*
908 ext::nstring-left-trim character-bag string
909 */
910{
911    return (LispStringTrim(builtin, 1, 0, 1));
912}
913
914LispObj *
915Lisp_StringRightTrim(LispBuiltin *builtin)
916/*
917 string-right-trim character-bag string
918 */
919{
920    return (LispStringTrim(builtin, 0, 1, 0));
921}
922
923LispObj *
924Lisp_NstringRightTrim(LispBuiltin *builtin)
925/*
926 ext::nstring-right-trim character-bag string
927 */
928{
929    return (LispStringTrim(builtin, 0, 1, 1));
930}
931
932static LispObj *
933LispStringCompare(LispBuiltin *builtin, int function, int ignore_case)
934{
935    int cmp1, cmp2;
936    LispObj *fixnum;
937    unsigned char *string1, *string2;
938    long start1, end1, start2, end2, offset, length;
939
940    LispGetStringArgs(builtin, (char**)&string1, (char**)&string2,
941		      &start1, &end1, &start2, &end2);
942
943    string1 += start1;
944    string2 += start2;
945
946    if (function == CHAR_EQUAL) {
947	length = end1 - start1;
948
949	if (length != (end2 - start2))
950	    return (NIL);
951
952	if (!ignore_case)
953	    return (memcmp(string1, string2, length) ? NIL : T);
954
955	for (; length; length--, string1++, string2++)
956	    if (toupper(*string1) != toupper(*string2))
957		return (NIL);
958	return (T);
959    }
960
961    end1 -= start1;
962    end2 -= start2;
963    length = MIN(end1, end2);
964    for (offset = 0;
965	 offset < length;
966	 string1++, string2++, offset++, start1++, start2++) {
967	cmp1 = *string1;
968	cmp2 = *string2;
969	if (ignore_case) {
970	    cmp1 = toupper(cmp1);
971	    cmp2 = toupper(cmp2);
972	}
973	if (cmp1 != cmp2) {
974	    fixnum = FIXNUM(start1);
975	    switch (function) {
976		case CHAR_LESS:
977		    return ((cmp1 < cmp2) ? fixnum : NIL);
978		case CHAR_LESS_EQUAL:
979		    return ((cmp1 <= cmp2) ? fixnum : NIL);
980		case CHAR_NOT_EQUAL:
981		    return (fixnum);
982		case CHAR_GREATER_EQUAL:
983		    return ((cmp1 >= cmp2) ? fixnum : NIL);
984		case CHAR_GREATER:
985		    return ((cmp1 > cmp2) ? fixnum : NIL);
986	    }
987	}
988    }
989
990    fixnum = FIXNUM(start1);
991    switch (function) {
992	case CHAR_LESS:
993	    return (start1 >= end1 && start2 < end2 ? fixnum : NIL);
994	case CHAR_LESS_EQUAL:
995	    return (start1 >= end1 ? fixnum : NIL);
996	case CHAR_NOT_EQUAL:
997	    return (start1 >= end1 && start2 >= end2 ? NIL : fixnum);
998	case CHAR_GREATER_EQUAL:
999	    return (start2 >= end2 ? fixnum : NIL);
1000	case CHAR_GREATER:
1001	    return (start2 >= end2 && start1 < end1 ? fixnum : NIL);
1002    }
1003
1004    return (NIL);
1005}
1006
1007LispObj *
1008Lisp_StringEqual_(LispBuiltin *builtin)
1009/*
1010 string= string1 string2 &key start1 end1 start2 end2
1011 */
1012{
1013    return (LispStringCompare(builtin, CHAR_EQUAL, 0));
1014}
1015
1016LispObj *
1017Lisp_StringLess(LispBuiltin *builtin)
1018/*
1019 string< string1 string2 &key start1 end1 start2 end2
1020 */
1021{
1022    return (LispStringCompare(builtin, CHAR_LESS, 0));
1023}
1024
1025LispObj *
1026Lisp_StringGreater(LispBuiltin *builtin)
1027/*
1028 string> string1 string2 &key start1 end1 start2 end2
1029 */
1030{
1031    return (LispStringCompare(builtin, CHAR_GREATER, 0));
1032}
1033
1034LispObj *
1035Lisp_StringLessEqual(LispBuiltin *builtin)
1036/*
1037 string<= string1 string2 &key start1 end1 start2 end2
1038 */
1039{
1040    return (LispStringCompare(builtin, CHAR_LESS_EQUAL, 0));
1041}
1042
1043LispObj *
1044Lisp_StringGreaterEqual(LispBuiltin *builtin)
1045/*
1046 string>= string1 string2 &key start1 end1 start2 end2
1047 */
1048{
1049    return (LispStringCompare(builtin, CHAR_GREATER_EQUAL, 0));
1050}
1051
1052LispObj *
1053Lisp_StringNotEqual_(LispBuiltin *builtin)
1054/*
1055 string/= string1 string2 &key start1 end1 start2 end2
1056 */
1057{
1058    return (LispStringCompare(builtin, CHAR_NOT_EQUAL, 0));
1059}
1060
1061LispObj *
1062Lisp_StringEqual(LispBuiltin *builtin)
1063/*
1064 string-equal string1 string2 &key start1 end1 start2 end2
1065 */
1066{
1067    return (LispStringCompare(builtin, CHAR_EQUAL, 1));
1068}
1069
1070LispObj *
1071Lisp_StringLessp(LispBuiltin *builtin)
1072/*
1073 string-lessp string1 string2 &key start1 end1 start2 end2
1074 */
1075{
1076    return (LispStringCompare(builtin, CHAR_LESS, 1));
1077}
1078
1079LispObj *
1080Lisp_StringGreaterp(LispBuiltin *builtin)
1081/*
1082 string-greaterp string1 string2 &key start1 end1 start2 end2
1083 */
1084{
1085    return (LispStringCompare(builtin, CHAR_GREATER, 1));
1086}
1087
1088LispObj *
1089Lisp_StringNotGreaterp(LispBuiltin *builtin)
1090/*
1091 string-not-greaterp string1 string2 &key start1 end1 start2 end2
1092 */
1093{
1094    return (LispStringCompare(builtin, CHAR_LESS_EQUAL, 1));
1095}
1096
1097LispObj *
1098Lisp_StringNotLessp(LispBuiltin *builtin)
1099/*
1100 string-not-lessp string1 string2 &key start1 end1 start2 end2
1101 */
1102{
1103    return (LispStringCompare(builtin, CHAR_GREATER_EQUAL, 1));
1104}
1105
1106LispObj *
1107Lisp_StringNotEqual(LispBuiltin *builtin)
1108/*
1109 string-not-equal string1 string2 &key start1 end1 start2 end2
1110 */
1111{
1112    return (LispStringCompare(builtin, CHAR_NOT_EQUAL, 1));
1113}
1114
1115LispObj *
1116LispStringUpcase(LispBuiltin *builtin, int inplace)
1117/*
1118 string-upcase string &key start end
1119 nstring-upcase string &key start end
1120 */
1121{
1122    LispObj *result;
1123    char *string, *newstring;
1124    long start, end, length, offset;
1125
1126    LispObj *ostring, *ostart, *oend;
1127
1128    oend = ARGUMENT(2);
1129    ostart = ARGUMENT(1);
1130    ostring = ARGUMENT(0);
1131    CHECK_STRING(ostring);
1132    LispCheckSequenceStartEnd(builtin, ostring, ostart, oend,
1133			      &start, &end, &offset);
1134    result = ostring;
1135    string = THESTR(ostring);
1136    length = STRLEN(ostring);
1137
1138    /* first check if something need to be done */
1139    for (offset = start; offset < end; offset++)
1140	if (string[offset] != toupper(string[offset]))
1141	    break;
1142
1143    if (offset >= end)
1144	return (result);
1145
1146    if (inplace) {
1147	CHECK_STRING_WRITABLE(ostring);
1148	newstring = string;
1149    }
1150    else {
1151	/* upcase a copy of argument */
1152	newstring = LispMalloc(length + 1);
1153	if (offset)
1154	    memcpy(newstring, string, offset);
1155	if (length > end)
1156	    memcpy(newstring + end, string + end, length - end);
1157	newstring[length] = '\0';
1158    }
1159
1160    for (; offset < end; offset++)
1161	newstring[offset] = toupper(string[offset]);
1162
1163    if (!inplace)
1164	result = LSTRING2(newstring, length);
1165
1166    return (result);
1167}
1168
1169LispObj *
1170Lisp_StringUpcase(LispBuiltin *builtin)
1171/*
1172 string-upcase string &key start end
1173 */
1174{
1175    return (LispStringUpcase(builtin, 0));
1176}
1177
1178LispObj *
1179Lisp_NstringUpcase(LispBuiltin *builtin)
1180/*
1181 nstring-upcase string &key start end
1182 */
1183{
1184    return (LispStringUpcase(builtin, 1));
1185}
1186
1187LispObj *
1188LispStringDowncase(LispBuiltin *builtin, int inplace)
1189/*
1190 string-downcase string &key start end
1191 nstring-downcase string &key start end
1192 */
1193{
1194    LispObj *result;
1195    char *string, *newstring;
1196    long start, end, length, offset;
1197
1198    LispObj *ostring, *ostart, *oend;
1199
1200    oend = ARGUMENT(2);
1201    ostart = ARGUMENT(1);
1202    ostring = ARGUMENT(0);
1203    CHECK_STRING(ostring);
1204    LispCheckSequenceStartEnd(builtin, ostring, ostart, oend,
1205			      &start, &end, &offset);
1206    result = ostring;
1207    string = THESTR(ostring);
1208    length = STRLEN(ostring);
1209
1210    /* first check if something need to be done */
1211    for (offset = start; offset < end; offset++)
1212	if (string[offset] != tolower(string[offset]))
1213	    break;
1214
1215    if (offset >= end)
1216	return (result);
1217
1218    if (inplace) {
1219	CHECK_STRING_WRITABLE(ostring);
1220	newstring = string;
1221    }
1222    else {
1223	/* downcase a copy of argument */
1224	newstring = LispMalloc(length + 1);
1225	if (offset)
1226	    memcpy(newstring, string, offset);
1227	if (length > end)
1228	    memcpy(newstring + end, string + end, length - end);
1229	newstring[length] = '\0';
1230    }
1231    for (; offset < end; offset++)
1232	newstring[offset] = tolower(string[offset]);
1233
1234    if (!inplace)
1235	result = LSTRING2(newstring, length);
1236
1237    return (result);
1238}
1239
1240LispObj *
1241Lisp_StringDowncase(LispBuiltin *builtin)
1242/*
1243 string-downcase string &key start end
1244 */
1245{
1246    return (LispStringDowncase(builtin, 0));
1247}
1248
1249LispObj *
1250Lisp_NstringDowncase(LispBuiltin *builtin)
1251/*
1252 nstring-downcase string &key start end
1253 */
1254{
1255    return (LispStringDowncase(builtin, 1));
1256}
1257
1258LispObj *
1259LispStringCapitalize(LispBuiltin *builtin, int inplace)
1260/*
1261 string-capitalize string &key start end
1262 nstring-capitalize string &key start end
1263 */
1264{
1265    LispObj *result;
1266    char *string, *newstring;
1267    long start, end, length, offset, upcase;
1268
1269    LispObj *ostring, *ostart, *oend;
1270
1271    oend = ARGUMENT(2);
1272    ostart = ARGUMENT(1);
1273    ostring = ARGUMENT(0);
1274    CHECK_STRING(ostring);
1275    LispCheckSequenceStartEnd(builtin, ostring, ostart, oend,
1276			      &start, &end, &offset);
1277    result = ostring;
1278    string = THESTR(ostring);
1279    length = STRLEN(ostring);
1280
1281    /* first check if something need to be done */
1282    for (upcase = 1, offset = start; offset < end; offset++) {
1283	if (upcase) {
1284	    if (!isalnum(string[offset]))
1285		continue;
1286	    if (string[offset] != toupper(string[offset]))
1287		break;
1288	    upcase = 0;
1289	}
1290	else {
1291	    if (isalnum(string[offset])) {
1292		if (string[offset] != tolower(string[offset]))
1293		    break;
1294	    }
1295	    else
1296		upcase = 1;
1297	}
1298    }
1299
1300    if (offset >= end)
1301	return (result);
1302
1303    if (inplace) {
1304	CHECK_STRING_WRITABLE(ostring);
1305	newstring = string;
1306    }
1307    else {
1308	/* capitalize a copy of argument */
1309	newstring = LispMalloc(length + 1);
1310	memcpy(newstring, string, length);
1311	newstring[length] = '\0';
1312    }
1313    for (; offset < end; offset++) {
1314	if (upcase) {
1315	    if (!isalnum(string[offset]))
1316		continue;
1317	    newstring[offset] = toupper(string[offset]);
1318	    upcase = 0;
1319	}
1320	else {
1321	    if (isalnum(newstring[offset]))
1322		newstring[offset] = tolower(string[offset]);
1323	    else
1324		upcase = 1;
1325	}
1326    }
1327
1328    if (!inplace)
1329	result = LSTRING2(newstring, length);
1330
1331    return (result);
1332}
1333
1334LispObj *
1335Lisp_StringCapitalize(LispBuiltin *builtin)
1336/*
1337 string-capitalize string &key start end
1338 */
1339{
1340    return (LispStringCapitalize(builtin, 0));
1341}
1342
1343LispObj *
1344Lisp_NstringCapitalize(LispBuiltin *builtin)
1345/*
1346 nstring-capitalize string &key start end
1347 */
1348{
1349    return (LispStringCapitalize(builtin, 1));
1350}
1351
1352LispObj *
1353Lisp_StringConcat(LispBuiltin *builtin)
1354/*
1355 string-concat &rest strings
1356 */
1357{
1358    char *buffer;
1359    long size, length;
1360    LispObj *object, *string;
1361
1362    LispObj *strings;
1363
1364    strings = ARGUMENT(0);
1365
1366    if (strings == NIL)
1367	return (STRING(""));
1368
1369    for (length = 1, object = strings; CONSP(object); object = CDR(object)) {
1370	string = CAR(object);
1371	CHECK_STRING(string);
1372	length += STRLEN(string);
1373    }
1374
1375    buffer = LispMalloc(length);
1376
1377    for (length = 0, object = strings; CONSP(object); object = CDR(object)) {
1378	string = CAR(object);
1379	size = STRLEN(string);
1380	memcpy(buffer + length, THESTR(string), size);
1381	length += size;
1382    }
1383    buffer[length] = '\0';
1384    object = LSTRING2(buffer, length);
1385
1386    return (object);
1387}
1388