pbot/Plugins/Plang.pm

242 lines
8.0 KiB
Perl
Raw Normal View History

2020-07-09 08:21:54 +02:00
# File: Plang.pm
#
2020-09-21 08:19:47 +02:00
# Purpose: Scripting language for creating advanced PBot factoids
2020-07-09 08:21:54 +02:00
# and interacting with various internal PBot APIs.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
package Plugins::Plang;
use parent 'Plugins::Plugin';
2021-06-19 06:23:34 +02:00
use PBot::Imports;
2020-07-09 08:21:54 +02:00
use Getopt::Long qw(GetOptionsFromArray);
sub initialize {
my ($self, %conf) = @_;
# load Plang modules
2020-07-09 08:21:54 +02:00
my $path = $self->{pbot}->{registry}->get_value('general', 'plang_dir') // 'Plang';
unshift @INC, "$path/lib" if not grep { $_ eq "$path/lib" } @INC;
2020-07-09 08:21:54 +02:00
# require all the Plang .pm modules for Module::Refresh
require "$path/Interpreter.pm";
require "$path/AstInterpreter.pm";
require "$path/Grammar.pm"; # FIXME: Module::Refresh doesn't like this one
require "$path/Parser.pm";
require "$path/Lexer.pm";
2020-08-22 07:28:23 +02:00
require "$path/Types.pm";
2020-09-21 08:19:47 +02:00
require "$path/Validator.pm";
2020-09-21 08:19:47 +02:00
# 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') // '';
2020-07-12 06:15:54 +02:00
# create our Plang interpreter object
2020-07-09 08:21:54 +02:00
$self->{plang} = Plang::Interpreter->new(embedded => 1, debug => $debug);
# register some PBot-specific built-in functions
2020-09-21 08:19:47 +02:00
$self->{plang}->add_builtin_function('factset', # function name
# parameters are [[type, param1 name, default arg], [type, param2 name, default arg], ...]
2020-08-22 07:28:23 +02:00
[
2020-09-21 08:19:47 +02:00
[['TYPE', 'String'], 'channel', undef], # param 1
[['TYPE', 'String'], 'keyword', undef], # param 2
[['TYPE', 'String'], 'text', undef], # param 3
2020-08-22 07:28:23 +02:00
],
2020-09-21 08:19:47 +02:00
['TYPE', 'String'], # return type
sub { $self->plang_builtin_factset(@_) }, # builtin subref
sub { $self->plang_validate_builtin_factset(@_) } # type-checker subref
2020-08-25 05:46:34 +02:00
);
2020-07-13 02:36:32 +02:00
$self->{plang}->add_builtin_function('factget',
2020-08-22 07:28:23 +02:00
[
[['TYPE', 'String'], 'channel', undef],
[['TYPE', 'String'], 'keyword', undef],
[['TYPE', 'String'], 'meta', [['TYPE', 'String'], 'action']]
],
['TYPEUNION', [['TYPE', 'String'], ['TYPE', 'Null']]],
2020-08-25 05:46:34 +02:00
sub { $self->plang_builtin_factget(@_) },
sub { $self->plang_validate_builtin_factget(@_) },
);
2020-07-13 02:36:32 +02:00
$self->{plang}->add_builtin_function('factappend',
2020-08-22 07:28:23 +02:00
[
[['TYPE', 'String'], 'channel', undef],
[['TYPE', 'String'], 'keyword', undef],
[['TYPE', 'String'], 'text', undef]
],
['TYPE', 'String'],
2020-08-25 05:46:34 +02:00
sub { $self->plang_builtin_factappend(@_) },
sub { $self->plang_validate_builtin_factappend(@_) },
);
2020-07-23 19:51:45 +02:00
$self->{plang}->add_builtin_function('userget',
2020-08-22 07:28:23 +02:00
[
[['TYPE', 'String'], 'name', undef]
],
2020-09-19 22:15:03 +02:00
['TYPEUNION', [['TYPE', 'Map'], ['TYPE', 'Null']]],
2020-08-25 05:46:34 +02:00
sub { $self->plang_builtin_userget(@_) },
sub { $self->plang_validate_builtin_userget(@_) },
);
2020-07-12 06:15:54 +02:00
# override the built-in `print` function to send to our output buffer instead
$self->{plang}->add_builtin_function('print',
2020-08-22 07:28:23 +02:00
[
[['TYPE', 'Any'], 'expr', undef],
[['TYPE', 'String'], 'end', [['TYPE', 'String'], "\n"]]
],
['TYPE', 'Null'],
2020-08-25 05:46:34 +02:00
sub { $self->plang_builtin_print(@_) },
sub { $self->plang_validate_builtin_print(@_) },
);
2020-07-12 06:15:54 +02:00
# register the `plang` command
2020-07-09 08:21:54 +02:00
$self->{pbot}->{commands}->register(sub { $self->cmd_plang(@_) }, "plang", 0);
# register the `plangrepl` command (does not reset environment)
$self->{pbot}->{commands}->register(sub { $self->cmd_plangrepl(@_) }, "plangrepl", 0);
2020-07-09 08:21:54 +02:00
}
2020-09-21 08:19:47 +02:00
# runs when plugin is unloaded
2020-07-09 08:21:54 +02:00
sub unload {
my $self = shift;
$self->{pbot}->{commands}->unregister("plang");
}
sub cmd_plang {
my ($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";
2020-07-12 06:15:54 +02:00
return $usage if not length $context->{arguments};
$self->{output} = ""; # collect output of the embedded Plang program
2020-07-26 10:02:36 +02:00
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);
}
2020-07-26 10:02:36 +02:00
};
if ($@) {
$self->{output} .= $@;
}
2020-07-12 06:15:54 +02:00
2020-07-13 02:36:32 +02:00
# return the output
2020-07-12 06:15:54 +02:00
return length $self->{output} ? $self->{output} : "No output.";
}
sub cmd_plangrepl {
my ($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
2020-07-26 10:02:36 +02:00
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 ($@) {
$self->{output} .= $@;
}
# return the output
return length $self->{output} ? $self->{output} : "No output.";
}
# overridden `print` built-in
2020-07-23 19:51:45 +02:00
sub plang_builtin_print {
2020-07-22 01:41:45 +02:00
my ($self, $plang, $context, $name, $arguments) = @_;
my ($expr, $end) = ($plang->output_value($arguments->[0]), $arguments->[1]->[1]);
$self->{output} .= "$expr$end";
2020-08-22 07:28:23 +02:00
return [['TYPE', 'Null'], undef];
}
2020-08-25 05:46:34 +02:00
sub plang_validate_builtin_print {
return [['TYPE', 'Null'], undef];
}
2020-07-12 06:15:54 +02:00
# our custom PBot built-in functions for Plang
sub is_locked {
my ($self, $channel, $keyword) = @_;
return $self->{pbot}->{factoids}->get_meta($channel, $keyword, 'locked');
}
2020-07-23 19:51:45 +02:00
sub plang_builtin_factget {
2020-07-22 01:41:45 +02:00
my ($self, $plang, $context, $name, $arguments) = @_;
2020-07-23 19:51:45 +02:00
my ($channel, $keyword, $meta) = ($arguments->[0]->[1], $arguments->[1]->[1], $arguments->[2]->[1]);
my $result = $self->{pbot}->{factoids}->get_meta($channel, $keyword, $meta);
if (defined $result) {
return [['TYPE', 'String'], $result];
} else {
return [['TYPE', 'Null'], undef];
}
2020-07-12 06:15:54 +02:00
}
2020-08-25 05:46:34 +02:00
sub plang_validate_builtin_factget {
return [['TYPE', 'String'], ""];
}
2020-07-23 19:51:45 +02:00
sub plang_builtin_factset {
2020-07-22 01:41:45 +02:00
my ($self, $plang, $context, $name, $arguments) = @_;
2020-07-23 19:51:45 +02:00
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);
2020-07-23 19:51:45 +02:00
$self->{pbot}->{factoids}->add_factoid('text', $channel, 'Plang', $keyword, $text);
2020-08-22 07:28:23 +02:00
return [['TYPE', 'String'], $text];
2020-07-12 06:15:54 +02:00
}
2020-07-09 08:21:54 +02:00
2020-08-25 05:46:34 +02:00
sub plang_validate_builtin_factset {
return [['TYPE', 'String'], ""];
}
2020-07-23 19:51:45 +02:00
sub plang_builtin_factappend {
2020-07-22 01:41:45 +02:00
my ($self, $plang, $context, $name, $arguments) = @_;
2020-07-23 19:51:45 +02:00
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);
2020-07-23 19:51:45 +02:00
my $action = $self->{pbot}->{factoids}->get_meta($channel, $keyword, 'action');
$action = "" if not defined $action;
$action .= $text;
2020-07-23 19:51:45 +02:00
$self->{pbot}->{factoids}->add_factoid('text', $channel, 'Plang', $keyword, $action);
2020-08-22 07:28:23 +02:00
return [['TYPE', 'String'], $action];
2020-07-09 08:21:54 +02:00
}
2020-08-25 05:46:34 +02:00
sub plang_validate_builtin_factappend {
return [['TYPE', 'String'], ""];
}
2020-07-23 19:51:45 +02:00
sub plang_builtin_userget {
my ($self, $plang, $context, $name, $arguments) = @_;
my ($username) = ($arguments->[0], $arguments->[1]);
my $user = $self->{pbot}->{users}->{users}->get_data($username->[1]);
if (not defined $user) {
2020-08-22 07:28:23 +02:00
return [['TYPE', 'Null'], undef];
2020-07-23 19:51:45 +02:00
}
my $hash = { %$user };
$hash->{password} = '<private>';
while (my ($key, $value) = each %$hash) {
2020-08-22 07:28:23 +02:00
$hash->{$key} = [['TYPE', 'String'], $value];
2020-07-23 19:51:45 +02:00
}
2020-08-22 07:28:23 +02:00
return [['TYPE', 'Map'], $hash];
2020-07-23 19:51:45 +02:00
}
2020-08-25 05:46:34 +02:00
sub plang_validate_builtin_userget {
return [['TYPE', 'Map'], {}];
}
2020-07-09 08:21:54 +02:00
1;