/* string functions */ #include "sl.h" #include "operators.h" #include "cvalues.h" #include "print.h" #include "read.h" #include "equal.h" #include "io.h" #include "str.h" static const u8int ws[] = { 0x20, 0x0a, 0x09, 0x0b, 0x0c, 0x0d, 0xc2, 0x85, 0xc2, 0xa0, 0xe1, 0x9a, 0x80, 0xe2, 0x80, 0x80, 0xe2, 0x80, 0x81, 0xe2, 0x80, 0x82, 0xe2, 0x80, 0x83, 0xe2, 0x80, 0x84, 0xe2, 0x80, 0x85, 0xe2, 0x80, 0x86, 0xe2, 0x80, 0x87, 0xe2, 0x80, 0x88, 0xe2, 0x80, 0x89, 0xe2, 0x80, 0x8a, 0xe2, 0x80, 0xa8, 0xe2, 0x80, 0xa9, 0xe2, 0x80, 0xaf, 0xe2, 0x81, 0x9f, 0xe3, 0x80, 0x80, 0x00 }; int isdigit_base(char c, int base) { if(base < 11) return c >= '0' && c < '0'+base; if(base == 32) return (c >= 'A' && c <= 'Z') || (c >= '2' && c <= '7'); return (c >= '0' && c <= '9') || (c >= 'a' && c < 'a'+base-10) || (c >= 'A' && c < 'A'+base-10); } sl_purefn BUILTIN("str?", strp) { argcount(nargs, 1); return isstr(args[0]) ? sl_t : sl_nil; } sl_purefn BUILTIN("str-width", str_width) { argcount(nargs, 1); if(isrune(args[0])){ int w = sl_wcwidth(torune(args[0])); return fixnum(w); } if(!isstr(args[0])) bthrow(type_error(nil, "str", args[0])); char *str = tostr(args[0]); usize len = cv_len(ptr(args[0])); bool err = false; ssize w = u8_strwidth(str, len, &err); return err ? sl_nil : fixnum(w); } BUILTIN("str-reverse", str_reverse) { argcount(nargs, 1); if(isrune(args[0])) return args[0]; if(!isstr(args[0])) bthrow(type_error(nil, "str", args[0])); usize len = cv_len(ptr(args[0])); sl_v ns = cvalue_str(len); u8_reverse(cvalue_data(ns), cvalue_data(args[0]), len); return ns; } BUILTIN("str", str) { if(nargs == 1 && isstr(args[0])) return args[0]; sl_v arg, buf = fn_builtin_buffer(nil, 0); sl_gc_handle(&buf); ios *s = value2c(ios*, buf); sl_v oldpr = sym_value(sl_printreadablysym); // FIXME: gc if cvalue sl_v oldpp = sym_value(sl_printprettysym); // FIXME: gc if cvalue set(sl_printreadablysym, sl_nil); set(sl_printprettysym, sl_nil); int i; FOR_ARGS(i, 0, arg, args){ USED(arg); sl_print(s, args[i]); } set(sl_printreadablysym, oldpr); set(sl_printprettysym, oldpp); sl_v outp = io_to_str(&buf); sl_free_gc_handles(1); return outp; } BUILTIN("str-split", str_split) { if(nargs < 2) argcount(nargs, 1); char *s = tostr(args[0]); usize len = cv_len(ptr(args[0])); // split on whitespace by default const char *delim0 = (const char*)ws, *delim = delim0; usize dlen = 5; int n = 1; // second is either a :trim or a separator if(nargs > n && args[n] != sl_trimsym){ delim = tostr(args[n]); dlen = cv_len(ptr(args[n])); n++; } bool trim = false; // it can only be a :trim X now if(nargs > n){ if(args[n] != sl_trimsym) bthrow(lerrorf(sl_errarg, "invalid argument at position %d", n)); n++; if(nargs <= n) argcount(nargs, n+1); trim = args[n] != sl_nil; } usize ssz, tokend, tokstart, i = 0; sl_v first = sl_nil, c = sl_nil, last; usize junk; sl_gc_handle(&first); sl_gc_handle(&last); do{ // find and allocate next token tokstart = tokend = i; while(i < len && !u8_memchr((u8int*)delim, u8_nextmemchar(s, &i), dlen, &junk)) tokend = i; ssz = tokend - tokstart; if(ssz == 0 && trim) continue; last = c; // save previous cons cell c = mk_cons(cvalue_str(ssz), sl_nil); if(delim != delim0){ // we've done allocation; reload movable pointers s = cvalue_data(args[0]); delim = cvalue_data(args[1]); } if(ssz) memmove(cvalue_data(car_(c)), &s[tokstart], ssz); // link new cell if(last == sl_nil) first = c; // first time, save first cons else ((sl_cons*)ptr(last))->cdr = c; // note this tricky condition: if the string ends with a // delimiter, we need to go around one more time to add an // empty string. this happens when (i == len && tokend < i) }while(i < len || (i == len && tokend != i)); sl_free_gc_handles(2); return first; } usize rune_index(sl_v str, usize runeidx) { char *s = tostr(str); sl_cv *pcv = ptr(str); usize startbytes, n; if(!isinlined(pcv) && pcv->startrune <= runeidx){ startbytes = pcv->startbytes; n = pcv->startrune; }else{ startbytes = 0; n = 0; } for(; n < runeidx && startbytes < pcv->len; n++) startbytes += u8_seqlen(s+startbytes); if(n != runeidx) bthrow(bounds_error(str, fixnum(runeidx))); if(!isinlined(pcv)){ pcv->startbytes = startbytes; pcv->startrune = runeidx; } return startbytes; } BUILTIN("str-sub", str_sub) { if(nargs != 2) argcount(nargs, 3); char *s = tostr(args[0]); usize lenbytes = cv_len(ptr(args[0])), n = tosize(args[1]); usize startbytes = rune_index(args[0], n), endbytes = lenbytes; if(nargs == 3 && args[2] != sl_nil){ usize endrune = tosize(args[2]); for(endbytes = startbytes; n < endrune && endbytes < lenbytes; n++) endbytes += u8_seqlen(s+endbytes); if(n != endrune) bthrow(bounds_error(args[0], args[2])); } sl_v ns = cvalue_str(endbytes-startbytes); s = cvalue_data(args[0]); // reload after alloc memmove(cvalue_data(ns), s+startbytes, endbytes-startbytes); return ns; } BUILTIN("str-rune", str_rune) { argcount(nargs, 2); usize startbytes = rune_index(args[0], tosize(args[1])); if(startbytes >= cv_len(ptr(args[0]))) bthrow(bounds_error(args[0], args[1])); char *s = tostr(args[0]); Rune r; chartorune(&r, s+startbytes); return mk_rune(r); } sl_purefn BUILTIN("rune->codepoint", rune_2_codepoint) { argcount(nargs, 1); if(!isrune(args[0])) bthrow(type_error(nil, "rune", args[0])); return fixnum(torune(args[0])); } sl_purefn BUILTIN("rune-upcase", rune_upcase) { argcount(nargs, 1); if(!isrune(args[0])) bthrow(type_error(nil, "rune", args[0])); return mk_rune(toupperrune(torune(args[0]))); } sl_purefn BUILTIN("rune-downcase", rune_downcase) { argcount(nargs, 1); if(!isrune(args[0])) bthrow(type_error(nil, "rune", args[0])); return mk_rune(tolowerrune(torune(args[0]))); } sl_purefn BUILTIN("rune-titlecase", rune_titlecase) { argcount(nargs, 1); if(!isrune(args[0])) bthrow(type_error(nil, "rune", args[0])); return mk_rune(totitlerune(torune(args[0]))); } sl_purefn BUILTIN("rune-alphabetic?", rune_alphabeticp) { argcount(nargs, 1); if(!isrune(args[0])) bthrow(type_error(nil, "rune", args[0])); return isalpharune(torune(args[0])) ? sl_t : sl_nil; } sl_purefn BUILTIN("rune-lower-case?", rune_lower_casep) { argcount(nargs, 1); if(!isrune(args[0])) bthrow(type_error(nil, "rune", args[0])); return islowerrune(torune(args[0])) ? sl_t : sl_nil; } sl_purefn BUILTIN("rune-upper-case?", rune_upper_casep) { argcount(nargs, 1); if(!isrune(args[0])) bthrow(type_error(nil, "rune", args[0])); return isupperrune(torune(args[0])) ? sl_t : sl_nil; } sl_purefn BUILTIN("rune-title-case?", rune_title_casep) { argcount(nargs, 1); if(!isrune(args[0])) bthrow(type_error(nil, "rune", args[0])); return istitlerune(torune(args[0])) ? sl_t : sl_nil; } sl_purefn BUILTIN("rune-numeric?", rune_numericp) { argcount(nargs, 1); if(!isrune(args[0])) bthrow(type_error(nil, "rune", args[0])); return isdigitrune(torune(args[0])) ? sl_t : sl_nil; } sl_purefn BUILTIN("rune-whitespace?", rune_whitespacep) { argcount(nargs, 1); if(!isrune(args[0])) bthrow(type_error(nil, "rune", args[0])); return isspacerune(torune(args[0])) ? sl_t : sl_nil; } BUILTIN("str-find", str_find) { usize startrune = 0; if(nargs == 3) startrune = tosize(args[2]); else argcount(nargs, 2); char *s = tostr(args[0]); sl_cv *pcv = ptr(args[0]); usize sbytes = pcv->len; if(sbytes == 0) return sl_nil; // needle char *nd; usize ndbytes; sl_v v = args[1]; char rbuf[UTFmax+1]; if(isrune(v)){ Rune r = torune(v); ndbytes = runetochar(rbuf, &r); nd = rbuf; nd[ndbytes] = 0; }else if(isstr(v)){ sl_cv *cv = ptr(v); ndbytes = cv_len(cv); nd = (char*)cv_data(cv); }else{ bthrow(type_error("needle", "(or str rune)", args[1])); } if(ndbytes == 0) return size_wrap(startrune); if(ndbytes > sbytes) return sl_nil; usize i = rune_index(args[0], startrune), n; // now search for the needle for(n = startrune; i < sbytes-ndbytes+1; n++){ if(s[i] == nd[0] && memcmp(&s[i+1], nd+1, ndbytes-1) == 0) return size_wrap(n); i += u8_seqlen(s+i); } return sl_nil; } static int get_radix_arg(sl_v arg) { int radix = tosize(arg); if(radix < 2 || radix > 36) cthrow(lerrorf(sl_errarg, "invalid radix: %d", radix), arg); return radix; } BUILTIN("str->num", str_2_num) { if(nargs < 1 || nargs > 2) argcount(nargs, 2); char *s = tostr(args[0]), *e; sl_v n; int base = 10; if(nargs == 2 && args[1] != sl_nil) base = get_radix_arg(args[1]); else if(s[0] == '#'){ char c = tolower((u8int)s[1]); if(((c == 'b' && (base = 2)) || (c == 'o' && (base = 8)) || (c == 'd' && (base = 10)) || (c == 'x' && (base = 16))) && (isdigit_base(s[2], base) || s[2] == '-')){ s += 2; }else if(isdigit((u8int)s[1])){ int r = strtol(s+1, &e, 10); if(*e == 'r' || *e == 'R'){ if(r < 2 || r > 36) bthrow(lerrorf(sl_errarg, "invalid radix: %d", r)); base = r; s = e+1; } } } if(!sl_read_numtok(s, &n, base)) return sl_nil; return n; } sl_purefn BUILTIN("str-utf8?", str_utf8p) { argcount(nargs, 1); char *s = tostr(args[0]); usize len = cv_len(ptr(args[0])); return u8_isvalid(s, len) ? sl_t : sl_nil; } void str_init(void) { }