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
|
<?php
namespace Parsers;
use ReflectionFunction;
class FunctionParser implements ParserInterface {
private $functions = [];
#===============================================================================
# Main regex for matching the whole function call
#===============================================================================
private const FUNCTION_PATTERN = '/
\{\s? # Opening curly brace `{`
(?<func>%s) # The function name in uppercase letters
(?:\:\s # A colon `:` followed by a required blank
(?<arg_list> # Capture group for the whole argument list
(?:%s)+ # One or more arguments (ARGUMENT_PATTERN_PARTIAL)
)
)?
\s?\} # Closing curly brace `}`
/x';
#===============================================================================
# Partial regex for matching/splitting the argument list
# - Thanks to @OnlineCop from the #regex community of the Libera IRC network! <3
#===============================================================================
private const ARGUMENT_PATTERN_PARTIAL =
'(?<arg> # Either a quoted string or a plain number
(?<qmark>["\']) # Either a single or double quote
(?>[^"\'\\\\]++ # String between the quotes
| [\\\\]. # A `\` followed by anything but literal newline
| (?!\k<qmark>)["\'] # A quote, but not our opening quote
)*+
\k<qmark> # Closing quote (same as opening quote)
|
[0-9]+ # ... or just a plain number
)
(?:,\s*)?';
#===============================================================================
# Register function
#===============================================================================
public function register(string $name, callable $callback): void {
$Function = new ReflectionFunction($callback);
$this->functions[$name] = [
'callback' => $callback,
'required' => $Function->getNumberOfRequiredParameters()
];
}
#===============================================================================
# Register multiple functions from array
#===============================================================================
public function registerFromArray(array $functions): void {
foreach($functions as $name => $callback) {
$this->register($name, $callback);
}
}
#===============================================================================
# Parse functions
#===============================================================================
public function parse(string $text): array {
$functionNames = array_keys($this->functions);
$functionNames = implode('|', $functionNames);
$pattern = self::FUNCTION_PATTERN;
$options = self::ARGUMENT_PATTERN_PARTIAL;
preg_match_all(sprintf($pattern, $functionNames, $options), $text, $matches);
foreach(array_map(function($name, $parameters) {
return [$name , $this->parseParameterString($parameters)];
}, $matches['func'], $matches['arg_list']) as $match) {
$functions[$match[0]][] = $match[1];
}
return $functions ?? [];
}
#===============================================================================
# Transform functions
#===============================================================================
public function transform(string $text): string {
$functionData = $this->functions;
$functionNames = array_keys($functionData);
$functionNames = implode('|', $functionNames);
$pattern = self::FUNCTION_PATTERN;
$options = self::ARGUMENT_PATTERN_PARTIAL;
return preg_replace_callback(sprintf($pattern, $functionNames, $options),
function($matches) use($functionData) {
$function = $matches['func'];
$callback = $functionData[$function]['callback'];
$required = $functionData[$function]['required'];
$arguments = $this->parseParameterString($matches['arg_list'] ?? '');
if(count($arguments) < $required) {
return sprintf('`{%s: *Missing arguments*}`', $function);
}
return $callback(...$arguments);
}, $text);
}
#===============================================================================
# Parse the parameter string found within the function shell
#===============================================================================
private function parseParameterString(string $parameters): array {
$pattern = sprintf('/%s/x', self::ARGUMENT_PATTERN_PARTIAL);
preg_match_all($pattern, $parameters, $matches);
return array_map(function($arg, $qmark) {
if(!$qmark) {
return $arg;
}
# If a quotation mark is matched, the argument has been enclosed
# between quotation marks when passed to the content function in
# the editor. Therefore, the quotation marks must be removed and
# we also need to take care of the backslash-escaped occurrences
# of the quotation marks inside the argument string.
$arg = substr($arg, 1);
$arg = substr($arg, 0, strlen($arg)-1);
$arg = str_replace('\\'.$qmark, $qmark, $arg);
return $arg;
}, $matches['arg'], $matches['qmark']);
}
}
|