File: string.liq

package info (click to toggle)
liquidsoap 2.1.3-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 12,924 kB
  • sloc: ml: 73,577; javascript: 24,836; sh: 3,440; makefile: 764; xml: 114; ansic: 96; lisp: 62; python: 35; perl: 8; ruby: 8
file content (273 lines) | stat: -rw-r--r-- 7,994 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# Match a string with an expression. Perl compatible regular expressions are
# recognized. Hence, special characters should be escaped. Alternatively, one
# can use the the `r/_/.test(_)` syntax for regular expressions.
# @category String
def string.match(~pattern, s) =
  regexp(pattern).test(s)
end

# Extract substrings from a string. Perl compatible regular expressions are
# recognized. Hence, special characters should be escaped. Returns a list of
# (index,value). If the list does not have a pair associated to some index, it
# means that the corresponding pattern was not found. Alter natively, one can
# use the `r/_/.exec(_)` syntax for regular expressions.
# @category String
def string.extract(~pattern, s) =
  regexp(pattern).exec(s)
end

# Replace all substrings matched by a pattern by another string returned by a
# function. Alternatively, one can use the `r/_/g.replace(_)` syntax for regular
# expressions.
# @category String
# @param ~pattern Pattern (regular expression) of substrings which should be replaced.
# @param f Function getting a matched substring an returning the string to replace it with.
# @param s String whose substrings should be replaced.
def string.replace(~pattern, f, s) =
  regexp(flags=["g"], pattern).replace(f, s)
end

# Split a string at "separator". Perl compatible regular expressions are
# recognized. Hence, special characters should be escaped. Alternatively, one
# can use the `r/_/.split(_)` syntax for regular expressions.
# @category String
def string.split(~separator, s) =
  regexp(separator).split(s)
end

# Test whether a string contains a given prefix, substring or suffix.
# @category String
# @param ~prefix Prefix to look for.
# @param ~substring Substring to look for.
# @param ~suffix Suffix to look for.
# @param s The string to look into.
def string.contains(~prefix="", ~substring="", ~suffix="", s)
  ans = ref(prefix == "" and substring == "" and suffix == "")

  if prefix != "" then
    ans := !ans or string.sub(s, start=0, length=string.length(prefix)) == prefix
  end

  if suffix != "" then
    suflen = string.length(suffix)
    ans := !ans or string.sub(s, start=string.length(s)-suflen, length=suflen) == suffix
  end

  if substring != "" then
    sublen = string.length(substring)
    for i = 0 to string.length(s)-sublen do
      ans := !ans or (string.sub(s, start=i, length=sublen) == substring)
    end
  end

  !ans
end

# Test whether a string is a valid integer.
# @category String
def string.is_int(s)
  string.match(pattern="^\\d+$", s)
end

# Convert a string to a int.
# @category String
def string.to_int(~default=0, s)
  int_of_string(default=default, s)
end

# Convert a string to a float.
# @category String
def string.to_float(~default=0., s)
  float_of_string(default=default, s)
end

let string.binary = ()

# Value of a positive (unsigned) integer encoded using native memory representation.
# @category String
# @param ~little_endian Whether the memory representation is little endian.
# @param s String containing the binary representation.
def string.binary.to_int(~little_endian=true, s)
  ans = ref(0)
  n = string.length(s)
  for i = 0 to n-1 do
    ans := lsl(!ans,8) + string.nth(s, if little_endian then n-1-i else i end)
  end
  !ans
end

# Encode a positive (unsigned) integer using native memory representation.
# @category String
# @param ~pad Minimum length in digits (pad on the left with zeros in order to reach it)
# @param ~little_endian Whether the memory representation is little endian.
# @param s String containing the binary representation.
def string.binary.of_int(~pad=0, ~little_endian=true, d)
  def rec f(d, s) =
    if d > 0 then
      c = string.hex_of_int(pad=2, (d mod 256))
      if little_endian then
        f(lsr(d, 8), "#{s}\\x#{c}")
      else
        f(lsr(d, 8), "\\x#{c}#{s}")
      end
    else
      s
    end
  end
  ret = d == 0 ? "\\x00" : f(d, "")
  ret = string.unescape(ret)
  len = string.length(ret)
  if len < pad then
    ans = string.make(char_code=0, pad-len)
    if little_endian then
      "#{ret}#{ans}"
    else
      "#{ans}#{ret}"
    end
  else
    ret
  end
