-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathPlang.pm
220 lines (176 loc) · 7.75 KB
/
Plang.pm
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
# File: Plang.pm
#
# Purpose: Scripting language for creating advanced PBot factoids
# and interacting with various internal PBot APIs.
# SPDX-FileCopyrightText: 2020-2023 Pragmatic Software <pragma78@gmail.com>
# SPDX-License-Identifier: MIT
package PBot::Plugin::Plang;
use parent 'PBot::Plugin::Base';
use PBot::Imports;
sub initialize($self, %conf) {
# load Plang module
my $path = $self->{pbot}->{registry}->get_value('general', 'plang_dir') // 'Plang';
unshift @INC, "$path/lib" if not grep { $_ eq "$path/lib" } @INC;
require "$path/Interpreter.pm";
# regset plang.debug <AST,VARS,FUNCS,etc>
# Plugin must be reloaded for this value to take effect.
my $debug = $self->{pbot}->{registry}->get_value('plang', 'debug') // '';
# create our Plang interpreter object
$self->{plang} = Plang::Interpreter->new(embedded => 1, debug => $debug);
# register some PBot-specific built-in functions
$self->{plang}->add_builtin_function('factset', # function name
# parameters are [[type, param1 name, default arg], [type, param2 name, default arg], ...]
[
[['TYPE', 'String'], 'channel', undef], # param 1
[['TYPE', 'String'], 'keyword', undef], # param 2
[['TYPE', 'String'], 'text', undef], # param 3
],
['TYPE', 'String'], # return type
sub { $self->plang_builtin_factset(@_) }, # builtin subref
sub { $self->plang_validate_builtin_factset(@_) } # type-checker subref
);
$self->{plang}->add_builtin_function('factget',
[
[['TYPE', 'String'], 'channel', undef],
[['TYPE', 'String'], 'keyword', undef],
[['TYPE', 'String'], 'meta', [['TYPE', 'String'], 'action']]
],
['TYPEUNION', [['TYPE', 'String'], ['TYPE', 'Null']]],
sub { $self->plang_builtin_factget(@_) },
sub { $self->plang_validate_builtin_factget(@_) },
);
$self->{plang}->add_builtin_function('factappend',
[
[['TYPE', 'String'], 'channel', undef],
[['TYPE', 'String'], 'keyword', undef],
[['TYPE', 'String'], 'text', undef]
],
['TYPE', 'String'],
sub { $self->plang_builtin_factappend(@_) },
sub { $self->plang_validate_builtin_factappend(@_) },
);
$self->{plang}->add_builtin_function('userget',
[
[['TYPE', 'String'], 'name', undef]
],
['TYPEUNION', [['TYPE', 'Map'], ['TYPE', 'Null']]],
sub { $self->plang_builtin_userget(@_) },
sub { $self->plang_validate_builtin_userget(@_) },
);
# override the built-in `print` function to send to our output buffer instead
$self->{plang}->add_builtin_function('print',
[
[['TYPE', 'Any'], 'expr', undef],
[['TYPE', 'String'], 'end', [['TYPE', 'String'], "\n"]]
],
['TYPE', 'Null'],
sub { $self->plang_builtin_print(@_) },
sub { $self->plang_validate_builtin_print(@_) },
);
# register the `plang` command
$self->{pbot}->{commands}->register(sub { $self->cmd_plang(@_) }, 'plang');
# register the `plangrepl` command (does not reset environment)
$self->{pbot}->{commands}->register(sub { $self->cmd_plangrepl(@_) }, 'plangrepl');
}
# runs when plugin is unloaded
sub unload($self) {
$self->{pbot}->{commands}->unregister('plang');
$self->{pbot}->{commands}->unregister('plangrepl');
delete $INC{"Plang/Interpreter.pm"};
}
sub cmd_plang($self, $context) {
my $usage = "Usage: plang <code>; see https://github.com/pragma-/Plang and https://github.com/pragma-/pbot/blob/master/doc/Plugins/Plang.md";
return $usage if not length $context->{arguments};
$self->{output} = ""; # collect output of the embedded Plang program
eval {
my $result = $self->{plang}->interpret_string($context->{arguments});
# check to see if we need to append final result to output
if (defined $result->[1]) {
$self->{output} .= $self->{plang}->{interpreter}->output_value({}, $result, literal => 1);
}
};
if (my $exception = $@) {
$self->{output} .= $exception;
}
# return the output
return length $self->{output} ? $self->{output} : "No output.";
}
sub cmd_plangrepl($self, $context) {
my $usage = "Usage: plangrepl <code>; see https://github.com/pragma-/Plang and https://github.com/pragma-/pbot/blob/master/doc/Plugins/Plang.md";
return $usage if not length $context->{arguments};
$self->{output} = ""; # collect output of the embedded Plang program
eval {
my $result = $self->{plang}->interpret_string($context->{arguments}, repl => 1);
# check to see if we need to append final result to output
$self->{output} .= $self->{plang}->{interpreter}->output_value({}, $result, repl => 1) if defined $result->[1];
};
if (my $exception = $@) {
$exception = $self->{plang}->{interpreter}->output_value({}, $exception);
$self->{output} .= "Run-time error: unhandled exception: $exception";
}
# return the output
return length $self->{output} ? $self->{output} : "No output.";
}
# overridden `print` built-in
sub plang_builtin_print($self, $plang, $scope, $name, $arguments) {
my ($expr, $end) = ($plang->output_value($scope, $arguments->[0]), $arguments->[1]->[1]);
$self->{output} .= "$expr$end";
return [['TYPE', 'Null'], undef];
}
sub plang_validate_builtin_print {
return [['TYPE', 'Null'], undef];
}
# our custom PBot built-in functions for Plang
sub is_locked($self, $channel, $keyword) {
return $self->{pbot}->{factoids}->{data}->get_meta($channel, $keyword, 'locked');
}
sub plang_builtin_factget($self, $plang, $scope, $name, $arguments) {
my ($channel, $keyword, $meta) = ($arguments->[0]->[1], $arguments->[1]->[1], $arguments->[2]->[1]);
my $result = $self->{pbot}->{factoids}->{data}->get_meta($channel, $keyword, $meta);
if (defined $result) {
return [['TYPE', 'String'], $result];
} else {
return [['TYPE', 'Null'], undef];
}
}
sub plang_validate_builtin_factget {
return [['TYPE', 'String'], ""];
}
sub plang_builtin_factset($self, $plang, $scope, $name, $arguments) {
my ($channel, $keyword, $text) = ($arguments->[0]->[1], $arguments->[1]->[1], $arguments->[2]->[1]);
die "Factoid $channel.$keyword is locked. Cannot set.\n" if $self->is_locked($channel, $keyword);
$self->{pbot}->{factoids}->{data}->add('text', $channel, 'Plang', $keyword, $text);
return [['TYPE', 'String'], $text];
}
sub plang_validate_builtin_factset {
return [['TYPE', 'String'], ""];
}
sub plang_builtin_factappend($self, $plang, $scope, $name, $arguments) {
my ($channel, $keyword, $text) = ($arguments->[0]->[1], $arguments->[1]->[1], $arguments->[2]->[1]);
die "Factoid $channel.$keyword is locked. Cannot append.\n" if $self->is_locked($channel, $keyword);
my $action = $self->{pbot}->{factoids}->{data}->get_meta($channel, $keyword, 'action');
$action = "" if not defined $action;
$action .= $text;
$self->{pbot}->{factoids}->{data}->add('text', $channel, 'Plang', $keyword, $action);
return [['TYPE', 'String'], $action];
}
sub plang_validate_builtin_factappend {
return [['TYPE', 'String'], ""];
}
sub plang_builtin_userget($self, $plang, $scope, $name, $arguments) {
my ($username) = ($arguments->[0], $arguments->[1]);
my $user = $self->{pbot}->{users}->{storage}->get_data($username->[1]);
if (not defined $user) {
return [['TYPE', 'Null'], undef];
}
my $hash = { %$user };
$hash->{password} = '<private>';
while (my ($key, $value) = each %$hash) {
$hash->{$key} = [['TYPE', 'String'], $value];
}
return [['TYPE', 'Map'], $hash];
}
sub plang_validate_builtin_userget {
return [['TYPE', 'Map'], {}];
}
1;