rupg-func 0.1.84

The 3,414 built-in functions, the 805 operators, the 249 casts, the aggregates and window functions, the regular expression engine.
Documentation
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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
//! The matcher: the tree of the parser compiled to a program of a backtracking machine.
//!
//! The program of a pattern tells if the pattern matches, and where the leftmost match starts. The program of a node of the tree of subexpressions tells where a match of the node can end, which the dissection of a match needs.
//!
//! A program with no back reference keeps a set of the pairs of instruction and position that it has tried, so each pair is tried once and the search takes time in proportion to the size of the program and the length of the string. A program with back references cannot do this, because the result then depends on the groups, and it stops an empty loop with a mark of the position at the start of each pass.

use std::collections::HashMap;

use super::parse::{Assert, DUPINF, Look, Node, Set, to_lower};
use super::{Code, flags};

/// The largest number of instructions of a program, after the copies of the counted repeats.
const MAX_INSTS: usize = 1 << 20;

/// The largest set of tried pairs, in bits.
const MAX_VISITED: usize = 1 << 26;

/// An instruction of the machine.
#[derive(Clone, Copy, Debug)]
enum Inst {
    /// A character of the set with this index.
    Set(usize),
    /// A constraint.
    Assert(Assert),
    /// A lookaround constraint, with the index of its program.
    Look(Look, usize),
    /// The position into this slot of the groups.
    Save(usize),
    /// The same text as the group with this number.
    Backref(usize),
    /// Try the first target, then the second.
    Split(usize, usize),
    Jmp(usize),
    /// The position into this mark.
    Mark(usize),
    /// Fail when the position is the same as at this mark.
    Progress(usize),
    Match,
}

/// One program: the main pattern or the pattern of a lookaround.
#[derive(Debug, Default)]
struct Prog {
    insts: Vec<Inst>,
}

/// The compiled pattern.
#[derive(Debug)]
pub(super) struct Program {
    progs: Vec<Prog>,
    sets: Vec<Set>,
    groups: usize,
    marks: usize,
    backrefs: bool,
    nlanch: bool,
    icase: bool,
}

/// The state of the compiler.
struct Compiler {
    progs: Vec<Prog>,
    sets: Vec<Set>,
    marks: usize,
    backrefs: bool,
}

impl Compiler {
    fn emit(&mut self, prog: usize, inst: Inst) -> Result<usize, Code> {
        let insts = &mut self.progs[prog].insts;
        if insts.len() >= MAX_INSTS {
            return Err(Code::TooBig);
        }
        insts.push(inst);
        Ok(insts.len() - 1)
    }

    fn here(&self, prog: usize) -> usize {
        self.progs[prog].insts.len()
    }

    fn patch(&mut self, prog: usize, at: usize, inst: Inst) {
        self.progs[prog].insts[at] = inst;
    }

    /// The instructions of a node, which a copy of a set reuses by its index.
    fn node(&mut self, prog: usize, node: &Node, set_of: &mut SetIds) -> Result<(), Code> {
        match node {
            Node::Empty => {}
            Node::Set(set) => {
                let id = set_of.get(set, &mut self.sets);
                self.emit(prog, Inst::Set(id))?;
            }
            Node::Assert(assert) => {
                self.emit(prog, Inst::Assert(*assert))?;
            }
            Node::Look(look, inner) => {
                let sub = self.progs.len();
                self.progs.push(Prog::default());
                self.node(sub, inner, set_of)?;
                self.emit(sub, Inst::Match)?;
                self.emit(prog, Inst::Look(*look, sub))?;
            }
            Node::Group(number, inner) => {
                if *number > 0 {
                    self.emit(prog, Inst::Save(2 * number))?;
                }
                self.node(prog, inner, set_of)?;
                if *number > 0 {
                    self.emit(prog, Inst::Save(2 * number + 1))?;
                }
            }
            Node::Backref(number) => {
                self.backrefs = true;
                self.emit(prog, Inst::Backref(*number))?;
            }
            Node::Concat(items) => {
                for item in items {
                    self.node(prog, item, set_of)?;
                }
            }
            Node::Alt(branches) => {
                let mut jumps = Vec::new();
                for (at, branch) in branches.iter().enumerate() {
                    if at + 1 < branches.len() {
                        let split = self.emit(prog, Inst::Split(0, 0))?;
                        self.node(prog, branch, set_of)?;
                        jumps.push(self.emit(prog, Inst::Jmp(0))?);
                        let next = self.here(prog);
                        self.patch(prog, split, Inst::Split(split + 1, next));
                    } else {
                        self.node(prog, branch, set_of)?;
                    }
                }
                let end = self.here(prog);
                for jump in jumps {
                    self.patch(prog, jump, Inst::Jmp(end));
                }
            }
            Node::Repeat(inner, min, max, _) => {
                for _ in 0..*min {
                    self.node(prog, inner, set_of)?;
                }
                if *max == DUPINF {
                    let mark = self.marks;
                    self.marks += 1;
                    let split = self.emit(prog, Inst::Split(0, 0))?;
                    self.emit(prog, Inst::Mark(mark))?;
                    self.node(prog, inner, set_of)?;
                    self.emit(prog, Inst::Progress(mark))?;
                    self.emit(prog, Inst::Jmp(split))?;
                    let end = self.here(prog);
                    self.patch(prog, split, Inst::Split(split + 1, end));
                } else {
                    let mut splits = Vec::new();
                    for _ in *min..*max {
                        splits.push(self.emit(prog, Inst::Split(0, 0))?);
                        self.node(prog, inner, set_of)?;
                    }
                    let end = self.here(prog);
                    for split in splits {
                        self.patch(prog, split, Inst::Split(split + 1, end));
                    }
                }
            }
        }
        Ok(())
    }
}

