-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathPatternMatchGen.php
More file actions
176 lines (158 loc) · 6 KB
/
PatternMatchGen.php
File metadata and controls
176 lines (158 loc) · 6 KB
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
<?php
/*
PatternMatchGen - automatic Asterisk dialplan pattern match generator
(C) 2021 PhreakNet.
This is a simple program that can be used to automatically generate dialplan pattern matches for use in the Asterisk dialplan. It does the opposite of what Asterisk does: instead of using a pattern match to represent a collection of extensions, it converts a collection of extensions into the corresponding pattern match representation(s).
The basic premise of this program is actually very simple. Given a starting number and an ending number, generate dialplan pattern matches for this number range, ideally using the fewest possible number of pattern matches, *without encompassing any numbers that do not fall in the provided range*. It's not really that difficult, it just involves a lot of math.
e.g. given 234, 742:
exten => _23[4-9]
exten => _2[4-9]XX
exten => _[3-6]XX
exten => _7[0-3]X
exten => _74[0-2]
This is what we will call the minimally encompassing set of pattern matches. The goal of this program is to generate this information.
For multiple ranges (disjoint number ranges), call the function multiple times.
Typically, what you'll want to do is retrieve a window range using SQL and then for each range that you get, call this function.
Usage:
Parameters:
$ptr = pointer to array to store result.
$min = starting number
$max = ending number
Return Value:
The constructor function does not explicitly return anything, but it fills an array containing key value pairs corresponding to the starting number and the quantity of additional numbers in that range (e.g. 0 for just that number, 1 for two consecutive numbers, etc.). This is sufficient to then generate pattern matches using some trivial logic.
Example:
$min = 234;
$max = 742;
$numberGroups = array();
$ptr = &$numberGroups;
new \PatternMatchGen($ptr, $min, $max);
foreach ($numberGroups as $start => $qty) {
$ext = \PatternMatchGen::matchCallback($start, $qty);
$ext .= ",1,Return(somethingcool)\n";
echo $ext; # print the full extenpattern,priority,app that we just generated.
}
Result:
exten => _23[4-9],1,Return(somethingcool)
exten => _2[4-9]XX,1,Return(somethingcool)
exten => _[3-6]XX,1,Return(somethingcool)
exten => _7[0-3]X,1,Return(somethingcool)
exten => _74[0-2],1,Return(somethingcool)
Demo Usage from the command line:
php PatternMatchGen.php <min> <max>
*/
/* Demo usage */
if (php_sapi_name() == 'cli') { # https://stackoverflow.com/a/9765564
if (isset($_SERVER['TERM']) && isset($argc) && count(get_included_files()) === 1) {
/* TERM is set on class invocations in a program, but $argc is only set on direct CLI invocation */
if ($argc === 3) {
$min = (int) $argv[1];
$max = (int) $argv[2];
$numberGroups = array();
$ptr = &$numberGroups;
new \PatternMatchGen($ptr, $min, $max);
foreach ($numberGroups as $start => $qty) {
echo \PatternMatchGen::matchCallback($start, $qty) . PHP_EOL;
}
} else {
echo "Usage: php PatternMatchGen.php <min> <max>" . PHP_EOL;
}
}
}
class PatternMatchGen {
public static function matchCallback($start, int $qty) {
if ($qty === 0) {
return 'exten => ' . $start;
}
$la = strlen($start);
$lb = strlen($qty);
$f = (int) substr($qty, 0, 1); # only the first digit and string length are needed. Any additional digits are 9's.
$fixedDigits = $la - $lb;
$pDigit = (int) substr($start, $fixedDigits, 1);
$ext = '';
$ext .= 'exten => _' . substr($start, 0, $fixedDigits);
if ($f === 9 && $pDigit === 0) {
$ext .= 'X';
} else if ($f === 8 && $pDigit === 1) {
$ext .= 'Z';
} else if ($f === 7 && $pDigit === 2) {
$ext .= 'N';
} else {
$ext .= '[' . $pDigit . '-' . ($pDigit + $f) . ']';
}
$ext .= str_repeat('X', $lb - 1);
return $ext;
}
private static function asn(&$ptr, $a, $b) {
if ($b < 0)
return;
$ptr[$a] = $b;
}
public function __construct(&$ptr, $start, $end) {
/*
$rec = 0
$amt = maximum depth (10 million should be high enough - 10M is more than a whole NPA (area code). No way in heck should we be pattern matching more than this many digits!)
*/
static::generate($ptr, 0, 10000000, $start, $end);
}
private static function generate(&$ptr, $rec, $amt, $start, $end) { # recursive function
$rec++;
if ($amt <= 10) {
if ($end < $start)
return;
$d = ceil( $start / 10 ) * 10;
if ($d <= $end) {
static::asn($ptr, $start, $d - $start - 1); # until next 10 (e.g. 23->29)
$diff = $end - $d;
if ($diff >= $amt * 2 && (($diff + 1) % $amt > 0)) { # opportunity for something like [0-2]X,31,32
$groups = floor($diff / $amt);
$top = $groups * $amt + $d;
# middle
static::asn($ptr, $d, ($groups * $amt) - 1);
# after
if ($end > $d + $amt) {
static::generate($ptr, $rec, $amt, $top, $end);
}
} else {
static::asn($ptr, $d, $end - $d);
}
} else {
static::asn($ptr, $start, $end - $start);
}
} else {
$cap = round($start + ($amt / 2) - 1, -(strlen($amt) - 1)); # e.g. should be -2 for up to nearest 100
# before
if ($cap > $start) {
static::generate($ptr, $rec, $amt / 10, $start, min($end, $cap - 1));
}
if ($end >= $cap + $amt) {
if (($end + 1) % $amt === 0 && ($end - ($cap + $amt) + 1) % $amt === 0) {
# We don't add $amt to $cap and use the combined value like in lower branches, to ensure that round intervals (e.g. _[1-3]XX) work properly (see issue #1)
static::asn($ptr, $cap, $end - $cap); # something like [3-9]X
} else {
$diff = $end - $cap;
if ($diff >= $amt * 2) { # opportunity for something like [0-4]X
$groups = floor($diff / $amt);
$top = $groups * $amt + $cap;
# middle
static::asn($ptr, $cap, ($groups * $amt) - 1);
# after
if ($end > $cap + $amt) {
static::generate($ptr, $rec, $amt, $top, $end);
}
} else {
# middle
static::asn($ptr, $cap, $amt - 1);
# after
if ($end > $cap + $amt) {
static::generate($ptr, $rec, $amt, $cap + $amt, $end);
}
}
}
} else {
# after
static::generate($ptr, $rec, $amt / 10, $cap, $end);
}
}
}
}
?>