| 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__ |