line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Positron::Expression; |
2
|
|
|
|
|
|
|
our $VERSION = 'v0.1.3'; # VERSION |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
=head1 NAME |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
Positron::Expression - a simple language for template parameters |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 VERSION |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
version v0.1.3 |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 SYNOPSIS |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
use Positron::Expression; |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
my $env = Positron::Environment->new({ key => 'value' }); |
17
|
|
|
|
|
|
|
my $value = Positron::Expression::evaluate($string, $env); |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 DESCRIPTION |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
A simple expression language for templating constructs. |
22
|
|
|
|
|
|
|
The main function, C, takes an expression as a string and a |
23
|
|
|
|
|
|
|
L object, and evaluates the two. |
24
|
|
|
|
|
|
|
The result is a scalar value. |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=head1 GRAMMAR |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
The grammar is basically built up of the following rules. |
29
|
|
|
|
|
|
|
The exact grammar is available as a package variable |
30
|
|
|
|
|
|
|
C<$Positron::Expression::grammar>; this is a string which could be fed to |
31
|
|
|
|
|
|
|
L starting at the token C. |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
However, the L path has been replaced with a version |
34
|
|
|
|
|
|
|
using plain regular expressions, so the string is no longer the direct |
35
|
|
|
|
|
|
|
definition of the grammar. |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
=head2 Whitespace |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
Whitespace is generally allowed between individual parts of the grammar. |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=head2 Literals |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
4 , -3.8 , "A string" , 'another string' , `a third string` |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
The grammar allows for literal strings and numbers. Numbers are integers or |
46
|
|
|
|
|
|
|
floating point numbers. Notations with exponents or with different bases are |
47
|
|
|
|
|
|
|
not supported. Negative numbers are possible. |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
Strings are delimited by double quotes, single quotes, or backticks. |
50
|
|
|
|
|
|
|
Strings cannot contain their own delimiters; with three delimiters to choose |
51
|
|
|
|
|
|
|
from, though, this should cover most use cases. |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
=head2 Variable lookups |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
a , key0 , ListValues , flag_not_possible |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
A single, non-deliminated word is looked up in the environment; that value |
58
|
|
|
|
|
|
|
is returned. |
59
|
|
|
|
|
|
|
This may be C if the environment does not contain such a key. |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
Words follow the rules for identifiers in most C-like languages (and Perl), |
62
|
|
|
|
|
|
|
in that they may start with a letter or an underscore, and contain only |
63
|
|
|
|
|
|
|
letters or underscores. |
64
|
|
|
|
|
|
|
Currently, only ASCII letters are supported; this will hopefully change in |
65
|
|
|
|
|
|
|
the future. |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=head2 Function calls |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
a() , b(0) , find_file("./root", filename) |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
Functions are looked up in the environment, like variables. |
72
|
|
|
|
|
|
|
They obey the same rules for identifiers, and are expected to return an |
73
|
|
|
|
|
|
|
anonymous function (a sub reference). |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
This function is then called with the evaluated arguments. |
76
|
|
|
|
|
|
|
In the last example above, C is looked up in the environment, and |
77
|
|
|
|
|
|
|
the resulting value passed as the second argument to the function. |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
All function calls are made in scalar context. |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=head2 Subselects |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
Subselects allow you to to select a part of something else, like getting the |
84
|
|
|
|
|
|
|
value for a given key in a hash, or an indexed entry in a list, or call a |
85
|
|
|
|
|
|
|
method on an object etc. |
86
|
|
|
|
|
|
|
In C, these are denoted with a dot, C<.>, hence the |
87
|
|
|
|
|
|
|
alternative name "dotted expression". |
88
|
|
|
|
|
|
|
Subselects can be chained. |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
The following subselects are possible: |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=head3 Array index |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
a.0 , b.4.-1 , c.$i |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
Arrays are indexed by appending an integer to the variable or expression holding |
97
|
|
|
|
|
|
|
the array. |
98
|
|
|
|
|
|
|
Like Perl, indices start with 0, and negative indices count from the back. |
99
|
|
|
|
|
|
|
The form C<< $ >> can be used to take any expression that evaluates |
100
|
|
|
|
|
|
|
to an integer as an index. |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=head3 Hash index |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
pos.x , server.link.url , authors."Ben Deutsch" , names.$current |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
Hashes are indexed by appending a key, an identifier or string, |
107
|
|
|
|
|
|
|
to the variable or expression holding the hash. |
108
|
|
|
|
|
|
|
Most keys in practice will fit the form of an identifier as above |
109
|
|
|
|
|
|
|
(letters, digits, underscores). |
110
|
|
|
|
|
|
|
If not, a quoted string can be used. |
111
|
|
|
|
|
|
|
The form C<< $ >> can again be used to take any expression that |
112
|
|
|
|
|
|
|
evaluates to a string as the key. |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=head3 Object attributes |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
obj.length , task.parent.priority , obj.$attr |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
Object attributes work just like hash indices above, except they are called |
119
|
|
|
|
|
|
|
on an object and look up that attribute. |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
(In Perl, this is the same as a method call without parameters.) |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=head3 Object method calls |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
img.make_src(320, 240) , abs(int(-4.2)) |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
Method calls work like a mixture between attributes and function calls. |
128
|
|
|
|
|
|
|
The method name is restricted to an actual key, however, and not a free-form |
129
|
|
|
|
|
|
|
string or a C<$>-expression. |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
Like functions, methods are called in scalar context. |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=head2 Nested expressions |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
hash.(var).length , ports.(resource.server) |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
Expressions can be nested with parentheses. |
138
|
|
|
|
|
|
|
The C expression above is equivalent to C, since |
139
|
|
|
|
|
|
|
C as an expression is a variable lookup in the environment. |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=head2 Boolean combinations |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
a ? !b , if ? then : else , !!empty |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
The C>, C<:> and C operands stand for "and", "or" and "not", respectively. |
146
|
|
|
|
|
|
|
This terminology, while a bit obscure, is the mirror of Python's |
147
|
|
|
|
|
|
|
C ternary operator replacement. |
148
|
|
|
|
|
|
|
In practice, this allows for some common use cases: |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=head3 Not |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
The C operator has a higher precedence than C> or C<:>, binding closer. |
153
|
|
|
|
|
|
|
It reverses the "truth" of the expression it precedes. |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
B: unlike pure Perl, a reference to an empty array or an empty hash counts as false! |
156
|
|
|
|
|
|
|
In Perl, it would be true because all references are true, barring overloading; only non-reference |
157
|
|
|
|
|
|
|
empty arrays and hashes are false. |
158
|
|
|
|
|
|
|
Positron's use is closer related to the Perl usages of C than C, |
159
|
|
|
|
|
|
|
and is typically what you mean. |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=head3 Conditional values: And |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
only_if ? value , first_cond ? second_cond ? result |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
The C> operator is a short-circuiting C<&&> or C equivalent. |
166
|
|
|
|
|
|
|
If the left hand side is false, it is returned, otherwise the right hand side is returned. |
167
|
|
|
|
|
|
|
It is chainable, and left associative. |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
The most common use case is text insertion with a condition which is C<''> when false; |
170
|
|
|
|
|
|
|
the right hand text is only inserted if the condition is true. |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=head3 Defaults: Or |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
first_try : second_try : third_try |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
The C<:> operator is a short-circuiting C<||> or C equivalent. |
177
|
|
|
|
|
|
|
If the left hand side is true, it is returned, otherwise the right hand side is returned. |
178
|
|
|
|
|
|
|
It is chainable, left associative, and has the same precedence as C>. |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
The most common use case is to provide a chain of fallback values, selecting the first |
181
|
|
|
|
|
|
|
fitting (i.e. true) one. |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=head3 Ternary Operator |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
if ? then : else |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
Taken together, the C> and C<:> operators form the well-known ternary operator: if the |
188
|
|
|
|
|
|
|
left-most term is true, the middle term is chosen; else the right-most term is. |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=cut |
191
|
|
|
|
|
|
|
|
192
|
33
|
|
|
33
|
|
123993
|
use v5.10; |
|
33
|
|
|
|
|
118
|
|
|
33
|
|
|
|
|
1854
|
|
193
|
33
|
|
|
33
|
|
178
|
use strict; |
|
33
|
|
|
|
|
55
|
|
|
33
|
|
|
|
|
1084
|
|
194
|
33
|
|
|
33
|
|
175
|
use warnings; |
|
33
|
|
|
|
|
168
|
|
|
33
|
|
|
|
|
1108
|
|
195
|
|
|
|
|
|
|
|
196
|
33
|
|
|
33
|
|
177
|
use Carp qw(croak); |
|
33
|
|
|
|
|
70
|
|
|
33
|
|
|
|
|
1866
|
|
197
|
33
|
|
|
33
|
|
17568
|
use Data::Dump qw(pp); |
|
33
|
|
|
|
|
181991
|
|
|
33
|
|
|
|
|
2164
|
|
198
|
33
|
|
|
33
|
|
38066
|
use IO::String qw(); |
|
33
|
|
|
|
|
192968
|
|
|
33
|
|
|
|
|
928
|
|
199
|
33
|
|
|
33
|
|
2754
|
use Positron::Environment; |
|
33
|
|
|
|
|
73
|
|
|
33
|
|
|
|
|
1366
|
|
200
|
|
|
|
|
|
|
#use Parse::RecDescent; # obsolete, see below |
201
|
33
|
|
|
33
|
|
198
|
use Scalar::Util qw(blessed); |
|
33
|
|
|
|
|
67
|
|
|
33
|
|
|
|
|
303730
|
|
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
# The following grammar is used by Parse::RecDescent to create a "parse tree". |
204
|
|
|
|
|
|
|
# Note that the Parse::RecDescent path is currently obsolete. |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
our $grammar = <<'EOT'; |
207
|
|
|
|
|
|
|
# We start with our "boolean / ternary" expressions |
208
|
|
|
|
|
|
|
expression: { @{$item[1]} == 1 ? $item[1]->[0] : ['expression', @{$item[1]}]; } |
209
|
|
|
|
|
|
|
alternative: '!' alternative { ['not', $item[2]]; } | operand |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# strings and numbers cannot start a dotted expression |
212
|
|
|
|
|
|
|
# in fact, numbers can have decimal points. |
213
|
|
|
|
|
|
|
operand: string | number | lterm ('.' rterm)(s) { ['dot', $item[1], @{$item[2]}] } | lterm |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
# The first part of a dotted expression is looked up in the environment. |
216
|
|
|
|
|
|
|
# The following parts are parts of whatever came before, and consequently looked |
217
|
|
|
|
|
|
|
# up there. |
218
|
|
|
|
|
|
|
lterm: '(' expression ')' { $item[2] } | funccall | identifier | '$' lterm { ['env', $item[2]] } |
219
|
|
|
|
|
|
|
rterm: '(' expression ')' { $item[2] } | methcall | key | string | integer | '$' lterm { $item[2] } |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
# Strings currently cannot contain their delimiters, sorry. |
222
|
|
|
|
|
|
|
string: '"' /[^"]*/ '"' { $item[2] } | /\'/ /[^\']*/ /\'/ { $item[2] } | '`' /[^`]*/ '`' { $item[2] } |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
identifier: /[a-zA-Z_]\w*/ {['env', $item[1]]} |
225
|
|
|
|
|
|
|
key: /[a-zA-Z_]\w*/ { $item[1] } |
226
|
|
|
|
|
|
|
number: /[+-]?\d+(?:\.\d+)?/ { $item[1] } |
227
|
|
|
|
|
|
|
integer: /[+-]?\d+/ { $item[1] } |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
# We need "function calls" and "method calls", since with the latter, the function |
230
|
|
|
|
|
|
|
# is *not* looked up in the environment. |
231
|
|
|
|
|
|
|
funccall: identifier '(' expression(s? /\s*,\s*/) ')' { ['funccall', $item[1], @{$item[3]}] } |
232
|
|
|
|
|
|
|
methcall: key '(' expression(s? /\s*,\s*/) ')' { ['methcall', $item[1], @{$item[3]}] } |
233
|
|
|
|
|
|
|
EOT |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
# A Parse::RecDescent object; currently obsolete |
236
|
|
|
|
|
|
|
our $parser = undef; |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=head1 FUNCTIONS |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
=head2 evaluate |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
my $value = Positron::Expression::evaluate($string, $environment); |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
Evaluates the expression in C<$string> with the L C<$env>. |
245
|
|
|
|
|
|
|
The result is always a scalar value, which may be a plain scalar or a reference. |
246
|
|
|
|
|
|
|
For example, the expression C with the environment C<< { x => [1] } >> |
247
|
|
|
|
|
|
|
will evaluate to a reference to an array with one element. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=cut |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
sub evaluate { |
252
|
530
|
|
|
530
|
1
|
1632
|
my ($string, $environment) = @_; |
253
|
530
|
100
|
100
|
|
|
2626
|
return undef unless defined $string and $string ne ''; |
254
|
518
|
|
|
|
|
7829
|
my $tree = parse($string); |
255
|
|
|
|
|
|
|
# Force scalar context, always |
256
|
518
|
|
|
|
|
1069
|
return scalar(_evaluate($tree, $environment)); |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
=head2 parse |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
my $tree = Positron::Expression::parse($string); |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
Parses the string in the first argument, and returns an abstract parse tree. |
264
|
|
|
|
|
|
|
The exact form of the tree is not important, it is usually a structure made |
265
|
|
|
|
|
|
|
of nested array references. The important part is that it contains no |
266
|
|
|
|
|
|
|
blessed references, only strings, numbers, arrays and hashes (that is, references |
267
|
|
|
|
|
|
|
to those). |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
This makes it easy to serialize the tree, for distributed caching or |
270
|
|
|
|
|
|
|
persistant storage, if parsing time is critical. |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
See also C to continue the evaluation. |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
=cut |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
# Obsolete interface based on Parse::RecDescent |
277
|
|
|
|
|
|
|
sub parse_recd { |
278
|
0
|
|
|
0
|
0
|
0
|
my ($string) = @_; |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
# lazy build, why not |
281
|
0
|
0
|
|
|
|
0
|
if (not $parser) { |
282
|
0
|
|
|
|
|
0
|
require Parse::RecDescent; |
283
|
0
|
|
|
|
|
0
|
$parser = Parse::RecDescent->new($grammar); |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
# We lazy-build the parser in any case, only then do we "fast abort" |
286
|
0
|
0
|
0
|
|
|
0
|
return undef unless defined $string and $string ne ''; |
287
|
0
|
|
|
|
|
0
|
my $try_string = $string; |
288
|
0
|
|
|
|
|
0
|
my $error_string = ''; |
289
|
|
|
|
|
|
|
#local *STDERR = IO::String->new($error_string); |
290
|
0
|
|
|
|
|
0
|
local *STDERR; |
291
|
0
|
|
|
|
|
0
|
open(STDERR, '>', \$error_string); |
292
|
0
|
|
|
|
|
0
|
my $tree = $parser->expression(\$try_string); |
293
|
|
|
|
|
|
|
#croak "Error string: $error_string"; |
294
|
0
|
0
|
|
|
|
0
|
if ($error_string) { |
295
|
0
|
|
|
|
|
0
|
croak "Oh no: $error_string"; |
296
|
|
|
|
|
|
|
} |
297
|
0
|
0
|
|
|
|
0
|
if ($try_string =~ m{ \S }xms) { |
298
|
0
|
|
|
|
|
0
|
$try_string =~ s{\A \s+ | \s+ \z}{}xmsg; |
299
|
0
|
|
|
|
|
0
|
croak "Expression error: superfluous text $try_string in expression $string"; |
300
|
|
|
|
|
|
|
} |
301
|
0
|
|
|
|
|
0
|
return $parser->expression($string); |
302
|
|
|
|
|
|
|
} |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
# current home-grown version |
305
|
|
|
|
|
|
|
sub parse { |
306
|
539
|
|
|
539
|
1
|
26331
|
my ($string) = @_; |
307
|
539
|
50
|
|
|
|
1221
|
return undef unless defined $string; |
308
|
539
|
|
|
|
|
1366
|
$string =~ s{\A\s+}{}xms; $string =~ s{\s+\z}{}xms; |
|
539
|
|
|
|
|
1090
|
|
309
|
539
|
50
|
|
|
|
1097
|
return undef if $string eq ''; |
310
|
539
|
|
|
|
|
1014
|
my $expression = expression($string); |
311
|
525
|
100
|
|
|
|
1212
|
if ($string =~ m{ \G \s* \S}xms) { |
312
|
4
|
|
|
|
|
9
|
croak "Syntax error: Superfluous text " . _critisize($string); |
313
|
|
|
|
|
|
|
} |
314
|
521
|
|
|
|
|
1083
|
return $expression; |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
# Starting characters: |
318
|
|
|
|
|
|
|
# identifier: ID_Start |
319
|
|
|
|
|
|
|
# key: ID_Start |
320
|
|
|
|
|
|
|
# number: + - \d |
321
|
|
|
|
|
|
|
# integer: + - \d |
322
|
|
|
|
|
|
|
# funccall: ID_Start |
323
|
|
|
|
|
|
|
# key: ID_Start |
324
|
|
|
|
|
|
|
# string: " ' ` |
325
|
|
|
|
|
|
|
# lterm: ( ID_Start $ |
326
|
|
|
|
|
|
|
# rterm: ( ID_Start $ " ' ` \d |
327
|
|
|
|
|
|
|
# operand: " ' ` \d ( ID_Start $ |
328
|
|
|
|
|
|
|
# alternative: ! " ' ` \d ( ID_Start $ |
329
|
|
|
|
|
|
|
# expression: ! " ' ` \d ( ID_Start $ |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
# Helper for 'parse' |
332
|
|
|
|
|
|
|
sub expression { |
333
|
568
|
|
|
568
|
0
|
1253
|
my $alternative = alternative($_[0]); |
334
|
555
|
|
|
|
|
845
|
my @others = (); |
335
|
|
|
|
|
|
|
#$_[0] =~ m{\G\s*}gc; # fast forward |
336
|
555
|
|
|
|
|
1376
|
while ($_[0] =~ m{\G \s* ([?:]) \s* }xmsgc) { |
337
|
|
|
|
|
|
|
# another alternative |
338
|
33
|
|
|
|
|
50
|
my $operator = $1; |
339
|
33
|
|
|
|
|
69
|
push @others, ($operator, alternative($_[0])); |
340
|
|
|
|
|
|
|
} |
341
|
554
|
100
|
|
|
|
1543
|
return (@others) ? ['expression', $alternative, @others] : $alternative; |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
# Helper for 'parse' |
345
|
|
|
|
|
|
|
sub alternative { |
346
|
615
|
100
|
|
615
|
0
|
1487
|
if ($_[0] =~ m{\G \s* (!) \s*}xmsgc) { |
347
|
14
|
|
|
|
|
35
|
return ['not', alternative($_[0])]; |
348
|
|
|
|
|
|
|
} else { |
349
|
601
|
|
|
|
|
1219
|
return operand($_[0]); |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
# Helper for 'parse' |
354
|
|
|
|
|
|
|
sub operand { |
355
|
601
|
100
|
|
601
|
0
|
3276
|
if ($_[0] =~ m{\G \s* (["'`])}xms) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
356
|
86
|
|
|
|
|
219
|
return string($_[0], $1); |
357
|
|
|
|
|
|
|
} elsif ($_[0] =~ m{\G \s* [\d+-]}xms) { |
358
|
49
|
|
|
|
|
97
|
return number($_[0]); |
359
|
|
|
|
|
|
|
} elsif ($_[0] =~ m{\G \s* [([:alpha:]_\$]}xms) { |
360
|
463
|
|
|
|
|
994
|
my $lterm = lterm($_[0]); |
361
|
460
|
|
|
|
|
715
|
my @rterms = (); |
362
|
460
|
|
|
|
|
1234
|
while ($_[0] =~ m{\G \s* \. \s*}xmsgc) { |
363
|
57
|
|
|
|
|
179
|
push @rterms, rterm($_[0]); |
364
|
|
|
|
|
|
|
} |
365
|
455
|
100
|
|
|
|
1504
|
return @rterms ? ['dot', $lterm, @rterms] : $lterm; |
366
|
|
|
|
|
|
|
} else { |
367
|
3
|
|
|
|
|
10
|
croak q{Syntax error: Operand expected } . _critisize($_[0]); |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
} |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
# Helper for 'parse' |
372
|
|
|
|
|
|
|
sub lterm { |
373
|
474
|
100
|
|
474
|
0
|
2236
|
if ($_[0] =~ m{ \G \s* \( \s* }xmsgc) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
374
|
10
|
|
|
|
|
37
|
my $expression = expression($_[0]); |
375
|
10
|
100
|
|
|
|
41
|
if ($_[0] =~ m{ \G \s* \) \s* }xmsgc) { |
376
|
9
|
|
|
|
|
17
|
return $expression; |
377
|
|
|
|
|
|
|
} else { |
378
|
1
|
|
|
|
|
3
|
croak q{Syntax error: Unbalanced parentheses: missing a ')' } . _critisize($_[0]); |
379
|
|
|
|
|
|
|
} |
380
|
|
|
|
|
|
|
} elsif ($_[0] =~ m{ \G \s* \$ }xmsgc) { |
381
|
7
|
|
|
|
|
17
|
my $lterm = lterm($_[0]); |
382
|
7
|
|
|
|
|
19
|
return ['env', $lterm]; |
383
|
|
|
|
|
|
|
} elsif ($_[0] =~ m{\G \s* [[:alpha:]_] }xms) { |
384
|
|
|
|
|
|
|
# funccall or plain identifier |
385
|
456
|
|
|
|
|
1053
|
my $identifier = identifier($_[0]); |
386
|
456
|
100
|
|
|
|
1056
|
if ($_[0] =~ m{ \G \s* \( \s* }xmsgc) { |
387
|
|
|
|
|
|
|
# argument list, go for funccall |
388
|
|
|
|
|
|
|
#$identifier = $identifier->[1]; # just the name |
389
|
10
|
|
|
|
|
20
|
my @arguments = (); |
390
|
10
|
|
|
|
|
40
|
while ($_[0] =~ m{ \G (?= [^)] ) }xmsgc) { |
391
|
10
|
100
|
|
|
|
27
|
if (@arguments) { |
392
|
|
|
|
|
|
|
# need a ',' before the next argument if we have some already. |
393
|
|
|
|
|
|
|
# trailing ',' are a-ok. |
394
|
4
|
100
|
|
|
|
18
|
$_[0] =~ m{ \s* , [[:space:],]* }xmsgc |
395
|
|
|
|
|
|
|
or croak q{Syntax error: Need commas in argument list } . _critisize($_[0]); |
396
|
|
|
|
|
|
|
} |
397
|
9
|
|
|
|
|
26
|
push @arguments, expression($_[0]); |
398
|
|
|
|
|
|
|
} |
399
|
|
|
|
|
|
|
# trailing ',' are a-ok. |
400
|
9
|
100
|
|
|
|
42
|
if ($_[0] =~ m{ \G [[:space:],]* \) \s* }xmsgc) { |
401
|
8
|
|
|
|
|
27
|
return ['funccall', $identifier, @arguments]; |
402
|
|
|
|
|
|
|
} else { |
403
|
1
|
|
|
|
|
3
|
croak q{Syntax error: Unbalanced parentheses: missing a ')' } . _critisize($_[0]); |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
} else { |
406
|
446
|
|
|
|
|
3749
|
return $identifier; |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
} else { |
409
|
1
|
|
|
|
|
4
|
croak q{Syntax error: Term expected } . _critisize($_[0]); |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
} |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
# Helper for 'parse' |
414
|
|
|
|
|
|
|
sub rterm { |
415
|
|
|
|
|
|
|
# second verse: same as the first! |
416
|
57
|
100
|
|
57
|
0
|
479
|
if ($_[0] =~ m{ \G \s* \( \s* }xmsgc) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
417
|
4
|
|
|
|
|
9
|
my $expression = expression($_[0]); |
418
|
4
|
100
|
|
|
|
15
|
if ($_[0] =~ m{ \G \s* \) \s* }xmsgc) { |
419
|
3
|
|
|
|
|
8
|
return $expression; |
420
|
|
|
|
|
|
|
} else { |
421
|
1
|
|
|
|
|
4
|
croak q{Syntax error: Unbalanced parentheses: missing a ')' } . _critisize($_[0]); |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
} elsif ($_[0] =~ m{ \G \s* \$ }xmsgc) { |
424
|
|
|
|
|
|
|
# yes, inside an rterm, it's an lterm, but as a key |
425
|
4
|
|
|
|
|
11
|
my $lterm = lterm($_[0]); |
426
|
3
|
|
|
|
|
11
|
return $lterm; |
427
|
|
|
|
|
|
|
} elsif ($_[0] =~ m{\G \s* (?=["'`])}xmsgc) { |
428
|
0
|
|
|
|
|
0
|
return string($_[0], $1); |
429
|
|
|
|
|
|
|
} elsif ($_[0] =~ m{\G \s* (?=[\d+-])}xmsgc) { |
430
|
6
|
|
|
|
|
17
|
return integer($_[0]); |
431
|
|
|
|
|
|
|
} elsif ($_[0] =~ m{\G \s* [[:alpha:]_] }xms) { |
432
|
|
|
|
|
|
|
# methcall or plain key |
433
|
43
|
|
|
|
|
90
|
my $identifier = identifier($_[0]); |
434
|
43
|
|
|
|
|
89
|
$identifier = $identifier->[1]; # just the name, in any case |
435
|
43
|
100
|
|
|
|
164
|
if ($_[0] =~ m{ \G \s* \( \s* }xmsgc) { |
436
|
|
|
|
|
|
|
# argument list, go for methcall |
437
|
10
|
|
|
|
|
19
|
my @arguments = (); |
438
|
10
|
|
|
|
|
35
|
while ($_[0] =~ m{ \G (?= [^)] ) }xmsgc) { |
439
|
7
|
100
|
|
|
|
18
|
if (@arguments) { |
440
|
|
|
|
|
|
|
# need a ',' before the next argument if we have some already. |
441
|
|
|
|
|
|
|
# trailing ',' are a-ok. |
442
|
2
|
100
|
|
|
|
15
|
$_[0] =~ m{ \s* , [[:space:],]* }xmsgc |
443
|
|
|
|
|
|
|
or croak q{Syntax error: Need commas in argument list } . _critisize($_[0]); |
444
|
|
|
|
|
|
|
} |
445
|
6
|
|
|
|
|
16
|
push @arguments, expression($_[0]); |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
# trailing ',' are a-ok. |
448
|
9
|
50
|
|
|
|
34
|
if ($_[0] =~ m{ \G [[:space:],]* \) \s* }xmsgc) { |
449
|
9
|
|
|
|
|
45
|
return ['methcall', $identifier, @arguments]; |
450
|
|
|
|
|
|
|
} else { |
451
|
0
|
|
|
|
|
0
|
croak q{Syntax error: Unbalanced parentheses: missing a ')' near } . _critisize($_[0]); |
452
|
|
|
|
|
|
|
} |
453
|
|
|
|
|
|
|
} else { |
454
|
33
|
|
|
|
|
126
|
return $identifier; |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
} else { |
457
|
|
|
|
|
|
|
# this can probably not be reached |
458
|
0
|
|
|
|
|
0
|
croak q{Syntax error: Term expected } . _critisize($_[0]); |
459
|
|
|
|
|
|
|
} |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
# Helper for 'parse' |
463
|
|
|
|
|
|
|
sub string { |
464
|
86
|
|
|
86
|
0
|
100
|
my ($contents, $delim); |
465
|
86
|
|
|
|
|
167
|
$delim = $_[1]; |
466
|
86
|
|
|
|
|
134
|
given ($delim) { |
467
|
86
|
100
|
|
|
|
273
|
when (q{"}) { $_[0] =~ m{ \G \s* " ([^"]*) " \s* }xmsgc and $contents = $1; } |
|
64
|
|
|
|
|
548
|
|
468
|
22
|
50
|
|
|
|
33
|
when (q{'}) { $_[0] =~ m{ \G \s* ' ([^']*) ' \s* }xmsgc and $contents = $1; } |
|
1
|
|
|
|
|
14
|
|
469
|
21
|
50
|
|
|
|
60
|
when (q{`}) { $_[0] =~ m{ \G \s* ` ([^`]*) ` \s* }xmsgc and $contents = $1; } |
|
21
|
|
|
|
|
188
|
|
470
|
0
|
|
|
|
|
0
|
default { die "Internal error: string called with invalid delimiter $delim"; } |
|
0
|
|
|
|
|
0
|
|
471
|
|
|
|
|
|
|
} |
472
|
86
|
100
|
|
|
|
250
|
if (defined $contents) { |
473
|
85
|
|
|
|
|
246
|
return $contents; |
474
|
|
|
|
|
|
|
} else { |
475
|
1
|
|
|
|
|
4
|
croak qq{Syntax error: Missing string delimiter '$delim' } . _critisize($_[0]); |
476
|
|
|
|
|
|
|
} |
477
|
|
|
|
|
|
|
} |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
# Helper for 'parse' |
480
|
|
|
|
|
|
|
sub identifier { |
481
|
499
|
50
|
|
499
|
0
|
1796
|
if ($_[0] =~ m{ \G ( [[:alpha:]_] [[:alnum:]_]*) \s* }xmsgc) { |
482
|
499
|
|
|
|
|
1821
|
return [ 'env', $1 ]; |
483
|
|
|
|
|
|
|
} else { |
484
|
|
|
|
|
|
|
# this can probably never be reached |
485
|
0
|
|
|
|
|
0
|
croak q{Syntax error: Invalid identifier } . _critisize($_[0]); |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
} |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
# Helper for 'parse' |
490
|
|
|
|
|
|
|
sub number { |
491
|
|
|
|
|
|
|
# TODO: can we get this to commit after the period? |
492
|
49
|
100
|
|
49
|
0
|
184
|
if ($_[0] =~ m{ \G \s* ([+-]? \d+ (?:\.\d+)? ) \s* }xmscg) { |
493
|
47
|
|
|
|
|
208
|
return $1; |
494
|
|
|
|
|
|
|
} else { |
495
|
2
|
|
|
|
|
9
|
croak q{Syntax error: Invalid number } . _critisize($_[0]); |
496
|
|
|
|
|
|
|
} |
497
|
|
|
|
|
|
|
} |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
# Helper for 'parse' |
500
|
|
|
|
|
|
|
sub integer { |
501
|
6
|
100
|
|
6
|
0
|
24
|
if ($_[0] =~ m{ \G \s* ([+-]? \d+ ) \s* }xmscg) { |
502
|
4
|
|
|
|
|
19
|
return $1; |
503
|
|
|
|
|
|
|
} else { |
504
|
2
|
|
|
|
|
5
|
croak q{Syntax error: Invalid integer } . _critisize($_[0]); |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
} |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
# Helper function: report errors "from the point of parsing" |
509
|
|
|
|
|
|
|
# The entire expression is assumed to be short, and one of many, so the entire |
510
|
|
|
|
|
|
|
# expression is included in the diagnostics to help you find it. |
511
|
|
|
|
|
|
|
sub _critisize { |
512
|
18
|
|
100
|
18
|
|
250
|
return qq{near '} . substr($_[0], pos($_[0]) || 0, 10) . q{' in '} . $_[0] . q{'}; |
513
|
|
|
|
|
|
|
} |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
=head2 reduce |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
my $value = Positron::Expression::reduce($tree, $environment); |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
The companion of C, this function takes an abstract parse tree and |
521
|
|
|
|
|
|
|
returns a scalar value. Essentially, |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
my $tree = Positron::Expression::parse($string); |
524
|
|
|
|
|
|
|
my $value = Positron::Expression::reduce($tree, $environment); |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
is equivalent to |
527
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
my $value = Positron::Expression::evaluate($string, $environment); |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
=cut |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
sub reduce { |
533
|
0
|
|
|
0
|
1
|
0
|
my ($tree, $environment) = @_; |
534
|
0
|
|
|
|
|
0
|
return scalar(_evaluate($tree, $environment)); |
535
|
|
|
|
|
|
|
} |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
=head2 true |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
In Perl, empty lists and hashes count as false. The only way for C |
540
|
|
|
|
|
|
|
to contain lists and hashes is as array or hash references. However, these count as C |
541
|
|
|
|
|
|
|
in Perl, even if they reference an empty array or hash. |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
To aid decisions in templates, the function C returns a false value for references to |
544
|
|
|
|
|
|
|
empty arrays or hashes, and a true value for non-empty ones. |
545
|
|
|
|
|
|
|
Other values, such as plain scalars, blessed references, subroutine references or C, |
546
|
|
|
|
|
|
|
are returned verbatim. |
547
|
|
|
|
|
|
|
Their truth values are therefore up to Perl (a reference blessed into a package with an |
548
|
|
|
|
|
|
|
overloaded C method may still return false, for example). |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
=cut |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
sub true { |
553
|
82
|
|
|
82
|
1
|
125
|
my ($it) = @_; |
554
|
82
|
100
|
|
|
|
152
|
if (ref($it)) { |
555
|
11
|
100
|
|
|
|
38
|
if (ref($it) eq 'ARRAY') { |
|
|
50
|
|
|
|
|
|
556
|
7
|
|
|
|
|
31
|
return @$it; |
557
|
|
|
|
|
|
|
} elsif (ref($it) eq 'HASH') { |
558
|
4
|
|
|
|
|
23
|
return scalar(keys %$it); |
559
|
|
|
|
|
|
|
} else { |
560
|
0
|
|
|
|
|
0
|
return $it; |
561
|
|
|
|
|
|
|
} |
562
|
|
|
|
|
|
|
} else { |
563
|
71
|
|
|
|
|
224
|
return $it; |
564
|
|
|
|
|
|
|
} |
565
|
|
|
|
|
|
|
} |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
# Recursive helper version of _evaluate |
568
|
|
|
|
|
|
|
sub _evaluate { |
569
|
1100
|
|
|
1100
|
|
1864
|
my ($tree, $env, $obj) = @_; |
570
|
1100
|
100
|
|
|
|
2036
|
if (not ref($tree)) { |
571
|
578
|
|
|
|
|
1459
|
return $tree; |
572
|
|
|
|
|
|
|
} else { |
573
|
522
|
|
|
|
|
1115
|
my ($operand, @args) = @$tree; |
574
|
522
|
100
|
|
|
|
1470
|
if ($operand eq 'env') { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
575
|
433
|
|
|
|
|
1083
|
my $key = _evaluate($args[0], $env); |
576
|
433
|
|
|
|
|
1518
|
return $env->get($key); |
577
|
|
|
|
|
|
|
} elsif ($operand eq 'funccall') { |
578
|
8
|
|
|
|
|
13
|
my $func = shift @args; # probably [env] |
579
|
8
|
|
|
|
|
18
|
$func = _evaluate($func, $env); |
580
|
8
|
100
|
100
|
|
|
79
|
return undef unless $func and ref($func) eq 'CODE'; # skip arguments then, too |
581
|
4
|
|
|
|
|
13
|
@args = map _evaluate($_, $env), @args; |
582
|
4
|
|
|
|
|
15
|
return $func->(@args); |
583
|
|
|
|
|
|
|
} elsif ($operand eq 'methcall') { |
584
|
|
|
|
|
|
|
# On error, do not evaluate arguments !? |
585
|
|
|
|
|
|
|
# Needs $obj argument |
586
|
5
|
100
|
|
|
|
19
|
return undef unless $obj; |
587
|
4
|
|
|
|
|
8
|
my $func = shift @args; # probably literal |
588
|
4
|
|
|
|
|
10
|
$func = _evaluate($func, $env); |
589
|
4
|
50
|
|
|
|
11
|
return undef unless $func; |
590
|
4
|
|
|
|
|
12
|
@args = map _evaluate($_, $env), @args; |
591
|
4
|
100
|
100
|
|
|
55
|
if (blessed($obj) and $obj->can($func)) { |
|
|
100
|
100
|
|
|
|
|
592
|
|
|
|
|
|
|
# actual method call |
593
|
1
|
|
|
|
|
7
|
return ($obj->can($func))->($obj, @args); |
594
|
|
|
|
|
|
|
} elsif (ref($obj) eq 'HASH' and ref($obj->{$func}) eq 'CODE') { |
595
|
|
|
|
|
|
|
# subroutine inside hash, still ok |
596
|
1
|
|
|
|
|
8
|
return ($obj->{$func})->(@args); |
597
|
|
|
|
|
|
|
} else { |
598
|
|
|
|
|
|
|
# neither, abort |
599
|
2
|
|
|
|
|
9
|
return undef; |
600
|
|
|
|
|
|
|
} |
601
|
|
|
|
|
|
|
} elsif ($operand eq 'not') { |
602
|
12
|
|
|
|
|
25
|
my $what = _evaluate($args[0], $env); |
603
|
12
|
|
|
|
|
26
|
return ! true($what); |
604
|
|
|
|
|
|
|
} elsif ($operand eq 'expression') { |
605
|
17
|
|
|
|
|
42
|
my $left = _evaluate(shift @args, $env); |
606
|
17
|
|
|
|
|
44
|
while (@args) { |
607
|
26
|
|
|
|
|
33
|
my $op = shift @args; |
608
|
26
|
|
|
|
|
37
|
my $right = shift @args; |
609
|
26
|
100
|
|
|
|
45
|
if ($op eq '?') { |
610
|
|
|
|
|
|
|
# and |
611
|
11
|
100
|
|
|
|
24
|
if (true($left)) { |
612
|
6
|
|
|
|
|
16
|
$left = _evaluate($right, $env); |
613
|
|
|
|
|
|
|
} |
614
|
|
|
|
|
|
|
} else { |
615
|
|
|
|
|
|
|
# or |
616
|
15
|
100
|
|
|
|
31
|
if (!true($left)) { |
617
|
7
|
|
|
|
|
15
|
$left = _evaluate($right, $env); |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
} |
620
|
|
|
|
|
|
|
} |
621
|
17
|
|
|
|
|
107
|
return $left; |
622
|
|
|
|
|
|
|
} elsif ($operand eq 'dot') { |
623
|
47
|
|
|
|
|
100
|
my $left = _evaluate(shift @args, $env); |
624
|
47
|
|
|
|
|
188
|
while (@args) { |
625
|
49
|
100
|
|
|
|
227
|
if (blessed($left)) { |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
626
|
4
|
|
|
|
|
9
|
my $key = shift @args; |
627
|
4
|
100
|
66
|
|
|
31
|
if (ref($key) and ref($key) eq 'ARRAY' and $key->[0] eq 'methcall' ) { |
|
|
|
66
|
|
|
|
|
628
|
|
|
|
|
|
|
# Method, like 'funccall' but pass the object as extra parameter |
629
|
2
|
|
|
|
|
5
|
$left = _evaluate($key, $env, $left); |
630
|
|
|
|
|
|
|
} else { |
631
|
|
|
|
|
|
|
# Attribute or similar. |
632
|
|
|
|
|
|
|
# In Perl, still a method (without additional arguments) |
633
|
2
|
|
|
|
|
6
|
$key = _evaluate($key, $env); |
634
|
2
|
100
|
66
|
|
|
40
|
return undef unless defined($key) and $left->can($key); |
635
|
1
|
|
|
|
|
6
|
$left = ($left->can($key))->($left); |
636
|
|
|
|
|
|
|
} |
637
|
|
|
|
|
|
|
} elsif (ref($left) eq 'HASH') { |
638
|
27
|
|
|
|
|
46
|
my $key = shift @args; |
639
|
27
|
100
|
66
|
|
|
167
|
if (ref($key) and ref($key) eq 'ARRAY' and $key->[0] eq 'methcall' ) { |
|
|
|
100
|
|
|
|
|
640
|
|
|
|
|
|
|
# "Method", i.e. function lookup in hash |
641
|
2
|
|
|
|
|
6
|
$left = _evaluate($key, $env, $left); |
642
|
|
|
|
|
|
|
} else { |
643
|
|
|
|
|
|
|
# Regular hash lookup |
644
|
25
|
|
|
|
|
53
|
$key = _evaluate($key, $env); |
645
|
25
|
50
|
|
|
|
79
|
return undef unless defined($key); |
646
|
25
|
|
|
|
|
85
|
$left = $left->{$key}; |
647
|
|
|
|
|
|
|
} |
648
|
|
|
|
|
|
|
} elsif (ref($left) eq 'ARRAY') { |
649
|
9
|
|
|
|
|
21
|
my $key = _evaluate(shift @args, $env); |
650
|
9
|
100
|
|
|
|
58
|
return undef unless defined($key); |
651
|
33
|
|
|
33
|
|
652
|
no warnings 'numeric'; |
|
33
|
|
|
|
|
77
|
|
|
33
|
|
|
|
|
7376
|
|
652
|
8
|
|
|
|
|
31
|
$left = $left->[ int($key) ]; |
653
|
|
|
|
|
|
|
} else { |
654
|
9
|
|
|
|
|
22
|
_warn("Asked to subselect a scalar"); |
655
|
9
|
|
|
|
|
68
|
return undef; |
656
|
|
|
|
|
|
|
} |
657
|
|
|
|
|
|
|
} |
658
|
36
|
|
|
|
|
258
|
return $left; |
659
|
|
|
|
|
|
|
} |
660
|
|
|
|
|
|
|
} |
661
|
|
|
|
|
|
|
} |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
# Helper function, if diagnostics are requested, outputs the "less than ideal" |
664
|
|
|
|
|
|
|
# condition the user may find interesting. |
665
|
|
|
|
|
|
|
sub _warn { |
666
|
9
|
|
|
9
|
|
12
|
my ($message) = @_; |
667
|
|
|
|
|
|
|
# TODO: warn the $message if debugging is requested |
668
|
9
|
|
|
|
|
12
|
return; |
669
|
|
|
|
|
|
|
} |
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
1; # End of Positron::Expression |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
__END__ |