remove README.Protocol and add a README that refers to the "real"
[fg:toms-fgdata.git] / Nasal / string.nas
1 var isupper = func(c) { c >= `A` and c <= `Z` }
2 var islower = func(c) { c >= `a` and c <= `z` }
3 var toupper = func(c) { islower(c) ? c + `A` - `a` : c }
4 var tolower = func(c) { isupper(c) ? c + `a` - `A` : c }
5
6 var isletter = func(c) { isupper(c) or islower(c) }
7 var isdigit = func(c) { c >= `0` and c <= `9` }
8 var isalnum = func(c) { isletter(c) or isdigit(c) }
9 var isspace = func(c) { c == ` ` or c == `\t` or c == `\n` or c == `\r` }
10
11
12 ##
13 # trim spaces at the left (lr < 0), at the right (lr > 0), or both (lr = 0)
14 #
15 var trim = func(s, lr = 0) {
16         var l = 0;
17         if (lr <= 0)
18                 for (; l < size(s); l += 1)
19                         if (!isspace(s[l]))
20                                 break;
21         var r = size(s) - 1;
22         if (lr >= 0)
23                 for (; r >= 0; r -= 1)
24                         if (!isspace(s[r]))
25                                 break;
26         return r < l ? "" : substr(s, l, r - l + 1);
27 }
28
29
30 ##
31 # return string converted to lower case letters
32 #
33 var lc = func(str) {
34         var s = "";
35         for (var i = 0; i < size(str); i += 1)
36                 s ~= chr(tolower(str[i]));
37         return s;
38 }
39
40
41 ##
42 # return string converted to upper case letters
43 #
44 var uc = func(str) {
45         var s = "";
46         for (var i = 0; i < size(str); i += 1)
47                 s ~= chr(toupper(str[i]));
48         return s;
49 }
50
51
52 ##
53 # case insensitive string compare and match functions
54 # (not very effective -- converting the array to be sorted
55 # first is faster)
56 #
57 var icmp = func(a, b) cmp(lc(a), lc(b));
58 var imatch = func(a, b) match(lc(a), lc(b));
59
60
61 ##
62 # check if string <str> matches shell style pattern <patt>
63 #
64 # Rules:
65 # ?   stands for any single character
66 # *   stands for any number (including zero) of arbitrary characters
67 # \   escapes the next character and makes it stand for itself; that is:
68 #     \? stands for a question mark (not the "any single character" placeholder)
69 # []  stands for a group of characters:
70 #     [abc]      stands for letters a, b or c
71 #     [^abc]     stands for any character but a, b, and c
72 #     [1-4]      stands for digits 1 to 4 (1, 2, 3, 4)
73 #     [1-4-]     stands for digits 1 to 4, and the minus
74 #     [-1-4]     same as above
75 #     [1-3-6]    stands for digits 1 to 3, minus, and 6
76 #     [1-3-6-9]  stands for digits 1 to 3, minus, and 6 to 9
77 #     [][]       stands for the closing and the opening bracket (']' must be first!)
78 #     [^^]       stands for all characters but the caret symbol
79 #
80 # Note that a minus can't be a range delimiter, as in [a--b]
81 #
82 # Example:
83 #     string.match(name, "*[0-9].xml"); ... true if 'name' ends with digit followed by ".xml"
84 #
85 var match = func(str, patt) {
86         var s = 0;
87         for (var p = 0; p < size(patt) and s < size(str); ) {
88                 if (patt[p] == `\\`) {
89                         if ((p += 1) >= size(patt))
90                                 return 0;  # pattern ends with backslash
91
92                 } elsif (patt[p] == `?`) {
93                         s += 1;
94                         p += 1;
95                         continue;
96
97                 } elsif (patt[p] == `*`) {
98                         for (; p < size(patt); p += 1)
99                                 if (patt[p] != `*`)
100                                         break;
101                         if (p >= size(patt))
102                                 return 1;
103
104                         for (; s < size(str); s += 1)
105                                 if (match(substr(str, s), substr(patt, p)))
106                                         return 1;
107                         continue;
108
109                 } elsif (patt[p] == `[`) {
110                         setsize(var x = [], 256);
111                         var invert = 0;
112                         if ((p += 1) < size(patt) and patt[p] == `^`) {
113                                 p += 1;
114                                 invert = 1;
115                         }
116                         for (var i = 0; p < size(patt); p += 1) {
117                                 if (patt[p] == `]` and i)
118                                         break;
119                                 x[patt[p]] = 1;
120                                 i += 1;
121
122                                 if (p + 2 < patt[p] and patt[p] != `-` and patt[p + 1] == `-`
123                                                 and patt[p + 2] != `]` and patt[p + 2] != `-`) {
124                                         var from = patt[p];
125                                         var to = patt[p += 2];
126                                         for (var c = from; c <= to; c += 1)
127                                                 x[c] = 1;
128                                 }
129                         }
130                         if (invert ? !!x[str[s]] : !x[str[s]])
131                                 return 0;
132                         s += 1;
133                         p += 1;
134                         continue;
135                 }
136
137                 if (str[s] != patt[p])
138                         return 0;
139                 s += 1;
140                 p += 1;
141         }
142         return s == size(str) and p == size(patt);
143 }
144
145
146 ##
147 # Removes superfluous slashes, empty and "." elements, expands
148 # all ".." elements, and turns all backslashes into slashes.
149 # The result will start with a slash if it started with a slash
150 # or backslash, it will end without slash. Should be applied on
151 # absolute property or file paths, otherwise ".." elements might
152 # be resolved wrongly.
153 #
154 var fixpath = func(path) {
155         var d = "";
156         for (var i = 0; i < size(path); i += 1)
157                 d ~= path[i] == `\\` ? "/" : chr(path[i]);
158
159         var prefix = d[0] == `/` ? "/" : "";
160         var stack = [];
161         foreach (var e; split("/", d)) {
162                 if (e == "." or e == "")
163                         continue;
164                 elsif (e == "..")
165                         pop(stack);
166                 else
167                         append(stack, e);
168         }
169         if (!size(stack))
170                 return "/";
171         path = stack[0];
172         foreach (var s; subvec(stack, 1))
173                 path ~= "/" ~ s;
174         return prefix ~ path;
175 }
176