/// The index of each set of the tree, so that the copies of a repeat share one set.
#[derive(Default)]
struct SetIds {
    ids: HashMap<*const Set, usize>,
}

impl SetIds {
    fn get(&mut self, set: &Set, sets: &mut Vec<Set>) -> usize {
        *self.ids.entry(std::ptr::from_ref(set)).or_insert_with(|| {
            sets.push(set.clone());
            sets.len() - 1
        })
    }
}

/// One step of the search to do later.
enum Job {
    /// Try the instruction at the position.
    Try(usize, usize),
    /// Put back the old value of a slot of the groups.
    Slot(usize, Option<usize>),
    /// Put back the old value of a mark.
    Mark(usize, Option<usize>),
}

/// The state of one search in one program.
struct Search<'a> {
    program: &'a Program,
    s: &'a [u32],
    /// The results of the lookarounds by program and position: 0 for not known, 1 for false, 2 for true.
    looks: Vec<Vec<u8>>,
}

fn is_word(c: u32) -> bool {
    char::from_u32(c).is_some_and(|c| c.is_ascii_alphanumeric() || c == '_')
}

impl Program {
    /// The program of a node, with the number of the groups of the pattern and the flags after the embedded options.
    pub(super) fn new(node: &Node, groups: usize, cflags: u32) -> Result<Self, Code> {
        let mut compiler =
            Compiler { progs: vec![Prog::default()], sets: Vec::new(), marks: 0, backrefs: false };
        let mut set_of = SetIds::default();
        compiler.node(0, node, &mut set_of)?;
        compiler.emit(0, Inst::Match)?;
        Ok(Program {
            progs: compiler.progs,
            sets: compiler.sets,
            groups,
            marks: compiler.marks,
            backrefs: compiler.backrefs,
            nlanch: cflags & flags::NLANCH != 0,
            icase: cflags & flags::ICASE != 0,
        })
    }

    /// The start of the leftmost match in `s` that starts at `from` or after it.
    pub(super) fn first(&self, s: &[u32], from: usize) -> Option<usize> {
        let mut search = Search { program: self, s, looks: vec![Vec::new(); self.progs.len()] };
        let mut visited = search.visited(0);
        (from..=s.len()).find(|&start| search.run(0, start, None, visited.as_mut(), None))
    }

    /// The ends of the matches from `start`: the item at a position is true when a match ends there.
    pub(super) fn ends(&self, s: &[u32], start: usize) -> Vec<bool> {
        let mut search = Search { program: self, s, looks: vec![Vec::new(); self.progs.len()] };
        let mut visited = search.visited(0);
        let mut ends = vec![false; s.len() + 1];
        search.run(0, start, None, visited.as_mut(), Some(&mut ends));
        ends
    }
}

