extend namespace String { public int rindex(string target, string pattern) /* * Return the right-most location of 'pattern' * in 'target' */ { int pl = length(pattern); for (int i = length(target) - pl; i >= 0; --i) { bool match_here() { for (int j = 0; j < pl; j++) if (target[i + j] != pattern[j]) return false; return true; } if (match_here()) return i; } return -1; } public string dirname(string path) /* * Return the directory portion of 'path' */ { int n = rindex(path, "/"); if (n == -1) return "."; return (substr(path, 0, n)); } public string[*] split(string s, string sep) /* * Split 's' at 'sep' boundaries, returning * an array of the resulting pieces */ { string[...] ss = {}; int i = -1; int j = 0; while(j < length(s)) { if (index(sep, new(s[j])) >= 0) { ss[dim(ss)] = substr(s, i + 1, j - i - 1); i = j; } j++; } ss[dim(ss)] = substr(s, i + 1, j - i - 1); return ss; } public void shiftn(&(poly[...]) array, int n) /* * Shift a growable array n elements: left is positive */ { int f = dim(array); for (int i = 0; i < f - n; i++) array[i] = array[i + n]; if (n < 0) for (int i = 0; i < -n; i++) make_uninit(&array[i]); if (n > 0) setdim(array, f - n); } public void shift(&(poly[...]) array) /* * Shift a growable array one element to the left */ { shiftn(&array, 1); } public string chump(string s) /* * Return s with any trailing newlines removed */ { int n = length(s); while(n > 0 && s[n - 1] == '\n') --n; return substr(s, 0, n); } public string chomp(string s) /* * Trim whitespace from begining and end of 's' */ { int l = length(s); int i = 0; while(i < l && Ctype::isspace(s[i])) i++; int j = l - 1; while(j >= i && Ctype::isspace(s[j])) --j; return substr(s, i, j - i + 1); } public bool inchars(int c, string s) /* * Return whether 'c' is in 's' */ { return index(s, String::new(c)) >= 0; } public string[*] wordsplit(string s, string sep) /* * Split 's' at boundaries consisting of sequences of * 'sep' characters, returning an array of the resulting * pieces */ { string[...] ss = {}; int i = 0; while(i < length(s)) { while (i < length(s) && inchars(s[i], sep)) i++; int j = i; while (j < length(s) && !inchars(s[j], sep)) j++; if (i == j) break; ss[dim(ss)] = substr(s, i, j - i); i = j; } return ss; } public typedef struct { string oq, cq, qq; } quote_context; typedef enum {normal, openq, xq} qstate; typedef string(string) dequote_t; public dequote_t make_dequote(quote_context q) /* * Construct a quote parsing function based on 'q' */ { public string _dequote(string s) { string result = ""; qstate t = qstate.normal; int c; for (int cur = 0; cur < length(s); cur++) { c = s[cur]; switch(t) { case qstate.normal: if (inchars(c, q.oq)) { t = qstate.openq; continue; } raise invalid_argument("leading garbage", 0, s); break; case qstate.openq: if (inchars(c, q.qq)) { t = qstate.xq; continue; } if (inchars(c, q.cq)) { if (cur != length(s) - 1) raise invalid_argument("trailing garbage", 0, s); t = qstate.normal; continue; } break; case qstate.xq: t = qstate.openq; break; } result = result + String::new(c); } if (t == qstate.normal || t == qstate.xq && inchars(c, q.cq)) return result; raise invalid_argument("unexpected end of string", 0, s); } return _dequote; } public dequote_t dequote = make_dequote( (quote_context) {oq = "\"", cq = "\"", qq = "\\"} ); typedef (string[*])(string) parse_csv_t; public parse_csv_t make_parse_csv(quote_context q) /* * Construct a CSV file parsing context from 'q' */ { dequote_t dq = make_dequote(q); public string[*] _parse_csv(string s) { string[...] ss = {}; string curs = ""; void consume() { string t = chomp(curs); if (t != "" && t[0] == '"') t = dq(t); ss[dim(ss)] = t; curs = ""; } qstate t = qstate.normal; int cur = 0; while (cur < length(s)) { int c = s[cur]; switch(t) { case qstate.normal: if (c == ',') { consume(); cur++; continue; } if (c == '"') t = qstate.openq; break; case qstate.openq: if (c == '"') t = qstate.xq; break; case qstate.xq: if (c == '"') { t = qstate.openq; break; } if (c == ',') { t = qstate.normal; continue; } t = qstate.normal; break; } curs = curs + String::new(c); cur++; } if (t == qstate.openq) raise invalid_argument("parse_csv: unexpected " + "end of csv line", 0, s); consume(); return ss; } return _parse_csv; } public parse_csv_t parse_csv = make_parse_csv( (quote_context) {oq = "\"", cq = "\"", qq = "\""} ); }