end

# Add a null character at the end of a string.
# @category String
# @param s String.
def string.null_terminated(s)
  s ^ "\000"
end

# Generate an identifier if no identifier was provided.
# @category String
# @param ~default Name from which identifier is generated if not present.
# @param id Proposed identifier.
def string.id.default(~default, id)
  null.default(id, {string.id(default)})
end

# Return a quoted copy of the given string.
# By default, the string is assumed to be `"utf8"` encoded and is escaped
# following JSON and javascript specification.
# @category String
# @param ~encoding One of: `"ascii"` or `"utf8"`. If `null`, `utf8` is tried first and `ascii` is used as a fallback if this fails.
def string.quote(~encoding=null(), s) =
  def special_char(~encoding, s)
    if s == "'" then
      false
    else
      string.escape.special_char(encoding=encoding, s)
    end
  end
  s = string.escape(special_char=special_char, encoding=encoding, s)
  "\"#{s}\""
end

let string.data_uri = ()

# Encode a string using the data uri format,
# i.e. `"data:<mime>[;base64],<data>"`.
# @category String
# @param ~base64 Encode data using the base64 format
# @param ~mime Data mime type
def string.data_uri.encode(~base64=true, ~(mime:string), s) =
  s = base64 ? ";base64,#{string.base64.encode(s)}" : ",#{s}"
  "data:#{mime}#{s}"
end

# Decode a string using the data uri format,
# i.e. `"data:<mime>[;base64],<data>"`.
# @category String
def string.data_uri.decode(s) =
  captured = r/^data:([\/\w]+)(;base64)?,(.+)$/.exec(s)
  if list.assoc.mem(1, captured) and list.assoc.mem(3, captured) then
    mime = list.assoc(1, captured)
    data = list.assoc(3, captured)
    data =
      if list.assoc.mem(2, captured) then
        string.base64.decode(data)
      else
        data
      end
    data.{mime=mime}
  else
    null()
  end
end

let string.getter = ()

# Flush all values from a string getter and return
# the concatenated result. If the getter is constant,
# return the constant string. Otherwise, call the getter
# repeatedly until it returns an empty string and return
# the concatenated result
# @category String
def string.getter.flush(~separator="", gen) =
  if getter.is_constant(gen) then
    getter.get(gen)
  else
    def rec f(data) =
      chunk = getter.get(gen)
      if chunk == "" then string.concat(separator=separator, data) else
        f([...data, chunk])
      end
    end
    f([])
  end
end

# Combine a list of string getters `[g1, ...]`
# and return a single getter `g` such that:
# `string.getter.flush(separator=s, g) = string.concat(separator=s, list.filter(fun (s) -> s != "", [string.getter.flush(g1), ...]))`
# @category String
def string.getter.concat(l) =
  len = list.length(l)
  pos = ref(0)

  def rec f() =
    if !pos == len then "" else
      gen = list.nth(l, !pos)
      ret = getter.get(gen)

      if ret == "" or getter.is_constant(gen) then
        ref.incr(pos)
      end

      ret == "" ? f () : ret
    end
  end

  getter(f)
end

let string.char.ascii = ()

# All ASCII characters code
# @category String
let string.char.ascii = list.init(128, fun (c) -> c)

# All ASCII control character codes
# @category String
let string.char.ascii.control = list.init(32, fun (c) -> c)

# All ASCII printable character codes
# @category String
let string.char.ascii.printable = list.init(96, fun (c) -> c+32)

# All ASCII alphabet character codes
# @category String
let string.char.ascii.alphabet = [
  # A-Z
  ...list.init(24, fun (c) -> c+65),
  # a-z
  ...list.init(24, fun (c) -> c+97),
]

# All ASCII number character codes
# @category String
let string.char.ascii.number = list.init(10, fun (c) -> c+48)

# Return a random ASCII character
# @category String
def string.char.ascii.random(range=[...string.char.ascii]) =
  string.char(list.nth(range, random.int(min=0, max=list.length(range)-1)))
end