impl Search<'_> {
    /// A new set of tried pairs for a program, or none when the program has back references or the set is too large.
    fn visited(&self, prog: usize) -> Option<Vec<u64>> {
        let bits = self.program.progs[prog].insts.len().checked_mul(self.s.len() + 1)?;
        (!self.program.backrefs && bits <= MAX_VISITED).then(|| vec![0; bits.div_ceil(64)])
    }

    /// True when the constraint holds at the position.
    fn holds(&self, assert: Assert, pos: usize) -> bool {
        let s = self.s;
        let newline = u32::from(b'\n');
        let word_before = pos > 0 && is_word(s[pos - 1]);
        let word_after = pos < s.len() && is_word(s[pos]);
        match assert {
            Assert::Bol => pos == 0 || (self.program.nlanch && s[pos - 1] == newline),
            Assert::Eol => pos == s.len() || (self.program.nlanch && s[pos] == newline),
            Assert::Bos => pos == 0,
            Assert::Eos => pos == s.len(),
            Assert::WordStart => !word_before && word_after,
            Assert::WordEnd => word_before && !word_after,
            Assert::Boundary => word_before != word_after,
            Assert::NotBoundary => word_before == word_after,
        }
    }

    /// True when the lookaround holds at the position.
    fn look(&mut self, look: Look, prog: usize, pos: usize) -> bool {
        if self.looks[prog].is_empty() {
            self.looks[prog] = vec![0; self.s.len() + 1];
        }
        let known = self.looks[prog][pos];
        let found = if known != 0 {
            known == 2
        } else {
            let found = match look {
                Look::Ahead | Look::NotAhead => {
                    let mut visited = self.visited(prog);
                    self.run(prog, pos, None, visited.as_mut(), None)
                }
                Look::Behind | Look::NotBehind => {
                    let mut visited = self.visited(prog);
                    (0..=pos)
                        .rev()
                        .any(|start| self.run(prog, start, Some(pos), visited.as_mut(), None))
                }
            };
            self.looks[prog][pos] = if found { 2 } else { 1 };
            found
        };
        matches!(look, Look::Ahead | Look::Behind) == found
    }

    /// The text of a group, when it is set.
    fn group(slots: &[Option<usize>], number: usize) -> Option<(usize, usize)> {
        Some((slots.get(2 * number).copied()??, slots.get(2 * number + 1).copied()??))
    }

    /// True when the text at the position is the same as the text of the group.
    fn backref(&self, slots: &[Option<usize>], number: usize, pos: usize) -> Option<usize> {
        let (start, end) = Self::group(slots, number)?;
        let len = end.checked_sub(start)?;
        let here = self.s.get(pos..pos + len)?;
        let same = if self.program.icase {
            here.iter().zip(&self.s[start..end]).all(|(&a, &b)| to_lower(a) == to_lower(b))
        } else {
            here == &self.s[start..end]
        };
        same.then_some(pos + len)
    }

    /// The search in a program from a start position. With an end, a match must end there. With a list of ends, the search does not stop at a match: it tries all the paths and marks the end of each match in the list.
    fn run(
        &mut self,
        prog: usize,
        start: usize,
        end: Option<usize>,
        mut visited: Option<&mut Vec<u64>>,
        mut ends: Option<&mut [bool]>,
    ) -> bool {
        let program = self.program;
        let insts = &program.progs[prog].insts;
        let width = self.s.len() + 1;
        let mut slots: Vec<Option<usize>> = vec![None; 2 * program.groups + 2];
        let mut marks: Vec<Option<usize>> = vec![None; program.marks];
        let mut jobs = vec![Job::Try(0, start)];
        while let Some(job) = jobs.pop() {
            let (mut pc, mut pos) = match job {
                Job::Try(pc, pos) => (pc, pos),
                Job::Slot(slot, old) => {
                    slots[slot] = old;
                    continue;
                }
                Job::Mark(mark, old) => {
                    marks[mark] = old;
                    continue;
                }
            };
            loop {
                if let Some(visited) = visited.as_deref_mut() {
                    let bit = pc * width + pos;
                    if visited[bit / 64] & (1 << (bit % 64)) != 0 {
                        break;
                    }
                    visited[bit / 64] |= 1 << (bit % 64);
                }
                match insts[pc] {
                    Inst::Set(id) => {
                        if pos < self.s.len() && program.sets[id].contains(self.s[pos]) {
                            pc += 1;
                            pos += 1;
                        } else {
                            break;
                        }
                    }
                    Inst::Assert(assert) => {
                        if !self.holds(assert, pos) {
                            break;
                        }
                        pc += 1;
                    }
                    Inst::Look(look, sub) => {
                        if !self.look(look, sub, pos) {
                            break;
                        }
                        pc += 1;
                    }
                    Inst::Save(slot) => {
                        jobs.push(Job::Slot(slot, slots[slot]));
                        slots[slot] = Some(pos);
                        pc += 1;
                    }
                    Inst::Backref(number) => match self.backref(&slots, number, pos) {
                        Some(next) => {
                            pc += 1;
                            pos = next;
                        }
                        None => break,
                    },
                    Inst::Split(first, second) => {
                        jobs.push(Job::Try(second, pos));
                        pc = first;
                    }
                    Inst::Jmp(to) => pc = to,
                    Inst::Mark(mark) => {
                        jobs.push(Job::Mark(mark, marks[mark]));
                        marks[mark] = Some(pos);
                        pc += 1;
                    }
                    Inst::Progress(mark) => {
                        if marks[mark] == Some(pos) {
                            break;
                        }
                        pc += 1;
                    }
                    Inst::Match => {
                        if let Some(ends) = ends.as_deref_mut() {
                            ends[pos] = true;
                            break;
                        }
                        if end.is_none_or(|end| end == pos) {
                            return true;
                        }
                        break;
                    }
                }
            }
        }
        false
    }
}