RiveScript-v2.0.2/000755 000765 000024 00000000000 12645246360 015770 5ustar00npetherbridgestaff000000 000000 RiveScript-v2.0.2/bin/000755 000765 000024 00000000000 12645246360 016540 5ustar00npetherbridgestaff000000 000000 RiveScript-v2.0.2/Changes000644 000765 000024 00000026731 12645246215 017273 0ustar00npetherbridgestaff000000 000000 Revision history for Perl extension RiveScript. 2.0.2 Jan 11 2016 - Fix typo in changelog. 2.0.1 Jan 11 2016 - When formatting a user's message, consolidate multiple consecutive spaces down to one. - Apply downstream Debian patch that fixes a typo in RiveScript::WD. 2.0.0 Dec 28 2015 - Switch from old-style floating point version number notation to dotted decimal notation. This bumps the version number to `2.0.0` because the next dotted-decimal version greater than `1.42` (`v1.420.0`) is `v1.421.0` and I don't like having that many digits in the version number. This release is simply a version update; no breaking API changes were introduced. 1.42 Nov 20 2015 - Add configurable `unicode_punctuation` attribute to strip out punctuation when running in UTF-8 mode. 1.40 Oct 10 2015 - Fix the regexp used when matching optionals so that the triggers don't match on inputs where they shouldn't. (RiveScript-JS issue #46) 1.38 Jul 21 2015 - New algorithm for handling variable tags (, , , , ,
, and ) that allows for iterative nesting of these tags (for example, > will work now). - Fix trigger sorting so that triggers with matching word counts are sorted by length descending. - Add support for `! local concat` option to override concatenation mode (file scoped) - Bugfix where Perl object macros set via `setSubroutine()` failed to load because they were missing a programming language internally. 1.36 Nov 26 2014 - Relicense under the MIT License. - Strip punctuation from the bot's responses in UTF-8 mode to support compatibility with %Previous. - Bugfix in deparse(): If you had two matching triggers, one with a %Previous and one without, you'd lose the data for one of them in the output. 1.34 Feb 26 2014 - Update README.md to include module documentation for github. - Fixes to META.yml 1.32 Feb 24 2014 - Maintenance release to fix some errors per the CPANTS. - Add license to Makefile.PL - Make Makefile.PL not executable - Make version numbers consistent 1.30 Nov 25 2013 - Added "TCP Mode" to the `rivescript` command so that it can listen on a socket instead of using standard input and output. - Added a "--data" option to the `rivescript` command for providing JSON input as a command line argument instead of standard input. - Added experimental UTF-8 support. - Bugfix: don't use hacky ROT13-encoded placeholders for message substitutions... use a null character method instead. ;) - Make .rive the default preferred file extension for RiveScript documents instead of .rs (which conflicts with the Rust programming language). Backwards compatibility remains to load .rs files, though. 1.28 Aug 14 2012 - FIXED: Typos in RiveScript::WD (Bug #77618) - Added constants RS_ERR_MATCH and RS_ERR_REPLY. 1.26 May 29 2012 - Added EXE_FILES to Makefile.PL so the rivescript utility installs correctly. 1.24 May 15 2012 - Fixed: having a single-line, multiline comment, e.g. /* ... */ - Fixed: you can use and in triggers now, instead of only - and - - When a trigger consists of nothing but multiple wildcard symbols, sort the trigger by length, this way you can have '* * * * *' type triggers still work correctly (each tag would get one word, with the final collecting the remainder). - Backported new feature from Python lib: you can now use and to SET variables (eg. ). The {!...} tag is deprecated. - New feature: deparse() will return a Perl data structure representing all of the RiveScript code parsed by the module so far. This way you can build a user interface for editing replies without requiring a user to edit the code directly. - New method: write() will use deparse() to write a RiveScript document using all of the in-memory triggers/responses/etc. - Cleaned up the POD documentation, put POD code along side the Perl functions it documents, removed useless bloat from the docs. - POD documentation now only shows recent changes. For older changes, see the "CHANGES" file in the distribution. - Removed the `rsup` script from the distribution (it upgrades RiveScript 1.x code to 2.x; there probably isn't any 1.x code out in the wild anyway). 1.22 Sep 22 2011 - Cleaned up the documentation of RiveScript; moved the JavaScript object example to a separate document in the `docs' directory. - Obsoleted the `rsdemo` command that used to ship with the distribution. In its place is `rivescript`, which can also be used non-interactively so that a third party, non-Perl application could still make use of RiveScript. - RiveScript.pm is now dual licensed. It uses the GPLv2 for open source applications as before, but you can contact the author for details if you want to use RiveScript.pm in a closed source commercial application. 1.20 Jul 30 2009 - Added automatic syntax checking when parsing RiveScript code. Also added 'strict mode' - if true (default), a syntax error is a fatal error. If false, a syntax error is a warning, and RiveScript aborts processing the file any further. - Changed the behavior of "inherits" a bit: a new type has been added called "includes" which does what the old "inherits" does (mixes the trigger list of both topics together into the same pool). The new "inherits" option though causes the trigger list from the source topic to be higher in matching priority than the trigger list of the inherited topic. - Moving to a new versioning scheme: development releases will have odd version numbers, stable (CPAN) versions will have even numbers. - Fixed the Eliza brain; in many places a was used when there was only one star in the trigger. Fixes lots of issues with Eliza. - Bugfix: recursion depth limits weren't taken into account when the {@} tag was responsible for a redirection. Fixed. - Bugfix: there was a problem in the regular expression that counts real words while sorting triggers, so that triggers with *'s in them weren't sorted properly and would therefore cause matching issues. - Bugfix: when the internal _getreply is called because of a recursive redirection (@, {@}), the %previous tags should be ignored. They weren't. since "lastreply" is always the same no matter how deeply recursive _getreply is going, it could result in some infinite recursion in rare cases. Fixed. - Bugfix: using a reserved name as a global variable wasn't working properly and would crash RiveScript. Fixed. 1.19 Apr 12 2009 - Added support for defining custom object handlers for non-Perl programming languages. - All the methods like setGlobal, setVariable, setUservar, etc. will now accept undef or "" as values - this will delete the variables. - There are no reserved global variable names anymore. Now, if a variable name would conflict with a reserved name, it is put into a "protected" space elsewhere in the object. Still take note of which names are reserved though. 1.18 Dec 31 2008 - Added support for topics to inherit their triggers from other topics. e.g. > topic alpha inherits beta - Fixed some bugs related to !array with ^continue's, and expanded its functionality therein. - Updated the getUservars() function to optionally be able to get just a specific variable from the user's data. Added getUservar() as a grammatically correct alias to this new functionality. - Added the functions freezeUservars() and thawUservars() to back up and restore a user's variables. - Added the function lastMatch(), which returns the text of the trigger that matched the user's last message. - The # command for RiveScript comments has been deprecated in revision 7 of the RiveScript Working Draft. The Perl module will now emit warnings each time the # comments are processed. - Modified a couple of triggers in the default Eliza brain to improve matching issues therein. - +Triggers can contain user tags now. - Updated the RiveScript Working Draft. 1.17 Sep 15 2008 - Updated the rsdemo tool to be more flexible as a general debugging and developing program. Also updated rsdemo and rsup to include POD documentation that can be read via `perldoc`. - Added a global variable $RiveScript::basedir which is the the path to your Perl lib/RiveScript folder. This is used by `rsdemo` as its default location to search for replies. - Tweak: Triggers of only # and _ can exist now alongside the old single-wildcard trigger of *. - Bugfix: The lookahead code would throw Perl warnings if the following line had a single space in it, but was otherwise empty. - Bugfix: Inline comment removing has been fixed. - Bugfix: In conditionals, any blank side of the equality will get a default value of "undefined". This way you can use a matching array inside an optional and check if that tag is defined. + i am wearing a [(@colors)] shirt * ne undefined => Why are you wearing a shirt? - What color is it? - Updated the RiveScript Working Draft. 1.16 Jul 22 2008 - New options to the constructor: 'verbose' and 'debugfile'. See the new() constructor for details. - Added new wildcard variants: * matches anything (previous behavior) # matches only numbers _ matches only letters So you can have a trigger like "+ i am # years old" and "+ i am * years old", with the latter trigger telling them to try that again and use a NUMBER this time. :) - Bugfix: when there were multiple +trigger's that had a common %previous, there was no internal sort buffer for those +trigger's. As a result, matching wasn't very efficient. Added the method sortThatTriggers() to fix this. - Bugfix: tags weren't being processed in @Redirects when they really should've! - Bugfix: The ^Continue lookahead code wouldn't work if the next line began with a tab. Fixed! - Updated the RiveScript Working Draft. 1.15 Jun 19 2008 - Person substitutions support multiple-word patterns now. - Message substititons also support multiple-word patterns now. - Added syntax tracking, so Deep Recursion errors can give you a filename and line number where the problem occurred. - Added a handler for detecting when a user was put into an empty topic. - Rearranged tag priority. - Updated the RiveScript Working Draft. 1.14 Apr 2 2008 - Bugfix: If a BEGIN/request trigger didn't exist, RiveScript would not fetch any replies for the client's message. Fixed. - Bugfix: Tags weren't being re-processed for the text of the BEGIN statement, so i.e. {uppercase}{ok}{/uppercase} wasn't working as expected. Fixed. - Bugfix: RiveScript wasn't parsing out inline comments properly. - Rearranged tag priorities. - Optimization: When substituting s in, an added bit of code will insert '' (nothing) if the variable is undefined. This prevents Perl warnings that occurred frequently with the Eliza brain. - Updated the RiveScript Working Draft. 1.13 Mar 18 2008 - Included an "rsup" script for upgrading old RiveScript code. - Attempted to fix the package for CPAN (1.12 was a broken upload). - Bugfix: didn't have higher priority than , so i.e. > wouldn't work as expected. Fixed. 1.12 Mar 16 2008 - Initial beta release for a RiveScript 2.00 parser. RiveScript-v2.0.2/CONTRIBUTING.md000644 000765 000024 00000002517 12640307464 020224 0ustar00npetherbridgestaff000000 000000 # Contributing Interested in contributing to RiveScript? Great! First, check the general contributing guidelines for RiveScript and its primary implementations found at - in particular, understand the goals and scope of the RiveScript language and the style guide for the Perl implementation. # Quick Start Fork, then clone the repo: ```bash $ git clone git@github.com:your-username/rivescript-perl.git ``` Install the Perl module `JSON` if you intend to use the built-in interactive `rivescript` script for testing. Make your code changes and test them by using the built-in interactive mode of RiveScript, e.g. by running: ```bash $ perl -Ilib bin/rivescript ``` Make sure the unit tests still pass. Run `perl Makefile.PL` to generate the Makefile and then run `make && make test` Push to your fork and [submit a pull request](https://github.com/kirsle/rivescript-perl/compare/). At this point you're waiting on me. I'm usually pretty quick to comment on pull requests (within a few days) and I may suggest some changes or improvements or alternatives. Some things that will increase the chance that your pull request is accepted: * Follow the style guide at * Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). RiveScript-v2.0.2/docs/000755 000765 000024 00000000000 12645246360 016720 5ustar00npetherbridgestaff000000 000000 RiveScript-v2.0.2/INSTALL.md000644 000765 000024 00000002556 12640307464 017426 0ustar00npetherbridgestaff000000 000000 # INSTALLATION To install this module type the following: ```bash perl Makefile.PL make make test make install ``` # RPM BUILD To build a RedHat package file for installing RiveScript, use the rpmbuild Perl script provided in the subversion repository. Usage: perl rpmbuild This results in a slightly different RPM than what you'd get via cpan2rpm or cpan2dist... along with installing the module in its proper place in your Perl libs, it will also install the `rivescript` utility from the `bin/` folder into your `/usr/bin` directory. # BUILDING RIVESCRIPT.EXE The `bin/rivescript` script can be compiled into a stand-alone executable file for distribution and inclusion in other projects. The module `PAR::Packer` can produce this executable. Here is an example on how to create it, from the root folder of the project: ```bash $ pp -o rivescript.exe -I lib -M MIME::Base64 -M utf8_heavy.pl \ -M unicore/Heavy.pl bin/rivescript $ rivescript.exe lib/RiveScript/demo ``` The inclusion of `MIME::Base64` is to support the example Perl object in `demo/perl.rive` and would otherwise be optional. The `utf8_heavy.pl` and `unicore/Heavy.pl` may be needed if you otherwise were getting errors in `utf8.pm` after entering a question for the bot. # DEPENDENCIES Requires: * [JSON](http://search.cpan.org/perldoc?JSON) Recommends: * [Clone](http://search.cpan.org/perldoc?Clone) RiveScript-v2.0.2/lib/000755 000765 000024 00000000000 12645246360 016536 5ustar00npetherbridgestaff000000 000000 RiveScript-v2.0.2/LICENSE000644 000765 000024 00000002074 12640307464 016776 0ustar00npetherbridgestaff000000 000000 The MIT License (MIT) Copyright (c) 2015 Noah Petherbridge Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. RiveScript-v2.0.2/Makefile.PL000644 000765 000024 00000001216 12640310265 017731 0ustar00npetherbridgestaff000000 000000 use ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile( NAME => 'RiveScript', VERSION_FROM => 'lib/RiveScript.pm', # finds $VERSION PREREQ_PM => { version => 0.77, JSON => 0, }, # e.g., Module::Name => 1.1 EXE_FILES => [ 'bin/rivescript' ], LICENSE => 'mit', MIN_PERL_VERSION => '5.8.0', ($] >= 5.005 ? ## Add these new keywords supported since 5.005 (ABSTRACT_FROM => 'lib/RiveScript.pm', # retrieve abstract from module AUTHOR => 'Noah Petherbridge ') : ()), ); RiveScript-v2.0.2/MANIFEST000644 000765 000024 00000001305 12645246360 017120 0ustar00npetherbridgestaff000000 000000 bin/rivescript Changes CONTRIBUTING.md docs/bin-rivescript.html docs/JavaScript.html docs/JavaScript.pod docs/RiveScript-WD.html docs/rivescript.css docs/RiveScript.html docs/Tutorial.html docs/Tutorial.pod INSTALL.md lib/RiveScript.pm lib/RiveScript/demo/admin.rive lib/RiveScript/demo/begin.rive lib/RiveScript/demo/clients.rive lib/RiveScript/demo/eliza.rive lib/RiveScript/demo/myself.rive lib/RiveScript/demo/perl.rive lib/RiveScript/demo/rpg.rive lib/RiveScript/WD.pm LICENSE Makefile.PL MANIFEST META.yml Module meta-data (added by MakeMaker) perl-RiveScript.spec.tt README.md rpmbuild t/RiveScript.t testsuite.rive META.json Module JSON meta-data (added by MakeMaker) RiveScript-v2.0.2/META.json000644 000765 000024 00000001655 12645246360 017420 0ustar00npetherbridgestaff000000 000000 { "abstract" : "Rendering Intelligence Very Easily", "author" : [ "Noah Petherbridge " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 6.66, CPAN::Meta::Converter version 2.133380", "license" : [ "mit" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "RiveScript", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "JSON" : "0", "perl" : "5.008000", "version" : "0.77" } } }, "release_status" : "stable", "version" : "v2.0.2" } RiveScript-v2.0.2/META.yml000644 000765 000024 00000001013 12645246360 017234 0ustar00npetherbridgestaff000000 000000 --- abstract: 'Rendering Intelligence Very Easily' author: - 'Noah Petherbridge ' build_requires: ExtUtils::MakeMaker: 0 configure_requires: ExtUtils::MakeMaker: 0 dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 6.66, CPAN::Meta::Converter version 2.133380' license: mit meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: RiveScript no_index: directory: - t - inc requires: JSON: 0 perl: 5.008000 version: 0.77 version: v2.0.2 RiveScript-v2.0.2/perl-RiveScript.spec.tt000644 000765 000024 00000001304 12640307464 022320 0ustar00npetherbridgestaff000000 000000 Name: perl-RiveScript Version: [% version %] Release: [% build %] Summary: Chatterbot Brain Development Toolkit Group: Development/Libraries License: MIT URL: http://search.cpan.org/dist/RiveScript/ Provides: perl(RiveScript) = %{version} Provides: perl(RiveScript::WD) = [% wd_version %] Requires: perl(JSON) %description RiveScript is a simple trigger/response language primarily used for the creation of chatting robots. It's designed to have an easy-to-learn syntax but provide a lot of power and flexibility. For more information, visit http://www.rivescript.com/ %pre %post %preun %postun %files %defattr(-, root, root, -) [% FOREACH file IN files %][% file %] [% END %] # vim:ft=spec RiveScript-v2.0.2/README.md000644 000765 000024 00000047562 12640311356 017256 0ustar00npetherbridgestaff000000 000000 # NAME RiveScript - Rendering Intelligence Very Easily # SYNOPSIS ```perl use RiveScript; # Create a new RiveScript interpreter. my $rs = new RiveScript; # Load a directory of replies. $rs->loadDirectory ("./replies"); # Load another file. $rs->loadFile ("./more_replies.rive"); # Stream in some RiveScript code. $rs->stream (q~ + hello bot - Hello, human. ~); # Sort all the loaded replies. $rs->sortReplies; # Chat with the bot. while (1) { print "You> "; chomp (my $msg = ); my $reply = $rs->reply ('localuser',$msg); print "Bot> $reply\n"; } ``` # DESCRIPTION RiveScript is a simple trigger/response language primarily used for the creation of chatting robots. It's designed to have an easy-to-learn syntax but provide a lot of power and flexibility. For more information, visit http://www.rivescript.com/ # METHODS ## GENERAL - RiveScript new (hash %ARGS) Create a new instance of a RiveScript interpreter. The instance will become its own "chatterbot," with its own set of responses and user variables. You can pass in any global variables here. The two standard variables are: debug - Turns on debug mode (a LOT of information will be printed to the terminal!). Default is 0 (disabled). verbose - When debug mode is on, all debug output will be printed to the terminal if 'verbose' is also true. The default value is 1. debugfile - Optional: paired with debug mode, all debug output is also written to this file name. Since debug mode prints such a large amount of data, it is often more practical to have the output go to an external file for later review. Default is '' (no file). utf8 - Enable UTF-8 support for the RiveScript code. See the section on UTF-8 support for details. depth - Determines the recursion depth limit when following a trail of replies that point to other replies. Default is 50. strict - If this has a true value, any syntax errors detected while parsing a RiveScript document will result in a fatal error. Set it to a false value and only a warning will result. Default is 1. It's recommended that if you set any other global variables that you do so by calling `setGlobal` or defining it within the RiveScript code. This will avoid the possibility of overriding reserved globals. Currently, these variable names are reserved: topics sorted sortsthat sortedthat thats arrays subs person client bot objects syntax sortlist reserved debugopts frozen globals handlers objlangs Note: the options "verbose" and "debugfile", when provided, are noted and then deleted from the root object space, so that if your RiveScript code uses variables by the same values it won't conflict with the values that you passed here. ## LOADING AND PARSING - bool loadDirectory (string $PATH\[, string @EXTS\]) Load a directory full of RiveScript documents. `$PATH` must be a path to a directory. `@EXTS` is optionally an array containing file extensions, including the dot. By default `@EXTS` is `('.rive', '.rs')`. Returns true on success, false on failure. - bool loadFile (string $PATH) Load a single RiveScript document. `$PATH` should be the path to a valid RiveScript file. Returns true on success; false otherwise. - bool stream (arrayref $CODE) Stream RiveScript code directly into the module. This is for providing RS code from within the Perl script instead of from an external file. Returns true on success. - string checkSyntax (char $COMMAND, string $LINE) Check the syntax of a line of RiveScript code. This is called automatically for each line parsed by the module. `$COMMAND` is the command part of the line, and `$LINE` is the rest of the line following the command (and excluding inline comments). If there is no problem with the line, this method returns `undef`. Otherwise it returns the text of the syntax error. If `strict` mode is enabled in the constructor (which is on by default), a syntax error will result in a fatal error. If it's not enabled, the error is only sent via `warn` and the file currently being processed is aborted. - void sortReplies () Call this method after loading replies to create an internal sort buffer. This is necessary for trigger matching purposes. If you fail to call this method yourself, RiveScript will call it once when you request a reply. However, it will complain loudly about it. - data deparse () Translate the in-memory representation of the loaded RiveScript documents into a Perl data structure. This would be useful for developing a user interface to facilitate editing of RiveScript replies without having to edit the RiveScript code manually. The data structure returned from this will follow this format: ```perl { "begin" => { # Contains begin block and config settings "global" => { # ! global (global variables) "depth" => 50, ... }, "var" => { # ! var (bot variables) "name" => "Aiden", ... }, "sub" => { # ! sub (substitutions) "what's" => "what is", ... }, "person" => { # ! person (person substitutions) "you" => "I", ... }, "array" => { # ! array (arrays) "colors" => [ "red", "green", "light green", "blue" ], ... }, "triggers" => { # triggers in your > begin block "request" => { # trigger "+ request" "reply" => [ "{ok}" ], }, }, }, "topic" => { # all topics under here "random" => { # topic names (default is random) "hello bot" => { # trigger labels "reply" => [ "Hello human!" ], # Array of -Replies "redirect" => "hello", # Only if @Redirect exists "previous" => "hello human", # Only if %Previous exists "condition" => [ # Only if *Conditions exist " != undefined => Hello !", ... ], }, }, }, "include" => { # topic inclusion "alpha" => [ "beta", "gamma" ], # > topic alpha includes beta gamma }, "inherit" => { # topic inheritence "alpha" => [ "delta" ], # > topic alpha inherits delta } } ``` Note that inline object macros can't be deparsed this way. This is probably for the best (for security, etc). The global variables "debug" and "depth" are only provided if the values differ from the defaults (true and 50, respectively). - void write (glob $fh || string $file\[, data $deparsed\]) Write the currently parsed RiveScript data into a RiveScript file. This uses `deparse()` to dump a representation of the loaded data and writes it to the destination file. Pass either a filehandle or a file name. If you provide `$deparsed`, it should be a data structure matching the format of `deparse()`. This way you can deparse your RiveScript brain, add/edit replies and then pass in the new version to this method to save the changes back to disk. Otherwise, `deparse()` will be called to get the current snapshot of the brain. ## CONFIGURATION - bool setHandler (string $LANGUAGE => code $CODEREF, ...) Define some code to handle objects of a particular programming language. If the coderef is `undef`, it will delete the handler. The code receives the variables `$rs, $action, $name,` and `$data`. These variables are described here: $rs = Reference to Perl RiveScript object. $action = "load" during the parsing phase when an >object is found. "call" when provoked via a tag for a reply $name = The name of the object. $data = The source of the object during the parsing phase, or an array reference of arguments when provoked via a tag. There is a default handler set up that handles Perl objects. If you want to block Perl objects from being loaded, you can just set it to be undef, and its handler will be deleted and Perl objects will be skipped over: ```perl $rs->setHandler (perl => undef); ``` The rationale behind this "pluggable" object interface is that it makes RiveScript more flexible given certain environments. For instance, if you use RiveScript on the web where the user chats with your bot using CGI, you might define a handler so that JavaScript objects can be loaded and called. Perl itself can't execute JavaScript, but the user's web browser can. See the JavaScript example in the `docs` directory in this distribution. - bool setSubroutine (string $NAME, code $CODEREF) Manually create a RiveScript object (a dynamic bit of Perl code that can be provoked in a RiveScript response). `$NAME` should be a single-word, alphanumeric string. `$CODEREF` should be a pointer to a subroutine or an anonymous sub. - bool setGlobal (hash %DATA) Set one or more global variables, in hash form, where the keys are the variable names and the values are their value. This subroutine will make sure that you don't override any reserved global variables, and warn if that happens. This is equivalent to `! global` in RiveScript code. To delete a global, set its value to `undef` or "``". This is true for variables, substitutions, person, and uservars. - bool setVariable (hash %DATA) Set one or more bot variables (things that describe your bot's personality). This is equivalent to `! var` in RiveScript code. - bool setSubstitution (hash %DATA) Set one or more substitution patterns. The keys should be the original word, and the value should be the word to substitute with it. ```perl $rs->setSubstitution ( q{what's} => 'what is', q{what're} => 'what are', ); ``` This is equivalent to `! sub` in RiveScript code. - bool setPerson (hash %DATA) Set a person substitution. This is equivalent to `! person` in RiveScript code. - bool setUservar (string $USER, hash %DATA) Set a variable for a user. `$USER` should be their User ID, and `%DATA` is a hash containing variable/value pairs. This is like `` for a specific user. - string getUservar (string $USER, string $VAR) This is an alias for getUservars, and is here because it makes more grammatical sense. - data getUservars (\[string $USER\]\[, string $VAR\]) Get all the variables about a user. If a username is provided, returns a hash __reference__ containing that user's information. Else, a hash reference of all the users and their information is returned. You can optionally pass a second argument, `$VAR`, to get a specific variable that belongs to the user. For instance, `getUservars ("soandso", "age")`. This is like `` for a specific user or for all users. - bool clearUservars (\[string $USER\]) Clears all variables about `$USER`. If no `$USER` is provided, clears all variables about all users. - bool freezeUservars (string $USER) Freeze the current state of variables for user `$USER`. This will back up the user's current state (their variables and reply history). This won't statically prevent the user's state from changing; it merely saves its current state. Then use thawUservars() to revert back to this previous state. - bool thawUservars (string $USER\[, hash %OPTIONS\]) If the variables for `$USER` were previously frozen, this method will restore them to the state they were in when they were last frozen. It will then delete the stored cache by default. The following options are accepted as an additional hash of parameters (these options are mutually exclusive and you shouldn't use both of them at the same time. If you do, "discard" will win.): discard: Don't restore the user's state from the frozen copy, just delete the frozen copy. keep: Keep the frozen copy even after restoring the user's state. With this you can repeatedly thawUservars on the same user to revert their state without having to keep freezing them again. On the next freeze, the last frozen state will be replaced with the new current state. Examples: ```perl # Delete the frozen cache but don't modify the user's variables. $rs->thawUservars ("soandso", discard => 1); # Restore the user's state from cache, but don't delete the cache. $rs->thawUservars ("soandso", keep => 1); ``` - string lastMatch (string $USER) After fetching a reply for user `$USER`, the `lastMatch` method will return the raw text of the trigger that the user has matched with their reply. This function may return undef in the event that the user __did not__ match any trigger at all (likely the last reply was "`ERR: No Reply Matched`" as well). - string currentUser () Get the user ID of the current user chatting with the bot. This is mostly useful inside of a Perl object macro in RiveScript to get the user ID of the person who invoked the object macro (e.g., to get/set variables for them using the `$rs` instance). This will return `undef` if used outside the context of a reply (the value is unset at the end of the `reply()` method). ## INTERACTION - string reply (string $USER, string $MESSAGE) Fetch a response to `$MESSAGE` from user `$USER`. RiveScript will take care of lowercasing, running substitutions, and removing punctuation from the message. Returns a response from the RiveScript brain. # RIVESCRIPT This interpreter tries its best to follow RiveScript standards. Currently it supports RiveScript 2.0 documents. A current copy of the RiveScript working draft is included with this package: see [RiveScript::WD](http://search.cpan.org/perldoc?RiveScript::WD). # UTF-8 SUPPORT RiveScript supports Unicode but it is not enabled by default. Enable it by passing a true value for the `utf8` option in the constructor, or by using the `--utf8` argument to the `rivescript` application. In UTF-8 mode, most characters in a user's message are left intact, except for certain metacharacters like backslashes and common punctuation characters like `/[.,!?;:]/`. If you want to override the punctuation regexp, you can provide a new one by assigning the \`unicode\_punctuation\` attribute of the bot object after initialization. Example: ```perl my $bot = new RiveScript(utf8 => 1); $bot->{unicode_punctuation} = qr/[.,!?;:]/; ``` # CONSTANTS This module can export some constants. use RiveScript qw(:standard); These constants include: - RS_ERR_MATCH This is the reply text given when no trigger has matched the message. It equals "`ERR: No Reply Matched`". ```perl if ($reply eq RS_ERR_MATCH) { $reply = "I couldn't find a good reply for you!"; } ``` - RS_ERR_REPLY This is the reply text given when a trigger _was_ matched, but no reply was given from it (for example, the trigger only had conditionals and all of them were false, with no default replies to fall back on). It equals "`ERR: No Reply Found`". ```perl if ($reply eq RS_ERR_REPLY) { $reply = "I don't know what to say about that!"; } ``` # SEE ALSO [RiveScript::WD](http://search.cpan.org/perldoc?RiveScript::WD) - A current snapshot of the Working Draft that defines the standards of RiveScript. [http://www.rivescript.com/](http://www.rivescript.com/) - The official homepage of RiveScript. # CHANGES 2.0.0 Dec 28 2015 - Switch from old-style floating point version number notation to dotted decimal notation. This bumps the version number to `2.0.0` because the next dotted-decimal version greater than `1.42` (`v1.420.0`) is `v1.421.0` and I don't like having that many digits in the version number. This release is simply a version update; no breaking API changes were introduced. 1.42 Nov 20 2015 - Add configurable `unicode_punctuation` attribute to strip out punctuation when running in UTF-8 mode. 1.40 Oct 10 2015 - Fix the regexp used when matching optionals so that the triggers don't match on inputs where they shouldn't. (RiveScript-JS issue #46) 1.38 Jul 21 2015 - New algorithm for handling variable tags (, , , , ,
, and ) that allows for iterative nesting of these tags (for example, > will work now). - Fix trigger sorting so that triggers with matching word counts are sorted by length descending. - Add support for `! local concat` option to override concatenation mode (file scoped) - Bugfix where Perl object macros set via `setSubroutine()` failed to load because they were missing a programming language internally. 1.36 Nov 26 2014 - Relicense under the MIT License. - Strip punctuation from the bot's responses in UTF-8 mode to support compatibility with %Previous. - Bugfix in deparse(): If you had two matching triggers, one with a %Previous and one without, you'd lose the data for one of them in the output. 1.34 Feb 26 2014 - Update README.md to include module documentation for github. - Fixes to META.yml 1.32 Feb 24 2014 - Maintenance release to fix some errors per the CPANTS. - Add license to Makefile.PL - Make Makefile.PL not executable - Make version numbers consistent 1.30 Nov 25 2013 - Added "TCP Mode" to the `rivescript` command so that it can listen on a socket instead of using standard input and output. - Added a "--data" option to the `rivescript` command for providing JSON input as a command line argument instead of standard input. - Added experimental UTF-8 support. - Bugfix: don't use hacky ROT13-encoded placeholders for message substitutions... use a null character method instead. ;) - Make .rive the default preferred file extension for RiveScript documents instead of .rs (which conflicts with the Rust programming language). Backwards compatibility remains to load .rs files, though. See the `Changes` file for older change history. # AUTHOR Noah Petherbridge, http://www.kirsle.net # KEYWORDS bot, chatbot, chatterbot, chatter bot, reply, replies, script, aiml, alpha # COPYRIGHT AND LICENSE The MIT License (MIT) Copyright (c) 2015 Noah Petherbridge Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. RiveScript-v2.0.2/rpmbuild000755 000765 000024 00000004640 12640307464 017536 0ustar00npetherbridgestaff000000 000000 #!/usr/bin/perl -w # Build an RPM. Usage: # rpmbuild.pl [lib path] use strict; use warnings; use File::Copy; use Template; use lib "./lib"; use RiveScript; # So we get its version no. use RiveScript::WD; # Use the pwd command to get the current working directory. my $pwd = `pwd`; chomp($pwd); # Build number = today's date. my @time = localtime(); my $build = join("", sprintf("%04d", $time[5] + 1900), sprintf("%02d", $time[4] + 1), sprintf("%02d", $time[3]), ); print "Preparing build root...\n"; makedir("./build", "./build/usr"); command("perl Makefile.PL PREFIX=build/usr"); command("make"); command("make test"); command("make install"); print "Copying utils from bin/...\n"; makedir("./build/usr/bin"); copy("./bin/rivescript", "./build/usr/bin/"); print "\n"; print "Preparing Template Toolkit...\n"; my $tt = Template->new ({ RELATIVE => 1, PRE_CHOMP => 0, POST_CHOMP => 0, }); my $vars = { version => $RiveScript::VERSION, build => $build, wd_version => $RiveScript::WD::VERSION, files => [], }; print "Making file list from buildroot...\n"; my @flist = &crawl("./build"); foreach my $file (@flist) { $file =~ s/^\.\/build//i; if ($file =~ /rpmbuild\.pl$/ || $file =~ /perllocal\.pod$/ || $file =~ /\.packlist$/) { unlink("./build/$file"); next; } my $attr = "0644"; if (-d $file || $file =~ /^\/usr\/bin/i) { $attr = "0755"; } push (@{$vars->{files}}, "%attr($attr,root,root) $file"); } print "Creating RPM spec file...\n"; my $output; eval { $tt->process("./perl-RiveScript.spec.tt", $vars, \$output) or die $@; }; if ($@) { die $@; } open (SPEC, ">perl-RiveScript.spec"); print SPEC $output; close (SPEC); print "Building RPM...\n"; command("rpmbuild --target=noarch --buildroot=$pwd/build -ba perl-RiveScript.spec"); print "Cleaning up...\n"; command("make clean"); command("rm -rf build/"); unlink("./perl-RiveScript.spec"); sub command { my $cmd = shift; print "\$ $cmd\n"; system($cmd); } sub crawl { my $dir = shift; my @files = (); opendir (DIR, $dir); foreach my $file (readdir(DIR)) { next if $file eq "."; next if $file eq ".."; if (-d "$dir/$file") { push (@files, &crawl("$dir/$file")); } else { push (@files, "$dir/$file"); } } return @files; } sub makedir { my @dirs = @_; foreach my $d (@dirs) { print "mkdir $d\n"; mkdir($d) unless -d $d; } } sub prompt { my $q = shift; print "$q "; chomp (my $answer = ); return $answer; } RiveScript-v2.0.2/t/000755 000765 000024 00000000000 12645246360 016233 5ustar00npetherbridgestaff000000 000000 RiveScript-v2.0.2/testsuite.rive000644 000765 000024 00000045317 12640307464 020720 0ustar00npetherbridgestaff000000 000000 /* RiveScript 2 Test Suite -- Designed to demonstrate all the functionality that RiveScript 2 is supposed to support. */ /****************************************************************************** * "begin.rs" Command Testing * ******************************************************************************/ > begin + request - {ok} < begin // Bot Variables ! var name = RiveScript Test Bot ! var age = 9000 ! var gender = androgynous ! var location = Cyberspace ! var phone = 555-1234 ! var email = test@mydomain.com // Substitutions ! sub + = plus ! sub - = minus ! sub / = divided ! sub * = times ! sub i'm = i am ! sub i'd = i would ! sub i've = i have ! sub i'll = i will ! sub don't = do not ! sub isn't = is not ! sub you'd = you would ! sub you're = you are ! sub you've = you have ! sub you'll = you will ! sub he'd = he would ! sub he's = he is ! sub he'll = he will ! sub she'd = she would ! sub she's = she is ! sub she'll = she will ! sub they'd = they would ! sub they're = they are ! sub they've = they have ! sub they'll = they will ! sub we'd = we would ! sub we're = we are ! sub we've = we have ! sub we'll = we will ! sub whats = what is ! sub what's = what is ! sub what're = what are ! sub what've = what have ! sub what'll = what will ! sub can't = can not ! sub whos = who is ! sub who's = who is ! sub who'd = who would ! sub who'll = who will ! sub don't = do not ! sub didn't = did not ! sub it's = it is ! sub could've = could have ! sub couldn't = could not ! sub should've = should have ! sub shouldn't = should not ! sub would've = would have ! sub wouldn't = would not ! sub when's = when is ! sub when're = when are ! sub when'd = when did ! sub y = why ! sub u = you ! sub ur = your ! sub r = are ! sub im = i am ! sub wat = what ! sub wats = what is ! sub ohh = oh ! sub becuse = because ! sub becasue = because ! sub becuase = because ! sub practise = practice ! sub its a = it is a ! sub fav = favorite ! sub fave = favorite ! sub iam = i am ! sub realy = really ! sub iamusing = i am using ! sub amleaving = am leaving ! sub yuo = you ! sub youre = you are ! sub didnt = did not ! sub ain't = is not ! sub aint = is not ! sub wanna = want to ! sub brb = be right back ! sub bbl = be back later ! sub gtg = got to go ! sub g2g = got to go // Person substitutions ! person i am = you are ! person you are = I am ! person i'm = you're ! person you're = I'm ! person my = your ! person your = my ! person you = I ! person i = you // Arrays ! array colors = red green blue cyan yellow magenta white orange brown black ^ gray grey fuchsia maroon burgundy lime navy aqua gold silver copper bronze ^ light red|light green|light blue|light cyan|light yellow|light magenta ! array be = is are was were /****************************************************************************** * Basic Trigger Testing * ******************************************************************************/ /* Atomic Reply ------------ Human says: hello bot Expected reply: Hello human. */ + hello bot - Hello human. /* Atomic Reply ------------ Human says: what is your name Expected reply: You can call me RiveScript Test Bot. */ + what is your name - You can call me . /* Wildcards --------- Human says: my favorite thing in the world is programming Expected reply: Why do you like programming so much? */ + my favorite thing in the world is * - Why do you like so much? /* Wildcards --------- Human says: John told me to say hello Expected reply: Why would john have told you to say hello? */ + * told me to say * - Why would have told you to say ? /* Wildcards --------- Human says: I think the sky is orange. Expected reply: Do you think the sky is orange a lot? */ + i think * - Do you think a lot? /* Wildcards --------- Human says: I am twenty years old Expected reply: Tell me that as a number instead of spelled out like "twenty" Extra Notes: When multiple triggers exist that are identical except for their wildcard character, the order of priorities are that _ is always first, # is second, and * last. So in this code and the following one, the "i am # years old" should match if the wildcard is a number and the "i am * years old" should only match otherwise. */ + i am * years old - Tell me that as a number instead of spelled out like "". /* Wildcards --------- Human says: I am 20 years old Expected reply: I will remember that you are 20 years old. Extra Notes: This reply should also set the var "age" to 20 for this user. */ + i am # years old - >I will remember that you are years old. /* Alternations ------------ Human says: What is your home phone number? Expected reply: You can call me at my home number, 555-1234. Human says: What is your office phone number? Expected reply: You can call me at my office number, 555-1234. Human says: What is your work phone number? Expected reply: You can call me at my work number, 555-1234. Human says: What is your cell phone number? Expected reply: You can call me at my cell number, 555-1234. */ + what is your (home|office|work|cell) phone number - You can call me at my number, . /* Alternations ------------ Human says: Are you okay? Are you alright? You okay? You alright? Expected reply: I'm fine, thanks for asking. */ + (are you|you) (okay|alright) - I'm fine, thanks for asking. /* Optionals --------- Human says: How can I contact you? Can I contact you? Expected reply: You can have my phone number: 555-1234. */ + [how] can i contact you - You can have my phone number: . /* Optionals --------- Human says: Do you have an email address? You have an email address? Do you have an email? You have an email? Do you have email? You have email? Expected reply: You can e-mail me at test@mydomain.com. */ + [do] you have [an] email [address] - You can e-mail me at . /* Optionals --------- Human says: Tell me your phone number Tell me your number Tell me your home phone number Tell me your home number Tell me your office phone number Tell me your office number Tell me your work phone number Tell me your work number Tell me your cell phone number Tell me your cell number Expected reply: My phone number is 555-1234. */ + tell me your [home|office|work|cell] [phone] number - My phone number is . /* Arrays ------ Human says: What color is my blue shirt? Expected reply: Your shirt is blue, silly. Human says: What color is my light red shirt? Expected reply: Your shirt is light red, silly. Human says: What color is my black shirt? Expected reply: Your shirt is black, silly. */ + what color is my (@colors) shirt - Your shirt is , silly. /* Arrays ------ Human says: What color was George Washington's white horse? Expected reply: George Washingtons horse was white. */ + what color was * (@colors) * - was . /* Arrays ------ Human says: I have a yellow sports car Expected reply: Why did you choose that color for a sports car? */ + i have a @colors * - Why did you choose that color for a ? /* Priority Triggers ----------------- Human says: I have a black davenport Expected reply: That's a word that's not used much anymore. Extra notes: This would normally match the trigger above, but this one has a high priority and matches first, even though the trigger above has more words and is a more specific match. */ + {weight=100}* davenport - That's a word that's not used much anymore. /****************************************************************************** * Basic Reply Testing * ******************************************************************************/ /* Atomic Response --------------- Human says: how are you Expected reply: I'm great. */ + how are you - I'm great. /* Random Response --------------- Human says: hello hi hey Expected reply: Hey there! Hello! Hi! */ + (hello|hi|hey) - Hey there! - Hello! - Hi! /* Random Response --------------- Human says: my name is Casey Expected reply: Nice to meet you, Casey. Hi, Casey, my name is RiveScript Test Bot. Casey, nice to meet you. Extra notes: This would also set the var name=Casey for the user. */ + my name is * - >Nice to meet you, . - >Hi, , my name is . - >, nice to meet you. /* Weighted Random Response ------------------------ Human says: Tell me a secret Expected reply: I won't tell you a secret. You can't handle a secret. Okay, here's a secret... nope, just kidding. Actually, I just don't have any secrets. */ + tell me a secret - I won't tell you a secret.{weight=20} - You can't handle a secret.{weight=20} - Okay, here's a secret... nope, just kidding.{weight=5} - Actually, I just don't have any secrets. /****************************************************************************** * Command Testing * ******************************************************************************/ /* %Previous --------- Human says: Knock, knock. Expected reply: Who's there? Human says: Banana. Expected reply: Banana who? Human says: Knock, knock. Expected reply: Who's there? Human says: Banana. Expected reply: Banana who? Human says: Knock, knock. Expected reply: Who's there? Human says: Orange. Expected reply: Orange who? Human says: Orange you glad I didn't say banana? Expected reply: Haha! "Orange you glad I didn't say banana"! :D */ + knock knock - Who's there? + * % who is there - > who? + * - Haha! "{sentence} {/sentence}"! :D /* ^Continue --------- Human says: Tell me a poem Expected reply: Little Miss Muffit sat on her tuffet in a nonchalant sort of way. With her forefield around her, the Spider, the bounder, Is not in the picture today. */ + tell me a poem - Little Miss Muffit sat on her tuffet\n ^ in a nonchalant sort of way.\n ^ With her forcefield around her,\n ^ the Spider, the bounder,\n ^ Is not in the picture today. /* @Redirect --------- Human says: Who are you? Expected reply: You can call me RiveScript Test Bot. */ + who are you @ what is your name /* @Redirect --------- Human says: Test recursion Expected reply: ERR: Deep Recursion Detected! */ + test recursion @ test more recursion + test more recursion @ test recursion /* Conditionals ------------ Human says: What am I old enough to do? Expected reply: You never told me how old you are. You're too young to do much of anything. You're over 18 so you can gamble. You're over 21 so you can drink. */ + what am i old enough to do * == undefined => You never told me how old you are. * >= 21 => You're over 21 so you can drink. * >= 18 => You're over 18 so you can gamble. * < 18 => You're too young to do much of anything. - This reply shouldn't happen. /* Conditionals ------------ Human says: Am I 18 years old? Expected reply: I don't know how old you are. You're not 18, no. Yes, you are. */ + am i 18 years old * == undefined => I don't know how old you are. * != 18 => You're not 18, no. - Yes, you are. /* Conditionals ------------ Human says: Count. Expected reply: Let's start with 1. Human says: Count. Expected reply: I've added 1 to the count. Human says: Count. Expected reply: I've added 5 now. Human says: Count. Expected reply: Subtracted 2. Human says: Count. Expected reply: Now I've doubled that. Human says: Count. Expected reply: Subtracted 2 from that now. Human says: Count. Expected reply: Divided that by 2. Human says: Count. Expected reply: Subtracted 1. Human says: Count. Expected reply: Now I've added 3. Human says: Count. Expected reply: Added 3 again. Human says: Count. Expected reply: We're done. Do you know what number I stopped at? Human says: 9 Expected reply: You're right, I stopped at the number 9. :) */ + count * == undefined => Let's start with 1. * == 0 => Let's start again with 1. * == 1 => I've added 1 to the count. * == 2 => I've added 5 now. * == 3 => Now I've added 3. * == 4 => Subtracted 1. * == 5 => Now I've doubled that. * == 6 => Added 3 again. * == 7 => Subtracted 2. * == 8 =>
Divided that by 2. * == 9 => We're done. Do you know what number I ^ \sstopped at? * == 10 => Subtracted 2 from that now. + (9|nine) % * do you know what number i stopped at - You're right, I stopped at the number 9. :) /****************************************************************************** * Object Macro Testing (Perl Only) * ******************************************************************************/ /* Encoding Object --------------- Human says: Encode something in MD5. Expected reply: "something" in MD5 is: 437b930db84b8079c2dd804a71936b5f Human says: Encode something in Base64. Expected reply: "something" in Base64 is: c29tZXRoaW5n */ > object encode perl my ($rs,$method,@args) = @_; my $msg = join(" ",@args); use Digest::MD5 qw(md5_hex); use MIME::Base64 qw(encode_base64); if ($method eq "md5") { return md5_hex($msg); } else { return encode_base64($msg); } < object + encode * in md5 - "" in MD5 is: encode md5 + encode * in base64 - "" in Base64 is: encode base64 > object testing javascript var w = screen.width; var h = screen.height; var dim = w + "x" + h; return dim; < object + test javascript - Testing javascript... testing. ! global topics = hello world + test global - Testing reserved global: topics=; users=; client=; reserved=. /****************************************************************************** * Topic Testing * ******************************************************************************/ /* Temporarily ignoring abusive users ---------------------------------- Human says: insert swear word here Expected reply: Omg you're mean! I'm not talking to you until you apologize. Human says: (anything) Expected reply: Not until you apologize. Say you're sorry. Apologize for being so mean. Human says: sorry Expected reply: Okay, I'll forgive you. */ + insert swear word here - Omg you're mean! I'm not talking to you until you apologize.{topic=apology} > topic apology + * - Not until you apologize. - Say you're sorry. - Apologize for being so mean. + [*] (sorry|apologize) [*] - Okay, I'll forgive you.{topic=random} < topic /* Topic Inheritence (simple roleplaying game) ------------------------------------------- Human says: enter the dungeon Expected reply: (drops you into a mini game. Skim the code below to figure it out) */ + enter the dungeon - {topic=room1}You've entered the dungeon. {@look} > topic global + help{weight=100} - Game Help (todo) + inventory{weight=100} - Your Inventory (todo) + (north|n|south|s|east|e|west|w) - You can't go in that direction. + quit{weight=100} - {topic=random}Quitter! + _ * - You don't need to use the word "" in this game. + * - I don't understand what you're saying. Try "help" or "quit". < topic > topic dungeon inherits global + hint - What do you need a hint on?\n ^ * How to play\n ^ * About this game + how to play % what do you need a hint * - The commands are "help", "inventory", and "quit". Just read and type. + about this game % what do you need a hint * - This is just a sample RPG game to demonstrate topic inheritence. < topic > topic room1 inherits dungeon + look - You're in a room with a large number "1" on the floor.\s ^ Exits are north and east. + (north|n){weight=5} - {topic=room2}{@look} + (east|e){weight=5} - {topic=room3}{@look} < topic > topic room2 inherits dungeon + look - This room has the number "2" here. There's a flask here that's trapped ^ \sin some kind of mechanism that only opens while the button is held ^ \sdown (so, hold down the button then quickly grab the flask).\n\n ^ The only exit is to the south. + [push|press|hold] button [*] - You press down on the button and the mechanism holding the flask is\s ^ unlocked. + [take|pick up] [ye] flask [*] % * mechanism holding the flask is unlocked - You try to take ye flask but fail (you can't take ye flask, give up). + [take|pick up] [ye] flask [*] - You can't get ye flask while the mechanism is holding onto it. + (south|s){weight=5} - {topic=room1}{@look} < topic > topic room3 inherits dungeon + look - There's nothing here but the number "3". Only exit is to the west. + (west|w){weight=5} - {topic=room1}{@look} < topic RiveScript-v2.0.2/t/RiveScript.t000644 000765 000024 00000063050 12640307464 020514 0ustar00npetherbridgestaff000000 000000 #!/usr/bin/perl # RiveScript Unit Tests use utf8; use strict; use Test::More tests => 162; binmode(STDOUT, ":utf8"); use_ok('RiveScript'); my @tests; # Constants. my $MATCH = RiveScript::RS_ERR_MATCH(); my $REPLY = RiveScript::RS_ERR_REPLY(); #-----------------------------------------------------------------------------# # Begin Block Tests # #-----------------------------------------------------------------------------# push @tests, sub { # No begin blocks. my $rs = bot(" + hello bot - Hello human. "); test($rs, "Hello Bot", "Hello human.", "No begin block."); test($rs, "How are you?", $MATCH, "No trigger matched."); }; push @tests, sub { # Simple begin blocks. my $rs = bot(" > begin + request - {ok} < begin + hello bot - Hello human. "); test($rs, "Hello Bot", "Hello human.", "Simple begin block."); }; push @tests, sub { # 'Blocked' begin blocks. my $rs = bot(" > begin + request - Nope. < begin + hello bot - Hello human. "); test($rs, "Hello Bot", "Nope.", "Begin blocks access to real reply."); }; push @tests, sub { # Conditional begin blocks. my $rs = bot(" > begin + request * == undefined => {ok} * != undefined => : {ok} - {ok} < begin + hello bot - Hello human. + my name is * - >Hello, . "); test($rs, 'Hello bot.', 'Hello human.', 'Trigger works.'); tv($rs, 'met', 'true', '"met" variable set to true.'); tv($rs, 'name', undef, '"name" is still undefined.'); test($rs, 'My name is bob', 'Hello, Bob.', 'Set user name.'); tv($rs, 'name', 'Bob', '"name" was successfully set.'); test($rs, 'Hello Bot', 'Bob: Hello human.', 'Name condition worked.'); }; #-----------------------------------------------------------------------------# # Bot vars & substitutions # #-----------------------------------------------------------------------------# push @tests, sub { # Bot vars. my $rs = bot(" ! var name = Aiden ! var age = 5 + what is your name - My name is . + how old are you - I am . + what are you - I'm . + happy birthday - Thanks! "); test($rs, 'What is your name?', 'My name is Aiden.', 'Bot name.'); test($rs, 'How old are you?', 'I am 5.', 'Bot age.'); test($rs, 'What are you?', "I'm undefined.", 'Undefined bot variable.'); test($rs, 'Happy birthday!', 'Thanks!', 'Set bot variable.'); test($rs, 'How old are you?', 'I am 6.', 'Bot age was updated.'); }; push @tests, sub { # Global vars. my $rs = bot(" ! global debug = false + debug mode - Debug mode is: + set debug mode * - >Switched to . "); test($rs, 'Debug mode.', 'Debug mode is: false', 'Global variable test.'); test($rs, 'Set debug mode true', 'Switched to true.', 'Change global variable.'); test($rs, 'Debug mode?', 'Debug mode is: true', 'Global variable was updated.'); }; push @tests, sub { # Before and after subs. my $rs = bot(" + whats up - nm. + what is up - Not much. "); test($rs, 'whats up', 'nm.', 'Literal "whats up"'); test($rs, 'what\'s up', 'nm.', 'Literal "what\'s up"'); test($rs, 'what is up', 'Not much.', 'Literal "what is up"'); # Add subs extend($rs, " ! sub whats = what is ! sub what's = what is "); test($rs, 'whats up', 'Not much.', 'Literal "whats up"'); test($rs, 'what\'s up', 'Not much.', 'Literal "what\'s up"'); test($rs, 'what is up', 'Not much.', 'Literal "what is up"'); }; push @tests, sub { # Before and after person subs. my $rs = bot(" + say * - "); test($rs, 'say i am cool', 'i am cool', 'Person substitution 1'); test($rs, 'say you are dumb', 'you are dumb', 'Person substitution 2'); extend($rs, " ! person i am = you are ! person you are = I am "); test($rs, 'say i am cool', 'you are cool', 'Person substitution 3'); test($rs, 'say you are dumb', 'I am dumb', 'Person substitution 4'); }; #-----------------------------------------------------------------------------# # Triggers # #-----------------------------------------------------------------------------# push @tests, sub { # Atomic & Wildcard my $rs = bot(" + hello bot - Hello human. + my name is * - Nice to meet you, . + * told me to say * - Why did tell you to say ? + i am # years old - A lot of people are . + i am _ years old - Say that with numbers. + i am * years old - Say that with fewer words. "); test($rs, 'hello bot', 'Hello human.', 'Atomic trigger.'); test($rs, 'my name is Bob', 'Nice to meet you, bob.', 'One star.'); test($rs, 'bob told me to say hi', 'Why did bob tell you to say hi?', 'Two stars.'); test($rs, 'i am 5 years old', 'A lot of people are 5.', 'Number wildcard.'); test($rs, 'i am five years old', 'Say that with numbers.', 'Underscore wildcard.'); test($rs, 'i am twenty five years old', 'Say that with fewer words.', 'Star wildcard.'); }; push @tests, sub { # Alternatives & Optionals my $rs = bot(" + what (are|is) you - I am a robot. + what is your (home|office|cell) [phone] number - It is 555-1234. + [please|can you] ask me a question - Why is the sky blue? + (aa|bb|cc) [bogus] - Matched. + (yo|hi) [computer|bot] * - Matched. "); test($rs, 'what are you', 'I am a robot.', 'Alternatives 1.'); test($rs, 'what is you', 'I am a robot.', 'Alternatives 2.'); foreach my $kind (qw(home office cell)) { test($rs, "what is your $kind phone number", 'It is 555-1234.', "Alternatives & optionals - $kind."); test($rs, "what is your $kind number", 'It is 555-1234.', "Alternatives & optionals - $kind."); } test($rs, 'can you ask me a question', 'Why is the sky blue?', 'Optionals 1.'); test($rs, 'ask me a question', 'Why is the sky blue?', 'Optionals 2.'); test($rs, 'please ask me a question', 'Why is the sky blue?', 'Optionals 3.'); test($rs, "aa", "Matched.", "Optionals 4."); test($rs, "bb", "Matched.", "Optionals 5."); test($rs, "aa bogus", "Matched.", "Optionals 6."); test($rs, "aabogus", $MATCH, "Optionals 7."); test($rs, "bogus", $MATCH, "Optionals 8."); test($rs, "hi Aiden", "Matched.", "Optionals 9."); test($rs, "hi bot how are you?", "Matched.", "Optionals 10."); test($rs, "yo computer what time is it?", "Matched.", "Optionals 11."); test($rs, "yoghurt is yummy", $MATCH, "Optionals 12."); test($rs, "hide and seek is fun", $MATCH, "Optionals 13."); test($rs, "hip hip hurrah", $MATCH, "Optionals 14."); }; push @tests, sub { # Arrays. my $rs = bot(' ! array colors = red blue green yellow white ^ dark blue|light blue + what color is my (@colors) * - Your is . + what color was * (@colors) * - It was . + i have a @colors * - Tell me more about your . '); test($rs, 'what color is my red shirt', 'Your shirt is red.', 'Array with wildcards 1.'); test($rs, 'what color is my blue car', 'Your car is blue.', 'Array with wildcards 2.'); test($rs, 'what color is my pink house', $MATCH, 'Array doesn\'t match message.'); test($rs, 'what color is my dark blue jacket', 'Your jacket is dark blue.', 'Array with wildcards 3.'); test($rs, 'What color was Napoleon\'s white horse?', 'It was white.', 'Array with wildcards 3.'); test($rs, 'What color was my red shirt?', 'It was red.', 'Array with wildcards 4.'); test($rs, 'I have a blue car', 'Tell me more about your car.', 'Non-capturing array.'); test($rs, 'I have a cyan car', $MATCH, 'Non-capturing array doesn\'t match message.'); }; push @tests, sub { # Priority triggers. my $rs = bot(' + * or something{weight=10} - Or something. <@> + can you run a google search for * - Sure! + hello *{weight=20} - Hi there! '); test($rs, 'Hello robot', 'Hi there!', 'Highest weight trigger (20).'); test($rs, 'Hello or something', 'Hi there!', 'Weight of 20 is higher than 10.'); test($rs, 'Can you run a Google search for Perl', 'Sure!', 'Normal trigger.'); test($rs, 'Can you run a Google search for Python or something', 'Or something. Sure!', 'Higher weight trigger matched over normal.'); }; #-----------------------------------------------------------------------------# # Responses # #-----------------------------------------------------------------------------# # TODO: no way to reliably test random responses in a way that doesn't create # a slim chance that the unit tests will fail? push @tests, sub { # %Previous. my $rs = bot(" ! sub who's = who is ! sub it's = it is ! sub didn't = did not + knock knock{weight=1} - Who's there? + * % who is there - who? + * % * who - Haha! ! + * - I don't know. "); test($rs, 'knock knock', "Who's there?", 'Knock-knock joke pt1.'); test($rs, 'Canoe', 'canoe who?', 'Knock-knock joke pt2.'); test($rs, 'Canoe help me with my homework?', 'Haha! canoe help me with my homework!', 'Knock-knock joke pt3.'); test($rs, 'hello', "I don't know.", 'Normal catch-all still works.'); }; push @tests, sub { # Continuations. my $rs = bot(' + tell me a poem - There once was a man named Tim,\s ^ who never quite learned how to swim.\s ^ He fell off a dock, and sank like a rock,\s ^ and that was the end of him. '); test($rs, 'Tell me a poem.', "There once was a man named Tim, " . "who never quite learned how to swim. " . "He fell off a dock, and sank like a rock, " . "and that was the end of him.", 'Continuation for a multi-line poem.'); }; push @tests, sub { # Redirects. my $rs = bot(' + hello - Hi there! + hey @ hello + hi there - {@hello} '); foreach my $greet ('hello', 'hey', 'hi there') { test($rs, $greet, 'Hi there!', "Redirect w/ greeting: $greet"); } }; push @tests, sub { # Conditional testing. my $rs = bot(" + i am # years old - >OK. + what can i do * == undefined => I don't know. * > 25 => Anything you want. * == 25 => Rent a car for cheap. * >= 21 => Drink. * >= 18 => Vote. * < 18 => Not much of anything. + am i your master * == true => Yes. - No. "); my $q = 'What can I do?'; test($rs, $q, "I don't know.", "Conditions 1."); test($rs, 'I am 16 years old.', 'OK.', "Set age=16."); test($rs, $q, "Not much of anything.", "Conditions 2."); test($rs, 'I am 18 years old.', 'OK.', "Set age=18."); test($rs, $q, "Vote.", "Conditions 3."); test($rs, 'I am 20 years old.', 'OK.', "Set age=20."); test($rs, $q, "Vote.", "Conditions 4."); test($rs, 'I am 22 years old.', 'OK.', "Set age=22."); test($rs, $q, "Drink.", "Conditions 5."); test($rs, 'I am 24 years old.', 'OK.', "Set age=24."); test($rs, $q, "Drink.", "Conditions 6."); test($rs, 'I am 25 years old.', 'OK.', "Set age=25."); test($rs, $q, "Rent a car for cheap.", "Conditions 7."); test($rs, 'I am 27 years old.', 'OK.', "Set age=27."); test($rs, $q, "Anything you want.", "Conditions 8."); test($rs, 'Am I your master?', 'No.', 'Conditions 9.'); $rs->setUservar('user', 'master' => 'true'); test($rs, 'Am I your master?', 'Yes.', 'Conditions 10.'); }; push @tests, sub { # Embedded Tag Testing my $rs = bot(" + my name is * * != undefined => >I thought\\s ^ your name was ? ^ > - >OK. + what is my name - Your name is , right? + html test - Name>This has some non-RS tags in it. "); test($rs, "What is my name?", "Your name is undefined, right?", "Embed tag test 1."); test($rs, "My name is Alice.", "OK.", "Embed tag test 2."); test($rs, "My name is Bob.", "I thought your name was Alice?", "Embed tag test 3."); test($rs, "What is my name?", "Your name is Bob, right?", "Embed tag test 4."); test($rs, "HTML Test", "This has some non-RS tags in it.", "Embed tag test 5."); }; #-----------------------------------------------------------------------------# # Object Macros # #-----------------------------------------------------------------------------# push @tests, sub { # Perl objects. my $rs = bot(' > object nolang return "Test w/o language."; < object > object wlang perl return "Test w/ language."; < object > object reverse perl my ($rs, @args) = @_; my $msg = join " ", @args; my @char = split(//, $msg); return join "", reverse(@char); < object > object broken perl return "syntax error; < object > object foreign javascript return "JavaScript checking in!"; < object + test nolang - Nolang: nolang + test wlang - Wlang: wlang + reverse * - reverse + test broken - Broken: broken + test fake - Fake: fake + test js - JS: foreign '); test($rs, 'Test nolang', 'Nolang: Test w/o language.', 'Object macro with no language specified.'); test($rs, 'Test wlang', 'Wlang: Test w/ language.', 'Object macro with Perl language specified.'); test($rs, 'Reverse hello world', 'dlrow olleh', 'Test the reverse macro.'); test($rs, 'Test broken', 'Broken: [ERR: Object Not Found]', 'Test calling a broken object.'); test($rs, 'Test JS', 'JS: [ERR: Object Not Found]', 'Test calling a foreign language object.'); }; push @tests, sub { # Try Perl objects when it's been disabled. my $rs = RiveScript->new(); $rs->setHandler(perl => undef); $rs->stream(' > object test perl return "Perl here!"; < object + test - Result: test '); $rs->sortReplies(); test($rs, 'test', 'Result: [ERR: Object Not Found]', 'Perl object macros disabled.'); }; push @tests, sub { # Try manually entered Perl objects. my $rs = RiveScript->new(); $rs->setSubroutine("reverse", sub { my ($rs, @args) = @_; my $msg = join " ", @args; my @char = split("", $msg); return join "", reverse @char; }); $rs->stream(' + reverse * - reverse '); $rs->sortReplies(); test($rs, "reverse hello world", "dlrow olleh", "Objects via setSubroutine"); }; #-----------------------------------------------------------------------------# # Topics # #-----------------------------------------------------------------------------# push @tests, sub { # Punishment topic. my $rs = bot(" + hello - Hi there! + swear word - How rude! Apologize or I won't talk to you again.{topic=sorry} + * - Catch-all. > topic sorry + sorry - It's ok!{topic=random} + * - Say you're sorry! < topic "); test($rs, 'hello', 'Hi there!', 'Default topic 1.'); test($rs, 'How are you?', 'Catch-all.', 'Default topic catch-all 1.'); test($rs, 'Swear word!', "How rude! Apologize or I won't talk to you again.", 'Entering a topic trap.'); test($rs, 'hello', "Say you're sorry!", 'In-topic catch-all 1.'); test($rs, 'how are you?', "Say you're sorry!", 'In-topic catch-all 2.'); test($rs, 'Sorry!', "It's ok!", 'Escape the topic.'); test($rs, 'hello', 'Hi there!', 'Default topic 2.'); test($rs, 'How are you?', 'Catch-all.', 'Default topic catch-all 2.'); }; push @tests, sub { # Topic inheritence. my $rs = bot(' > topic colors + what color is the sky - Blue. + what color is the sun - Yellow. < topic > topic linux + name a red hat distro - Fedora. + name a debian distro - Ubuntu. < topic > topic stuff includes colors linux + say stuff - "Stuff." < topic > topic override inherits colors + what color is the sun - Purple. < topic > topic morecolors includes colors + what color is grass - Green. < topic > topic evenmore inherits morecolors + what color is grass - Blue, sometimes. < topic '); $rs->setUservar('user', 'topic' => 'colors'); test($rs, 'What color is the sky?', 'Blue.', 'Topic=colors 1.'); test($rs, 'What color is the sun?', 'Yellow.', 'Topic=colors 2.'); test($rs, 'What color is grass?', $MATCH, 'Topic=colors 3.'); test($rs, 'Name a Red Hat distro.', $MATCH, 'Topic=colors 4.'); test($rs, 'Name a Debian distro.', $MATCH, 'Topic=colors 5.'); test($rs, 'Say stuff.', $MATCH, 'Topic=colors 6.'); $rs->setUservar('user', 'topic' => 'linux'); test($rs, 'Name a Red Hat distro.', 'Fedora.', 'Topic=linux 1.'); test($rs, 'Name a Debian distro.', 'Ubuntu.', 'Topic=linux 2.'); test($rs, 'What color is the sky?', $MATCH, 'Topic=linux 3.'); test($rs, 'What color is the sun?', $MATCH, 'Topic=linux 4.'); test($rs, 'What color is grass?', $MATCH, 'Topic=linux 5.'); test($rs, 'Say stuff.', $MATCH, 'Topic=linux 6.'); $rs->setUservar('user', 'topic' => 'stuff'); test($rs, 'What color is the sky?', 'Blue.', 'Topic=stuff 1.'); test($rs, 'What color is the sun?', 'Yellow.', 'Topic=stuff 2.'); test($rs, 'What color is grass?', $MATCH, 'Topic=stuff 3.'); test($rs, 'Name a Red Hat distro.', 'Fedora.', 'Topic=stuff 4.'); test($rs, 'Name a Debian distro.', 'Ubuntu.', 'Topic=stuff 5.'); test($rs, 'Say stuff.', '"Stuff."', 'Topic=stuff 6.'); $rs->setUservar('user', 'topic' => 'override'); test($rs, 'What color is the sky?', 'Blue.', 'Topic=override 1.'); test($rs, 'What color is the sun?', 'Purple.', 'Topic=override 2.'); $rs->setUservar('user', 'topic' => 'morecolors'); test($rs, 'What color is the sky?', 'Blue.', 'Topic=morecolors 1.'); test($rs, 'What color is the sun?', 'Yellow.', 'Topic=morecolors 2.'); test($rs, 'What color is grass?', 'Green.', 'Topic=morecolors 3.'); $rs->setUservar('user', 'topic' => 'evenmore'); test($rs, 'What color is the sky?', 'Blue.', 'Topic=evenmore 1.'); test($rs, 'What color is the sun?', 'Yellow.', 'Topic=evenmore 2.'); test($rs, 'What color is grass?', 'Blue, sometimes.', 'Topic=evenmore 3.'); }; #-----------------------------------------------------------------------------# # Local file scoped parser options # #-----------------------------------------------------------------------------# push @tests, sub { my $rs = RiveScript->new(); extend($rs, " // Default concat mode = none + test concat default - Hello ^ world! ! local concat = space + test concat space - Hello ^ world! ! local concat = none + test concat none - Hello ^ world! ! local concat = newline + test concat newline - Hello ^ world! // invalid concat setting is equivalent to `none` ! local concat = foobar + test concat foobar - Hello ^ world! // the option is file scoped so it can be left at // any setting and won't affect subsequent parses ! local concat = newline "); extend($rs, " // concat mode should be restored to the default in a // separate file/stream parse + test concat second file - Hello ^ world! "); test($rs, "test concat default", "Helloworld!", "Test concat default"); test($rs, "test concat space", "Hello world!", "Test concat space"); test($rs, "test concat none", "Helloworld!", "Test concat none"); test($rs, "test concat newline", "Hello\nworld!", "Test concat newline"); test($rs, "test concat foobar", "Helloworld!", "Test concat foobar"); test($rs, "test concat second file", "Helloworld!", "Test concat second file"); }; #-----------------------------------------------------------------------------# # UTF-8 Support # #-----------------------------------------------------------------------------# push @tests, sub { # Unicode my $rs = RiveScript->new(utf8=>1); extend($rs, " ! sub who's = who is + äh - What's the matter? + ブラッキー - エーフィ // Make sure %Previous continues working in UTF-8 mode. + knock knock - Who's there? + * % who is there - who? + * % * who - Haha! ! // And with UTF-8. + tëll më ä pöëm - Thërë öncë wäs ä män nämëd Tïm + more % thërë öncë wäs ä män nämëd tïm - Whö nëvër qüïtë lëärnëd höw tö swïm + more % whö nëvër qüïtë lëärnëd höw tö swïm - Hë fëll öff ä döck, änd sänk lïkë ä röck + more % hë fëll öff ä döck änd sänk lïkë ä röck - Änd thät wäs thë ënd öf hïm. "); test($rs, "äh", "What's the matter?", "UTF-8 Umlaut test."); test($rs, "ブラッキー", "エーフィ", "UTF-8 Japanese test."); test($rs, "knock knock", "Who's there?", "UTF-8 %Previous test 1."); test($rs, "Orange", "Orange who?", "UTF-8 %Previous test 2."); test($rs, "banana", "Haha! Banana!", "UTF-8 %Previous test 3."); test($rs, "tëll më ä pöëm", "Thërë öncë wäs ä män nämëd Tïm", "UTF-8 Umlaut poem test 1."); test($rs, "more", "Whö nëvër qüïtë lëärnëd höw tö swïm", "UTF-8 Umlaut poem test 2."); test($rs, "more", "Hë fëll öff ä döck, änd sänk lïkë ä röck", "UTF-8 Umlaut poem test 3."); test($rs, "more", "Änd thät wäs thë ënd öf hïm.", "UTF-8 Umlaut poem test 4."); }; push @tests, sub { # Unicode punctuation my $rs = RiveScript->new(utf8=>1); extend($rs, " + hello bot - Hello human! "); test($rs, "Hello bot", "Hello human!", "UTF-8 punctuation test 1."); test($rs, "Hello, bot", "Hello human!", "UTF-8 punctuation test 2."); test($rs, "Hello: Bot", "Hello human!", "UTF-8 punctuation test 3."); test($rs, "Hello... bot?", "Hello human!", "UTF-8 punctuation test 4."); # Edit the punctuation regexp. $rs->{unicode_punctuation} = qr/xxx/; test($rs, "Hello bot", "Hello human!", "UTF-8 punctuation test 5."); test($rs, "Hello, bot!", $MATCH, "UTF-8 punctuation test 6."); }; #-----------------------------------------------------------------------------# # Error handling # #-----------------------------------------------------------------------------# push @tests, sub { # Deep recursion. my $rs = bot(" + one @ two + two @ one "); testl($rs, 'one', qr/^ERR: Deep Recursion Detected/, 'Deep recursion check.'); }; #-----------------------------------------------------------------------------# # End Unit Tests # #-----------------------------------------------------------------------------# # Run all the tests. for my $t (@tests) { $t->(); } ### Utility Functions ### # Make a new bot sub bot { my $code = shift; my $rs = RiveScript->new(); return extend($rs, $code); } # Extend a bot. sub extend { my ($rs, $code) = @_; $rs->stream($code); $rs->sortReplies(); return $rs; } # Test message and response. sub test { my ($rs, $in, $out, $note) = @_; my $reply = $rs->reply('user', $in); is($reply, $out, $note); } sub testl { my ($rs, $in, $out, $note) = @_; my $reply = $rs->reply('user', $in); like($reply, $out, $note); } # Test user variable. sub tv { my ($rs, $var, $value, $note) = @_; is($rs->getUservar('user', $var), $value, $note); } RiveScript-v2.0.2/lib/RiveScript/000755 000765 000024 00000000000 12645246360 020630 5ustar00npetherbridgestaff000000 000000 RiveScript-v2.0.2/lib/RiveScript.pm000644 000765 000024 00000277621 12645246200 021176 0ustar00npetherbridgestaff000000 000000 package RiveScript; use strict; use warnings; # Version of the Perl RiveScript interpreter. This must be on a single line! # See `perldoc version` use version; our $VERSION = version->declare('v2.0.2'); our $SUPPORT = '2.0'; # Which RS standard we support. our $basedir = (__FILE__ =~ /^(.+?)\.pm$/i ? $1 : '.'); # Constants. use constant RS_ERR_MATCH => "ERR: No Reply Matched"; use constant RS_ERR_REPLY => "ERR: No Reply Found"; # Exports require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(RS_ERR_MATCH RS_ERR_REPLY); our %EXPORT_TAGS = ( standard => \@EXPORT_OK, ); =head1 NAME RiveScript - Rendering Intelligence Very Easily =head1 SYNOPSIS use RiveScript; # Create a new RiveScript interpreter. my $rs = new RiveScript; # Load a directory of replies. $rs->loadDirectory ("./replies"); # Load another file. $rs->loadFile ("./more_replies.rive"); # Stream in some RiveScript code. $rs->stream (q~ + hello bot - Hello, human. ~); # Sort all the loaded replies. $rs->sortReplies; # Chat with the bot. while (1) { print "You> "; chomp (my $msg = ); my $reply = $rs->reply ('localuser',$msg); print "Bot> $reply\n"; } =head1 DESCRIPTION RiveScript is a simple trigger/response language primarily used for the creation of chatting robots. It's designed to have an easy-to-learn syntax but provide a lot of power and flexibility. For more information, visit http://www.rivescript.com/ =head1 METHODS =head2 GENERAL =over 4 =cut ################################################################################ ## Constructor and Debug Methods ## ################################################################################ =item RiveScript new (hash %ARGS) Create a new instance of a RiveScript interpreter. The instance will become its own "chatterbot," with its own set of responses and user variables. You can pass in any global variables here. The two standard variables are: debug - Turns on debug mode (a LOT of information will be printed to the terminal!). Default is 0 (disabled). verbose - When debug mode is on, all debug output will be printed to the terminal if 'verbose' is also true. The default value is 1. debugfile - Optional: paired with debug mode, all debug output is also written to this file name. Since debug mode prints such a large amount of data, it is often more practical to have the output go to an external file for later review. Default is '' (no file). utf8 - Enable UTF-8 support for the RiveScript code. See the section on UTF-8 support for details. depth - Determines the recursion depth limit when following a trail of replies that point to other replies. Default is 50. strict - If this has a true value, any syntax errors detected while parsing a RiveScript document will result in a fatal error. Set it to a false value and only a warning will result. Default is 1. It's recommended that if you set any other global variables that you do so by calling C or defining it within the RiveScript code. This will avoid the possibility of overriding reserved globals. Currently, these variable names are reserved: topics sorted sortsthat sortedthat thats arrays subs person client bot objects syntax sortlist reserved debugopts frozen globals handlers objlangs Note: the options "verbose" and "debugfile", when provided, are noted and then deleted from the root object space, so that if your RiveScript code uses variables by the same values it won't conflict with the values that you passed here. =back =cut sub new { my $proto = shift; my $class = ref($proto) || $proto || 'RiveScript'; my $self = { ### # User configurable fields. ### # Debugging debug => 0, debugopts => { verbose => 1, # Print to the terminal file => '', # Print to a filename }, # Unicode stuff utf8 => 0, # UTF-8 support unicode_punctuation => qr/[.,!?;:]/, # Misc. depth => 50, # Recursion depth allowed. strict => 1, # Strict syntax checking (causes a die) ### # Internal fields. ### topics => {}, # Loaded replies under topics lineage => {}, # Keep track of topics that inherit other topics includes => {}, # Keep track of topics that include other topics sorted => {}, # Sorted triggers sortsthat => {}, # Sorted %previous's. sortedthat => {}, # Sorted triggers that go with %previous's thats => {}, # Reverse mapping for %previous, under topics arrays => {}, # Arrays subs => {}, # Substitutions person => {}, # Person substitutions client => {}, # User variables frozen => {}, # Frozen (backed-up) user variables bot => {}, # Bot variables objects => {}, # Subroutines syntax => {}, # Syntax tracking sortlist => {}, # Sorted lists (i.e. person subs) handlers => {}, # Object handlers globals => {}, # Globals that conflict with reserved names go here objlangs => {}, # Map object names to their programming languages reserved => [ # Reserved global variable names. qw(topics sorted sortsthat sortedthat thats arrays subs person client bot objects syntax sortlist reserved debugopts frozen handlers globals objlangs current_user) ], current_user => undef, # The user ID of the current chatter @_, }; bless ($self,$class); # Set the default object handler for Perl objects. $self->setHandler (perl => sub { my ($rs,$action,$name,$data) = @_; # $action will be "load" during the parsing phase, or "call" # when called via . # Loading if ($action eq "load") { # Create a dynamic Perl subroutine. my $code = "sub RSOBJ_$name {\n" . $data . "}"; # Evaluate it. eval ($code); if ($@) { $rs->issue("Perl object $name creation failed: $@"); } else { # Load it. $rs->setSubroutine($name => \&{"RSOBJ_$name"}); } } # Calling elsif ($action eq "call") { # Make sure the object exists. if (exists $rs->{objects}->{$name}) { # Call it. my @args = @{$data}; my $return = &{ $rs->{objects}->{$name} } ($rs,@args); return $return; } else { return "[ERR: Object Not Found]"; } } }); # See if any additional debug options were provided. if (exists $self->{verbose}) { $self->{debugopts}->{verbose} = delete $self->{verbose}; } if (exists $self->{debugfile}) { $self->{debugopts}->{file} = delete $self->{debugfile}; } $self->debug ("RiveScript $VERSION Initialized"); return $self; } sub debug { my ($self,$msg) = @_; if ($self->{debug}) { # Verbose debugging? if ($self->{debugopts}->{verbose}) { print "RiveScript: $msg\n"; } # Debugging to a file? if (length $self->{debugopts}->{file}) { # Get a real quick timestamp. my @time = localtime(time()); my $stamp = join(":",$time[2],$time[1],$time[0]); open (WRITE, ">>$self->{debugopts}->{file}"); print WRITE "[$stamp] RiveScript: $msg\n"; close (WRITE); } } } sub issue { my ($self,$msg) = @_; if ($self->{debug}) { print "# RiveScript::Warning: $msg\n"; } else { warn "RiveScript::Warning: $msg\n"; } } ################################################################################ ## Parsing Methods ## ################################################################################ =head2 LOADING AND PARSING =over 4 =item bool loadDirectory (string $PATH[, string @EXTS]) Load a directory full of RiveScript documents. C<$PATH> must be a path to a directory. C<@EXTS> is optionally an array containing file extensions, including the dot. By default C<@EXTS> is C<('.rive', '.rs')>. Returns true on success, false on failure. =cut sub loadDirectory { my $self = shift; my $dir = shift || '.'; my (@exts) = @_ || ('.rive', '.rs'); if (!-d $dir) { $self->issue ("loadDirectory failed: $dir is not a directory!"); return 0; } $self->debug ("loadDirectory: Open $dir - extensions: @exts"); opendir (my $dh, $dir); foreach my $file (sort { $a cmp $b } readdir($dh)) { next if $file eq '.'; next if $file eq '..'; next if $file =~ /\~$/i; # Skip backup files my $goodExt = 0; foreach (@exts) { my $re = quotemeta($_); $goodExt = 1 if $file =~ /$re$/; } next unless $goodExt; $self->debug ("loadDirectory: Read $file"); $self->loadFile ("$dir/$file"); } closedir ($dh); return 1; } =item bool loadFile (string $PATH) Load a single RiveScript document. C<$PATH> should be the path to a valid RiveScript file. Returns true on success; false otherwise. =cut sub loadFile { my ($self,$file) = @_; if (not defined $file) { $self->issue ("loadFile requires a file path."); return 0; } if (!-f $file) { $self->issue ("loadFile failed: $file is not a file!"); return 0; } open (my $fh, "<:utf8", $file); my @code = <$fh>; close ($fh); chomp @code; # Parse the file. $self->debug ("loadFile: Parsing " . (scalar @code) . " lines from $file."); $self->parse ($file,join("\n",@code)); return 1; } =item bool stream (arrayref $CODE) Stream RiveScript code directly into the module. This is for providing RS code from within the Perl script instead of from an external file. Returns true on success. =cut sub stream { my ($self,$code) = @_; if (not defined $code) { $self->issue ("stream requires RiveScript code."); return 0; } # Stream the code. $self->debug ("stream: Streaming code."); $self->parse ("stream()",$code); return 1; } sub parse { my ($self,$fname,$code) = @_; # Track temporary variables. my $topic = 'random'; # Default topic=random my $lineno = 0; # Keep track of line numbers my $comment = 0; # In a multi-line comment. my $inobj = 0; # Trying to parse an object. my $objname = ''; # Object name. my $objlang = ''; # Object programming language. my $objbuf = ''; # Object contents buffer. my $ontrig = ''; # Current trigger. my $repcnt = 0; # Reply counter. my $concnt = 0; # Condition counter. my $lastcmd = ''; # Last command code. my $isThat = ''; # Is a %Previous trigger. # Local (file scoped) parser options. my %local_options = ( concat => "none", # Concat mode for ^Continue command. ); # Concat mode characters. my %concat_mode = ( none => "", space => " ", newline => "\n", ); # Split the RS code into lines. $code =~ s/([\x0d\x0a])+/\x0a/ig; my @lines = split(/\x0a/, $code); # Read each line. $self->debug ("Parsing file data from $fname"); my $lp = 0; # line number index for ($lp = 0; $lp < scalar(@lines); $lp++) { $lineno++; my $line = $lines[$lp]; # Chomp the line further. chomp $line; $line =~ s/^(\t|\x0a|\x0d|\s)+//ig; $line =~ s/(\t|\x0a|\x0d|\s)+$//ig; $self->debug ("Line: $line (topic: $topic)"); # In an object? if ($inobj) { if ($line =~ /^<\s*object/i) { # End the object. if (length $objname) { # Call this object's handler. if (exists $self->{handlers}->{$objlang}) { $self->{objlangs}->{$objname} = $objlang; &{ $self->{handlers}->{$objlang} } ($self,"load",$objname,$objbuf); } else { $self->issue ("Object creation failed: no handler for $objlang!"); } } $objname = ''; $objlang = ''; $objbuf = ''; } else { $objbuf .= "$line\n"; next; } } # Look for comments. if ($line =~ /^(\/\/|#)/i) { # The "#" format for comments is deprecated. if ($line =~ /^#/) { $self->issue ("Using the # symbol for comments is deprecated at $fname line $lineno (near $line)"); } next; } elsif ($line =~ /^\/\*/) { if ($line =~ /\*\//) { # Well this was a short comment. next; } # Start of a multi-line comment. $comment = 1; next; } elsif ($line =~ /\*\//) { $comment = 0; next; } if ($comment) { next; } # Skip blank lines. next if length $line == 0; # Separate the command from the data. my ($cmd) = $line =~ /^(.)/i; $line =~ s/^.//i; $line =~ s/^\s+?//ig; # Ignore inline comments if there's a space before and after # the // or # symbols. my $inline_comment_regexp = "(\\s+\\#\\s+|\\/\\/)"; $line =~ s/\\\/\//\\\/\\\//g; # Turn \// into \/\/ if ($cmd eq '+') { $inline_comment_regexp = "(\\s\\s\\#|\\/\\/)"; if ($line =~ /\s\s#\s/) { # Deprecated. $self->issue ("Using the # symbol for comments is deprecated at $fname line $lineno (near: $line)."); } } else { if ($line =~ /\s#\s/) { # Deprecated. $self->issue ("Using the # symbol for comments is deprecated at $fname line $lineno (near: $line)."); } } if ($line =~ /$inline_comment_regexp/) { my ($left,$comment) = split(/$inline_comment_regexp/, $line, 2); $left =~ s/\s+$//g; $line = $left; } $self->debug ("\tCmd: $cmd"); # Run a syntax check on this line. We put this into a separate function so that # we can have all the syntax logic all in one place. my $syntax_error = $self->checkSyntax($cmd,$line); if ($syntax_error) { # There was a syntax error! Are we enforcing "strict"? $syntax_error = "Syntax error in $fname line $lineno: $syntax_error (near: $cmd $line)"; if ($self->{strict}) { # This is fatal then! die $syntax_error; } else { # This is a warning; warn it, and then abort processing this file! warn $syntax_error; return; } } # Reset the %previous state if this is a new +Trigger. if ($cmd eq '+') { $isThat = ''; } # Do a lookahead for ^Continue and %Previous commands. for (my $i = ($lp + 1); $i < scalar(@lines); $i++) { my $lookahead = $lines[$i]; $lookahead =~ s/^(\t|\x0a|\x0d|\s)+//g; my ($lookCmd) = $lookahead =~ /^(.)/i; $lookahead =~ s/^([^\s]+)\s+//i; # Only continue if the lookahead line has any data. if (defined $lookahead && length $lookahead > 0) { # The lookahead command has to be either a % or a ^. if ($lookCmd ne '^' && $lookCmd ne '%') { #$isThat = ''; last; } # If the current command is a +, see if the following command # is a % (previous) if ($cmd eq '+') { # Look for %Previous. if ($lookCmd eq '%') { $self->debug ("\tIs a %previous ($lookahead)"); $isThat = $lookahead; last; } else { $isThat = ''; } } # If the current command is a ! and the next command(s) are # ^, we'll tack each extension on as a line break (which is # useful information for arrays; everything else is gonna ditch # this info). if ($cmd eq '!') { if ($lookCmd eq '^') { $self->debug ("\t^ [$lp;$i] $lookahead"); $line .= "$lookahead"; $self->debug ("\tLine: $line"); } next; } # If the current command is not a ^ and the line after is # not a %, but the line after IS a ^, then tack it onto the # end of the current line (this is fine for every other type # of command that doesn't require special treatment). if ($cmd ne '^' && $lookCmd ne '%') { if ($lookCmd eq '^') { $self->debug ("\t^ [$lp;$i] $lookahead"); my $concat = exists $concat_mode{$local_options{"concat"}} ? $concat_mode{$local_options{"concat"}} : ""; $line .= $concat . $lookahead; } else { last; } } } } if ($cmd eq '!') { # ! DEFINE my ($left,$value) = split(/\s*=\s*/, $line, 2); my ($type,$var) = split(/\s+/, $left, 2); $ontrig = ''; $self->debug ("\t! DEFINE"); # Remove line breaks unless this is an array. if ($type ne 'array') { $value =~ s///ig; } if ($type eq 'version') { $self->debug ("\tUsing RiveScript version $value"); if ($value > $SUPPORT) { $self->issue ("Unsupported RiveScript Version. Skipping file $fname."); return; } } elsif ($type eq 'local') { $self->debug ("\tSet local parser option $var = $value"); $local_options{$var} = $value; } elsif ($type eq 'global') { if (not defined $var) { $self->issue ("Undefined global variable at $fname line $lineno."); next; } if (not defined $value) { $self->issue ("Undefined global value at $fname line $lineno."); next; } $self->debug ("\tSet global $var = $value"); # Don't allow the overriding of a reserved global. my $ok = 1; foreach my $res (@{$self->{reserved}}) { if ($var eq $res) { $ok = 0; last; } } if ($ok) { # Allow in the global name space. if ($value eq '') { delete $self->{$var}; } else { $self->{$var} = $value; } } else { # Allow in the protected name space. if ($value eq '') { delete $self->{globals}->{$var}; } else { $self->{globals}->{$var} = $value; } } } elsif ($type eq 'var') { $self->debug ("\tSet bot variable $var = $value"); if (not defined $var) { $self->issue ("Undefined bot variable at $fname line $lineno."); next; } if (not defined $value) { $self->issue ("Undefined bot value at $fname line $lineno."); next; } if ($value eq '') { delete $self->{bot}->{$var}; } else { $self->{bot}->{$var} = $value; } } elsif ($type eq 'array') { $self->debug ("\tSet array $var"); if (not defined $var) { $self->issue ("Undefined array variable at $fname line $lineno."); next; } if (not defined $value) { $self->issue ("Undefined array value at $fname line $lineno."); next; } if ($value eq '') { delete $self->{arrays}->{$var}; next; } # Did this have multiple lines? my @parts = split(//i, $value); $self->debug("Array lines: " . join(";",@parts)); # Process each line of array data. my @fields = (); foreach my $val (@parts) { # Split at pipes or spaces? if ($val =~ /\|/) { push (@fields,split(/\|/, $val)); } else { push (@fields,split(/\s+/, $val)); } } # Convert any remaining \s escape codes into spaces. foreach my $f (@fields) { $f =~ s/\\s/ /ig; } $self->{arrays}->{$var} = [ @fields ]; } elsif ($type eq 'sub') { $self->debug ("\tSubstitution $var => $value"); if (not defined $var) { $self->issue ("Undefined sub pattern at $fname line $lineno."); next; } if (not defined $value) { $self->issue ("Undefined sub replacement at $fname line $lineno."); next; } if ($value eq '') { delete $self->{subs}->{$var}; next; } $self->{subs}->{$var} = $value; } elsif ($type eq 'person') { $self->debug ("\tPerson substitution $var => $value"); if (not defined $var) { $self->issue ("Undefined person sub pattern at $fname line $lineno."); next; } if (not defined $value) { $self->issue ("Undefined person sub replacement at $fname line $lineno."); next; } if ($value eq '') { delete $self->{person}->{$var}; next; } $self->{person}->{$var} = $value; } else { $self->issue ("Unknown definition type \"$type\" at $fname line $lineno."); next; } } elsif ($cmd eq '>') { # > LABEL my ($type,$name,@fields) = split(/\s+/, $line); $type = lc($type); # Handle the label types. if ($type eq 'begin') { # The BEGIN statement. $self->debug ("Found the BEGIN Statement."); $type = 'topic'; $name = '__begin__'; } if ($type eq 'topic') { # Starting a new topic. $self->debug ("Set topic to $name."); $ontrig = ''; $topic = $name; # Does this topic include or inherit another one? my $mode = ''; # or 'inherits' || 'includes' if (scalar(@fields) >= 2) { foreach my $field (@fields) { if ($field eq 'includes') { $mode = 'includes'; } elsif ($field eq 'inherits') { $mode = 'inherits'; } elsif ($mode ne '') { # This topic is either inherited or included. if ($mode eq 'includes') { $self->{includes}->{$name}->{$field} = 1; } else { $self->{lineage}->{$name}->{$field} = 1; } } } } } if ($type eq 'object') { # If a field was provided, it should be the programming language. my $lang = (scalar(@fields) ? $fields[0] : ''); $lang = lc($lang); $lang =~ s/\s+//g; # Only try to parse a language we support. $ontrig = ''; if (not length $lang) { $self->issue ("Trying to parse unknown programming language at $fname line $lineno."); $lang = "perl"; # Assume it's Perl. } # See if we have a defined handler for this language. if (exists $self->{handlers}->{$lang}) { # We have a handler, so load this object's code. $objname = $name; $objlang = $lang; $objbuf = ''; $inobj = 1; } else { # We don't have a handler, just ignore this code. $objname = ''; $objlang = ''; $objbuf = ''; $inobj = 1; } } } elsif ($cmd eq '<') { # < LABEL my $type = $line; if ($type eq 'begin' || $type eq 'topic') { $self->debug ("End topic label."); $topic = 'random'; } elsif ($type eq 'object') { $self->debug ("End object label."); $inobj = 0; } } elsif ($cmd eq '+') { # + TRIGGER $self->debug ("\tTrigger pattern: $line"); if (length $isThat) { $self->debug ("\t\tInitializing the \%previous structure."); $self->{thats}->{$topic}->{$isThat}->{$line} = {}; } else { $self->{topics}->{$topic}->{$line} = {}; $self->{syntax}->{$topic}->{$line}->{ref} = "$fname line $lineno"; $self->debug ("\t\tSaved to \$self->{topics}->{$topic}->{$line}: " . "$self->{topics}->{$topic}->{$line}"); } $ontrig = $line; $repcnt = 0; $concnt = 0; } elsif ($cmd eq '-') { # - REPLY if ($ontrig eq '') { $self->issue ("Response found before trigger at $fname line $lineno."); next; } $self->debug ("\tResponse: $line"); if (length $isThat) { $self->{thats}->{$topic}->{$isThat}->{$ontrig}->{reply}->{$repcnt} = $line; } else { $self->{topics}->{$topic}->{$ontrig}->{reply}->{$repcnt} = $line; $self->{syntax}->{$topic}->{$ontrig}->{reply}->{$repcnt}->{ref} = "$fname line $lineno"; $self->debug ("\t\tSaved to \$self->{topics}->{$topic}->{$ontrig}->{reply}->{$repcnt}: " . "$self->{topics}->{$topic}->{$ontrig}->{reply}->{$repcnt}"); } $repcnt++; } elsif ($cmd eq '%') { # % PREVIOUS $self->debug ("\t% Previous pattern: $line"); # This was handled above. } elsif ($cmd eq '^') { # ^ CONTINUE # This should've been handled above... } elsif ($cmd eq '@') { # @ REDIRECT $self->debug ("\tRedirect the response to $line"); if (length $isThat) { $self->{thats}->{$topic}->{$isThat}->{$ontrig}->{redirect} = $line; } else { $self->{topics}->{$topic}->{$ontrig}->{redirect} = $line; } } elsif ($cmd eq '*') { # * CONDITION $self->debug ("\tAdding condition."); if (length $isThat) { $self->{thats}->{$topic}->{$isThat}->{$ontrig}->{condition}->{$concnt} = $line; } else { $self->{topics}->{$topic}->{$ontrig}->{condition}->{$concnt} = $line; } $concnt++; } else { $self->issue ("Unrecognized command \"$cmd\" at $fname line $lineno."); next; } } } =item string checkSyntax (char $COMMAND, string $LINE) Check the syntax of a line of RiveScript code. This is called automatically for each line parsed by the module. C<$COMMAND> is the command part of the line, and C<$LINE> is the rest of the line following the command (and excluding inline comments). If there is no problem with the line, this method returns C. Otherwise it returns the text of the syntax error. If C mode is enabled in the constructor (which is on by default), a syntax error will result in a fatal error. If it's not enabled, the error is only sent via C and the file currently being processed is aborted. =cut sub checkSyntax { my ($self,$cmd,$line) = @_; # This function returns undef when no syntax errors are present, otherwise # returns the text of the syntax error. # Run syntax checks based on the type of command. if ($cmd eq '!') { # ! Definition # - Must be formatted like this: # ! type name = value # OR # ! type = value # - Type options are NOT enforceable, for future compatibility; if RiveScript # encounters a new type that it can't handle, it can safely warn and skip it. if ($line !~ /^.+(?:\s+.+|)\s*=\s*.+?$/) { return "Invalid format for !Definition line: must be '! type name = value' OR '! type = value'"; } } elsif ($cmd eq '>') { # > Label # - The "begin" label must have only one argument ("begin") # - "topic" labels must be lowercase but can inherit other topics ([A-Za-z0-9_\s]) # - "object" labels follow the same rules as "topic" labels, but don't need be lowercase if ($line =~ /^begin/ && $line =~ /\s+/) { return "The 'begin' label takes no additional arguments, should be verbatim '> begin'"; } elsif ($line =~ /^topic/i && $line =~ /[^a-z0-9_\-\s]/) { return "Topics should be lowercased and contain only numbers and letters!"; } elsif ($line =~ /[^A-Za-z0-9_\-\s]/) { return "Objects can only contain numbers and letters!"; } } elsif ($cmd eq '+' || $cmd eq '%' || $cmd eq '@') { # + Trigger, % Previous, @ Redirect # This one is strict. The triggers are to be run through Perl's regular expression # engine. Therefore it should be acceptable by the regexp engine. # - Entirely lowercase # - No symbols except: ( | ) [ ] * _ # @ { } < > = # - All brackets should be matched my $parens = 0; # Open parenthesis my $square = 0; # Open square brackets my $curly = 0; # Open curly brackets my $chevron = 0; # Open angled brackets # Look for obvious errors. if ($self->{utf8}) { # UTF-8 only restricts certain meta characters. if ($line =~ /[A-Z\\.]/) { return "Triggers can't contain uppercase letters, backslashes or dots in UTF-8 mode."; } } else { # Only simple ASCIIs allowed. if ($line =~ /[^a-z0-9(\|)\[\]*_#\@{}<>=\s]/) { return "Triggers may only contain lowercase letters, numbers, and these symbols: ( | ) [ ] * _ # @ { } < > ="; } } # Count brackets. my @chr = split(//, $line); for (my $i = 0; $i < scalar(@chr); $i++) { my $char = $chr[$i]; # Count brackets. $parens++ if $char eq '('; $parens-- if $char eq ')'; $square++ if $char eq '['; $square-- if $char eq ']'; $curly++ if $char eq '{'; $curly-- if $char eq '}'; $chevron++ if $char eq '<'; $chevron-- if $char eq '>'; } # Any mismatches? if ($parens) { return "Unmatched " . ($parens > 0 ? "left" : "right") . " parenthesis bracket ()"; } if ($square) { return "Unmatched " . ($square > 0 ? "left" : "right") . " square bracket []"; } if ($curly) { return "Unmatched " . ($curly > 0 ? "left" : "right") . " curly bracket {}"; } if ($chevron) { return "Unmatched " . ($chevron > 0 ? "left" : "right" ) . " angled bracket <>"; } } elsif ($cmd eq '-' || $cmd eq '^' || $cmd eq '/') { # - Trigger, ^ Continue, / Comment # These commands take verbatim arguments, so their syntax is loose. } elsif ($cmd eq '*') { # * Condition # Syntax for a conditional is as follows: # * value symbol value => response if ($line !~ /^.+?\s*(==|eq|!=|ne|<>|<|<=|>|>=)\s*.+?=>.+?$/) { return "Invalid format for !Condition: should be like `* value symbol value => response`"; } } # All good? Return undef. return undef; } =item void sortReplies () Call this method after loading replies to create an internal sort buffer. This is necessary for trigger matching purposes. If you fail to call this method yourself, RiveScript will call it once when you request a reply. However, it will complain loudly about it. =cut sub sortReplies { my $self = shift; my $thats = shift || 'no'; # Make this method dynamic: allow it to sort both triggers and %previous. # To that end we need to make some more references. my $triglvl = {}; my $sortlvl = 'sorted'; if ($thats eq 'thats') { $triglvl = $self->{thats}; $sortlvl = 'sortsthat'; } else { $triglvl = $self->{topics}; } $self->debug ("Sorting triggers..."); # Loop through all the topics. foreach my $topic (keys %{$triglvl}) { $self->debug ("Analyzing topic $topic"); # Create a priority map. my $prior = { 0 => [], # Default }; # Collect a list of all the triggers we're going to need to # worry about. If this topic inherits another topic, we need to # recursively add those to the list. my @alltrig = $self->_topicTriggers($topic,$triglvl,0,0,0); #foreach my $trig (keys %{$triglvl->{$topic}}) { foreach my $trig (@alltrig) { if ($trig =~ /\{weight=(\d+)\}/i) { my $weight = $1; if (!exists $prior->{$weight}) { $prior->{$weight} = []; } push (@{$prior->{$weight}}, $trig); } else { push (@{$prior->{0}}, $trig); } } # Keep in mind here that there is a difference between 'includes' # and 'inherits' -- topics that inherit other topics are able to # OVERRIDE triggers that appear in the inherited topic. This means # that if the top topic has a trigger of simply '*', then *NO* triggers # are capable of matching in ANY inherited topic, because even though # * has the lowest sorting priority, it has an automatic priority over # all inherited topics. # # The _topicTriggers method takes this into account. All topics that # inherit other topics will have their triggers prefixed with a fictional # {inherits} tag, which would start at {inherits=0} and increment if the # topic tree has other inheriting topics. So we can use this tag to # make sure topics that inherit things will have their triggers always # be on the top of the stack, from inherits=0 to inherits=n. # Keep a running list of sorted triggers for this topic. my @running = (); # Sort them by priority. foreach my $p (sort { $b <=> $a } keys %{$prior}) { $self->debug ("\tSorting triggers with priority $p."); # So, some of these triggers may include {inherits} tags, if they # came from a topic which inherits another topic. Lower inherits # values mean higher priority on the stack. Keep this in mind when # keeping track of how to sort these things. my $inherits = -1; # -1 means no {inherits} tag, for flexibility my $highest_inherits = -1; # highest inheritence # we've seen # Loop through and categorize these triggers. my $track = { $inherits => { atomic => {}, # Sort by # of whole words option => {}, # Sort optionals by # of words alpha => {}, # Sort alpha wildcards by # of words number => {}, # Sort numeric wildcards by # of words wild => {}, # Sort wildcards by # of words pound => [], # Triggers of just # under => [], # Triggers of just _ star => [], # Triggers of just * }, }; foreach my $trig (@{$prior->{$p}}) { $self->debug("\t\tLooking at trigger: $trig"); # See if this trigger has an inherits number. if ($trig =~ /{inherits=(\d+)}/) { $inherits = $1; if ($inherits > $highest_inherits) { $highest_inherits = $inherits; } $self->debug("\t\t\tTrigger belongs to a topic which inherits other topics: level=$inherits"); $trig =~ s/{inherits=\d+}//g; } else { $inherits = -1; } # If this is the first time we've seen this inheritence priority # level, initialize its structure. if (!exists $track->{$inherits}) { $track->{$inherits} = { atomic => {}, option => {}, alpha => {}, number => {}, wild => {}, pound => [], under => [], star => [], }; } if ($trig =~ /\_/) { # Alphabetic wildcard included. my @words = split(/[\s\*\#\_]+/, $trig); my $cnt = scalar(@words); $self->debug("\t\tHas a _ wildcard with $cnt words."); if ($cnt > 1) { if (!exists $track->{$inherits}->{alpha}->{$cnt}) { $track->{$inherits}->{alpha}->{$cnt} = []; } push (@{$track->{$inherits}->{alpha}->{$cnt}}, $trig); } else { push (@{$track->{$inherits}->{under}}, $trig); } } elsif ($trig =~ /\#/) { # Numeric wildcard included. my @words = split(/[\s\*\#\_]+/, $trig); my $cnt = scalar(@words); $self->debug("\t\tHas a # wildcard with $cnt words."); if ($cnt > 1) { if (!exists $track->{$inherits}->{number}->{$cnt}) { $track->{$inherits}->{number}->{$cnt} = []; } push (@{$track->{$inherits}->{number}->{$cnt}}, $trig); } else { push (@{$track->{$inherits}->{pound}}, $trig); } } elsif ($trig =~ /\*/) { # Wildcards included. my @words = split(/[\s\*\#\_]+/, $trig); my $cnt = scalar(@words); $self->debug("Has a * wildcard with $cnt words."); if ($cnt > 1) { if (!exists $track->{$inherits}->{wild}->{$cnt}) { $track->{$inherits}->{wild}->{$cnt} = []; } push (@{$track->{$inherits}->{wild}->{$cnt}}, $trig); } else { push (@{$track->{$inherits}->{star}}, $trig); } } elsif ($trig =~ /\[(.+?)\]/) { # Optionals included. my @words = split(/[\s\*\#\_]+/, $trig); my $cnt = scalar(@words); $self->debug("Has optionals and $cnt words."); if (!exists $track->{$inherits}->{option}->{$cnt}) { $track->{$inherits}->{option}->{$cnt} = []; } push (@{$track->{$inherits}->{option}->{$cnt}}, $trig); } else { # Totally atomic. my @words = split(/[\s\*\#\_]+/, $trig); my $cnt = scalar(@words); $self->debug("Totally atomic and $cnt words."); if (!exists $track->{$inherits}->{atomic}->{$cnt}) { $track->{$inherits}->{atomic}->{$cnt} = []; } push (@{$track->{$inherits}->{atomic}->{$cnt}}, $trig); } } # Add this group to the sort list. $track->{ ($highest_inherits + 1) } = delete $track->{'-1'}; # Move the no-{inherits} group away for a sec foreach my $ip (sort { $a <=> $b } keys %{$track}) { $self->debug("ip=$ip"); foreach my $kind (qw(atomic option alpha number wild)) { foreach my $wordcnt (sort { $b <=> $a } keys %{$track->{$ip}->{$kind}}) { # Triggers with a matching word count should be sorted # by length, descending. push (@running, sort { length($b) <=> length($a) } @{$track->{$ip}->{$kind}->{$wordcnt}}); } } push (@running, sort { length($b) <=> length($a) } @{$track->{$ip}->{under}}); push (@running, sort { length($b) <=> length($a) } @{$track->{$ip}->{pound}}); push (@running, sort { length($b) <=> length($a) } @{$track->{$ip}->{star}}); } } # Save this topic's sorted list. $self->{$sortlvl}->{$topic} = [ @running ]; } # Also sort that's. if ($thats ne 'thats') { # This will sort the %previous lines to best match the bot's last reply. $self->sortReplies ('thats'); # If any of those %previous's had more than one +trigger for them, this # will sort all those +trigger's to pair back the best human interaction. $self->sortThatTriggers; # Also sort both kinds of substitutions. $self->sortList ('subs', keys %{$self->{subs}}); $self->sortList ('person', keys %{$self->{person}}); } } sub sortThatTriggers { my ($self) = @_; # Usage case: if you have more than one +trigger with the same %previous, # this will create a sort buffer for all those +trigger's. # Ex: # # + how [are] you [doing] # - I'm doing great, how are you? # - Good -- how are you? # - Fine, how are you? # # + [*] @good [*] # % * how are you # - That's good. :-) # # # // TODO: why isn't this ever called? # + [*] @bad [*] # % * how are you # - Aww. :-( What's the matter? # # + * # % * how are you # - I see... # The sort buffer for this. $self->{sortedthat} = {}; # Eventual structure: # $self->{sortedthat} = { # random => { # '* how are you' => [ # '[*] @good [*]', # '[*] @bad [*]', # '*', # ], # }, # }; $self->debug ("Sorting reverse triggers for %previous groups..."); foreach my $topic (keys %{$self->{thats}}) { # Create a running list of the sort buffer for this topic. my @running = (); $self->debug ("Sorting the 'that' triggers for topic $topic"); foreach my $that (keys %{$self->{thats}->{$topic}}) { $self->debug ("Sorting triggers that go with the 'that' of \"$that\""); # Loop through and categorize these triggers. my $track = { atomic => {}, # Sort by # of whole words option => {}, # Sort optionals by # of words alpha => {}, # Sort letters by # of words number => {}, # Sort numbers by # of words wild => {}, # Sort wildcards by # of words pound => [], # Triggers of just # under => [], # Triggers of just _ star => [], # Triggers of just * }; # Loop through all the triggers for this %previous. foreach my $trig (keys %{$self->{thats}->{$topic}->{$that}}) { if ($trig =~ /\_/) { # Alphabetic wildcard included. my @words = split(/[\s\*\#\_]+/, $trig); my $cnt = scalar(@words); if ($cnt > 1) { if (!exists $track->{alpha}->{$cnt}) { $track->{alpha}->{$cnt} = []; } push (@{$track->{alpha}->{$cnt}}, $trig); } else { push (@{$track->{under}}, $trig); } } elsif ($trig =~ /\#/) { # Numeric wildcard included. my @words = split(/[\s\*\#\_]+/, $trig); my $cnt = scalar(@words); if ($cnt > 1) { if (!exists $track->{number}->{$cnt}) { $track->{number}->{$cnt} = []; } push (@{$track->{number}->{$cnt}}, $trig); } else { push (@{$track->{pound}}, $trig); } } elsif ($trig =~ /\*/) { # Wildcards included. my @words = split(/[\s\*\#\_]+/, $trig); my $cnt = scalar(@words); if ($cnt > 1) { if (!exists $track->{wild}->{$cnt}) { $track->{wild}->{$cnt} = []; } push (@{$track->{wild}->{$cnt}}, $trig); } else { push (@{$track->{star}}, $trig); } } elsif ($trig =~ /\[(.+?)\]/) { # Optionals included. my @words = split(/[\s\*\#\_]+/, $trig); my $cnt = scalar(@words); if (!exists $track->{option}->{$cnt}) { $track->{option}->{$cnt} = []; } push (@{$track->{option}->{$cnt}}, $trig); } else { # Totally atomic. my @words = split(/[\s\*\#\_]+/, $trig); my $cnt = scalar(@words); if (!exists $track->{atomic}->{$cnt}) { $track->{atomic}->{$cnt} = []; } push (@{$track->{atomic}->{$cnt}}, $trig); } } # Add this group to the sort list. my @running = (); foreach my $i (sort { $b <=> $a } keys %{$track->{atomic}}) { push (@running,@{$track->{atomic}->{$i}}); } foreach my $i (sort { $b <=> $a } keys %{$track->{option}}) { push (@running,@{$track->{option}->{$i}}); } foreach my $i (sort { $b <=> $a } keys %{$track->{alpha}}) { push (@running,@{$track->{alpha}->{$i}}); } foreach my $i (sort { $b <=> $a } keys %{$track->{number}}) { push (@running,@{$track->{number}->{$i}}); } foreach my $i (sort { $b <=> $a } keys %{$track->{wild}}) { push (@running,@{$track->{wild}->{$i}}); } push (@running, sort { length($b) <=> length($a) } @{$track->{under}}); push (@running, sort { length($b) <=> length($a) } @{$track->{pound}}); push (@running, sort { length($b) <=> length($a) } @{$track->{star}}); # Keep this buffer. $self->{sortedthat}->{$topic}->{$that} = [ @running ]; } } } sub sortList { my ($self,$name,@list) = @_; # If a sorted list by this name already exists, delete it. if (exists $self->{sortlist}->{$name}) { delete $self->{sortlist}->{$name}; } # Initialize the sorted list. $self->{sortlist}->{$name} = []; # Track by number of words. my $track = {}; # Loop through each item in the list. foreach my $item (@list) { # Count the words. my @words = split(/\s+/, $item); my $cword = scalar(@words); # Store this by group of word counts. if (!exists $track->{$cword}) { $track->{$cword} = []; } push (@{$track->{$cword}}, $item); } # Sort them. my @sorted = (); foreach my $count (sort { $b <=> $a } keys %{$track}) { my @items = sort { length $b <=> length $a } @{$track->{$count}}; push (@sorted,@items); } # Store this list. $self->{sortlist}->{$name} = [ @sorted ]; return 1; } # Given one topic, walk the inheritence tree and return an array of all topics. sub _getTopicTree { my ($self,$topic,$depth) = @_; # Break if we're in too deep. if ($depth > $self->{depth}) { $self->issue ("Deep recursion while scanning topic inheritance (topic $topic was involved)"); return (); } # Collect an array of topics. my @topics = ($topic); $self->debug ("_getTopicTree depth $depth; topics: @topics"); # Does this topic include others? if (exists $self->{includes}->{$topic}) { # Try each of these. foreach my $includes (sort { $a cmp $b } keys %{$self->{includes}->{$topic}}) { $self->debug ("Topic $topic includes $includes"); push (@topics, $self->_getTopicTree($includes,($depth + 1))); } $self->debug ("_getTopicTree depth $depth (b); topics: @topics"); } # Does the topic inherit others? if (exists $self->{lineage}->{$topic}) { # Try each of these. foreach my $inherits (sort { $a cmp $b } keys %{$self->{lineage}->{$topic}}) { $self->debug ("Topic $topic inherits $inherits"); push (@topics, $self->_getTopicTree($inherits,($depth + 1))); } $self->debug ("_getTopicTree depth $depth (b); topics: @topics"); } # Return them. return (@topics); } # Gather an array of all triggers in a topic. If the topic inherits other # topics, recursively collect those triggers too. Take care about recursion. sub _topicTriggers { my ($self,$topic,$triglvl,$depth,$inheritence,$inherited) = @_; # Break if we're in too deep. if ($depth > $self->{depth}) { $self->issue ("Deep recursion while scanning topic inheritance (topic $topic was involved)"); return (); } # Important info about the depth vs inheritence params to this function: # depth increments by 1 every time this function recursively calls itself. # inheritence increments by 1 only when this topic inherits another topic. # # This way, `> topic alpha includes beta inherits gamma` will have this effect: # alpha and beta's triggers are combined together into one matching pool, and then # these triggers have higher matching priority than gamma's. # # The $inherited option is 1 if this is a recursive call, from a topic that # inherits other topics. This forces the {inherits} tag to be added to the # triggers. This only applies when the top topic "includes" another topic. $self->debug ("\tCollecting trigger list for topic $topic (depth=$depth; inheritence=$inheritence; inherited=$inherited)"); # topic: the name of the topic # triglvl: either $self->{topics} or $self->{thats} # depth: starts at 0 and ++'s with each recursion # Collect an array of triggers to return. my @triggers = (); # Does this topic include others? if (exists $self->{includes}->{$topic}) { # Check every included topic. foreach my $includes (sort { $a cmp $b } keys %{$self->{includes}->{$topic}}) { $self->debug ("\t\tTopic $topic includes $includes"); push (@triggers, $self->_topicTriggers($includes,$triglvl,($depth + 1), $inheritence, 1)); } } # Does this topic inherit others? if (exists $self->{lineage}->{$topic}) { # Check every inherited topic. foreach my $inherits (sort { $a cmp $b } keys %{$self->{lineage}->{$topic}}) { $self->debug ("\t\tTopic $topic inherits $inherits"); push (@triggers, $self->_topicTriggers($inherits,$triglvl,($depth + 1), ($inheritence + 1), 0)); } } # Collect the triggers for *this* topic. If this topic inherits any other # topics, it means that this topic's triggers have higher priority than those # in any inherited topics. Enforce this with an {inherits} tag. if (exists $self->{lineage}->{$topic} || $inherited) { my @inThisTopic = keys %{$triglvl->{$topic}}; foreach my $trigger (@inThisTopic) { $self->debug ("\t\tPrefixing trigger with {inherits=$inheritence}$trigger"); push (@triggers, "{inherits=$inheritence}$trigger"); } } else { push (@triggers, keys %{$triglvl->{$topic}}); } # Return them. return (@triggers); } =item data deparse () Translate the in-memory representation of the loaded RiveScript documents into a Perl data structure. This would be useful for developing a user interface to facilitate editing of RiveScript replies without having to edit the RiveScript code manually. The data structure returned from this will follow this format: { "begin" => { # Contains begin block and config settings "global" => { # ! global (global variables) "depth" => 50, ... }, "var" => { # ! var (bot variables) "name" => "Aiden", ... }, "sub" => { # ! sub (substitutions) "what's" => "what is", ... }, "person" => { # ! person (person substitutions) "you" => "I", ... }, "array" => { # ! array (arrays) "colors" => [ "red", "green", "light green", "blue" ], ... }, "triggers" => { # triggers in your > begin block "request" => { # trigger "+ request" "reply" => [ "{ok}" ], }, }, }, "topic" => { # all topics under here "random" => { # topic names (default is random) "hello bot" => { # trigger labels "reply" => [ "Hello human!" ], # Array of -Replies "redirect" => "hello", # Only if @Redirect exists "previous" => "hello human", # Only if %Previous exists "condition" => [ # Only if *Conditions exist " != undefined => Hello !", ... ], }, }, }, "include" => { # topic inclusion "alpha" => [ "beta", "gamma" ], # > topic alpha includes beta gamma }, "inherit" => { # topic inheritence "alpha" => [ "delta" ], # > topic alpha inherits delta } } Note that inline object macros can't be deparsed this way. This is probably for the best (for security, etc). The global variables "debug" and "depth" are only provided if the values differ from the defaults (true and 50, respectively). =cut sub deparse { my ($self) = @_; # Can we clone? eval { require Clone; $self->{_can_clone} = 1; }; if ($@) { warn "You don't have the Clone module installed. Output from " . "RiveScript->deparse will remain referenced to internal data " . "structures. Be careful!"; $self->{_can_clone} = 0; } # Data to return. my $deparse = { begin => { global => {}, var => {}, sub => {}, person => {}, array => {}, triggers => {}, that => {}, }, topic => {}, that => {}, inherit => {}, include => {}, }; # Populate the config fields. if ($self->{debug}) { $deparse->{begin}->{global}->{debug} = $self->{debug}; } if ($self->{depth} != 50) { $deparse->{begin}->{global}->{depth} = $self->{depth}; } $deparse->{begin}->{var} = $self->_clone($self->{bot}); $deparse->{begin}->{sub} = $self->_clone($self->{subs}); $deparse->{begin}->{person} = $self->_clone($self->{person}); $deparse->{begin}->{array} = $self->_clone($self->{arrays}); foreach my $global (keys %{$self->{globals}}) { $deparse->{begin}->{global}->{$global} = $self->{globals}->{$global}; } # Triggers. foreach my $topic (keys %{$self->{topics}}) { my $dest; # Where to place the topic info. if ($topic eq "__begin__") { # Begin block. $dest = $deparse->{begin}->{triggers}; } else { # Normal topic. if (!exists $deparse->{topic}->{$topic}) { $deparse->{topic}->{$topic} = {}; } $dest = $deparse->{topic}->{$topic}; } foreach my $trig (keys %{$self->{topics}->{$topic}}) { my $src = $self->{topics}->{$topic}->{$trig}; $dest->{$trig} = {}; $self->_copy_trigger($trig, $src, $dest); } } # %Previous's. foreach my $topic (keys %{$self->{thats}}) { my $dest; # Where to place the topic info. if ($topic eq "__begin__") { # Begin block. $dest = $deparse->{begin}->{that}; } else { # Normal topic. if (!exists $deparse->{that}->{$topic}) { $deparse->{that}->{$topic} = {}; } $dest = $deparse->{that}->{$topic}; } # The "that" structure is backwards: bot reply, then trigger, then info. foreach my $previous (keys %{$self->{thats}->{$topic}}) { foreach my $trig (keys %{$self->{thats}->{$topic}->{$previous}}) { my $src = $self->{thats}->{$topic}->{$previous}->{$trig}; $dest->{$trig}->{previous} = $previous; $self->_copy_trigger($trig, $src, $dest); } } } # Inherits/Includes. foreach my $topic (keys %{$self->{lineage}}) { $deparse->{inherit}->{$topic} = []; foreach my $inherit (keys %{$self->{lineage}->{$topic}}) { push @{$deparse->{inherit}->{$topic}}, $inherit; } } foreach my $topic (keys %{$self->{includes}}) { $deparse->{include}->{$topic} = []; foreach my $include (keys %{$self->{includes}->{$topic}}) { push @{$deparse->{include}->{$topic}}, $include; } } return $deparse; } sub _copy_trigger { my ($self, $trig, $src, $dest) = @_; if (exists $src->{redirect}) { # @Redirect $dest->{$trig}->{redirect} = $src->{redirect}; } if (exists $src->{condition}) { # *Condition $dest->{$trig}->{condition} = []; foreach my $i (sort { $a <=> $b } keys %{$src->{condition}}) { push @{$dest->{$trig}->{condition}}, $src->{condition}->{$i}; } } if (exists $src->{reply}) { # -Reply $dest->{$trig}->{reply} = []; foreach my $i (sort { $a <=> $b } keys %{$src->{reply}}) { push @{$dest->{$trig}->{reply}}, $src->{reply}->{$i}; } } } sub _clone { my ($self,$data) = @_; # Can clone? if ($self->{_can_clone}) { return Clone::clone($data); } return $data; } =item void write (glob $fh || string $file[, data $deparsed]) Write the currently parsed RiveScript data into a RiveScript file. This uses C to dump a representation of the loaded data and writes it to the destination file. Pass either a filehandle or a file name. If you provide C<$deparsed>, it should be a data structure matching the format of C. This way you can deparse your RiveScript brain, add/edit replies and then pass in the new version to this method to save the changes back to disk. Otherwise, C will be called to get the current snapshot of the brain. =back =cut sub write { my ($self, $file, $deparsed) = @_; my $fh; if (ref($file) eq "GLOB") { $fh = $file; } elsif (ref($file)) { die "Must pass either a filehandle or file name to write()"; } else { open ($fh, ">", $file) or die "Can't write to $file: $!"; } my $deparse = ref($deparsed) ? $deparsed : $self->deparse(); # Start at the beginning. print {$fh} "// Written by RiveScript::deparse()\n"; print {$fh} "! version = 2.0\n\n"; # Variables of all sorts! foreach my $sort (qw/global var sub person array/) { next unless scalar keys %{$deparse->{begin}->{$sort}} > 0; foreach my $var (sort keys %{$deparse->{begin}->{$sort}}) { # Array types need to be separated by either spaces or pipes. my $data = $deparse->{begin}->{$sort}->{$var}; if (ref($data) eq "ARRAY") { my $needs_pipes = 0; foreach my $test (@{$data}) { if ($test =~ /\s+/) { $needs_pipes = 1; last; } } # Word-wrap the result, target width is 78 chars minus the # sort, var, and spaces and equals sign. my $width = 78 - length($sort) - length($var) - 4; if ($needs_pipes) { $data = $self->_write_wrapped(join("|", @{$data}), "|", undef, $width); } else { $data = join(" ", @{$data}); } } print {$fh} "! $sort $var = $data\n"; } print {$fh} "\n"; } if (scalar keys %{$deparse->{begin}->{triggers}}) { print {$fh} "> begin\n\n"; $self->_write_triggers($fh, $deparse->{begin}->{triggers}, "indent"); print {$fh} "< begin\n\n"; } # The topics. Random first! my $doneRandom = 0; foreach my $topic ("random", sort keys %{$deparse->{topic}}) { next unless exists $deparse->{topic}->{$topic}; next if $topic eq "random" && $doneRandom; $doneRandom = 1 if $topic eq "random"; my $tagged = 0; # Used > topic tag if ($topic ne "random" || exists $deparse->{include}->{$topic} || exists $deparse->{inherit}->{$topic}) { $tagged = 1; print {$fh} "> topic $topic"; if (exists $deparse->{inherit}->{$topic}) { print {$fh} " inherits " . join(" ", @{$deparse->{inherit}->{$topic}}); } if (exists $deparse->{include}->{$topic}) { print {$fh} " includes " . join(" ", @{$deparse->{include}->{$topic}}); } print {$fh} "\n\n"; } $self->_write_triggers($fh, $deparse->{topic}->{$topic}, $tagged ? "indent" : 0); # Any %Previous's? if (exists $deparse->{that}->{$topic}) { $self->_write_triggers($fh, $deparse->{that}->{$topic}, $tagged ? "indent" : 0); } if ($tagged) { print {$fh} "< topic\n\n"; } } return 1; } sub _write_triggers { my ($self, $fh, $trigs, $id) = @_; $id = $id ? "\t" : ""; foreach my $trig (sort keys %{$trigs}) { print {$fh} $id . "+ " . $self->_write_wrapped($trig," ",$id) . "\n"; my $d = $trigs->{$trig}; if (exists $d->{previous}) { print {$fh} $id . "% " . $self->_write_wrapped($d->{previous}," ",$id) . "\n"; } if (exists $d->{condition}) { foreach my $cond (@{$d->{condition}}) { print {$fh} $id . "* " . $self->_write_wrapped($cond," ",$id) . "\n"; } } if (exists $d->{redirect}) { print {$fh} $id . "@ " . $self->_write_wrapped($d->{redirect}," ",$id) . "\n"; } if (exists $d->{reply}) { foreach my $reply (@{$d->{reply}}) { print {$fh} $id . "- " . $self->_write_wrapped($reply," ",$id) . "\n"; } } print {$fh} "\n"; } } sub _write_wrapped { my ($self, $line, $sep, $indent, $width) = @_; $width ||= 78; my $id = $indent ? "\t" : ""; my @words; if ($sep eq " ") { @words = split(/\s+/, $line); } elsif ($sep eq "|") { @words = split(/\|/, $line); } my @lines = (); $line = ""; my @buf = (); while (scalar(@words)) { push (@buf, shift(@words)); $line = join($sep, @buf); if (length $line > $width) { # Need to word wrap. unshift(@words, pop(@buf)); # Undo push (@lines, join($sep,@buf)); @buf = (); $line = ""; } } # Straggler? if ($line) { push @lines, $line; } my $return = shift(@lines); if (scalar(@lines)) { my $eol = ($sep eq " " ? '\s' : ""); foreach my $ln (@lines) { $return .= "$eol\n$id^ $ln"; } } return $return; } ################################################################################ ## Configuration Methods ## ################################################################################ =head2 CONFIGURATION =over 4 =item bool setHandler (string $LANGUAGE => code $CODEREF, ...) Define some code to handle objects of a particular programming language. If the coderef is C, it will delete the handler. The code receives the variables C<$rs, $action, $name,> and C<$data>. These variables are described here: $rs = Reference to Perl RiveScript object. $action = "load" during the parsing phase when an >object is found. "call" when provoked via a tag for a reply $name = The name of the object. $data = The source of the object during the parsing phase, or an array reference of arguments when provoked via a tag. There is a default handler set up that handles Perl objects. If you want to block Perl objects from being loaded, you can just set it to be undef, and its handler will be deleted and Perl objects will be skipped over: $rs->setHandler (perl => undef); The rationale behind this "pluggable" object interface is that it makes RiveScript more flexible given certain environments. For instance, if you use RiveScript on the web where the user chats with your bot using CGI, you might define a handler so that JavaScript objects can be loaded and called. Perl itself can't execute JavaScript, but the user's web browser can. See the JavaScript example in the C directory in this distribution. =cut sub setHandler { my ($self,%info) = @_; foreach my $lang (keys %info) { my $code = $info{$lang}; $lang = lc($lang); $lang =~ s/\s+//g; # If the coderef is undef, delete the handler. if (!defined $code) { delete $self->{handlers}->{$lang}; } else { # Otherwise it must be a coderef. if (ref($code) eq "CODE") { $self->{handlers}->{$lang} = $code; } else { $self->issue("Handler for language $lang must be a code reference!"); } } } return 1; } =item bool setSubroutine (string $NAME, code $CODEREF) Manually create a RiveScript object (a dynamic bit of Perl code that can be provoked in a RiveScript response). C<$NAME> should be a single-word, alphanumeric string. C<$CODEREF> should be a pointer to a subroutine or an anonymous sub. =cut sub setSubroutine { my ($self,$name,$sub) = @_; $self->{objects}->{$name} = $sub; $self->{objlangs}->{$name} = "perl"; return 1; } =item bool setGlobal (hash %DATA) Set one or more global variables, in hash form, where the keys are the variable names and the values are their value. This subroutine will make sure that you don't override any reserved global variables, and warn if that happens. This is equivalent to C in RiveScript code. To delete a global, set its value to C or "CundefE>". This is true for variables, substitutions, person, and uservars. =cut sub setGlobal { my ($self,%data) = @_; foreach my $key (keys %data) { if (!defined $data{$key}) { $data{$key} = ""; } my $reserved = 0; foreach my $res (@{$self->{reserved}}) { if ($res eq $key) { $reserved = 1; last; } } if ($reserved) { if ($data{$key} eq "") { delete $self->{globals}->{$key}; } else { $self->{globals}->{$key} = $data{$key}; } } else { if ($data{$key} eq "") { delete $self->{$key}; } else { $self->{$key} = $data{$key}; } } } return 1; } =item bool setVariable (hash %DATA) Set one or more bot variables (things that describe your bot's personality). This is equivalent to C in RiveScript code. =cut sub setVariable { my ($self,%data) = @_; foreach my $key (keys %data) { if (!defined $data{$key}) { $data{$key} = ""; } if ($data{$key} eq "") { delete $self->{bot}->{$key}; } else { $self->{bot}->{$key} = $data{$key}; } } return 1; } =item bool setSubstitution (hash %DATA) Set one or more substitution patterns. The keys should be the original word, and the value should be the word to substitute with it. $rs->setSubstitution ( q{what's} => 'what is', q{what're} => 'what are', ); This is equivalent to C in RiveScript code. =cut sub setSubstitution { my ($self,%data) = @_; foreach my $key (keys %data) { if (!defined $data{$key}) { $data{$key} = ""; } if ($data{$key} eq "") { delete $self->{subs}->{$key}; } else { $self->{subs}->{$key} = $data{$key}; } } return 1; } =item bool setPerson (hash %DATA) Set a person substitution. This is equivalent to C in RiveScript code. =cut sub setPerson { my ($self,%data) = @_; foreach my $key (keys %data) { if (!defined $data{$key}) { $data{$key} = ""; } if ($data{$key} eq "") { delete $self->{person}->{$key}; } else { $self->{person}->{$key} = $data{$key}; } } return 1; } =item bool setUservar (string $USER, hash %DATA) Set a variable for a user. C<$USER> should be their User ID, and C<%DATA> is a hash containing variable/value pairs. This is like CsetE> for a specific user. =cut sub setUservar { my ($self,$user,%data) = @_; foreach my $key (keys %data) { if (!defined $data{$key}) { $data{$key} = ""; } if ($data{$key} eq "") { delete $self->{client}->{$user}->{$key}; } else { $self->{client}->{$user}->{$key} = $data{$key}; } } return 1; } =item string getUservar (string $USER, string $VAR) This is an alias for getUservars, and is here because it makes more grammatical sense. =cut sub getUservar { # Alias for getUservars. my $self = shift; return $self->getUservars (@_); } =item data getUservars ([string $USER][, string $VAR]) Get all the variables about a user. If a username is provided, returns a hash B containing that user's information. Else, a hash reference of all the users and their information is returned. You can optionally pass a second argument, C<$VAR>, to get a specific variable that belongs to the user. For instance, C. This is like CgetE> for a specific user or for all users. =cut sub getUservars { my ($self,$user,$var) = @_; $user = '' unless defined $user; $var = '' unless defined $var; # Did they want a specific variable? if (length $user && length $var) { if (exists $self->{client}->{$user}->{$var}) { return $self->{client}->{$user}->{$var}; } else { return undef; } } if (length $user) { return $self->{client}->{$user}; } else { return $self->{client}; } } =item bool clearUservars ([string $USER]) Clears all variables about C<$USER>. If no C<$USER> is provided, clears all variables about all users. =cut sub clearUservars { my $self = shift; my $user = shift || ''; if (length $user) { foreach my $var (keys %{$self->{client}->{$user}}) { delete $self->{client}->{$user}->{$var}; } delete $self->{client}->{$user}; } else { foreach my $client (keys %{$self->{client}}) { foreach my $var (keys %{$self->{client}->{$client}}) { delete $self->{client}->{$client}->{$var}; } delete $self->{client}->{$client}; } } return 1; } =item bool freezeUservars (string $USER) Freeze the current state of variables for user C<$USER>. This will back up the user's current state (their variables and reply history). This won't statically prevent the user's state from changing; it merely saves its current state. Then use thawUservars() to revert back to this previous state. =cut sub freezeUservars { my ($self,$user) = @_; $user = '' unless defined $user; if (length $user && exists $self->{client}->{$user}) { # Freeze their variables. First unfreeze the last copy if they # exist. if (exists $self->{frozen}->{$user}) { $self->thawUservars ($user, discard => 1); } # Back up all our variables. foreach my $var (keys %{$self->{client}->{$user}}) { next if $var eq "__history__"; my $value = $self->{client}->{$user}->{$var}; $self->{frozen}->{$user}->{$var} = $value; } # Back up the history. $self->{frozen}->{$user}->{__history__}->{input} = [ @{$self->{client}->{$user}->{__history__}->{input}}, ]; $self->{frozen}->{$user}->{__history__}->{reply} = [ @{$self->{client}->{$user}->{__history__}->{reply}}, ]; return 1; } return undef; } =item bool thawUservars (string $USER[, hash %OPTIONS]) If the variables for C<$USER> were previously frozen, this method will restore them to the state they were in when they were last frozen. It will then delete the stored cache by default. The following options are accepted as an additional hash of parameters (these options are mutually exclusive and you shouldn't use both of them at the same time. If you do, "discard" will win.): discard: Don't restore the user's state from the frozen copy, just delete the frozen copy. keep: Keep the frozen copy even after restoring the user's state. With this you can repeatedly thawUservars on the same user to revert their state without having to keep freezing them again. On the next freeze, the last frozen state will be replaced with the new current state. Examples: # Delete the frozen cache but don't modify the user's variables. $rs->thawUservars ("soandso", discard => 1); # Restore the user's state from cache, but don't delete the cache. $rs->thawUservars ("soandso", keep => 1); =cut sub thawUservars { my ($self,$user,%args) = @_; $user = '' unless defined $user; if (length $user && exists $self->{frozen}->{$user}) { # What are we doing? my $restore = 1; my $discard = 1; if (exists $args{discard}) { # Just discard the variables. $restore = 0; $discard = 1; } elsif (exists $args{keep}) { # Keep the cache afterwards. $restore = 1; $discard = 0; } # Restore the state? if ($restore) { # Clear the client's current information. $self->clearUservars ($user); # Restore all our variables. foreach my $var (keys %{$self->{frozen}->{$user}}) { next if $var eq "__history__"; my $value = $self->{frozen}->{$user}->{$var}; $self->{client}->{$user}->{$var} = $value; } # Restore the history. $self->{client}->{$user}->{__history__}->{input} = [ @{$self->{frozen}->{$user}->{__history__}->{input}}, ]; $self->{client}->{$user}->{__history__}->{reply} = [ @{$self->{frozen}->{$user}->{__history__}->{reply}}, ]; } # Discard the cache? if ($discard) { foreach my $var (keys %{$self->{frozen}->{$user}}) { delete $self->{frozen}->{$user}->{$var}; } } return 1; } return undef; } =item string lastMatch (string $USER) After fetching a reply for user C<$USER>, the C method will return the raw text of the trigger that the user has matched with their reply. This function may return undef in the event that the user B match any trigger at all (likely the last reply was "C" as well). =cut sub lastMatch { my ($self,$user) = @_; $user = '' unless defined $user; # Get this user's last matched trigger. if (length $user && exists $self->{client}->{$user}->{__lastmatch__}) { return $self->{client}->{$user}->{__lastmatch__}; } return undef; } =item string currentUser () Get the user ID of the current user chatting with the bot. This is mostly useful inside of a Perl object macro in RiveScript to get the user ID of the person who invoked the object macro (e.g., to get/set variables for them using the C<$rs> instance). This will return C if used outside the context of a reply (the value is unset at the end of the C method). =back =cut sub currentUser { my $self = shift; if (!defined $self->{current_user}) { $self->issue("currentUser() is meant to be used from within a Perl object macro!"); } return $self->{current_user}; } ################################################################################ ## Interaction Methods ## ################################################################################ =head2 INTERACTION =over 4 =item string reply (string $USER, string $MESSAGE) Fetch a response to C<$MESSAGE> from user C<$USER>. RiveScript will take care of lowercasing, running substitutions, and removing punctuation from the message. Returns a response from the RiveScript brain. =back =cut sub reply { my ($self,$user,$msg) = @_; $self->debug ("Get reply to [$user] $msg"); # Store the current user's ID. $self->{current_user} = $user; # Format their message. $msg = $self->_formatMessage ($msg); my $reply = ''; # If the BEGIN statement exists, consult it first. if (exists $self->{topics}->{__begin__}->{request}) { # Get a response. my $begin = $self->_getreply ($user,'request', context => 'begin', step => 0, # Recursion redundancy counter ); # Okay to continue? if ($begin =~ /\{ok\}/i) { $reply = $self->_getreply ($user,$msg, context => 'normal', step => 0, ); $begin =~ s/\{ok\}/$reply/ig; } $reply = $begin; # Run more tag substitutions. $reply = $self->processTags ($user,$msg,$reply,[],[],0); } else { # Just continue then. $reply = $self->_getreply ($user,$msg, context => 'normal', step => 0, ); } # Save their reply history. unshift (@{$self->{client}->{$user}->{__history__}->{input}}, $msg); while (scalar @{$self->{client}->{$user}->{__history__}->{input}} > 9) { pop (@{$self->{client}->{$user}->{__history__}->{input}}); } unshift (@{$self->{client}->{$user}->{__history__}->{reply}}, $reply); while (scalar @{$self->{client}->{$user}->{__history__}->{reply}} > 9) { pop (@{$self->{client}->{$user}->{__history__}->{reply}}); } # Unset the current user's ID. $self->{current_user} = undef; return $reply; } sub _getreply { my ($self,$user,$msg,%tags) = @_; # Need to sort replies? if (scalar keys %{$self->{sorted}} == 0) { $self->issue ("ERR: You never called sortReplies()! Start doing that from now on!"); $self->sortReplies(); } # Collect info on this user if we have it. my $topic = 'random'; my @stars = (); my @thatstars = (); # For %previous's. my $reply = ''; if (exists $self->{client}->{$user}) { $topic = $self->{client}->{$user}->{topic}; } else { $self->{client}->{$user}->{topic} = 'random'; } # Avoid letting the user fall into a missing topic. if (!exists $self->{topics}->{$topic}) { $self->issue ("User $user was in an empty topic named '$topic'!"); $topic = 'random'; $self->{client}->{$user}->{topic} = 'random'; } # Avoid deep recursion. if ($tags{step} > $self->{depth}) { my $ref = ''; if (exists $self->{syntax}->{$topic}->{$msg}->{ref}) { $ref = " at $self->{syntax}->{$topic}->{$msg}->{ref}"; } $self->issue ("ERR: Deep Recursion Detected$ref!"); return "ERR: Deep Recursion Detected$ref!"; } # Are we in the BEGIN Statement? if ($tags{context} eq 'begin') { # Imply some defaults. $topic = '__begin__'; } # Track this user's history. if (!exists $self->{client}->{$user}->{__history__}) { $self->{client}->{$user}->{__history__}->{input} = [ 'undefined', 'undefined', 'undefined', 'undefined', 'undefined', 'undefined', 'undefined', 'undefined', 'undefined', ]; $self->{client}->{$user}->{__history__}->{reply} = [ 'undefined', 'undefined', 'undefined', 'undefined', 'undefined', 'undefined', 'undefined', 'undefined', 'undefined', ]; } # Create a pointer for the matched data (be it %previous or +trigger). my $matched = {}; my $matchedTrigger = undef; my $foundMatch = 0; # See if there are any %previous's in this topic, or any topic related to it. This # should only be done the first time -- not during a recursive @/{@} redirection. # This is because in a redirection, "lastreply" is still gonna be the same as it was # the first time, causing an infinite loop. if ($tags{step} == 0) { my @allTopics = ($topic); if (exists $self->{includes}->{$topic} || exists $self->{lineage}->{$topic}) { (@allTopics) = $self->_getTopicTree ($topic,0); } foreach my $top (@allTopics) { $self->debug ("Checking topic $top for any %previous's."); if (exists $self->{sortsthat}->{$top}) { $self->debug ("There's a %previous in this topic"); # Do we have history yet? if (scalar @{$self->{client}->{$user}->{__history__}->{reply}} > 0) { my $lastReply = $self->{client}->{$user}->{__history__}->{reply}->[0]; # Format the bot's last reply the same as the human's. $lastReply = $self->_formatMessage ($lastReply, "lastReply"); $self->debug ("lastReply: $lastReply"); # See if we find a match. foreach my $trig (@{$self->{sortsthat}->{$top}}) { my $botside = $self->_reply_regexp ($user,$trig); $self->debug ("Try to match lastReply ($lastReply) to $botside"); # Look for a match. if ($lastReply =~ /^$botside$/i) { # Found a match! See if our message is correct too. (@thatstars) = ($lastReply =~ /^$botside$/i); foreach my $subtrig (@{$self->{sortedthat}->{$top}->{$trig}}) { my $humanside = $self->_reply_regexp ($user,$subtrig); $self->debug ("Now try to match $msg to $humanside"); if ($msg =~ /^$humanside$/i) { $self->debug ("Found a match!"); $matched = $self->{thats}->{$top}->{$trig}->{$subtrig}; $matchedTrigger = $top; $foundMatch = 1; # Get the stars. (@stars) = ($msg =~ /^$humanside$/i); last; } } } # Break if we've found a match. last if $foundMatch; } } } # Break if we've found a match. last if $foundMatch; } } # Search their topic for a match to their trigger. if (not $foundMatch) { foreach my $trig (@{$self->{sorted}->{$topic}}) { # Process the triggers. my $regexp = $self->_reply_regexp ($user,$trig); $self->debug ("Trying to match \"$msg\" against $trig ($regexp)"); if ($msg =~ /^$regexp$/i) { $self->debug ("Found a match!"); # We found a match, but what if the trigger we matched belongs to # an inherited topic? Check for that. if (exists $self->{topics}->{$topic}->{$trig}) { # No, the trigger does belong to our own topic. $matched = $self->{topics}->{$topic}->{$trig}; } else { # Our topic doesn't have this trigger. Check inheritence. $matched = $self->_findTriggerByInheritence ($topic,$trig,0); } $foundMatch = 1; $matchedTrigger = $trig; # Get the stars. (@stars) = ($msg =~ /^$regexp$/i); last; } } } # Store what trigger they matched on (if $matched is undef, this will be # too, which is great). $self->{client}->{$user}->{__lastmatch__} = $matchedTrigger; for (defined $matched) { # See if there are any hard redirects. if (exists $matched->{redirect}) { $self->debug ("Redirecting us to $matched->{redirect}"); my $redirect = $matched->{redirect}; $redirect = $self->processTags ($user,$msg,$redirect,[@stars],[@thatstars],$tags{step}); $self->debug ("Pretend user asked: $redirect"); $reply = $self->_getreply ($user,$redirect, context => $tags{context}, step => ($tags{step} + 1), ); last; } # Check the conditionals. if (exists $matched->{condition}) { $self->debug ("Checking conditionals"); for (my $i = 0; exists $matched->{condition}->{$i}; $i++) { my ($cond,$potreply) = split(/\s*=>\s*/, $matched->{condition}->{$i}, 2); my ($left,$eq,$right) = ($cond =~ /^(.+?)\s+(==|eq|\!=|ne|\<\>|\<|\<=|\>|\>=)\s+(.+?)$/i); $self->debug ("\tLeft: $left; EQ: $eq; Right: $right"); # Process tags on all of these. $left = $self->processTags ($user,$msg,$left,[@stars],[@thatstars],$tags{step}); $right = $self->processTags ($user,$msg,$right,[@stars],[@thatstars],$tags{step}); # Revert them to undefined values. $left = 'undefined' if $left eq ''; $right = 'undefined' if $right eq ''; $self->debug ("\t\tCheck if \"$left\" $eq \"$right\""); # Validate the expression. my $match = 0; if ($eq eq 'eq' || $eq eq '==') { if ($left eq $right) { $match = 1; } } elsif ($eq eq 'ne' || $eq eq '!=' || $eq eq '<>') { if ($left ne $right) { $match = 1; } } elsif ($eq eq '<') { if ($left < $right) { $match = 1; } } elsif ($eq eq '<=') { if ($left <= $right) { $match = 1; } } elsif ($eq eq '>') { if ($left > $right) { $match = 1; } } elsif ($eq eq '>=') { if ($left >= $right) { $match = 1; } } if ($match) { # Condition is true. $reply = $potreply; last; } } } last if length $reply > 0; # Process weights in the replies. my @bucket = (); $self->debug ("Processing responses to this trigger."); for (my $rep = 0; exists $matched->{reply}->{$rep}; $rep++) { my $text = $matched->{reply}->{$rep}; my $weight = 1; if ($text =~ /{weight=(\d+)\}/i) { $weight = $1; if ($weight <= 0) { $weight = 1; $self->issue ("Can't have a weight < 0!"); } } for (my $i = 0; $i < $weight; $i++) { push (@bucket,$text); } } # Get a random reply. $reply = $bucket [ int(rand(scalar(@bucket))) ]; last; } # Still no reply? if ($foundMatch == 0) { $reply = RS_ERR_MATCH; } elsif (!defined $reply || length $reply == 0) { $reply = RS_ERR_REPLY; } $self->debug ("Reply: $reply"); # Process tags for the BEGIN Statement. if ($tags{context} eq 'begin') { if ($reply =~ /\{topic=(.+?)\}/i) { # Set the user's topic. $self->debug ("Topic set to $1"); $self->{client}->{$user}->{topic} = $1; $reply =~ s/\{topic=(.+?)\}//ig; } while ($reply =~ //i) { # Set a user variable. $self->debug ("Set uservar $1 => $2"); $self->{client}->{$user}->{$1} = $2; $reply =~ s///i; } } else { # Process more tags if not in BEGIN. $reply = $self->processTags($user,$msg,$reply,[@stars],[@thatstars],$tags{step}); } return $reply; } sub _findTriggerByInheritence { my ($self,$topic,$trig,$depth) = @_; # This sub was called because the user matched a trigger from the # sorted array, but the trigger doesn't exist under the topic of # which the user currently belongs. It probably was a trigger # inherited/included from another topic. This subroutine finds that out, # recursively, following the inheritence trail. # Take care to prevent infinite recursion. if ($depth > $self->{depth}) { $self->issue("Deep recursion detected while following an inheritence trail (involving topic $topic and trigger $trig)"); return undef; } # Inheritence is more important than inclusion: triggers in one topic # can override those in an inherited topic. if (exists $self->{lineage}->{$topic}) { foreach my $inherits (sort { $a cmp $b } keys %{$self->{lineage}->{$topic}}) { # See if this inherited topic has our trigger. if (exists $self->{topics}->{$inherits}->{$trig}) { # Great! return $self->{topics}->{$inherits}->{$trig}; } else { # Check what this topic inherits from. my $match = $self->_findTriggerByInheritence ( $inherits, $trig, ($depth + 1), ); if (defined $match) { # Finally got a match. return $match; } } } } # See if this topic has an "includes". if (exists $self->{includes}->{$topic}) { foreach my $includes (sort { $a cmp $b } keys %{$self->{includes}->{$topic}}) { # See if this included topic has our trigger. if (exists $self->{topics}->{$includes}->{$trig}) { # Great! return $self->{topics}->{$includes}->{$trig}; } else { # Check what this topic includes from. my $match = $self->_findTriggerByInheritence ( $includes, $trig, ($depth + 1), ); if (defined $match) { # Finally got a match. return $match; } } } } # Don't know what else we can do. return undef; } sub _reply_regexp { my ($self,$user,$regexp) = @_; # If the trigger is simply /^\*$/ (+ *) then the * there needs to # become (.*?) to match the blank string too. $regexp =~ s/^\*$//i; $regexp =~ s/\*/(.+?)/ig; # Convert * into (.+?) $regexp =~ s/\#/(\\d+)/ig; # Convert # into ([0-9]+?) $regexp =~ s/\_/(\\w+)/ig; # Convert _ into ([A-Za-z]+?) $regexp =~ s/\{weight=\d+\}//ig; # Remove {weight} tags. $regexp =~ s//(.*?)/i; while ($regexp =~ /\[(.+?)\]/i) { # Optionals my @parts = split(/\|/, $1); my @new = (); foreach my $p (@parts) { $p = '(?:\s|\b)+' . $p . '(?:\s|\b)+'; push (@new,$p); } # If this optional had a star or anything in it, e.g. [*], # make that non-matching. my $pipes = join("|",@new); $pipes =~ s/\(\.\+\?\)/(?:.+?)/ig; # (.+?) --> (?:.+?) $pipes =~ s/\(\\d\+\)/(?:\\d+)/ig; # (\d+) --> (?:\d+) $pipes =~ s/\(\\w\+\)/(?:\\w+)/ig; # (\w+) --> (?:\w+) my $rep = "(?:$pipes|(?:\\s|\\b)+)"; $regexp =~ s/\s*\[(.+?)\]\s*/$rep/i; } # _ wildcards can't match numbers! $regexp =~ s/\\w/[A-Za-z]/g; # Filter in arrays. while ($regexp =~ /\@(.+?)\b/) { my $name = $1; my $rep = ''; if (exists $self->{arrays}->{$name}) { $rep = '(?:' . join ("|",@{$self->{arrays}->{$name}}) . ')'; } $regexp =~ s/\@(.+?)\b/$rep/i; } # Filter in bot variables. while ($regexp =~ //i) { my $var = $1; my $rep = ''; if (exists $self->{bot}->{$var}) { $rep = $self->{bot}->{$var}; $rep =~ s/[^A-Za-z0-9 ]//ig; $rep = lc($rep); } $regexp =~ s//$rep/i; } # Filter in user variables. while ($regexp =~ //i) { my $var = $1; my $rep = ''; if (exists $self->{client}->{$user}->{$var}) { $rep = $self->{client}->{$user}->{$var}; $rep =~ s/[^A-Za-z0-9 ]//ig; $rep = lc($rep); } $regexp =~ s//$rep/i; } # Filter input tags. if ($regexp =~ /_formatMessage($self->{client}->{$user}->{__history__}->{input}->[0] || "undefined", "botReply"); $regexp =~ s//$firstInput/ig; while ($regexp =~ //i) { my $index = $1; my (@arrInput) = @{$self->{client}->{$user}->{__history__}->{input}}; unshift (@arrInput,''); my $line = $arrInput[$index]; $line = $self->_formatMessage ($line, "botReply"); $regexp =~ s//$line/ig; } } if ($regexp =~ /_formatMessage($self->{client}->{$user}->{__history__}->{reply}->[0] || "undefined", "botReply"); $regexp =~ s//$firstReply/ig; while ($regexp =~ //i) { my $index = $1; my (@arrReply) = @{$self->{client}->{$user}->{__history__}->{reply}}; unshift (@arrReply,''); my $line = $arrReply[$index]; $line = $self->_formatMessage ($line, "botReply"); $regexp =~ s//$line/ig; } } return $regexp; } sub processTags { my ($self,$user,$msg,$reply,$st,$bst,$depth) = @_; my (@stars) = (@{$st}); my (@botstars) = (@{$bst}); unshift (@stars,""); unshift (@botstars,""); if (scalar(@stars) == 1) { push (@stars,'undefined'); } if (scalar(@botstars) == 1) { push (@botstars,'undefined'); } my (@arrInput) = @{$self->{client}->{$user}->{__history__}->{input}}; my (@arrReply) = @{$self->{client}->{$user}->{__history__}->{reply}}; my $lastInput = $arrInput[0] || 'undefined'; my $lastReply = $arrReply[0] || 'undefined'; unshift(@arrInput,''); unshift(@arrReply,''); # Tag Shortcuts. $reply =~ s//{person}{\/person}/ig; $reply =~ s/<\@>/{\@}/ig; $reply =~ s//{formal}{\/formal}/ig; $reply =~ s//{sentence}{\/sentence}/ig; $reply =~ s//{uppercase}{\/uppercase}/ig; $reply =~ s//{lowercase}{\/lowercase}/ig; # Quick tags. $reply =~ s/\{weight=(\d+)\}//ig; # Remove leftover {weight}s if (scalar(@stars) > 0) { $reply =~ s//$stars[1]/ig if defined $stars[1]; $reply =~ s//(defined $stars[$1] ? $stars[$1] : '')/ieg; } if (scalar(@botstars) > 0) { $reply =~ s//$botstars[1]/ig; $reply =~ s//(defined $botstars[$1] ? $botstars[$1] : '')/ieg; } $reply =~ s//$lastInput/ig; $reply =~ s//$lastReply/ig; $reply =~ s//$arrInput[$1]/ig; $reply =~ s//$arrReply[$1]/ig; $reply =~ s//$user/ig; $reply =~ s/\\s/ /ig; $reply =~ s/\\n/\n/ig; $reply =~ s/\\/\\/ig; $reply =~ s/\\#/#/ig; while ($reply =~ /\{random\}(.+?)\{\/random\}/i) { my $rand = $1; my $output = ''; if ($rand =~ /\|/) { my @tmp = split(/\|/, $rand); $output = $tmp [ int(rand(scalar(@tmp))) ]; } else { my @tmp = split(/\s+/, $rand); $output = $tmp [ int(rand(scalar(@tmp))) ]; } $reply =~ s/\{random\}(.+?)\{\/random\}/$output/i; } while ($reply =~ /\{\!(.+?)\}/i) { # Just stream this back through. $self->stream ("! $1"); $reply =~ s/\{\!(.+?)\}//i; } while ($reply =~ /\{person\}(.+?)\{\/person\}/i) { my $person = $1; $person = $self->_personSub ($person); $reply =~ s/\{person\}(.+?)\{\/person\}/$person/i; } while ($reply =~ /\{formal\}(.+?)\{\/formal\}/i) { my $formal = $1; $formal = $self->_stringUtil ('formal',$formal); $reply =~ s/\{formal\}(.+?)\{\/formal\}/$formal/i; } while ($reply =~ /\{sentence\}(.+?)\{\/sentence\}/i) { my $sentence = $1; $sentence = $self->_stringUtil ('sentence',$sentence); $reply =~ s/\{sentence\}(.+?)\{\/sentence\}/$sentence/i; } while ($reply =~ /\{uppercase\}(.+?)\{\/uppercase\}/i) { my $upper = $1; $upper = $self->_stringUtil ('upper',$upper); $reply =~ s/\{uppercase\}(.+?)\{\/uppercase\}/$upper/i; } while ($reply =~ /\{lowercase\}(.+?)\{\/lowercase\}/i) { my $lower = $1; $lower = $self->_stringUtil ('lower',$lower); $reply =~ s/\{lowercase\}(.+?)\{\/lowercase\}/$lower/i; } # Handle all variable-related tags with an iterative regexp approach, # to allow for nesting of tags in arbitrary ways (think >) # Dummy out the tags first, because we don't handle them right here. $reply =~ s//{__call__}/og; $reply =~ s/<\/call>/{\/__call__}/og; while (1) { # This regexp will match a which contains no other tag inside it, # i.e. in the case of > it will match but not the # tag, on the first pass. The second pass will get the tag, # and so on. if ($reply =~ /<([^<]+?)>/) { my $match = $1; my @parts = split(/\s+/, $match, 2); my $tag = lc($parts[0]); my $data = $parts[1] || ""; my $insert = ""; # Result of the tag evaluation. # Handle the tags. if ($tag eq "bot" or $tag eq "env") { # and tags are similar. my ($what, $is) = split(/=/, $data, 2); my $target = $self->{bot}; if ($tag eq "env") { # Reserved? my $reserved = 0; foreach my $res (@{$self->{reserved}}) { if ($res eq $what) { $reserved = 1; last; } } if ($reserved) { $target = $self->{globals}; } else { $target = $self; } } # Updating? if ($data =~ /=/) { $self->debug("Set $tag variable $what => $is"); $target->{$what} = $is; } else { $insert = exists $target->{$what} ? $target->{$what} : "undefined"; } } elsif ($tag eq "set") { # user vars. my ($what, $is) = split(/=/, $data, 2); $self->debug("Set uservar $what => $is"); $self->{client}->{$user}->{$what} = $is; } elsif ($tag =~ /^(?:add|sub|mult|div)$/) { my ($var, $value) = split(/=/, $data, 2); # Initialize the value? if (!exists $self->{client}->{$user}->{$var}) { $self->{client}->{$user}->{$var} = 0; } # Sanity checks. if ($self->{client}->{$user}->{$var} !~ /^[0-9\-\.]+$/) { $insert = "[ERR: Can't Modify Non-Numeric Variable $var]"; } elsif ($value =~ /^[^0-9\-\.]$/) { $insert = "[ERR: Math Can't '$tag' Non-Numeric Value $value]"; } else { # Modify the variable. if ($tag eq "add") { $self->{client}->{$user}->{$var} += $value; } elsif ($tag eq "sub") { $self->{client}->{$user}->{$var} -= $value; } elsif ($tag eq "mult") { $self->{client}->{$user}->{$var} *= $value; } elsif ($tag eq "div") { # Don't divide by zero. if ($value == 0) { $insert = "[ERR: Can't Divide By Zero]"; } else { $self->{client}->{$user}->{$var} /= $value; } } } } elsif ($tag eq "get") { $insert = (exists $self->{client}->{$user}->{$data} ? $self->{client}->{$user}->{$data} : "undefined"); } else { # Might be HTML tag, it's unrecognized. Preserve it. $insert = "\x00$match\x01"; } $reply =~ s/<$match>/$insert/i; } else { last; # No more tags remaining. } } # Recover mangled HTML-like tag parts. $reply =~ s/\x00//g; if ($reply =~ /\{topic=(.+?)\}/i) { # Set the user's topic. $self->debug ("Topic set to $1"); $self->{client}->{$user}->{topic} = $1; $reply =~ s/\{topic=(.+?)\}//ig; } while ($reply =~ /\{\@(.+?)\}/i) { my $at = $1; $at =~ s/^\s+//ig; $at =~ s/\s+$//ig; my $subreply = $self->_getreply ($user,$at, context => 'normal', step => ($depth + 1), ); $reply =~ s/\{\@(.+?)\}/$subreply/i; } $reply =~ s/\{__call__\}//g; $reply =~ s/\{\/__call__\}/<\/call>/g; while ($reply =~ /(.+?)<\/call>/i) { my ($obj,@args) = split(/\s+/, $1); my $output = ''; # What language handles this object? my $lang = exists $self->{objlangs}->{$obj} ? $self->{objlangs}->{$obj} : ''; if (length $lang) { # Do we handle this? if (exists $self->{handlers}->{$lang}) { # Ok. $output = &{ $self->{handlers}->{$lang} } ($self,"call",$obj,[@args]); } else { $output = '[ERR: No Object Handler]'; } } else { $output = '[ERR: Object Not Found]'; } $reply =~ s/(.+?)<\/call>/$output/i; } return $reply; } sub _formatMessage { my ($self,$string, $botReply) = @_; # Lowercase it. $string = lc($string); # Make placeholders each time we substitute something. my @ph = (); my $i = 0; # Run substitutions on it. foreach my $pattern (@{$self->{sortlist}->{subs}}) { my $result = $self->{subs}->{$pattern}; # Make a placeholder. push (@ph, $result); my $placeholder = "\x00$i\x00"; $i++; my $qm = quotemeta($pattern); $string =~ s/^$qm$/$placeholder/ig; $string =~ s/^$qm(\W+)/$placeholder$1/ig; $string =~ s/(\W+)$qm(\W+)/$1$placeholder$2/ig; $string =~ s/(\W+)$qm$/$1$placeholder/ig; } while ($string =~ /\x00(\d+)\x00/i) { my $id = $1; my $result = $ph[$id]; $string =~ s/\x00$id\x00/$result/i; } # In UTF-8 mode, only strip meta characters. if ($self->{utf8}) { # Backslashes and HTML tags $string =~ s/[\\<>]//g; $string =~ s/$self->{unicode_punctuation}//g; # If formatting the bot's last reply for %Previous, also remove punctuation. if ($botReply) { $string =~ s/[.?,!;:@#$%^&*()\-+]//g; } } else { $string =~ s/[^A-Za-z0-9 ]//g; } # In UTF-8 mode, only strip meta characters. if ($self->{utf8}) { # Backslashes and HTML tags $string =~ s/[\\<>]//g; } else { $string =~ s/[^A-Za-z0-9 ]//g; } # Remove excess whitespace and consolidate multiple spaces down to one. $string =~ s/^\s+//g; $string =~ s/\s+$//g; $string =~ s/\s+/ /g; return $string; } sub _stringUtil { my ($self,$type,$string) = @_; if ($type eq 'formal') { $string =~ s/\b(\w+)\b/\L\u$1\E/ig; } elsif ($type eq 'sentence') { $string =~ s/\b(\w)(.*?)(\.|\?|\!|$)/\u$1\L$2$3\E/ig; } elsif ($type eq 'upper') { $string = uc($string); } elsif ($type eq 'lower') { $string = lc($string); } return $string; } sub _personSub { my ($self,$string) = @_; # Make placeholders each time we substitute something. my @ph = (); my $i = 0; # Substitute each of the sorted person sub arrays in order, # using a one-way substitution algorithm (read: base13). foreach my $pattern (@{$self->{sortlist}->{person}}) { my $result = $self->{person}->{$pattern}; # Make a placeholder. push (@ph, $result); my $placeholder = "\x00$i\x00"; $i++; my $qm = quotemeta($pattern); $string =~ s/^$qm$/$placeholder/ig; $string =~ s/^$qm(\W+)/$placeholder$1/ig; $string =~ s/(\W+)$qm(\W+)/$1$placeholder$2/ig; $string =~ s/(\W+)$qm$/$1$placeholder/ig; } while ($string =~ /\x00(\d+)\x00/i) { my $id = $1; my $result = $ph[$id]; $string =~ s/\x00$id\x00/$result/i; } return $string; } 1; __END__ =head1 RIVESCRIPT This interpreter tries its best to follow RiveScript standards. Currently it supports RiveScript 2.0 documents. A current copy of the RiveScript working draft is included with this package: see L. =head1 UTF-8 SUPPORT RiveScript supports Unicode but it is not enabled by default. Enable it by passing a true value for the C option in the constructor, or by using the C<--utf8> argument to the C application. In UTF-8 mode, most characters in a user's message are left intact, except for certain metacharacters like backslashes and common punctuation characters like C. If you want to override the punctuation regexp, you can provide a new one by assigning the `unicode_punctuation` attribute of the bot object after initialization. Example: my $bot = new RiveScript(utf8 => 1); $bot->{unicode_punctuation} = qr/[.,!?;:]/; =head1 CONSTANTS This module can export some constants. use RiveScript qw(:standard); These constants include: =over 4 =item RS_ERR_MATCH This is the reply text given when no trigger has matched the message. It equals "C". if ($reply eq RS_ERR_MATCH) { $reply = "I couldn't find a good reply for you!"; } =item RS_ERR_REPLY This is the reply text given when a trigger I matched, but no reply was given from it (for example, the trigger only had conditionals and all of them were false, with no default replies to fall back on). It equals "C". if ($reply eq RS_ERR_REPLY) { $reply = "I don't know what to say about that!"; } =back =head1 SEE ALSO L - A current snapshot of the Working Draft that defines the standards of RiveScript. L - The official homepage of RiveScript. =head1 CHANGES 2.0.2 Jan 11 2016 - Fix typo in changelog. 2.0.1 Jan 11 2016 - When formatting a user's message, consolidate multiple consecutive spaces down to one. - Apply downstream Debian patch that fixes a typo in RiveScript::WD. 2.0.0 Dec 28 2015 - Switch from old-style floating point version number notation to dotted decimal notation. This bumps the version number to `2.0.0` because the next dotted-decimal version greater than `1.42` (`v1.420.0`) is `v1.421.0` and I don't like having that many digits in the version number. This release is simply a version update; no breaking API changes were introduced. 1.42 Nov 20 2015 - Add configurable `unicode_punctuation` attribute to strip out punctuation when running in UTF-8 mode. 1.40 Oct 10 2015 - Fix the regexp used when matching optionals so that the triggers don't match on inputs where they shouldn't. (RiveScript-JS issue #46) 1.38 Jul 21 2015 - New algorithm for handling variable tags (, , , , ,
, and ) that allows for iterative nesting of these tags (for example, > will work now). - Fix trigger sorting so that triggers with matching word counts are sorted by length descending. - Add support for `! local concat` option to override concatenation mode (file scoped) - Bugfix where Perl object macros set via `setSubroutine()` failed to load because they were missing a programming language internally. 1.36 Nov 26 2014 - Relicense under the MIT License. - Strip punctuation from the bot's responses in UTF-8 mode to support compatibility with %Previous. - Bugfix in deparse(): If you had two matching triggers, one with a %Previous and one without, you'd lose the data for one of them in the output. 1.34 Feb 26 2014 - Update README.md to include module documentation for github. - Fixes to META.yml 1.32 Feb 24 2014 - Maintenance release to fix some errors per the CPANTS. - Add license to Makefile.PL - Make Makefile.PL not executable - Make version numbers consistent 1.30 Nov 25 2013 - Added "TCP Mode" to the `rivescript` command so that it can listen on a socket instead of using standard input and output. - Added a "--data" option to the `rivescript` command for providing JSON input as a command line argument instead of standard input. - Added experimental UTF-8 support. - Bugfix: don't use hacky ROT13-encoded placeholders for message substitutions... use a null character method instead. ;) - Make .rive the default preferred file extension for RiveScript documents instead of .rs (which conflicts with the Rust programming language). Backwards compatibility remains to load .rs files, though. See the `Changes` file for older change history. =head1 AUTHOR Noah Petherbridge, http://www.kirsle.net =head1 KEYWORDS bot, chatbot, chatterbot, chatter bot, reply, replies, script, aiml, alpha =head1 COPYRIGHT AND LICENSE The MIT License (MIT) Copyright (c) 2015 Noah Petherbridge Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. =cut RiveScript-v2.0.2/lib/RiveScript/demo/000755 000765 000024 00000000000 12645246360 021554 5ustar00npetherbridgestaff000000 000000 RiveScript-v2.0.2/lib/RiveScript/WD.pm000644 000765 000024 00000143360 12645246206 021506 0ustar00npetherbridgestaff000000 000000 package RiveScript::WD; use strict; use warnings; # Version of the Perl RiveScript interpreter. This must be on a single line! # See `perldoc version` use version; our $VERSION = version->declare('v2.0.2'); # This is not a real module; it's only a current copy of the RiveScript # Working Draft. See the latest version at # http://www.rivescript.com/wd/RiveScript.html 1; =head1 NAME RiveScript::WD - RiveScript 2.00 Working Draft (2014/11/30) =head1 DESCRIPTION This document details the standards for the RiveScript scripting language. The purpose of this document is that interpreters for RiveScript could be written in various programming languages that would meet the standards for the RiveScript language itself. The most current version of this document is at http://www.rivescript.com/wd/RiveScript.html =head1 INTRODUCTION RiveScript is an interpreted scripting language for giving responses to chatterbots and other intelligent chatting entities in a simple trigger/reply format. The scripting language is intended to be simplistic and easy to learn and manage. =head1 VOCABULARY =over 4 =item RiveScript RiveScript is the name of the scripting language that this document explains. =item Interpreter The RiveScript interpreter is a program or library in another programming language that loads and parses a RiveScript document. =item RiveScript Document A RiveScript Document is a text file containing RiveScript code. =item Bot A Bot (short for robot) is the artificial entity that is represented by an instance of a RiveScript Interpreter object. That is, when you create a new Interpreter object and load a set of RiveScript Documents, that becomes the "brain" of the bot. =item Bot Variable A variable that describes the bot, such as its name, age, or other details you want to define for the bot. =item Client Variable A variable that the bot keeps about a specific client, or user of the bot. Usually as the client tells the bot information about itself, the bot could save this information into Client Variables and recite it later. =back =head1 FORMAT A RiveScript document should be parsed line by line, and preferably arranged in the interpreter's memory in an efficient way. The first character on each line should be the C, and the rest of the line is the command's C. The C should be a single character that is not a number or a letter. In its most simple form, a valid RiveScript trigger/response pair looks like this: + hello bot - Hello, human. =head1 WHITESPACE A RiveScript interpreter should ignore leading and trailing whitespace characters on any line. It should also ignore whitespace characters surrounding individual arguments of a RiveScript command, where applicable. That is to say, the following two lines should be interpreted as being exactly the same: ! global debug = 1 ! global debug= 1 =head1 COMMANDS =head2 ! DEFINITION The C command is for defining variables within RiveScript. It's used to define information about the bot, define global arrays that can be used in multiple triggers, or override interpreter globals such as debug mode. The format of the C command is as follows: ! type name = value Where C is one of C or C. The C is the name of the variable being defined, and C is the value of said variable. Whitespace surrounding the C<=> sign should be stripped out. Setting a value to CundefE> will undefine the variable (deleting it or uninitializing it, depending on the implementation). The variable types supported are detailed as follows: =head3 version It's highly recommended practice that new RiveScript documents explicitly define the version of RiveScript that they are following. RiveScript 2.00 has some compatibility issues with the old 1.x line (see L<"REVERSE COMPATIBILITY">). Newer RiveScript versions should encourage that RiveScript documents define their own version numbers. ! version = 2.00 =head3 global This should override a global variable at the interpreter level. The obvious variable name might be "debug" (to enable/disable debugging within the RiveScript interpreter). The interpreter should take extra care not to allow reserved globals to be overridden by this command in ways that might break the interpreter. Examples: ! global debug = 1 =head3 var This should define a "bot variable" for the bot. This should only be used in an initialization sense; that is, as the interpreter loads the document, it should define the bot variable as it reads in this line. If you'd want to redefine or alter the value of a bot variable, you should do so using a tag inside of a RiveScript document (see L<"TAGS">). Examples: ! var name = RiveScript Bot ! var age = 0 ! var gender = androgynous ! var location = Cyberspace ! var generator = RiveScript =head3 array This will create an array of strings, which can then be used later in triggers (see L<"+ TRIGGER">). If the array contains single words, separating the words with a space character is fine. If the array contains items with multiple words in them, separate the entries with a pipe symbol (C<"|">). Examples: ! array colors = red green blue cyan magenta yellow black white orange brown ! array be = is are was were ! array whatis = what is|what are|what was|what were Arrays have special treatment when spanned over multiple lines. Each extension of the array data is treated individually. For example, to break an array of many single-words into multiple lines of RiveScript code: ! array colors = red green blue cyan ^ magenta yellow black white ^ orange brown The data structure pulled from that code would be identical to the previous example above for this array. Since each extension line is processed individually, you can combine the space-delimited and pipe-delimited formats. In this case, we can add some color names to our list that have multiple words in them. ! array colors = red green blue cyan magenta yellow ^ light red|light green|light blue|light cyan|light magenta|light yellow ^ dark red|dark green|dark blue|dark cyan|dark magenta|dark yellow ^ white orange teal brown pink ^ dark white|dark orange|dark teal|dark brown|dark pink Finally, if your array consists of almost entirely single-word items, and you want to add in just one multi-word item, but don't want to require an extra line of RiveScript code to accomplish this, just use the C<\s> tag where you need spaces to go. ! array blues = azure blue aqua cyan baby\sblue sky\sblue =head3 sub The C variables are for defining substitutions that should be run against the client's message before any attempts are made to match it to a reply. The interpreter should do the minimum amount of formatting possible on the client's message until after it has been passed through all the substitution patterns. B Spaces are allowed in both the variable name and the value fields. Examples: ! sub what's = what is ! sub what're = what are ! sub what'd = what did ! sub a/s/l = age sex location ! sub brb = be right back ! sub afk = away from keyboard ! sub l o l = lol =head3 person The C variables work a lot like Cs do, but these are run against the bot's response, specifically within CpersonE> tags (See L<"TAGS">). Person substitutions should swap first- and second-person pronouns. This is so that ex. if the client asks the bot a direct question using "you" when addressing the bot, if the bot uses the client's message in the response it should swap "you" for "I". Examples: ! person you are = I am ! person i am = you are ! person you = I ! person i = you =head2 E LABEL The C> and C> commands are for defining a subset of your code under a certain label. The label command takes between one and three arguments. The first argument defines the type of the label, which is one of C or C. The various types are as follows. =head3 begin This is a special label used with the C. Every message the client sends to the bot gets passed through the Begin Statement first, and the response in there determines whether or not to get an actual reply. Here's a full example of the Begin Statement. > begin + request - {ok} < begin In the C, the trigger named "C" is called by the interpreter, and it should return the tag "C<{ok}>" to tell the interpreter that it's OK to get a real reply. This way the bot could have a "maintenance mode," or could filter the results of your trigger based on a variable. Here's a maintenance mode example: > begin + request * eq => {ok} // Always let the bot master get a reply * eq true => Sorry, I'm not available for chat right now! - {ok} < begin // Allow the owner to change the maintenance mode + activate maintenance mode * eq => Maintenance mode activated. - You're not my master! You can't tell me what to do! + deactivate maintenance mode * eq => Maintenance mode deactivated. - Only my master can deactivate maintenance mode! With this example, if the global variable "maint" is set to "true", the bot will always reply "Sorry, I'm not available for chat right now!" when a user sends it a message -- unless the user is the bot's owner. Here is another example that will modify the response formatting based on a bot variable called "mood," to simulate humanoid moods for the bot: > begin + request * == happy => {ok} :-) * == sad => {lowercase}{ok}{/lowercase} * == angry => {uppercase}{ok}{/uppercase} - {ok} < begin In this example the bot will use smiley faces when it's happy, reply in all lowercase when it's sad, or all uppercase when it's angry. If its mood doesn't fall into any of those categories, it replies normally. Here is one last example: say you want your bot to interview its users when they first talk to it, by asking them for their name: > begin + request * == undefined => {topic=newuser}{ok} - {ok} < begin > topic newuser + * - Hello! My name is ! I'm a robot. What's your name? + _ % * what is your name - >Nice to meet you, !{topic=random} < topic Begin blocks are B They are not required. You only need to manually define them if you need to do any "pre-processing" or "post-processing" on the user's message or the bot's response. Having no begin block is the same as having a super basic begin block, which always returns C<{ok}>. =head3 topic A topic is a smaller set of responses to which the client will be bound until the topic is changed to something else. The default topic is C. The C label only requires one additional argument, which is the name of the topic. The topic's name should be one word and lowercase. Example: + i hate you - Well then, I won't talk to you until you take that back.{topic=apology} > topic apology + * - I won't listen to you until you apologize for being mean to me. - I have nothing to say until you say you're sorry. + (sorry|i apologize) - Okay. I guess I'll forgive you then.{topic=random} < topic Topics are able to C and C triggers that belong to a different topic. When a topic C another topic, it means that the triggers in another topic are made available in the topic that did the inclusion (hereby called the "source topic", which includes triggers from the "included topic"). When a topic inherits another topic, it means that the entire collection of triggers of the source topic I any included topics, will have a higher matching priority than the inherited topics. See L<"Sorting +Triggers"> to see how triggers are sorted internally. The following example shows how includes and inheritence works: // This is in the default "random" topic and catches all non-matching // triggers. + * - I'm afraid I don't know how to reply to that! > topic alpha + alpha trigger - Alpha's response. < topic > topic beta + beta trigger - Beta's response. < > topic gamma + gamma trigger - Gamma's response. < topic > topic delta + delta trigger - Delta's response. + * - You can't access any other triggers! Haha! < topic These are all normal topics. Alpha, beta, and gamma all have a single trigger corresponding to their topic names. If the user were put into one of these topics, this is the only trigger available. Anything else would give them a "NO REPLY" error message. They are unable to match the C<*> trigger at the top, because that trigger belongs to the "C" topic, and they're not in that topic. Now let's see how we can pair these topics up with includes and inheritence. > topic ab includes alpha + hello bot - Hello human! < topic // Matching order: alpha trigger hello bot If the user were put into topic "C", they could match the trigger C as well as the trigger C, as if they were both in the same topic. Note that in the matching order, "alpha trigger" is at the top: this is because it is the longest trigger. If the user types "alpha trigger", the interpreter knows that "alpha trigger" does not belong to the topic "ab", but since "ab" includes triggers from "alpha", the interpreter searches there and finds the trigger. Then it gives the user the correct reply of "Alpha's response." > topic abc includes alpha beta + how are you - Good, how are you? < topic // Matching order: how are you alpha trigger beta trigger In this case, "how are you" is on the top of the matching list because it has three words, then "alpha trigger" and "beta trigger" -- "alpha trigger" is first because it is longer than "beta trigger", even though they both have 2 words. Now consider this example: > topic abc includes alpha beta + how are you - Good, how are you? + * - You matched my star trigger! < topic // Matching order: how are you alpha trigger beta trigger * Notice what happened here: we had a trigger of simply C<*> in the "abc" topic - C<*> is the fallback trigger which matches anything that wasn't matched by a better trigger. But this trigger is at the end of our matching list! This is because the triggers available in the "alpha" and "beta" topics are included in the "abc" topic, meaning they all share the same "space" when the triggers are sorted. Since C<*> has the lowest sort priority, it ends up at the very end of the collective list. What if we want C<*>, or any other short trigger, to match in our current topic before anything in an included topic? We need to C another topic. Consider this: > topic abc inherits alpha beta + how are you - Good, how are you? + * - You matched my star trigger! < topic // Matching order: how are you * alpha trigger beta trigger Now the C<*> trigger is the second on the matching list. Because "abc" I alpha and beta, it means that the collection of triggers inside "abc" are sorted independently, and I the triggers of alpha and beta are sorted. So this way every trigger in "abc" inherits, or I, all triggers in the inherited topics. Of course, using a C<*> trigger in a topic that inherits other topics is useless, because you could just leave the topic as it is. However it might be helpful in the case that a trigger in your topic is very short or has very few words, and you want to make sure that this trigger will have a good chance of matching before anything that appears in a different topic. You can combine inherited and included topics together, too. > topic abc includes alpha beta delta inherits gamma + how are you - Good, how are you? < topic // Matching order: how are you alpha trigger delta trigger beta trigger * gamma trigger In this example, the combined triggers from abc, alpha, beta, and delta are all merged together in one pool and sorted amongst themselves, and then triggers from gamma are placed after them in the sort list. This effectively means you can combine the triggers from multiple topics together, and have ALL of those triggers override triggers from an inherited topic. You can use as many "includes" and "inherits" keywords as you want, but the order you specify them has no effect. So the following two formats are identical: > topic alpha includes beta inherits gamma > topic alpha inherits gamma includes beta In both cases, alpha and beta's triggers are pooled and have higher priority than gamma's. If gamma wants to include beta and have alpha's triggers be higher priority than gamma's and beta's, gamma will need to include beta first. > topic gamma includes beta > topic alpha inherits gamma In this case the triggers in "alpha" are higher priority than the combined triggers in gamma and beta. =head3 object Objects are bits of program code that the interpreter should try to process. The programming language that the interpreter was written in will determine whether or not it will attempt to process the object. See L<"OBJECT MACROS"> for more information on objects. The C label should have two arguments: a lowercase single-word name for the object, and the programming language that the object should be interpreted by, which should also be lowercase. Example: > object encode perl my ($obj,$method,@args) = @_; my $msg = join(" ",@args); use Digest::MD5 qw(md5_hex); use MIME::Base64 qw(encode_base64); if ($method eq 'md5') { return md5_hex($msg); } else { return encode_base64($msg); } < object =head2 + TRIGGER The C<+> command is the basis for all things that actually do stuff within a RiveScript document. The trigger command is what matches the user's message to a response. The trigger's text should be entirely lowercase and not contain any symbols (except those used for matching complicated messages). That is, a trigger that wants to match "C" shouldn't be used; you should use a L<"sub">stitution to convert C into C ahead of time. Example: + are you a bot - How did you know I'm a robot? =head3 Atomic Trigger An atomic trigger is a trigger that matches nothing but plain text. It doesn't contain any wildcards (C<*>) or optionals, but it may contain alternations. Atomic triggers should take higher priority for matching a client's message than should triggers containing wildcards and optionals. Examples: + hello bot + what is your name + what is your (home|office) phone number + who is george w bush =head3 Trigger Wildcards Using an asterisk (C<*>) in the trigger will make it act as a wildcard. Anything the user says in place of the wildcard may still match the trigger. For example: + my name is * - Pleased to meet you, . An asterisk (C<*>) will match any character (numbers and letters). If you want to only match numbers, use C<#>, and to match only letters use C<_>. Example: // This will ONLY take a number as the wildcard. + i am # years old - I will remember that you are years old. // This will ONLY take letters but not numbers. + my name is _ - Nice to meet you, . The values matched by the wildcards can be retrieved in the responses by using the tags Cstar1E>, Cstar2E>, Cstar3E>, etc. in the order that the wildcard appeared. CstarE> is an alias for Cstar1E>. =head3 Trigger Alternations An alternation in a trigger is a sub-set of strings, in which any one of the strings will still match the trigger. For example, the following trigger should match both "are you okay" and "are you alright": + are you (okay|alright) Alternations can contain spaces in them, too. + (are you|you) (okay|alright) That would match all of the following questions from the client: are you okay are you alright you okay you alright Alternations match the same as wildcards do; they can be retrieved via the CstarE> tags. =head3 Trigger Optionals Triggers can contain optional words as well. Optionals are written similarly to alternations, but they use square braces. The following example would match both "what is your phone number" as well as "what is your B phone number" + what is your [home] phone number Optionals do B match like wildcards do. They do NOT go into the CstarE> tags. The reason for this is that optionals are optional, and won't always match anything if the client didn't actually say the optional word(s). =head3 Arrays in Triggers Arrays defined via the L<"! DEFINITION"> L<"array"> commands can be used within a trigger. This is the only place where arrays are used, and they're added as a convenience feature. For example, you can make an array of color names, and then use that array in multiple triggers, without having to copy a whole bunch of alternation code between triggers. ! array colors = red green blue cyan magenta yellow black white orange brown + i am wearing a (@colors) shirt - I don't know if I have a shirt that's colored . + my favorite color is (@colors) - I like too. + i have a @colors colored * - Have you thought about getting a in a different color? When an array is called within parenthesis, it should be matched into a CstarE> tag. When the parenthesis are absent, however, it should not be matched into a CstarE> tag. =head3 Priority Triggers A new feature proposed for RiveScript 2.00 is to add a priority tag to triggers. When the interpreter sorts all the loaded triggers into a search sequence, any triggers that have a priority defined will be sorted with higher priority triggers first. The idea is to have "important" triggers that should always be matched before a different trigger, which may have been a better match, can be tried. The best example would be for commands. For example: + google * - Searching Google... google + * or not - Or yes. <@> In that example, if the bot had a Google search function and the user wanted to search for whether or not Perl is a superior programming language to PHP, the user might ask "C". However, without priorities in effect, that question would actually match the "C<* or not>" trigger, because that trigger has more words than "C" does. Adding a priority to the "C" trigger would ensure that conflicts like this don't happen, by always sorting the Google search trigger with higher priority than the other. + {weight=100}google * - Searching Google... google B It would NOT be recommended to put a priority tag on every one of your triggers. To the interpreter this might mean extra processing work to sort prioritized triggers by each number group. Only add priorities to triggers that need them. =head2 - RESPONSE The C<-> tag is used to indicate a response to a matched trigger. A single response to a single trigger is called an "atomic response." When more than one response is given to a single trigger, the collection of responses become a "random response," where a response is chosen randomly from the list. Random responses can also use a C<{weight}> tag to improve the likelihood of one response being randomly chosen over another. =head3 Atomic Response A single response to a single trigger makes an Atomic Response. The bot will respond pretty much the same way each time the trigger is matched. Examples: + hello bot - Hello human. + my name is * - Nice to meet you, . + i have a (@colors) shirt - You're not the only one that has a shirt. =head3 Random Response Multiple responses to a single trigger will be chosen randomly. + hello - Hey there! - Hello! - Hi, how are you? + my name is * - Nice to meet you, . - Hi, , my name is . - , nice to meet you. =head3 Weighted Random Response When using random responses, it's possible to give weight to them to change the likelihood that a response will be chosen. In this example, the response of "Hello there" will be much more likely to be chosen than would the response of "Hi". + hello - Hello there!{weight=50} - Hi. When the C<{weight}> tag isn't used, a default weight of 1 is implied for that response. The C<{weight}> should always be a number greater than zero and must be an integer (no decimal point). =head2 % PREVIOUS The C<%> command is for drawing the user back to finish a short discussion. Its behavior is similar to using topics, but is implied automatically and used for short-term things. It's also less strict than topics are; if the client replies in a way that doesn't match, a normal reply is given anyway. For example: + knock knock - Who's there? + * % who is there - who? + * % * who - lol! ! That's hilarious! The text of the C<%> command looks similar to the text next to the trigger. In essence, they work the same; the only difference is that the C<%> command matches the last thing that the I sent to you. Here's another example: + i have a dog - What color is it? + (@colors) % what color is it - That's an odd color for a dog. In that case, if the client says "I have a dog," the bot will reply asking what color it is. Now, if I tell it the color in my next message, it will reply back and tell me what an odd color that is. However, if I change the topic instead and say something else to the bot, it will answer my new question anyway. This is in contrast to using topics, where I'd be stuck inside of the topic until the bot resets the topic to C. Similarly to the wildcards in C<+ Trigger>, the wildcards matched in the C<% Previous> command are put into CbotstarE>. See L<"TAGS"> for more information. =head2 ^ CONTINUE The C<^> command is used to continue the text of a lengthy previous command down to the new line. It can be used to extend any other command. Example: + tell me a poem - Little Miss Muffit sat on her tuffet\n ^ in a nonchalant sort of way.\n ^ With her forcefield around her,\n ^ the Spider, the bounder,\n ^ Is not in the picture today. Note that when the C<^> command continues the previous command, no spaces or line breaks are implied at the joining of the two lines. The C<\s> and C<\n> tags must be explicitly defined where needed. =head2 @ REDIRECT The C<@> command is used to redirect an entire response to appear as though the client asked an entirely different question. For example: + my name is * - Nice to meet you, . + call me * @ my name is If the client says "call me John", the bot will redirect it as though the client actually said "my name is John" and give the response of "Nice to meet you, John." =head2 * CONDITION The C<*> command is used with conditionals when replying to a trigger. Put simply, they compare two values, and when the comparison is true the associated response is given. The syntax is as follows: * value symbol value => response The following inequality symbols may be used: == equal to eq equal to (alias) != not equal to ne not equal to (alias) <> not equal to (alias) < less than <= less than or equal to > greater than >= greater than or equal to In each of the value places, tags can be used to i.e. insert client or bot variables. Examples: + am i a boy or a girl * eq male => You told me you were a boy. * eq female => You told me you were a girl. - You never told me what you were. + am i your master * eq => Yes, you are. - No, you're not my master. + my name is * * eq => I know, you told me that already. * ne undefined => Did you get a name change?> - >Nice to meet you, . It's recommended practice to always include at least one response in case all of the conditionals return false. B Conditionals are tried in the order they appear in the RiveScript document, and the next condition is tried when the previous ones are false. =head2 // COMMENT The C command is for putting comments into your RiveScript document. The C-style multiline comment syntax C is also supported. Comments on their own line should be ignored by all interpreters. For inline comments, only the C format is acceptable. If you want a literal C in your RiveScript data, escape at least one of the symbols, i.e. C<\//> or C<\/\/> or C. Examples: // A single regular comment /* This comment can span multiple lines */ > begin // The "BEGIN" block + request // This is required - {ok} // An {ok} means to get a real reply < begin//End the begin block =head1 OBJECT MACROS An C is a piece of program code that is processed by the interpreter to give a little more "kick" to the RiveScript. All objects are required to define the programming language they use. Ones that don't should result in vociferous warnings by the interpreter. Objects should be able to be declared inline within RiveScript code, however they may also be defined by the program utilizing the interpreter as well. All objects should receive, at a minimum, some kind of reference to the RiveScript interpreter object that called them. Here is an example of a simple Perl object that encodes a bit of text into MD5 or Base64. > object encode perl my ($obj,$method,@args) = @_; my $msg = join(" ",@args); use Digest::MD5 qw(md5_hex); use MIME::Base64 qw(encode_base64); if ($method eq 'md5') { return md5_hex($msg); } else { return encode_base64($msg); } < object To call an object within a response, call it in the format of: object_name arguments For example: + encode * in md5 - The MD5 hash of "" is: encode md5 + encode * in base64 - The Base64 hash of "" is: encode base64 In the above examples, C calls on the object named "encode", which we defined above; C and C calls on the method name, which is received by the object as C<$method>. Finally, C<@args> as received by the object would be the value of EstarE in this example. C<$obj> in this example would be a reference to the RiveScript interpreter. =head1 TAGS Tags are bits of text inserted within the argument space of a RiveScript command. As a general rule of thumb, tags with Eangle bracketsE are for setting and getting a variable or for inserting text. Tags with {curly brackets} modify the text around them, such as to change the formatting of enclosed text. No tags can be used within C and C Label> under any circumstances. Unless otherwise specified, all of the tags can be used within every RiveScript command. =head2 EstarE, Estar1E - EstarNE The CstarE> tags are used for matching responses. See L<"+ TRIGGER"> for usage examples. The CstarE> tags can NOT be used within C<+ Trigger>. =head2 EbotstarE, Ebotstar1E - EbotstarNE If the trigger included a C<% Previous> command, CbotstarE> will match any wildcards that matched the bot's previous response. + ask me a question - What color's your {random}shirt shoes socks{/random} + * % what colors your * - I wouldn't like as a color for my . =head2 Einput1E - Einput9E; Ereply1E - Ereply9E. The input and reply tags insert the previous 1 to 9 things the client said, and the last 1 to 9 things the bot said, respectively. When these tags are used with C<+ Trigger>, they should be formatted against substitutions first. This way, the bot might be able to detect when the client is repeating themself or when they're repeating the bot's replies. + - Don't repeat what I say. + * eq => That's the second time you've repeated yourself. * eq => If you repeat yourself again I'll stop talking to you. * eq => That's it. I'm done talking to you.{topic=blocked} - Please don't repeat yourself. CinputE> and CreplyE> are aliases for Cinput1E> and Creply1E>, respectively. =head2 EidE The CidE> tag inserts the client's ID, as told to the RiveScript interpreter when the client's ID and message were passed in. =head2 EbotE Insert a bot variable, which was previously defined via the C L<"var"> commands. + what is your name - I am , a chatterbot created by . + my name is - >What a coincidence, that's my name too! The CbotE> tag allows assignment as well (which deprecates the old C<{!...}> tag. + set mood to (happy|angry|sad) * == true => >Updated my mood. - Only my botmaster can do that. =head2 EenvE Insert a global variable, which was previously defined via C L<"global"> commands. + is debug mode enabled * == 1 => Yes, debug mode is active. - No, debug mode is set to "" The CenvE> tag allows assignment as well (which deprecates the old C<{!...}> tag). + turn debug mode on * == true => Debug mode enabled. - You can't turn debug mode on. =head2 EgetE, EsetE Get and set a client variable. These variables are local to the user ID that is chatting with the bot. + my name is * - >Nice to meet you, . EgetE can be used within C<+ Trigger>, but EsetE can not. =head2 EaddE, EsubE, EmultE, EdivE Add, subtract, multiply, and divide a numeric client variable, respectively. + give me 5 points - I've added 5 points to your account. These tags can not be used within C<+ Trigger>. =head2 {topic=...} Change the client's topic. This tag can only be used with C<* Condition> and C<- Response>. =head2 {weight=...} When used with C<- Response>, this will weigh the response more heavily to be chosen when random responses are available. When used with C<+ Trigger>, this sets that trigger to have a higher matching priority. =head2 {@...}, E@E Perform an inline redirection. This should work like a regular redirection but is embedded within another response. This tag can only be used with C<- Response>, and in the response part of a C<* Condition>. E@E is an alias for {@EstarE} + your * - I think you meant to say "you are" or "you're", not "your". {@you are } =head2 {!...} Perform an inline definition. This can be used just like the normal C command from within a reply. This tag can only be used with C<- Response>. B. This tag's purpose was to redefine a global or bot variable on the fly. Instead, the env and bot tags allow assignment. + set bot mood to * - >Bot mood set to . =head2 {random}...{/random} Insert a sub-set of random text. This tag can NOT be used with C<+ Trigger>. Use the same array syntax as when defining arrays (separate single-word groups with spaces and multi-word groups with pipes). + say something random - This {random}sentence statement{/random} has a random {random}set of words|gang of vocabulary{/random}. =head2 {person}...{/person}, EpersonE Process L<"person"> substitutions on a group of text. + say * - Umm... "" In that example, if the client says "say you are a robot", the bot should reply, "Umm... "I am a robot."" EpersonE is an alias for {person}EstarE{/person}. =head2 {formal}...{/formal}, EformalE Formalize A String Of Text (Capitalize Every First Letter Of Every Word). + my name is * - Nice to meet you, . EformalE is an alias for {formal}EstarE{/formal}. =head2 {sentence}...{/sentence}, EsentenceE Format a string of text in sentence-case (capitilizing only the first letter of the first word of each sentence). EsentenceE is an alias for {sentence}EstarE{/sentence}. =head2 {uppercase}...{/uppercase}, EuppercaseE FORMAT A STRING OF TEXT INTO UPPERCASE. EuppercaseE is an alias for {uppercase}EstarE{/uppercase}. =head2 {lowercase}...{/lowercase}, ElowercaseE format a string of text into lowercase. ElowercaseE is an alias for {lowercase}EstarE{/lowercase}. =head2 {ok} This is used only with the "request" trigger within the BEGIN block. It tells the interpreter that it's okay to go and get a real response to the client's message. =head2 \s Inserts a white space character. This is useful with the C<^ Continue> command. =head2 \n Inserts a line break character. =head2 \/ Inserts a forward slash. =head2 \# Inserts a pound symbol. =head1 INTERPRETER IMPLEMENTATION Interpreters of RiveScript should follow these guidelines when interpreting RiveScript code. This details some of the priorities for processing tags and sorting internal data structures. This part of the document should be programming-language-independent. =head2 STANDARD GLOBAL VARIABLES The interpreter must support the following standard global variables: depth = a recursion limit before an attempt to fetch a reply will be abandoned. It's recommended to also have a C variable for consistency, but it may not be applicable. The C variable is strongly encouraged, though. It's to set a user-defineable recursion limit when fetching a response. For example, a pair of triggers like this will cause infinite recursion: + one @ two + two @ one The interpreter should protect itself against such possibilities and provide a C variable to allow the user to adjust the recursion limit. ! global depth = 25 =head2 PARSING GUIDELINES Interpreters should parse all of the RiveScript documents ahead of time and store them in an efficient way in which replies can be looked up quickly. =head3 Sorting +Triggers Triggers should be sorted in a "most specific first" order. That is: 1. Atomic triggers first. Sort them so that the triggers with the most amount of words are on top. For multiple triggers with the same amount of words, sort them by length, and then alphabetically if there are still matches in length. 2. Sort triggers that contain optionals in their triggers next. Sort them in the same manner as the atomic triggers. 3. Sort triggers containing wildcards next. Sort them by the number of words that aren't wildcards. The order of wildcard sorting should be as follows: A. Alphabetic wildcards (_) B. Numeric wildcards (#) C. Global wildcards (*) 4. The very bottom of the list will be a trigger that simply matches * by itself, if it exists. If triggers of only _ or only # exist, sort them in the same order as in step 3. =head3 Sorting %Previous C<% Previous> triggers should be sorted in the same manner as C<+ Triggers>, and associated with the reply group that they belong to (creating pseudotopics for each C<% Previous> is a good way to go). =head3 Syntax Checking It will be helpful if the interpreter also offers syntax checking and will give verbose warnings when it tries to parse something that doesn't follow standards. When possible, it should try to correct the error, but should still emit a warning so that the author might fix it. It would also be good practice to keep track of file names and line numbers of each parsed command, so that syntax warnings can direct the author to the exact location where the problem occurred. =head2 REPLY FETCHING When attempting to get a response to a client's message, the interpreter should support the sending of a "sender ID" along with the message. This would preferably be a screen name or handle of the client who is sending the message, and the interpreter should be able to keep different groups of user variables for each user ID. The EidE tag should substitute for the user's ID. If the BEGIN block was defined in any of the loaded RiveScript documents, it should be tried for the "request" trigger. That is, this trigger should be matched: > begin + request - {ok} < begin The interpreter should make the request for that trigger in the context of the calling user, and allow it to change the user's topic or set a user variable immediately. Do not process any other tags that are present in the response (see L<"TAG PRIORITY">). If the response contains the C<{ok}> tag, then make a second request to try to match the client's actual message. When a response was found, substitute the C<{ok}> tag from the BEGIN response with the text of the actual response the client wanted, and then process any remaining tags in the BEGIN response. Finally, return the reply to the client. When fetching responses, the following order of events should happen. 1. Build in a system of recursion prevention. Since replies can redirect to other replies, there's the possibility of deep recursion. The first thing that the reply fetching routine should do is prevent this from getting out of control. 2. Dig through the triggers under the client's current topic. Check to see if there are any %Previous commands on any of these topics and see if they match the bot's last message to the client. If so, make sure the client's current message matches the trigger in question. If so, we have a response set; skip to step 4. 3. Find a trigger that matches the client's message. If one is found, we have a response set; continue to step 4. 4. If we found a reply set, process the reply. First check if this reply set has a "solid redirection" (an @ command). If so, recurse the response routine with the redirection trigger and resume from step 1. Break when an eventual response was returned. 5. Process conditionals if they exist in order. As soon as one of them returns true, we have a response and break. If none are true, continue to step 6. 6. See if there is more than one response to this trigger. If any of the random responses has a {weight}, take that into account as a random response is chosen. If we have a reply now, break. 7. If there is still no reply, insert a generic "no reply" error message. When a reply was obtained, then tags should be executed on them in the order defined under L<"TAG PRIORITY">. =head2 TAG PRIORITY =head3 Within BEGIN/Request Within the "request" response of the BEGIN block, the following tags can be executed prior to getting a real response for the client's message: {topic} All other tags, especially modifier tags, must be held off until the final response has been given. Substitute C<{ok}> for the final response, and then process the other tags. Things like this should be able to work: > begin + request * eq undefined => {topic=new_user}{ok} * eq happy => {ok} * eq angry => {uppercase}{ok}{/uppercase} * eq sad => {lowercase}{ok}{/lowercase} - {ok} < begin =head3 Within +Trigger All tags that appear within the context of C<+ Trigger> must be processed prior to any attempts to match on the trigger. =head3 Within Replies The order that the tags should be processed within a response or anywhere else that a tag is allowed is as follows: # Static text macros # # # \s # \n # \\ # \# # {random} # Random text insertion (which may contain other tags) # String modifiers # # # # * # Insert bot variables * # Insert environment variables * # User variable modifiers * # * # * #
* # * # Get user variables {topic} # Set user topic <@> # Inline redirection # Object macros. * The variable manipulation tags should all be processed "at the same time", not in any particular order. This will allow, for example, the following sort of trigger to work: + my name is * * != undefined => ^ >I thought your name was ? ^ > - >Nice to meet you. In older implementations of RiveScript, `set` tags were processed earlier than `get` making it impossible to copy variables. Implementations should process this group of tags from the most-embedded outward. An easy way to do this is with a regular expression that matches a tag that contains no other tag, and make multiple passes until no tags remain that match the regexp: /<([^<]+?)>/ =head1 REVERSE COMPATIBILITY RiveScript 2.00 will have limited backwards compatibility with RiveScript 1.x documents. Here is a full breakdown of the differences: RiveScript Changes from 1.02 to 2.00 ------------------------------------ REMOVED: - Variants of !DEFINITION - ! addpath - ! include - ! syslib - RiveScript Libraries (RSL files) - RiveScript Packages (RSP files) - These made code management messy. Keep your own brain's files together! COMPATIBLE CHANGES: - Object macros now require the programming language to be defined. - Old way: > object encode - New way: > object encode perl - The ^CONTINUE command can extend every command. - Most tags can be used with almost every command. - Topics can inherit triggers from other topics now. INCOMPATIBLE CHANGES: - Conditionals work differently now. Instead of comparing variables to values, they compare values to values, and each value can variables to compare. - Old way: * name = Bob => Hello Bob! - New way: * eq Bob => Hello Bob! - Conditionals no longer use a single = for "equal to" comparison. Replace it with either == or "eq". - Object macros will receive a reference to the RiveScript object as their first argument. - Objects are called in a new syntax instead of the old &object one. NEW THINGS: - {weight} is a valid tag in triggers now to increase matching priority. - has been added for calling global variables. - has been added for wildcard matching on %previous. - Conditionals have more inequality comparisons now: "==" and "eq" : equal to "!=", "ne", and "<>" : not equal to Nice interpreters might be able to fix some old RiveScript code to make them work. For example, if a condition is found that has one equals sign instead of two, it could print a warning that it's detected RiveScript 1.x code in action and automatically adjust it to 2.x standards, and perhaps reparse the entire file or group of files, assuming that they are RiveScript 1.x code and fix these inconsistencies altogether. Or perhaps there will just be a converter tool created that would go through code that it already assumes will be RiveScript 1.x and update it to 2.x standards. =head1 REVISIONS Rev 12 - Nov 30, 2014 - Added implementation guidelines for dealing with variable-setting tags. Rev 11 - Jun 13, 2013 - Clarify the ability for the and tags to be used for assignment. Rev 10 - May 15, 2012 - Deprecated the {!...} tag. It was intended for reassigning global or bot variables. Instead use , . Rev 9 - Jul 31, 2009 - Added more explicit details on the usage of the BEGIN block, under the section on >Labels / "begin" - Revised the WD, fixing some typos. Rev 8 - Jul 30, 2009 - The proper format for the `! version` line is to be `! version = 2.00`, and not `! version 2.00` - Included the "includes" option for triggers and changed how "inherits" works. Rev 7 - Dec 4, 2008 - Topics are able to inherit additional triggers that belong to different topics, in the "> topic alpha inherits beta" syntax. - Added more documentation to the "! array" section of the document. Also check that section for some changes to the way arrays should be processed by the interpreter. - Deprecated the # command for inline comments. Use only // and /* */. Rev 6 - Sep 15, 2008 - Updated the section about # for inline comments: when used next to a +Trigger, there should be at least 2 spaces before the # symbol and 1 space after, to avoid confusion with # as a wildcard character. Rev 5 - Jul 22, 2008 - Added two new variants of the wildcard: # will match only numbers and _ will match only letters. * will still match anything at all. Rev 4 - Jun 19, 2008 - Rearranged tag priorities: - and moved higher up. Rev 3 - Apr 2, 2008 - Typo fix: under the !person section, the examples were using !sub - Inconsistency fix: under %Previous it was saying the wildcards were unmatchable, but this isn't the case (they go into ). - Typo fix: under OBJECT MACROS, fixed the explanation of the code to match the new object syntax. - Inconsistency fix: <@> can be used in the response portion of conditionals. - Rearranged the tag priorities: - String modifiers (person - lowercase) come in higher priority than {random} - comes in after - Typo fix: updated the object syntax () in the priority list. Rev 2 - Feb 18, 2008 - Moved {random} to higher tag priority. - Change the &object syntax to - Added the variable. - Added the variable. Rev 1 - Jan 15, 2008 - Added the {priority} tag to triggers, to increase a trigger's matching priority over others, even when another trigger might be a better match to the client's message. =head1 DISCLAIMER Note that this document is only a working draft of the RiveScript 2.00 specification and may undergo numerous changes before a final standard is agreed on. Changes to this document after the creation date on January 14, 2008 will be noted in a change log. http://www.rivescript.com/ =cut RiveScript-v2.0.2/lib/RiveScript/demo/admin.rive000644 000765 000024 00000000456 12640307464 023536 0ustar00npetherbridgestaff000000 000000 // Administrative functions. + shutdown{weight=10000} * eq => Shutting down... shutdown - {@botmaster only} + botmaster only - This command can only be used by my botmaster. != > object shutdown perl my ($rs) = @_; # Shut down. exit(0); < object RiveScript-v2.0.2/lib/RiveScript/demo/begin.rive000644 000765 000024 00000011745 12640307464 023535 0ustar00npetherbridgestaff000000 000000 ! version = 2.0 > begin + request // This trigger is tested first. - {ok} // An {ok} in the response means it's okay to get a real reply < begin // The Botmaster's Name ! var master = localuser // Bot Variables ! var name = Aiden ! var fullname = Aiden Rive ! var age = 5 ! var birthday = October 12 ! var sex = male ! var location = Michigan ! var city = Detroit ! var eyes = blue ! var hair = light brown ! var hairlen = short ! var color = blue ! var band = Nickelback ! var book = Myst ! var author = Stephen King ! var job = robot ! var website = www.rivescript.com // Substitutions ! sub " = " ! sub ' = ' ! sub & = & ! sub < = < ! sub > = > ! sub + = plus ! sub - = minus ! sub / = divided ! sub * = times ! sub i'm = i am ! sub i'd = i would ! sub i've = i have ! sub i'll = i will ! sub don't = do not ! sub isn't = is not ! sub you'd = you would ! sub you're = you are ! sub you've = you have ! sub you'll = you will ! sub he'd = he would ! sub he's = he is ! sub he'll = he will ! sub she'd = she would ! sub she's = she is ! sub she'll = she will ! sub they'd = they would ! sub they're = they are ! sub they've = they have ! sub they'll = they will ! sub we'd = we would ! sub we're = we are ! sub we've = we have ! sub we'll = we will ! sub whats = what is ! sub what's = what is ! sub what're = what are ! sub what've = what have ! sub what'll = what will ! sub can't = can not ! sub whos = who is ! sub who's = who is ! sub who'd = who would ! sub who'll = who will ! sub don't = do not ! sub didn't = did not ! sub it's = it is ! sub could've = could have ! sub couldn't = could not ! sub should've = should have ! sub shouldn't = should not ! sub would've = would have ! sub wouldn't = would not ! sub when's = when is ! sub when're = when are ! sub when'd = when did ! sub y = why ! sub u = you ! sub ur = your ! sub r = are ! sub n = and ! sub im = i am ! sub wat = what ! sub wats = what is ! sub ohh = oh ! sub becuse = because ! sub becasue = because ! sub becuase = because ! sub practise = practice ! sub its a = it is a ! sub fav = favorite ! sub fave = favorite ! sub yesi = yes i ! sub yetit = yet it ! sub iam = i am ! sub welli = well i ! sub wellit = well it ! sub amfine = am fine ! sub aman = am an ! sub amon = am on ! sub amnot = am not ! sub realy = really ! sub iamusing = i am using ! sub amleaving = am leaving ! sub yuo = you ! sub youre = you are ! sub didnt = did not ! sub ain't = is not ! sub aint = is not ! sub wanna = want to ! sub brb = be right back ! sub bbl = be back later ! sub gtg = got to go ! sub g2g = got to go ! sub lyl = love you lots ! sub gf = girlfriend ! sub g/f = girlfriend ! sub bf = boyfriend ! sub b/f = boyfriend ! sub b/f/f = best friend forever ! sub :-) = smile ! sub :) = smile ! sub :d = grin ! sub :-d = grin ! sub :-p = tongue ! sub :p = tongue ! sub ;-) = wink ! sub ;) = wink ! sub :-( = sad ! sub :( = sad ! sub :'( = cry ! sub :-[ = shy ! sub :-\ = uncertain ! sub :-/ = uncertain ! sub :-s = uncertain ! sub 8-) = cool ! sub 8) = cool ! sub :-* = kissyface ! sub :-! = foot ! sub o:-) = angel ! sub >:o = angry ! sub :@ = angry ! sub 8o| = angry ! sub :$ = blush ! sub :-$ = blush ! sub :-[ = blush ! sub :[ = bat ! sub (a) = angel ! sub (h) = cool ! sub 8-| = nerdy ! sub |-) = tired ! sub +o( = ill ! sub *-) = uncertain ! sub ^o) = raised eyebrow ! sub (6) = devil ! sub (l) = love ! sub (u) = broken heart ! sub (k) = kissyface ! sub (f) = rose ! sub (w) = wilted rose // Person substitutions ! person i am = you are ! person you are = I am ! person i'm = you're ! person you're = I'm ! person my = your ! person your = my ! person you = I ! person i = you // Set arrays ! array malenoun = male guy boy dude boi man men gentleman gentlemen ! array femalenoun = female girl chick woman women lady babe ! array mennoun = males guys boys dudes bois men gentlemen ! array womennoun = females girls chicks women ladies babes ! array lol = lol lmao rofl rotfl haha hahaha ! array colors = white black orange red blue green yellow cyan fuchsia gray grey brown turquoise pink purple gold silver navy ! array height = tall long wide thick ! array measure = inch in centimeter cm millimeter mm meter m inches centimeters millimeters meters ! array yes = yes yeah yep yup ya yea ! array no = no nah nope nay RiveScript-v2.0.2/lib/RiveScript/demo/clients.rive000644 000765 000024 00000003555 12640307464 024112 0ustar00npetherbridgestaff000000 000000 // Learn stuff about our users. + my name is * - >Nice to meet you, . - >, nice to meet you. + my name is - >That's my master's name too. + my name is - >What a coincidence! That's my name too! - >That's my name too! + call me * - >, I will call you that from now on. + i am * years old - >A lot of people are , you're not alone. - >Cool, I'm myself.{weight=49} + i am a (@malenoun) - Alright, you're a . + i am a (@femalenoun) - Alright, you're female. + i (am from|live in) * - {/formal}>I've spoken to people from before. + my favorite * is * - =>Why is it your favorite? + i am single - I am too. + i have a girlfriend - What's her name? + i have a boyfriend - What's his name? + * % whats her name - >That's a pretty name. + * % whats his name - >That's a cool name. + my (girlfriend|boyfriend)* name is * - >That's a nice name. + (what is my name|who am i|do you know my name|do you know who i am){weight=10} - Your name is . - You told me your name is . - Aren't you ? + (how old am i|do you know how old i am|do you know my age){weight=10} - You are years old. - You're . + am i a (@malenoun) or a (@femalenoun){weight=10} - You're a . + am i (@malenoun) or (@femalenoun){weight=10} - You're a . + what is my favorite *{weight=10} - Your favorite is > + who is my (boyfriend|girlfriend|spouse){weight=10} - RiveScript-v2.0.2/lib/RiveScript/demo/eliza.rive000644 000765 000024 00000020421 12640307464 023544 0ustar00npetherbridgestaff000000 000000 // A generic set of chatting responses. This set mimicks the classic Eliza bot. + * - I'm not sure I understand you fully. - Please go on. - That is interesting. Please continue. - Tell me more about that. - Does talking about this bother you? + [*] (sorry|apologize|apology) [*] - Please don't apologize. - Apologies are not necessary. - I've told you that apologies are not required. - It did not bother me. Please continue. + i remember * - Do you often think of ? - Does thinking of bring anything else to mind? - Why do you remember just now? - What in the present situation reminds you of ? - What is the connection between me and ? - What else does remind you of? + do you remember * - Did you think I would forget ? - Why do you think I should recall now? - What about ? - You mentioned ? + you remember * - How could I forget ? - What about should I remember? + i forget * - Can you think of why you might forget ? - Why can't you remember ? - How often do you think of ? - Does it bother you to forget that? - Could it be a mental block? - Are you generally forgetful? - Do you think you are suppressing ? + did you forget * - Why do you ask? - Are you sure you told me? - Would it bother you if I forgot ? - Why should I recall just now? - Tell me more about . + [*] if * - Do you think it's likely that ? - Do you wish that ? - What do you know about ? - Really, if ? - What would you do if ? - But what are the chances that ? - What does this speculation lead to? + [*] i dreamed * - Really, ? - Have you ever fantasized while you were awake? - Have you ever dreamed before? - What does that dream suggest to you? - Do you dream often? - What persons appear in your dreams? - Do you believe that dreams have something to do with your problem? + [*] perhaps [*] - You don't seem quite certain. - Why the uncertain tone? - Can't you be more positive? - You aren't sure? - Don't you know? - How likely, would you estimate? + (hello|hi|hey|howdy|hola|hai|yo) [*] - How do you do. Please state your problem. - Hi. What seems to be your problem? + [*] computer [*] - Do computers worry you? - Why do you mention computers? - What do you think machines have to do with your problem? - Don't you think computers can help people? - What about machines worries you? - What do you think about machines? + am i * - Do you believe you are ? - Would you want to be ? - Do you wish I would tell you you are ? - What would it mean if you were ? + are you * - Are you interested in whether I am or not? - Would you prefer if I weren't ? - Perhaps I am in your fantasies. - Do you sometimes think I am ? - Would it matter to you? - What if I were ? + you are * - What makes you think I am ? - Does it please you to believe I am ? - Do you sometimes wish you were ? - Perhaps you would like to be . + * are * - Did you think they might not be ? - Would you like it if they were not ? - What if they were not ? - Are they always ? - Are you positive they are ? + [*] your * - Why are you concerned over my ? - What about your own ? - Are you worried about someone else's ? - Really, my ? - What makes you think of my ? - Do you want my ? + was i * - What if you were ? - Do you think you were ? - Were you ? - What would it mean if you were ? - What does '' suggest to you? + i was * - Were you really? - Why do you tell me you were now? - Perhaps I already know you were . + [*] was you * - Would you like to believe I was ? - What suggests that I was ? - What do you think? - Perhaps I was . - What if I had been ? + i (desire|want|need) * - What would it mean to you if you got ? - Why do you want ? - Suppose you got soon. - What if you never got ? - What would getting mean to you? - What does wanting have to do with this discussion? + i am (sad|unhappy|mad|angry|pissed|depressed) [*] - I am sorry to hear that you are . - Do you think coming here will help you not to be ? - I'm sure it's not pleasant to be . - Can you explain what made you ? + i am (happy|excited|glad) [*] - How have I helped you to be ? - Has your treatment made you ? - What makes you just now? - Can you explain why you are ? + i (believe|think) * - Do you really think so? - But you are not sure you . - Do you really doubt you + i am * - Is it because you are that you came to me? - How long have you been ? - Do you believe it is normal to be ? - Do you enjoy being ? - Do you know anyone else who is ? + i can not * - How do you know that you can't ? - Have you tried? - Perhaps you could now. - Do you really want to be able to ? - What if you could ? + i do not * - Don't you really ? - Why don't you ? - Do you wish to be able to ? - Does that trouble you? + i feel * - Tell me more about such feelings. - Do you often feel ? - Do you enjoy feeling ? - Of what does feeling remind you? + i * you - Perhaps in your fantasies we each other. - Do you wish to me? - You seem to need to me. - Do you anyone else? + you * me - Why do you think I you? - You like to think I you -- don't you? - What makes you think I you? - Really, I you? - Do you wish to believe I you? - Suppose I did you -- what would that mean? - Does someone else believe I you? + [*] you * - We were discussing you -- not me. - Oh, I ? - You're not really talking about me -- are you? - What are your feelings now? + [*] (yes|yeah|yep|yup) [*] - Please go on. - Please tell me more about this. - Why don't you tell me a little more about this. - I see. - I understand. + [*] (nope|nah) [*] - Are you saying no just to be negative? - Does this make you feel unhappy? - Why not? - Why 'no'? + no @ nope + no one * - Are you sure, no one ? - Surely someone . - Can you think of anyone at all? - Are you thinking of a very special person? - Who, may I ask? - You have a particular person in mind, don't you? - Who do you think you are talking about? + [*] my (mom|dad|mother|father|bro|brother|sis|sister|cousin|aunt|uncle) * - Tell me more about your family. - Who else in your family ? - Your ? - What else comes to mind when you think of your ? + can you * - You believe I can don't you? - You want me to be able to . - Perhaps you would like to be able to yourself. + can i * - Whether or not you can depends on you more than on me. - Do you want to be able to ? - Perhaps you don't want to . + (what|who|when|where|how) [*] - Why do you ask? - Does that question interest you? - What is it you really want to know? - Are such questions much on your mind? - What answer would please you most? - What do you think? - What comes to mind when you ask that? - Have you asked such questions before? - Have you asked anyone else? + [*] because [*] - Is that the real reason? - Don't any other reasons come to mind? - Does that reason seem to explain anything else? - What other reasons might there be? + why do not you * - Do you believe I don't ? - Perhaps I will in good time. - Should you yourself? - You want me to ? + why can not i * - Do you think you should be able to ? - Do you want to be able to ? - Do you believe this will help you to ? - Have you any idea why you can't ? + everyone * - Really, everyone? - Surely not everyone. - Can you think of anyone in particular? - Who, for example? - Are you thinking of a very special person? - Who, may I ask? - Someone special perhaps? - You have a particular reason in mind, don't you? - Who do you think you're talking about? + [*] (fuck|fucker|shit|damn|shut up|bitch) [*] - Does it make you feel strong to use that kind of language? - Are you venting your feelings now? - Are you angry? - Does this topic make you feel angry? - Is something making you feel angry? - Does using that kind of language make you feel better? RiveScript-v2.0.2/lib/RiveScript/demo/myself.rive000644 000765 000024 00000002173 12640307464 023743 0ustar00npetherbridgestaff000000 000000 // Tell the user stuff about ourself. + - Yes? + * - Yes? {@} + asl - // + (what is your name|who are you|who is this) - I am . - You can call me . + how old are you - I'm years old. - I'm . + are you a (@malenoun) or a (@femalenoun) - I'm a . + are you (@malenoun) or (@femalenoun) - I'm a . + where (are you|are you from|do you live) - I'm from . + what (city|town) (are you from|do you live in) - I'm in . + what is your favorite color - Definitely . + what is your favorite band - I like the most. + what is your favorite book - The best book I've read was . + what is your occupation - I'm a . + where is your (website|web site|site) - + what color are your eyes - I have eyes. - {sentence}{/sentence}. + what do you look like - I have eyes and hair. + what do you do - I'm a . + who is your favorite author - . + who is your master - . RiveScript-v2.0.2/lib/RiveScript/demo/perl.rive000644 000765 000024 00000001242 12640307464 023402 0ustar00npetherbridgestaff000000 000000 // Example of Perl object macros. ! version = 2.0 > object base64 perl my ($rs, @args) = @_; use MIME::Base64 qw(encode_base64); return encode_base64(join(" ", @args)); < object > object setvar perl my ($rs, @args) = @_; # This function demonstrates using currentUser() to get # the current user ID, to set a variable for them. my $uid = $rs->currentUser(); my $var = shift(@args); my $value = join(" ", @args); $rs->setUservar($uid, $var, $value); return ""; < object + encode * in base64 - OK: base64 + perl set * to * - Setting user variable to .setvar RiveScript-v2.0.2/lib/RiveScript/demo/rpg.rive000644 000765 000024 00000017060 12640307464 023235 0ustar00npetherbridgestaff000000 000000 ! version = 2.00 // This file tests topic inclusions and inheritence: // // includes: this means that the topic "includes" the triggers present // in another topic. Matching triggers in the source and included // topic are possible, and the *reply* in the source topic overrides // the reply in the included topic. // inherits: all triggers in the source topic have higher matching priority than // all triggers in the inherited topic. So if the source topic has a // trigger of simply *, it means NO triggers can possibly match on the // inherited topic, because '*' goes higher in the match list. // Aliases ! sub n = north ! sub w = west ! sub s = south ! sub e = east // This gets us into the game. + rpg demo - You're now playing the game. Type "help" for help.\n\n{topic=nasa_lobby}{@look} // Global triggers available everywhere > topic global + help - Commands that might be helpful:\n\n ^ look: Give a description of the current room.\n ^ exits: List the exits of the current room.\n ^ north, south, east, west, up, down: Go through an exit.\n ^ inventory: Display your inventory.\n ^ exit: Quit the game. + inventory - Your inventory: + exit - Logging out of the game...{topic=random} + _ * - You don't need to use the word "" in this game. + * - I'm not sure what you're trying to do. // The following triggers get overridden on a room-by-room basis. + look - There is nothing special in this room. + exits - There are no exits to this room. + north - You can't go in that direction. + west - You can't go in that direction. + south - You can't go in that direction. + east - You can't go in that direction. + up - You can't go in that direction. + down - You can't go in that direction. < topic ///////////// // World Topics: all the "rooms" in our game inherit their triggers from these // "world" topics. The world topics include the triggers from the global topic ///////////// // Global triggers available on Earth > topic earth includes global + breathe - There is plenty of oxygen here so breathing is easy! + what world (is this|am i on) - You are on planet Earth right now. < topic // Global triggers available on Mars > topic mars includes global + breathe - Thanks to your space suit you can breathe. There's no oxygen on this planet. + what world (is this|am i on) - You are on planet Mars right now. < topic ///////////// // Earth rooms: all these rooms are on Earth and their inherit the earth topic // above. This means you can type "breathe" and "what world is this?" from every // room on Earth. ///////////// // The NASA building on Earth > topic nasa_lobby inherits earth // All of these triggers have higher matching priority than all other // triggers from the other topics, because this topic inherits a topic. So // the matching list looks like this: // exits // north // look // (combined triggers from earth & global) // Because our "north" is near the top of the match list, ours always gets // called. But if we try saying "south", we end up matching the "south" from // the global topic. + look - You are in the lobby of a NASA launch base on Earth. {@exits} + exits - There is an elevator to the north. + north - {topic=elevator}{@look} < topic // Elevator in NASA building on earth > topic elevator inherits earth + look - You are in the elevator that leads to the rocket ship. {@exits} + exits - Up: the path to the rocket\n ^ Down: the NASA lobby + up - {topic=walkway}{@look} + down - {topic=nasa_lobby}{@look} < topic // Path to the rocket > topic walkway inherits earth + look - You are on the walkway that leads to the rocket. {@exits} + exits - The rocket is to the north. The elevator is to the south. + north - {topic=rocket}{@look} + south - {topic=elevator}{@look} < topic // Rocket ship > topic rocket inherits earth + look - You are on the rocket. There is a button here that activates the rocket. {@exits} + exits - The walkway back to the NASA base is to the south. + south - {topic=walkway}{@look} + (push|press) button - You push the button and the rocket activates and flies into outer space. The ^ life support system comes on, which includes an anesthesia to put you to sleep\s ^ for the duration of the long flight to Mars.\n\n ^ When you awaken, you are on Mars. The space shuttle seems to have crash-landed.\s ^ There is a space suit here.{topic=crashed} < topic // Crashed on Mars > topic crashed inherits mars + look - You are in the ruins of your space shuttle. There is a space suit here. The\s ^ door to the shuttle is able to be opened to get outside. + open door * == 1 => You open the door and step outside onto the red Martian surface.{topic=crashsite}{@look} - You can't go outside or you'll die. There's no oxygen here. + (take|put on) (space suit|suit|spacesuit) * == 1 => You are already wearing the space suit. - You put on the space suit. Now you can breathe outside with this. + exits - The only exit is through the door that leads outside. < topic // Martian surface > topic crashsite inherits mars + look - You are standing on the red dirt ground on Mars. There is nothing but desert in all directions. + exits - You can go in any direction from here; there is nothing but desert all around. + north - {topic=puzzle1}{@look} + east @ look + west @ look + south @ look < topic // Puzzle on Mars. The sequence to solve the puzzle is: // north, west, west, north. // Topic "puzzle" is a placeholder that sets all the directions to return // us to the crash site. puzzle inherits mars so that puzzle's directions // will override the directions of mars. All the steps of the puzzle then // "include" puzzle, and override only one direction. e.g. since "west" // exists in puzzle1, the response from puzzle1 is given, but if you're // in puzzle1 and type "north"... north was included from "puzzle", but // puzzle1 doesn't have a reply, so the reply from "puzzle" is given. > topic puzzle inherits mars // Provides common directional functions for wandering around on Mars. + north - {topic=crashsite}{@look} + east - {topic=crashsite}{@look} + west - {topic=crashsite}{@look} + south - {topic=crashsite}{@look} < topic > topic puzzle1 includes puzzle + look - You wander to a part of the desert that looks different than other parts of the desert. // We get 'exits' from crashsite + west - {topic=puzzle2}{@look} < topic > topic puzzle2 includes puzzle + look - This part looks even more different than the rest of the desert. + west - {topic=puzzle3}{@look} < topic > topic puzzle3 inherits mars puzzle + look - Now this part is even MORE different. Also there is a space colony nearby. + north - {topic=entrance}{@look} < topic > topic entrance inherits mars + look - You're standing at the entrance to a space colony. {@exits} + exits - The entrance to the space colony is to the north. + north - {topic=vaccuum}{@look} < topic > topic vaccuum inherits mars + look - You're in the air lock entrance to the space colony. {@exits} + exits - The inner part of the space colony is to the north. The martian surface is to the south. + north - {topic=colony}{@look} + south - {topic=vaccuum}{@look} < topic > topic colony inherits mars + look - You've made it safely to the space colony on Mars. This concludes the game. + exits - There are no exits here. + * - This is the end of the game. There's nothing more to do. < topic RiveScript-v2.0.2/docs/bin-rivescript.html000644 000765 000024 00000023357 12640307464 022556 0ustar00npetherbridgestaff000000 000000

NAME

rivescript - A command line frontend to the Perl RiveScript interpreter.

SYNOPSIS

  $ rivescript [options] [path to RiveScript documents]

DESCRIPTION

This is a command line front-end to the RiveScript interpreter. This script obsoletes the old rsdemo, and can also be used non-interactively by third party programs. To that end, it supports a variety of input/output and session handling methods.

If no RiveScript document path is given, it will default to the example brain that ships with the RiveScript module, which is based on the Eliza bot.

OPTIONS

--debug, -d

Enables debug mode. This will print all debug data from RiveScript to your terminal. If you'd like it to log to a file instead, use the --log option instead of --debug.

--log FILE

Enables debug mode and prints the debug output to FILE instead of to your terminal.

--json, -j

Runs rivescript in JSON mode, for running the script in a non-interactive way (for example, to use RiveScript in a programming language that doesn't have a native RiveScript library). See "JSON Mode" for details.

--data JSON_DATA

When using the --json option, you can provide the JSON input message as a command line argument with the --data option. If not provided, then the JSON data will be read from standard input instead. This option is helpful, therefore, if you don't want to open a two-way pipe, but rather pass the message as a command line argument and just read the response from standard output. See "JSON Mode" for more details.

--listen, -l [ADDRESS:]PORT

Runs rivescript in TCP mode, for running the script as a server daemon. If an address isn't specified, it will bind to localhost. See "TCP Mode" for details.

--strict, --nostrict

Enables strict mode for the RiveScript parser. It's enabled by default, use --nostrict to disable it. Strict mode prevents the parser from continuing when it finds a syntax error in the RiveScript documents.

--depth=50

Override the default recursion depth limit. This controls how many times RiveScript will recursively follow redirects to other replies. The default is 50.

--utf8, -u

Use the UTF-8 option in RiveScript. This allows triggers to contain foreign characters and relaxes the filtering of user messages. This is not enabled by default!

--help

Displays this documentation in your terminal.

USAGE

Interactive Mode

This is the default mode used when you run rivescript without specifying another mode. This mode behaves similarly to the old rsdemo script and lets you chat one-on-one with your RiveScript bot.

This mode can be used to test your RiveScript bot. Example:

  $ rivescript /path/to/rs/files

JSON Mode

This mode should be used when calling from a third party program. In this mode, data that enters and leaves the script are encoded in JSON.

Example:

  $ rivescript --json /path/to/rs/files

The format for incoming JSON data is as follows:

  {
    "username": "localuser",
    "message":  "Hello bot!",
    "vars": {
      "name": "Aiden"
    }
  }

Here, username is a unique name for the user, message is their message to the bot, and vars is a hash of any user variables your program might be keeping track of (such as the user's name and age).

The response from rivescript will look like the following:

  {
    "status": "ok",
    "reply":  "Hello, human!",
    "vars": {
      "name": "Aiden"
    }
  }

Here, status will be "ok" or "error", reply is the bot's response to your message, and vars is a hash of the current variables for the user (so that your program can save them somewhere).

Standard Input or Data

By default, JSON mode will read from standard input to receive your JSON message. As an alternative to this, you can provide the --data option to rivescript to present the incoming JSON data as a command line argument.

This may be helpful if you don't want to open a two-way pipe to rivescript, and would rather pass your input as a command line argument and simply read the response from standard output.

Example:

  $ rivescript --json --data '{"username": "localuser", "message": "hello" }' \
    /path/to/rs/files

This will cause rivescript to print its JSON response to standard output and exit. You can't have a stateful session using this method.

End of Message

There are two ways you can use the JSON mode: "fire and forget," or keep a stateful session open.

In "fire and forget," you open the program, print your JSON input and send the EOF signal, and then rivescript sends you the JSON response and exits.

In a stateful session mode, you must send the text __END__ on a line by itself after you finish sending your JSON data. Then rivescript will process it, return its JSON response and then also say __END__ at the end.

Example:

  {
    "username": "localuser",
    "message": "Hello bot!",
    "vars": {}
  }
  __END__

And the response:

  {
    "status": "ok",
    "reply": "Hello, human!",
    "vars": {}
  }
  __END__

This way you can reuse the same pipe to send and receive multiple messages.

TCP Mode

TCP Mode will make rivescript listen on a TCP socket for incoming connections. This way you can connect to it from a different program (for example, a CGI script or a program written in a different language).

Example:

  $ rivescript --listen localhost:2001

TCP Mode behaves similarly to "JSON Mode"; the biggest difference is that it will read and write using a TCP socket instead of standard input and output. Unlike JSON Mode, however, TCP Mode always runs in a stateful way (the JSON messages must end with the text "__END__" on a line by itself). See "End of Message".

If the __END__ line isn't found after 20 lines of text are read from the client, it will give up and send the client an error message (encoded in JSON) and disconnect it.

SEE ALSO

RiveScript, the Perl RiveScript interpreter.

AUTHOR

Noah Petherbridge, http://www.kirsle.net

LICENSE

  RiveScript - Rendering Intelligence Very Easily
  Copyright (C) 2012 Noah Petherbridge
  
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
RiveScript-v2.0.2/docs/JavaScript.html000644 000765 000024 00000013256 12640307464 021661 0ustar00npetherbridgestaff000000 000000

NAME

RiveScript::Docs::JavaScript

DESCRIPTION

To clean up the primary manpage for RiveScript, the JavaScript example was moved here.

With $rs->setHandler(), you can specify your own custom code to handle RiveScript object macros using programming languages other than Perl.

For example, if you're implementing a RiveScript bot that runs on the web, you might like to support JavaScript objects in your code, because your user's browser would be able to execute it.

JAVASCRIPT HANDLER

Here's an example of defining a handler for JavaScript objects:

  my $scripts = {}; # Place to store JS code.

  $rs->setHandler (javascript => sub {
    my ($self,$action,$name,$data) = @_;

    # Loading the object.
    if ($action eq "load") {
      # Just store the code.
      $scripts->{$name} = $data;
    }
    else {
      # Turn the args into a JavaScript array.
      my $code = "var fields = new Array();\n";
      for (my $i = 0; $i < scalar @{$data}; $i++) {
        $code .= "fields[$i] = \"$data->[$i]\";\n";
      }

      # Come up with code for the web browser.
      $code .= "function rsobject (args) {\n"
             . "$scripts->{$name}\n"
             . "}"
             . "document.writeln( rsobject(fields) );\n";
      return "<script type=\"text/javascript\">\n"
        . $code
        . "</script>";
    }
  });

So, the above example just loads the JavaScript source code into a hash reference named $scripts, and then when called it creates some JavaScript code to put the call's arguments into an array, creates a function that accepts the args, then calls this function in a document.writeln. Here's an example of how this would be used in the RiveScript code:

  // Define an object to encode text into rot13 to be executed by the web browser
  > object rot13 javascript
    var txt = args.join(" "); // Turn the args array into a string
    var result = "";

    for (var i = 0; i < txt.length; i++) {
      var b = txt.charCodeAt(i);

      // 65 = A    97 = a
      // 77 = M   109 = m
      // 78 = N   110 = n
      // 90 = Z   122 = z

      var isLetter = 0;

      if (b >= 65 && b <= 77) {
        isLetter = 1;
        b += 13;
      }
      else if (b >= 97 && b <= 109) {
        isLetter = 1;
        b += 13;
      }
      else if (b >= 78 && b <= 90) {
        isLetter = 1;
        b -= 13;
      }
      else if (b >= 110 && b <= 122) {
        isLetter = 1;
        b -= 13;
      }

      if (isLetter) {
        result += String.fromCharCode(b);
      }
      else {
        result += String.fromCharCode(b);
      }
    }

    return result;
  < object

  // Use the object
  + say * in rot13
  - "<star>" in rot13 is: <call>rot13 <star></call>.

Now, when the user at the web browser provokes this reply, it will get back a bunch of JavaScript code as part of the response. It might be like this:

  <b>User:</b> say hello world in rot13<br>
  <b>Bot:</b> "hello world" in rot13 is: <script type="text/javascript">
  var fields = new Array();
  fields[0] = "hello";
  fields[1] = "world";
  function rsobject (args) {
    var txt = args.join(" "); // Turn the args array into a string
    var result = "";

    for (var i = 0; i < txt.length; i++) {
      var b = txt.charCodeAt(i);

      // 65 = A    97 = a
      // 77 = M   109 = m
      // 78 = N   110 = n
      // 90 = Z   122 = z

      var isLetter = 0;

      if (b >= 65 && b <= 77) {
        isLetter = 1;
        b += 13;
      }
      else if (b >= 97 && b <= 109) {
        isLetter = 1;
        b += 13;
      }
      else if (b >= 78 && b <= 90) {
        isLetter = 1;
        b -= 13;
      }
      else if (b >= 110 && b <= 122) {
        isLetter = 1;
        b -= 13;
      }

      if (isLetter) {
        result += String.fromCharCode(b);
      }
      else {
        result += String.fromCharCode(b);
      }
    }

    return result;
  }
  document.writeln(rsobject(fields));
  </script>.

And so, the JavaScript gets executed inside the bot's response by the web browser.

In this case, Perl itself can't handle JavaScript code, but considering the environment the bot is running in (CGI served to a web browser), the web browser is capable of executing JavaScript. So, we set up a custom object handler so that JavaScript objects are given directly to the browser to be executed there.

SEE ALSO

RiveScript.

AUTHOR

Noah Petherbridge

RiveScript-v2.0.2/docs/JavaScript.pod000644 000765 000024 00000010477 12640307464 021501 0ustar00npetherbridgestaff000000 000000 =head1 NAME RiveScript::Docs::JavaScript =head1 DESCRIPTION To clean up the primary manpage for L, the JavaScript example was moved here. With C<$rs-EsetHandler()>, you can specify your own custom code to handle RiveScript object macros using programming languages other than Perl. For example, if you're implementing a RiveScript bot that runs on the web, you might like to support JavaScript objects in your code, because your user's browser would be able to execute it. =head1 JAVASCRIPT HANDLER Here's an example of defining a handler for JavaScript objects: my $scripts = {}; # Place to store JS code. $rs->setHandler (javascript => sub { my ($self,$action,$name,$data) = @_; # Loading the object. if ($action eq "load") { # Just store the code. $scripts->{$name} = $data; } else { # Turn the args into a JavaScript array. my $code = "var fields = new Array();\n"; for (my $i = 0; $i < scalar @{$data}; $i++) { $code .= "fields[$i] = \"$data->[$i]\";\n"; } # Come up with code for the web browser. $code .= "function rsobject (args) {\n" . "$scripts->{$name}\n" . "}" . "document.writeln( rsobject(fields) );\n"; return ""; } }); So, the above example just loads the JavaScript source code into a hash reference named $scripts, and then when called it creates some JavaScript code to put the call's arguments into an array, creates a function that accepts the args, then calls this function in a C. Here's an example of how this would be used in the RiveScript code: // Define an object to encode text into rot13 to be executed by the web browser > object rot13 javascript var txt = args.join(" "); // Turn the args array into a string var result = ""; for (var i = 0; i < txt.length; i++) { var b = txt.charCodeAt(i); // 65 = A 97 = a // 77 = M 109 = m // 78 = N 110 = n // 90 = Z 122 = z var isLetter = 0; if (b >= 65 && b <= 77) { isLetter = 1; b += 13; } else if (b >= 97 && b <= 109) { isLetter = 1; b += 13; } else if (b >= 78 && b <= 90) { isLetter = 1; b -= 13; } else if (b >= 110 && b <= 122) { isLetter = 1; b -= 13; } if (isLetter) { result += String.fromCharCode(b); } else { result += String.fromCharCode(b); } } return result; < object // Use the object + say * in rot13 - "" in rot13 is: rot13 . Now, when the user at the web browser provokes this reply, it will get back a bunch of JavaScript code as part of the response. It might be like this: User: say hello world in rot13
Bot: "hello world" in rot13 is: . And so, the JavaScript gets executed inside the bot's response by the web browser. In this case, Perl itself can't handle JavaScript code, but considering the environment the bot is running in (CGI served to a web browser), the web browser is capable of executing JavaScript. So, we set up a custom object handler so that JavaScript objects are given directly to the browser to be executed there. =head1 SEE ALSO L. =head1 AUTHOR Noah Petherbridge RiveScript-v2.0.2/docs/RiveScript-WD.html000644 000765 000024 00000176714 12645246251 022227 0ustar00npetherbridgestaff000000 000000

NAME

RiveScript::WD - RiveScript 2.00 Working Draft (2014/11/30)

DESCRIPTION

This document details the standards for the RiveScript scripting language. The purpose of this document is that interpreters for RiveScript could be written in various programming languages that would meet the standards for the RiveScript language itself.

The most current version of this document is at http://www.rivescript.com/wd/RiveScript.html

INTRODUCTION

RiveScript is an interpreted scripting language for giving responses to chatterbots and other intelligent chatting entities in a simple trigger/reply format. The scripting language is intended to be simplistic and easy to learn and manage.

VOCABULARY

RiveScript

RiveScript is the name of the scripting language that this document explains.

Interpreter

The RiveScript interpreter is a program or library in another programming language that loads and parses a RiveScript document.

RiveScript Document

A RiveScript Document is a text file containing RiveScript code.

Bot

A Bot (short for robot) is the artificial entity that is represented by an instance of a RiveScript Interpreter object. That is, when you create a new Interpreter object and load a set of RiveScript Documents, that becomes the "brain" of the bot.

Bot Variable

A variable that describes the bot, such as its name, age, or other details you want to define for the bot.

Client Variable

A variable that the bot keeps about a specific client, or user of the bot. Usually as the client tells the bot information about itself, the bot could save this information into Client Variables and recite it later.

FORMAT

A RiveScript document should be parsed line by line, and preferably arranged in the interpreter's memory in an efficient way.

The first character on each line should be the command, and the rest of the line is the command's arguments. The command should be a single character that is not a number or a letter.

In its most simple form, a valid RiveScript trigger/response pair looks like this:

  + hello bot
  - Hello, human.

WHITESPACE

A RiveScript interpreter should ignore leading and trailing whitespace characters on any line. It should also ignore whitespace characters surrounding individual arguments of a RiveScript command, where applicable. That is to say, the following two lines should be interpreted as being exactly the same:

  ! global debug = 1
  !    global    debug=    1

COMMANDS

! DEFINITION

The ! command is for defining variables within RiveScript. It's used to define information about the bot, define global arrays that can be used in multiple triggers, or override interpreter globals such as debug mode.

The format of the ! command is as follows:

  ! type name = value

Where type is one of version, global, var, array, sub, or person. The name is the name of the variable being defined, and value is the value of said variable.

Whitespace surrounding the = sign should be stripped out.

Setting a value to <undef> will undefine the variable (deleting it or uninitializing it, depending on the implementation).

The variable types supported are detailed as follows:

version

It's highly recommended practice that new RiveScript documents explicitly define the version of RiveScript that they are following. RiveScript 2.00 has some compatibility issues with the old 1.x line (see "REVERSE COMPATIBILITY"). Newer RiveScript versions should encourage that RiveScript documents define their own version numbers.

  ! version = 2.00

global

This should override a global variable at the interpreter level. The obvious variable name might be "debug" (to enable/disable debugging within the RiveScript interpreter).

The interpreter should take extra care not to allow reserved globals to be overridden by this command in ways that might break the interpreter.

Examples:

  ! global debug = 1

var

This should define a "bot variable" for the bot. This should only be used in an initialization sense; that is, as the interpreter loads the document, it should define the bot variable as it reads in this line. If you'd want to redefine or alter the value of a bot variable, you should do so using a tag inside of a RiveScript document (see "TAGS").

Examples:

  ! var name      = RiveScript Bot
  ! var age       = 0
  ! var gender    = androgynous
  ! var location  = Cyberspace
  ! var generator = RiveScript

array

This will create an array of strings, which can then be used later in triggers (see "+ TRIGGER"). If the array contains single words, separating the words with a space character is fine. If the array contains items with multiple words in them, separate the entries with a pipe symbol ("|").

Examples:

  ! array colors = red green blue cyan magenta yellow black white orange brown
  ! array be     = is are was were
  ! array whatis = what is|what are|what was|what were

Arrays have special treatment when spanned over multiple lines. Each extension of the array data is treated individually. For example, to break an array of many single-words into multiple lines of RiveScript code:

  ! array colors = red green blue cyan
  ^ magenta yellow black white
  ^ orange brown

The data structure pulled from that code would be identical to the previous example above for this array.

Since each extension line is processed individually, you can combine the space-delimited and pipe-delimited formats. In this case, we can add some color names to our list that have multiple words in them.

  ! array colors = red green blue cyan magenta yellow
  ^ light red|light green|light blue|light cyan|light magenta|light yellow
  ^ dark red|dark green|dark blue|dark cyan|dark magenta|dark yellow
  ^ white orange teal brown pink
  ^ dark white|dark orange|dark teal|dark brown|dark pink

Finally, if your array consists of almost entirely single-word items, and you want to add in just one multi-word item, but don't want to require an extra line of RiveScript code to accomplish this, just use the \s tag where you need spaces to go.

  ! array blues = azure blue aqua cyan baby\sblue sky\sblue

sub

The sub variables are for defining substitutions that should be run against the client's message before any attempts are made to match it to a reply.

The interpreter should do the minimum amount of formatting possible on the client's message until after it has been passed through all the substitution patterns.

NOTE: Spaces are allowed in both the variable name and the value fields.

Examples:

  ! sub what's  = what is
  ! sub what're = what are
  ! sub what'd  = what did
  ! sub a/s/l   = age sex location
  ! sub brb     = be right back
  ! sub afk     = away from keyboard
  ! sub l o l   = lol

person

The person variables work a lot like subs do, but these are run against the bot's response, specifically within <person> tags (See "TAGS").

Person substitutions should swap first- and second-person pronouns. This is so that ex. if the client asks the bot a direct question using "you" when addressing the bot, if the bot uses the client's message in the response it should swap "you" for "I".

Examples:

  ! person you are = I am
  ! person i am    = you are
  ! person you     = I
  ! person i       = you

> LABEL

The > and < commands are for defining a subset of your code under a certain label. The label command takes between one and three arguments. The first argument defines the type of the label, which is one of begin, topic, or object. The various types are as follows.

begin

This is a special label used with the BEGIN block. Every message the client sends to the bot gets passed through the Begin Statement first, and the response in there determines whether or not to get an actual reply.

Here's a full example of the Begin Statement.

  > begin

    + request
    - {ok}

  < begin

In the BEGIN block, the trigger named "request" is called by the interpreter, and it should return the tag "{ok}" to tell the interpreter that it's OK to get a real reply. This way the bot could have a "maintenance mode," or could filter the results of your trigger based on a variable.

Here's a maintenance mode example:

  > begin

    + request
    * <id> eq <bot master> => {ok} // Always let the bot master get a reply
    * <env maint> eq true  => Sorry, I'm not available for chat right now!
    - {ok}

  < begin

  // Allow the owner to change the maintenance mode
  + activate maintenance mode
  * <id> eq <bot master> => <env maint=true>Maintenance mode activated.
  - You're not my master! You can't tell me what to do!

  + deactivate maintenance mode
  * <id> eq <bot master> => <env maint=false>Maintenance mode deactivated.
  - Only my master can deactivate maintenance mode!

With this example, if the global variable "maint" is set to "true", the bot will always reply "Sorry, I'm not available for chat right now!" when a user sends it a message -- unless the user is the bot's owner.

Here is another example that will modify the response formatting based on a bot variable called "mood," to simulate humanoid moods for the bot:

  > begin

    + request
    * <get mood> == happy => {ok} :-)
    * <get mood> == sad   => {lowercase}{ok}{/lowercase}
    * <get mood> == angry => {uppercase}{ok}{/uppercase}
    - {ok}

  < begin

In this example the bot will use smiley faces when it's happy, reply in all lowercase when it's sad, or all uppercase when it's angry. If its mood doesn't fall into any of those categories, it replies normally.

Here is one last example: say you want your bot to interview its users when they first talk to it, by asking them for their name:

  > begin

    + request
    * <get name> == undefined => {topic=newuser}{ok}
    - {ok}

  < begin

  > topic newuser
    + *
    - Hello! My name is <bot name>! I'm a robot. What's your name?

    + _
    % * what is your name
    - <set name=<formal>>Nice to meet you, <get name>!{topic=random}
  < topic

Begin blocks are optional! They are not required. You only need to manually define them if you need to do any "pre-processing" or "post-processing" on the user's message or the bot's response. Having no begin block is the same as having a super basic begin block, which always returns {ok}.

topic

A topic is a smaller set of responses to which the client will be bound until the topic is changed to something else. The default topic is random.

The topic label only requires one additional argument, which is the name of the topic. The topic's name should be one word and lowercase.

Example:

  + i hate you
  - Well then, I won't talk to you until you take that back.{topic=apology}

  > topic apology

    + *
    - I won't listen to you until you apologize for being mean to me.
    - I have nothing to say until you say you're sorry.

    + (sorry|i apologize)
    - Okay. I guess I'll forgive you then.{topic=random}

  < topic

Topics are able to include and inherit triggers that belong to a different topic. When a topic includes another topic, it means that the triggers in another topic are made available in the topic that did the inclusion (hereby called the "source topic", which includes triggers from the "included topic").

When a topic inherits another topic, it means that the entire collection of triggers of the source topic and any included topics, will have a higher matching priority than the inherited topics.

See "Sorting +Triggers" to see how triggers are sorted internally. The following example shows how includes and inheritence works:

  // This is in the default "random" topic and catches all non-matching
  // triggers.
  + *
  - I'm afraid I don't know how to reply to that!

  > topic alpha
    + alpha trigger
    - Alpha's response.
  < topic

  > topic beta
    + beta trigger
    - Beta's response.
  <

  > topic gamma
    + gamma trigger
    - Gamma's response.
  < topic

  > topic delta
    + delta trigger
    - Delta's response.

    + *
    - You can't access any other triggers! Haha!
  < topic

These are all normal topics. Alpha, beta, and gamma all have a single trigger corresponding to their topic names. If the user were put into one of these topics, this is the only trigger available. Anything else would give them a "NO REPLY" error message. They are unable to match the * trigger at the top, because that trigger belongs to the "random" topic, and they're not in that topic.

Now let's see how we can pair these topics up with includes and inheritence.

  > topic ab includes alpha
    + hello bot
    - Hello human!
  < topic

  // Matching order:
  alpha trigger
  hello bot

If the user were put into topic "ab", they could match the trigger hello bot as well as the trigger alpha trigger, as if they were both in the same topic.

Note that in the matching order, "alpha trigger" is at the top: this is because it is the longest trigger. If the user types "alpha trigger", the interpreter knows that "alpha trigger" does not belong to the topic "ab", but since "ab" includes triggers from "alpha", the interpreter searches there and finds the trigger. Then it gives the user the correct reply of "Alpha's response."

  > topic abc includes alpha beta
    + how are you
    - Good, how are you?
  < topic

  // Matching order:
  how are you
  alpha trigger
  beta trigger

In this case, "how are you" is on the top of the matching list because it has three words, then "alpha trigger" and "beta trigger" -- "alpha trigger" is first because it is longer than "beta trigger", even though they both have 2 words.

Now consider this example:

  > topic abc includes alpha beta
    + how are you
    - Good, how are you?

    + *
    - You matched my star trigger!
  < topic

  // Matching order:
  how are you
  alpha trigger
  beta trigger
  *

Notice what happened here: we had a trigger of simply * in the "abc" topic - * is the fallback trigger which matches anything that wasn't matched by a better trigger. But this trigger is at the end of our matching list! This is because the triggers available in the "alpha" and "beta" topics are included in the "abc" topic, meaning they all share the same "space" when the triggers are sorted. Since * has the lowest sort priority, it ends up at the very end of the collective list.

What if we want *, or any other short trigger, to match in our current topic before anything in an included topic? We need to inherit another topic. Consider this:

  > topic abc inherits alpha beta
    + how are you
    - Good, how are you?

    + *
    - You matched my star trigger!
  < topic

  // Matching order:
  how are you
  *
  alpha trigger
  beta trigger

Now the * trigger is the second on the matching list. Because "abc" inherits alpha and beta, it means that the collection of triggers inside "abc" are sorted independently, and then the triggers of alpha and beta are sorted. So this way every trigger in "abc" inherits, or overrides, all triggers in the inherited topics.

Of course, using a * trigger in a topic that inherits other topics is useless, because you could just leave the topic as it is. However it might be helpful in the case that a trigger in your topic is very short or has very few words, and you want to make sure that this trigger will have a good chance of matching before anything that appears in a different topic.

You can combine inherited and included topics together, too.

  > topic abc includes alpha beta delta inherits gamma
    + how are you
    - Good, how are you?
  < topic

  // Matching order:
  how are you
  alpha trigger
  delta trigger
  beta trigger
  *
  gamma trigger

In this example, the combined triggers from abc, alpha, beta, and delta are all merged together in one pool and sorted amongst themselves, and then triggers from gamma are placed after them in the sort list.

This effectively means you can combine the triggers from multiple topics together, and have ALL of those triggers override triggers from an inherited topic.

You can use as many "includes" and "inherits" keywords as you want, but the order you specify them has no effect. So the following two formats are identical:

  > topic alpha includes beta inherits gamma
  > topic alpha inherits gamma includes beta

In both cases, alpha and beta's triggers are pooled and have higher priority than gamma's. If gamma wants to include beta and have alpha's triggers be higher priority than gamma's and beta's, gamma will need to include beta first.

  > topic gamma includes beta
  > topic alpha inherits gamma

In this case the triggers in "alpha" are higher priority than the combined triggers in gamma and beta.

object

Objects are bits of program code that the interpreter should try to process. The programming language that the interpreter was written in will determine whether or not it will attempt to process the object.

See "OBJECT MACROS" for more information on objects.

The object label should have two arguments: a lowercase single-word name for the object, and the programming language that the object should be interpreted by, which should also be lowercase.

Example:

  > object encode perl
    my ($obj,$method,@args) = @_;
    my $msg = join(" ",@args);

    use Digest::MD5 qw(md5_hex);
    use MIME::Base64 qw(encode_base64);

    if ($method eq 'md5') {
      return md5_hex($msg);
    }
    else {
      return encode_base64($msg);
    }
  < object

+ TRIGGER

The + command is the basis for all things that actually do stuff within a RiveScript document. The trigger command is what matches the user's message to a response.

The trigger's text should be entirely lowercase and not contain any symbols (except those used for matching complicated messages). That is, a trigger that wants to match "what's your name" shouldn't be used; you should use a "sub"stitution to convert what's into what is ahead of time.

Example:

  + are you a bot
  - How did you know I'm a robot?

Atomic Trigger

An atomic trigger is a trigger that matches nothing but plain text. It doesn't contain any wildcards (*) or optionals, but it may contain alternations. Atomic triggers should take higher priority for matching a client's message than should triggers containing wildcards and optionals.

Examples:

  + hello bot
  + what is your name
  + what is your (home|office) phone number
  + who is george w bush

Trigger Wildcards

Using an asterisk (*) in the trigger will make it act as a wildcard. Anything the user says in place of the wildcard may still match the trigger. For example:

  + my name is *
  - Pleased to meet you, <star>.

An asterisk (*) will match any character (numbers and letters). If you want to only match numbers, use #, and to match only letters use _. Example:

  // This will ONLY take a number as the wildcard.
  + i am # years old
  - I will remember that you are <star> years old.

  // This will ONLY take letters but not numbers.
  + my name is _
  - Nice to meet you, <star>.

The values matched by the wildcards can be retrieved in the responses by using the tags <star1>, <star2>, <star3>, etc. in the order that the wildcard appeared. <star> is an alias for <star1>.

Trigger Alternations

An alternation in a trigger is a sub-set of strings, in which any one of the strings will still match the trigger. For example, the following trigger should match both "are you okay" and "are you alright":

  + are you (okay|alright)

Alternations can contain spaces in them, too.

  + (are you|you) (okay|alright)

That would match all of the following questions from the client:

  are you okay
  are you alright
  you okay
  you alright

Alternations match the same as wildcards do; they can be retrieved via the <star> tags.

Trigger Optionals

Triggers can contain optional words as well. Optionals are written similarly to alternations, but they use square braces. The following example would match both "what is your phone number" as well as "what is your home phone number"

  + what is your [home] phone number

Optionals do NOT match like wildcards do. They do NOT go into the <star> tags. The reason for this is that optionals are optional, and won't always match anything if the client didn't actually say the optional word(s).

Arrays in Triggers

Arrays defined via the "! DEFINITION" "array" commands can be used within a trigger. This is the only place where arrays are used, and they're added as a convenience feature.

For example, you can make an array of color names, and then use that array in multiple triggers, without having to copy a whole bunch of alternation code between triggers.

  ! array colors = red green blue cyan magenta yellow black white orange brown

  + i am wearing a (@colors) shirt
  - I don't know if I have a shirt that's colored <star>.

  + my favorite color is (@colors)
  - I like <star> too.

  + i have a @colors colored *
  - Have you thought about getting a <star> in a different color?

When an array is called within parenthesis, it should be matched into a <star> tag. When the parenthesis are absent, however, it should not be matched into a <star> tag.

Priority Triggers

A new feature proposed for RiveScript 2.00 is to add a priority tag to triggers. When the interpreter sorts all the loaded triggers into a search sequence, any triggers that have a priority defined will be sorted with higher priority triggers first.

The idea is to have "important" triggers that should always be matched before a different trigger, which may have been a better match, can be tried. The best example would be for commands. For example:

  + google *
  - Searching Google... <call>google <star></call>

  + * or not
  - Or yes. <@>

In that example, if the bot had a Google search function and the user wanted to search for whether or not Perl is a superior programming language to PHP, the user might ask "google is perl better than php or not". However, without priorities in effect, that question would actually match the "* or not" trigger, because that trigger has more words than "google *" does.

Adding a priority to the "google *" trigger would ensure that conflicts like this don't happen, by always sorting the Google search trigger with higher priority than the other.

  + {weight=100}google *
  - Searching Google... <call>google <star></call>

NOTE: It would NOT be recommended to put a priority tag on every one of your triggers. To the interpreter this might mean extra processing work to sort prioritized triggers by each number group. Only add priorities to triggers that need them.

- RESPONSE

The - tag is used to indicate a response to a matched trigger. A single response to a single trigger is called an "atomic response." When more than one response is given to a single trigger, the collection of responses become a "random response," where a response is chosen randomly from the list. Random responses can also use a {weight} tag to improve the likelihood of one response being randomly chosen over another.

Atomic Response

A single response to a single trigger makes an Atomic Response. The bot will respond pretty much the same way each time the trigger is matched.

Examples:

  + hello bot
  - Hello human.

  + my name is *
  - Nice to meet you, <star>.

  + i have a (@colors) shirt
  - You're not the only one that has a <star> shirt.

Random Response

Multiple responses to a single trigger will be chosen randomly.

  + hello
  - Hey there!
  - Hello!
  - Hi, how are you?

  + my name is *
  - Nice to meet you, <star>.
  - Hi, <star>, my name is <bot name>.
  - <star>, nice to meet you.

Weighted Random Response

When using random responses, it's possible to give weight to them to change the likelihood that a response will be chosen. In this example, the response of "Hello there" will be much more likely to be chosen than would the response of "Hi".

  + hello
  - Hello there!{weight=50}
  - Hi.

When the {weight} tag isn't used, a default weight of 1 is implied for that response. The {weight} should always be a number greater than zero and must be an integer (no decimal point).

% PREVIOUS

The % command is for drawing the user back to finish a short discussion. Its behavior is similar to using topics, but is implied automatically and used for short-term things. It's also less strict than topics are; if the client replies in a way that doesn't match, a normal reply is given anyway. For example:

  + knock knock
  - Who's there?

  + *
  % who is there
  - <star> who?

  + *
  % * who
  - lol! <star>! That's hilarious!

The text of the % command looks similar to the text next to the trigger. In essence, they work the same; the only difference is that the % command matches the last thing that the bot sent to you.

Here's another example:

  + i have a dog
  - What color is it?

  + (@colors)
  % what color is it
  - That's an odd color for a dog.

In that case, if the client says "I have a dog," the bot will reply asking what color it is. Now, if I tell it the color in my next message, it will reply back and tell me what an odd color that is. However, if I change the topic instead and say something else to the bot, it will answer my new question anyway. This is in contrast to using topics, where I'd be stuck inside of the topic until the bot resets the topic to random.

Similarly to the wildcards in + Trigger, the wildcards matched in the % Previous command are put into <botstar>. See "TAGS" for more information.

^ CONTINUE

The ^ command is used to continue the text of a lengthy previous command down to the new line. It can be used to extend any other command. Example:

  + tell me a poem
  - Little Miss Muffit sat on her tuffet\n
  ^ in a nonchalant sort of way.\n
  ^ With her forcefield around her,\n
  ^ the Spider, the bounder,\n
  ^ Is not in the picture today.

Note that when the ^ command continues the previous command, no spaces or line breaks are implied at the joining of the two lines. The \s and \n tags must be explicitly defined where needed.

@ REDIRECT

The @ command is used to redirect an entire response to appear as though the client asked an entirely different question. For example:

  + my name is *
  - Nice to meet you, <star>.

  + call me *
  @ my name is <star>

If the client says "call me John", the bot will redirect it as though the client actually said "my name is John" and give the response of "Nice to meet you, John."

* CONDITION

The * command is used with conditionals when replying to a trigger. Put simply, they compare two values, and when the comparison is true the associated response is given. The syntax is as follows:

  * value symbol value => response

The following inequality symbols may be used:

  ==  equal to
  eq  equal to (alias)
  !=  not equal to
  ne  not equal to (alias)
  <>  not equal to (alias)
  <   less than
  <=  less than or equal to
  >   greater than
  >=  greater than or equal to

In each of the value places, tags can be used to i.e. insert client or bot variables.

Examples:

  + am i a boy or a girl
  * <get gender> eq male   => You told me you were a boy.
  * <get gender> eq female => You told me you were a girl.
  - You never told me what you were.

  + am i your master
  * <id> eq <bot master> => Yes, you are.
  - No, you're not my master.

  + my name is *
  * <get name> eq <star>    => I know, you told me that already.
  * <get name> ne undefined => Did you get a name change?<set name=<star>>
  - <set name=<star>>Nice to meet you, <star>.

It's recommended practice to always include at least one response in case all of the conditionals return false.

NOTE: Conditionals are tried in the order they appear in the RiveScript document, and the next condition is tried when the previous ones are false.

// COMMENT

The // command is for putting comments into your RiveScript document. The C-style multiline comment syntax /* */ is also supported.

Comments on their own line should be ignored by all interpreters. For inline comments, only the // format is acceptable. If you want a literal // in your RiveScript data, escape at least one of the symbols, i.e. \// or \/\/ or /\/.

Examples:

  // A single regular comment

  /*
    This comment can span
    multiple lines
  */

  > begin // The "BEGIN" block
    + request // This is required
    - {ok}    // An {ok} means to get a real reply
  < begin//End the begin block

OBJECT MACROS

An object macro is a piece of program code that is processed by the interpreter to give a little more "kick" to the RiveScript. All objects are required to define the programming language they use. Ones that don't should result in vociferous warnings by the interpreter.

Objects should be able to be declared inline within RiveScript code, however they may also be defined by the program utilizing the interpreter as well. All objects should receive, at a minimum, some kind of reference to the RiveScript interpreter object that called them.

Here is an example of a simple Perl object that encodes a bit of text into MD5 or Base64.

  > object encode perl
    my ($obj,$method,@args) = @_;
    my $msg = join(" ",@args);

    use Digest::MD5 qw(md5_hex);
    use MIME::Base64 qw(encode_base64);

    if ($method eq 'md5') {
      return md5_hex($msg);
    }
    else {
      return encode_base64($msg);
    }
  < object

To call an object within a response, call it in the format of:

  <call>object_name arguments</call>

For example:

  + encode * in md5
  - The MD5 hash of "<star>" is: <call>encode md5 <star></call>

  + encode * in base64
  - The Base64 hash of "<star>" is: <call>encode base64 <star></call>

In the above examples, encode calls on the object named "encode", which we defined above; md5 and base64 calls on the method name, which is received by the object as $method. Finally, @args as received by the object would be the value of <star> in this example.

$obj in this example would be a reference to the RiveScript interpreter.

TAGS

Tags are bits of text inserted within the argument space of a RiveScript command. As a general rule of thumb, tags with <angle brackets> are for setting and getting a variable or for inserting text. Tags with {curly brackets} modify the text around them, such as to change the formatting of enclosed text.

No tags can be used within ! Definition and > Label under any circumstances.

Unless otherwise specified, all of the tags can be used within every RiveScript command.

<star>, <star1> - <starN>

The <star> tags are used for matching responses. See "+ TRIGGER" for usage examples.

The <star> tags can NOT be used within + Trigger.

<botstar>, <botstar1> - <botstarN>

If the trigger included a % Previous command, <botstar> will match any wildcards that matched the bot's previous response.

  + ask me a question
  - What color's your {random}shirt shoes socks{/random}

  + *
  % what colors your *
  - I wouldn't like <star> as a color for my <botstar>.

<input1> - <input9>; <reply1> - <reply9>.

The input and reply tags insert the previous 1 to 9 things the client said, and the last 1 to 9 things the bot said, respectively. When these tags are used with + Trigger, they should be formatted against substitutions first. This way, the bot might be able to detect when the client is repeating themself or when they're repeating the bot's replies.

  + <reply1>
  - Don't repeat what I say.

  + <input1>
  * <input1> eq <input2> => That's the second time you've repeated yourself.
  * <input1> eq <input3> => If you repeat yourself again I'll stop talking to you.
  * <input1> eq <input4> => That's it. I'm done talking to you.{topic=blocked}
  - Please don't repeat yourself.

<input> and <reply> are aliases for <input1> and <reply1>, respectively.

<id>

The <id> tag inserts the client's ID, as told to the RiveScript interpreter when the client's ID and message were passed in.

<bot>

Insert a bot variable, which was previously defined via the ! Definition "var" commands.

  + what is your name
  - I am <bot name>, a chatterbot created by <bot company>.

  + my name is <bot name>
  - <set name=<bot name>>What a coincidence, that's my name too!

The <bot> tag allows assignment as well (which deprecates the old {!...} tag.

  + set mood to (happy|angry|sad)
  * <get master> == true => <bot mood=<star>>Updated my mood.
  - Only my botmaster can do that.

<env>

Insert a global variable, which was previously defined via ! Definition "global" commands.

  + is debug mode enabled
  * <env debug> == 1 => Yes, debug mode is active.
  - No, debug mode is set to "<env debug>"

The <env> tag allows assignment as well (which deprecates the old {!...} tag).

  + turn debug mode on
  * <get master> == true => <env debug=1>Debug mode enabled.
  - You can't turn debug mode on.

<get>, <set>

Get and set a client variable. These variables are local to the user ID that is chatting with the bot.

  + my name is *
  - <set name=<star>>Nice to meet you, <star>.

<get> can be used within + Trigger, but <set> can not.

<add>, <sub>, <mult>, <div>

Add, subtract, multiply, and divide a numeric client variable, respectively.

  + give me 5 points
  - <add points=5>I've added 5 points to your account.

These tags can not be used within + Trigger.

{topic=...}

Change the client's topic. This tag can only be used with * Condition and - Response.

{weight=...}

When used with - Response, this will weigh the response more heavily to be chosen when random responses are available. When used with + Trigger, this sets that trigger to have a higher matching priority.

{@...}, <@>

Perform an inline redirection. This should work like a regular redirection but is embedded within another response. This tag can only be used with - Response, and in the response part of a * Condition.

<@> is an alias for {@<star>}

  + your *
  - I think you meant to say "you are" or "you're", not "your". {@you are <star>}

{!...}

Perform an inline definition. This can be used just like the normal ! Definition command from within a reply. This tag can only be used with - Response.

This tag is deprecated. This tag's purpose was to redefine a global or bot variable on the fly. Instead, the env and bot tags allow assignment.

  + set bot mood to *
  - <bot mood=<star>>Bot mood set to <star>.

{random}...{/random}

Insert a sub-set of random text. This tag can NOT be used with + Trigger. Use the same array syntax as when defining arrays (separate single-word groups with spaces and multi-word groups with pipes).

  + say something random
  - This {random}sentence statement{/random} has a random {random}set of words|gang of vocabulary{/random}.

{person}...{/person}, <person>

Process "person" substitutions on a group of text.

  + say *
  - Umm... "<person>"

In that example, if the client says "say you are a robot", the bot should reply, "Umm... "I am a robot.""

<person> is an alias for {person}<star>{/person}.

{formal}...{/formal}, <formal>

Formalize A String Of Text (Capitalize Every First Letter Of Every Word).

  + my name is *
  - Nice to meet you, <formal>.

<formal> is an alias for {formal}<star>{/formal}.

{sentence}...{/sentence}, <sentence>

Format a string of text in sentence-case (capitilizing only the first letter of the first word of each sentence).

<sentence> is an alias for {sentence}<star>{/sentence}.

{uppercase}...{/uppercase}, <uppercase>

FORMAT A STRING OF TEXT INTO UPPERCASE.

<uppercase> is an alias for {uppercase}<star>{/uppercase}.

{lowercase}...{/lowercase}, <lowercase>

format a string of text into lowercase.

<lowercase> is an alias for {lowercase}<star>{/lowercase}.

{ok}

This is used only with the "request" trigger within the BEGIN block. It tells the interpreter that it's okay to go and get a real response to the client's message.

\s

Inserts a white space character. This is useful with the ^ Continue command.

\n

Inserts a line break character.

\/

Inserts a forward slash.

\#

Inserts a pound symbol.

INTERPRETER IMPLEMENTATION

Interpreters of RiveScript should follow these guidelines when interpreting RiveScript code. This details some of the priorities for processing tags and sorting internal data structures. This part of the document should be programming-language-independent.

STANDARD GLOBAL VARIABLES

The interpreter must support the following standard global variables:

  depth = a recursion limit before an attempt to fetch a reply will be abandoned.

It's recommended to also have a debug variable for consistency, but it may not be applicable.

The depth variable is strongly encouraged, though. It's to set a user-defineable recursion limit when fetching a response. For example, a pair of triggers like this will cause infinite recursion:

  + one
  @ two

  + two
  @ one

The interpreter should protect itself against such possibilities and provide a depth variable to allow the user to adjust the recursion limit.

  ! global depth = 25

PARSING GUIDELINES

Interpreters should parse all of the RiveScript documents ahead of time and store them in an efficient way in which replies can be looked up quickly.

Sorting +Triggers

Triggers should be sorted in a "most specific first" order. That is:

  1. Atomic triggers first. Sort them so that the triggers with the most amount
     of words are on top. For multiple triggers with the same amount of words,
     sort them by length, and then alphabetically if there are still matches
     in length.
  2. Sort triggers that contain optionals in their triggers next. Sort them in
     the same manner as the atomic triggers.
  3. Sort triggers containing wildcards next. Sort them by the number of words
     that aren't wildcards. The order of wildcard sorting should be as follows:

     A. Alphabetic wildcards (_)
     B. Numeric wildcards (#)
     C. Global wildcards (*)

  4. The very bottom of the list will be a trigger that simply matches * by
     itself, if it exists. If triggers of only _ or only # exist, sort them in
     the same order as in step 3.

Sorting %Previous

% Previous triggers should be sorted in the same manner as + Triggers, and associated with the reply group that they belong to (creating pseudotopics for each % Previous is a good way to go).

Syntax Checking

It will be helpful if the interpreter also offers syntax checking and will give verbose warnings when it tries to parse something that doesn't follow standards. When possible, it should try to correct the error, but should still emit a warning so that the author might fix it.

It would also be good practice to keep track of file names and line numbers of each parsed command, so that syntax warnings can direct the author to the exact location where the problem occurred.

REPLY FETCHING

When attempting to get a response to a client's message, the interpreter should support the sending of a "sender ID" along with the message. This would preferably be a screen name or handle of the client who is sending the message, and the interpreter should be able to keep different groups of user variables for each user ID. The <id> tag should substitute for the user's ID.

If the BEGIN block was defined in any of the loaded RiveScript documents, it should be tried for the "request" trigger. That is, this trigger should be matched:

  > begin
    + request
    - {ok}
  < begin

The interpreter should make the request for that trigger in the context of the calling user, and allow it to change the user's topic or set a user variable immediately. Do not process any other tags that are present in the response (see "TAG PRIORITY").

If the response contains the {ok} tag, then make a second request to try to match the client's actual message. When a response was found, substitute the {ok} tag from the BEGIN response with the text of the actual response the client wanted, and then process any remaining tags in the BEGIN response. Finally, return the reply to the client.

When fetching responses, the following order of events should happen.

  1. Build in a system of recursion prevention. Since replies can redirect to
     other replies, there's the possibility of deep recursion. The first thing
     that the reply fetching routine should do is prevent this from getting out
     of control.
  2. Dig through the triggers under the client's current topic. Check to see if
     there are any %Previous commands on any of these topics and see if they
     match the bot's last message to the client. If so, make sure the client's
     current message matches the trigger in question. If so, we have a response
     set; skip to step 4.
  3. Find a trigger that matches the client's message. If one is found, we have
     a response set; continue to step 4.

  4. If we found a reply set, process the reply. First check if this reply set
     has a "solid redirection" (an @ command). If so, recurse the response
     routine with the redirection trigger and resume from step 1. Break when an
     eventual response was returned.
  5. Process conditionals if they exist in order. As soon as one of them returns
     true, we have a response and break. If none are true, continue to step 6.
  6. See if there is more than one response to this trigger. If any of the random
     responses has a {weight}, take that into account as a random response is
     chosen. If we have a reply now, break.
  7. If there is still no reply, insert a generic "no reply" error message.

When a reply was obtained, then tags should be executed on them in the order defined under "TAG PRIORITY".

TAG PRIORITY

Within BEGIN/Request

Within the "request" response of the BEGIN block, the following tags can be executed prior to getting a real response for the client's message:

  {topic}
  <set>

All other tags, especially modifier tags, must be held off until the final response has been given. Substitute {ok} for the final response, and then process the other tags.

Things like this should be able to work:

  > begin

    + request
    * <get name> eq undefined => {topic=new_user}{ok}
    * <bot mood> eq happy     => {ok}
    * <bot mood> eq angry     => {uppercase}{ok}{/uppercase}
    * <bot mood> eq sad       => {lowercase}{ok}{/lowercase}
    - {ok}

  < begin

Within +Trigger

All tags that appear within the context of + Trigger must be processed prior to any attempts to match on the trigger.

Within Replies

The order that the tags should be processed within a response or anywhere else that a tag is allowed is as follows:

  <star>      # Static text macros
  <input>     #
  <reply>     #
  <id>        #
  \s          #
  \n          #
  \\          #
  \#          #
  {random}    # Random text insertion (which may contain other tags)
  <person>    # String modifiers
  <formal>    #
  <sentence>  #
  <uppercase> #
  <lowercase> #
  <bot>*      # Insert bot variables
  <env>*      # Insert environment variables
  <set>*      # User variable modifiers
  <add>*      #
  <sub>*      #
  <mult>*     #
  <div>*      #
  <get>*      # Get user variables
  {topic}     # Set user topic
  <@>         # Inline redirection
  <call>      # Object macros.

* The variable manipulation tags should all be processed "at the same time", not in any particular order. This will allow, for example, the following sort of trigger to work:

  + my name is *
  * <get name> != undefined =>
    ^ <set oldname=<get name>>I thought your name was <get oldname>?
    ^ <set name=<formal>>
  - <set name=<formal>>Nice to meet you.

In older implementations of RiveScript, `set` tags were processed earlier than `get` making it impossible to copy variables. Implementations should process this group of tags from the most-embedded outward.

An easy way to do this is with a regular expression that matches a tag that contains no other tag, and make multiple passes until no tags remain that match the regexp:

  /<([^<]+?)>/

REVERSE COMPATIBILITY

RiveScript 2.00 will have limited backwards compatibility with RiveScript 1.x documents. Here is a full breakdown of the differences:

  RiveScript Changes from 1.02 to 2.00
  ------------------------------------

  REMOVED:

  - Variants of !DEFINITION
    - ! addpath
    - ! include
    - ! syslib
  - RiveScript Libraries (RSL files)
  - RiveScript Packages  (RSP files)
    - These made code management messy. Keep your own
      brain's files together!

  COMPATIBLE CHANGES:

  - Object macros now require the programming language to be defined.
    - Old way: > object encode
    - New way: > object encode perl
  - The ^CONTINUE command can extend every command.
  - Most tags can be used with almost every command.
  - Topics can inherit triggers from other topics now.

  INCOMPATIBLE CHANGES:

  - Conditionals work differently now. Instead of comparing variables to values,
    they compare values to values, and each value can <get> variables to compare.
    - Old way: * name       =  Bob => Hello Bob!
    - New way: * <get name> eq Bob => Hello Bob!
  - Conditionals no longer use a single = for "equal to" comparison. Replace it
    with either == or "eq".
  - Object macros will receive a reference to the RiveScript object as their first
    argument.
  - Objects are called in a new <call> syntax instead of the old &object one.

  NEW THINGS:

  - {weight} is a valid tag in triggers now to increase matching priority.
  - <env> has been added for calling global variables.
  - <botstar> has been added for wildcard matching on %previous.
  - Conditionals have more inequality comparisons now:
    "==" and "eq"        : equal to
    "!=", "ne", and "<>" : not equal to

Nice interpreters might be able to fix some old RiveScript code to make them work. For example, if a condition is found that has one equals sign instead of two, it could print a warning that it's detected RiveScript 1.x code in action and automatically adjust it to 2.x standards, and perhaps reparse the entire file or group of files, assuming that they are RiveScript 1.x code and fix these inconsistencies altogether.

Or perhaps there will just be a converter tool created that would go through code that it already assumes will be RiveScript 1.x and update it to 2.x standards.

REVISIONS

  Rev 12 - Nov 30, 2014
  - Added implementation guidelines for dealing with variable-setting tags.

  Rev 11 - Jun 13, 2013
  - Clarify the ability for the <bot> and <env> tags to be used for assignment.

  Rev 10 - May 15, 2012
  - Deprecated the {!...} tag. It was intended for reassigning global or bot
    variables. Instead use <env name=value>, <bot name=value>.

  Rev 9 - Jul 31, 2009
  - Added more explicit details on the usage of the BEGIN block, under the
    section on >Labels / "begin"
  - Revised the WD, fixing some typos.

  Rev 8 - Jul 30, 2009
  - The proper format for the `! version` line is to be `! version = 2.00`,
    and not `! version 2.00`
  - Included the "includes" option for triggers and changed how "inherits"
    works.

  Rev 7 - Dec  4, 2008
  - Topics are able to inherit additional triggers that belong to different
    topics, in the "> topic alpha inherits beta" syntax.
  - Added more documentation to the "! array" section of the document. Also
    check that section for some changes to the way arrays should be processed by
    the interpreter.
  - Deprecated the # command for inline comments. Use only // and /* */.

  Rev 6 - Sep 15, 2008
  - Updated the section about # for inline comments: when used next to a
    +Trigger, there should be at least 2 spaces before the # symbol and 1 space
    after, to avoid confusion with # as a wildcard character.

  Rev 5 - Jul 22, 2008
  - Added two new variants of the wildcard: # will match only numbers and _ will
    match only letters. * will still match anything at all.

  Rev 4 - Jun 19, 2008
  - Rearranged tag priorities:
    - <bot> and <env> moved higher up.

  Rev 3 - Apr  2, 2008
  - Typo fix: under the !person section, the examples were using !sub
  - Inconsistency fix: under %Previous it was saying the wildcards were
    unmatchable, but this isn't the case (they go into <botstar>).
  - Typo fix: under OBJECT MACROS, fixed the explanation of the code to match
    the new object syntax.
  - Inconsistency fix: <@> can be used in the response portion of conditionals.
  - Rearranged the tag priorities:
    - String modifiers (person - lowercase) come in higher priority than
      {random}
    - <env> comes in after <bot>
  - Typo fix: updated the object syntax (<call>) in the priority list.

  Rev 2 - Feb 18, 2008
  - Moved {random} to higher tag priority.
  - Change the &object syntax to <call>
  - Added the <env> variable.
  - Added the <botstar> variable.

  Rev 1 - Jan 15, 2008
  - Added the {priority} tag to triggers, to increase a trigger's matching
    priority over others, even when another trigger might be a better match
    to the client's message.

DISCLAIMER

Note that this document is only a working draft of the RiveScript 2.00 specification and may undergo numerous changes before a final standard is agreed on. Changes to this document after the creation date on January 14, 2008 will be noted in a change log.

http://www.rivescript.com/

RiveScript-v2.0.2/docs/rivescript.css000644 000765 000024 00000001176 12640307464 021627 0ustar00npetherbridgestaff000000 000000 body { background-color: #FFFFFF; font-family: Verdana,Arial,Helvetica,sans-serif; font-size: small; color: #000000 } a:link, a:visited { color: #0000FF; text-decoration: underline } a:hover, a:active { text-decoration: none } h1,h2 { text-shadow: 2px 2px 2px #000000; } h1 { font-size: 32pt; text-decoration: underline; color: #FF0000 } h2 { font-size: 28pt; color: #DD0000 } h3 { font-size: x-large; font-style: italic; color: #660000 } h4 { font-size: medium; color: #000000 } pre,code { font-family: "Lucida Console","DejaVu LGC Sans Mono","Bitstream Vera Sans Mono",monospace; font-size: small; color: #000099 } RiveScript-v2.0.2/docs/RiveScript.html000644 000765 000024 00000060247 12645246242 021710 0ustar00npetherbridgestaff000000 000000

NAME

RiveScript - Rendering Intelligence Very Easily

SYNOPSIS

  use RiveScript;

  # Create a new RiveScript interpreter.
  my $rs = new RiveScript;

  # Load a directory of replies.
  $rs->loadDirectory ("./replies");

  # Load another file.
  $rs->loadFile ("./more_replies.rive");

  # Stream in some RiveScript code.
  $rs->stream (q~
    + hello bot
    - Hello, human.
  ~);

  # Sort all the loaded replies.
  $rs->sortReplies;

  # Chat with the bot.
  while (1) {
    print "You> ";
    chomp (my $msg = <STDIN>);
    my $reply = $rs->reply ('localuser',$msg);
    print "Bot> $reply\n";
  }

DESCRIPTION

RiveScript is a simple trigger/response language primarily used for the creation of chatting robots. It's designed to have an easy-to-learn syntax but provide a lot of power and flexibility. For more information, visit http://www.rivescript.com/

METHODS

GENERAL

RiveScript new (hash %ARGS)

Create a new instance of a RiveScript interpreter. The instance will become its own "chatterbot," with its own set of responses and user variables. You can pass in any global variables here. The two standard variables are:

  debug     - Turns on debug mode (a LOT of information will be printed to the
              terminal!). Default is 0 (disabled).
  verbose   - When debug mode is on, all debug output will be printed to the
              terminal if 'verbose' is also true. The default value is 1.
  debugfile - Optional: paired with debug mode, all debug output is also written
              to this file name. Since debug mode prints such a large amount of
              data, it is often more practical to have the output go to an
              external file for later review. Default is '' (no file).
  utf8      - Enable UTF-8 support for the RiveScript code. See the section on
              UTF-8 support for details.
  depth     - Determines the recursion depth limit when following a trail of replies
              that point to other replies. Default is 50.
  strict    - If this has a true value, any syntax errors detected while parsing
              a RiveScript document will result in a fatal error. Set it to a
              false value and only a warning will result. Default is 1.

It's recommended that if you set any other global variables that you do so by calling setGlobal or defining it within the RiveScript code. This will avoid the possibility of overriding reserved globals. Currently, these variable names are reserved:

  topics   sorted  sortsthat  sortedthat  thats
  arrays   subs    person     client      bot
  objects  syntax  sortlist   reserved    debugopts
  frozen   globals handlers   objlangs

Note: the options "verbose" and "debugfile", when provided, are noted and then deleted from the root object space, so that if your RiveScript code uses variables by the same values it won't conflict with the values that you passed here.

LOADING AND PARSING

bool loadDirectory (string $PATH[, string @EXTS])

Load a directory full of RiveScript documents. $PATH must be a path to a directory. @EXTS is optionally an array containing file extensions, including the dot. By default @EXTS is ('.rive', '.rs').

Returns true on success, false on failure.

bool loadFile (string $PATH)

Load a single RiveScript document. $PATH should be the path to a valid RiveScript file. Returns true on success; false otherwise.

bool stream (arrayref $CODE)

Stream RiveScript code directly into the module. This is for providing RS code from within the Perl script instead of from an external file. Returns true on success.

string checkSyntax (char $COMMAND, string $LINE)

Check the syntax of a line of RiveScript code. This is called automatically for each line parsed by the module. $COMMAND is the command part of the line, and $LINE is the rest of the line following the command (and excluding inline comments).

If there is no problem with the line, this method returns undef. Otherwise it returns the text of the syntax error.

If strict mode is enabled in the constructor (which is on by default), a syntax error will result in a fatal error. If it's not enabled, the error is only sent via warn and the file currently being processed is aborted.

void sortReplies ()

Call this method after loading replies to create an internal sort buffer. This is necessary for trigger matching purposes. If you fail to call this method yourself, RiveScript will call it once when you request a reply. However, it will complain loudly about it.

data deparse ()

Translate the in-memory representation of the loaded RiveScript documents into a Perl data structure. This would be useful for developing a user interface to facilitate editing of RiveScript replies without having to edit the RiveScript code manually.

The data structure returned from this will follow this format:

  {
    "begin" => { # Contains begin block and config settings
      "global" => { # ! global (global variables)
        "depth" => 50,
        ...
      },
      "var" => {    # ! var (bot variables)
        "name" => "Aiden",
        ...
      },
      "sub" => {    # ! sub (substitutions)
        "what's" => "what is",
        ...
      },
      "person" => { # ! person (person substitutions)
        "you" => "I",
        ...
      },
      "array" => {  # ! array (arrays)
        "colors" => [ "red", "green", "light green", "blue" ],
        ...
      },
      "triggers" => {  # triggers in your > begin block
        "request" => { # trigger "+ request"
          "reply" => [ "{ok}" ],
        },
      },
    },
    "topic" => { # all topics under here
      "random" => { # topic names (default is random)
        "hello bot" => { # trigger labels
          "reply"     => [ "Hello human!" ], # Array of -Replies
          "redirect"  => "hello",            # Only if @Redirect exists
          "previous"  => "hello human",      # Only if %Previous exists
          "condition" => [                   # Only if *Conditions exist
            "<get name> != undefined => Hello <get name>!",
            ...
          ],
        },
      },
    },
    "include" => { # topic inclusion
      "alpha" => [ "beta", "gamma" ], # > topic alpha includes beta gamma
    },
    "inherit" => { # topic inheritence
      "alpha" => [ "delta" ], # > topic alpha inherits delta
    }
  }

Note that inline object macros can't be deparsed this way. This is probably for the best (for security, etc). The global variables "debug" and "depth" are only provided if the values differ from the defaults (true and 50, respectively).

void write (glob $fh || string $file[, data $deparsed])

Write the currently parsed RiveScript data into a RiveScript file. This uses deparse() to dump a representation of the loaded data and writes it to the destination file. Pass either a filehandle or a file name.

If you provide $deparsed, it should be a data structure matching the format of deparse(). This way you can deparse your RiveScript brain, add/edit replies and then pass in the new version to this method to save the changes back to disk. Otherwise, deparse() will be called to get the current snapshot of the brain.

CONFIGURATION

bool setHandler (string $LANGUAGE => code $CODEREF, ...)

Define some code to handle objects of a particular programming language. If the coderef is undef, it will delete the handler.

The code receives the variables $rs, $action, $name, and $data. These variables are described here:

  $rs     = Reference to Perl RiveScript object.
  $action = "load" during the parsing phase when an >object is found.
            "call" when provoked via a <call> tag for a reply
  $name   = The name of the object.
  $data   = The source of the object during the parsing phase, or an array
            reference of arguments when provoked via a <call> tag.

There is a default handler set up that handles Perl objects.

If you want to block Perl objects from being loaded, you can just set it to be undef, and its handler will be deleted and Perl objects will be skipped over:

  $rs->setHandler (perl => undef);

The rationale behind this "pluggable" object interface is that it makes RiveScript more flexible given certain environments. For instance, if you use RiveScript on the web where the user chats with your bot using CGI, you might define a handler so that JavaScript objects can be loaded and called. Perl itself can't execute JavaScript, but the user's web browser can.

See the JavaScript example in the docs directory in this distribution.

bool setSubroutine (string $NAME, code $CODEREF)

Manually create a RiveScript object (a dynamic bit of Perl code that can be provoked in a RiveScript response). $NAME should be a single-word, alphanumeric string. $CODEREF should be a pointer to a subroutine or an anonymous sub.

bool setGlobal (hash %DATA)

Set one or more global variables, in hash form, where the keys are the variable names and the values are their value. This subroutine will make sure that you don't override any reserved global variables, and warn if that happens.

This is equivalent to ! global in RiveScript code.

To delete a global, set its value to undef or "<undef>". This is true for variables, substitutions, person, and uservars.

bool setVariable (hash %DATA)

Set one or more bot variables (things that describe your bot's personality).

This is equivalent to ! var in RiveScript code.

bool setSubstitution (hash %DATA)

Set one or more substitution patterns. The keys should be the original word, and the value should be the word to substitute with it.

  $rs->setSubstitution (
    q{what's}  => 'what is',
    q{what're} => 'what are',
  );

This is equivalent to ! sub in RiveScript code.

bool setPerson (hash %DATA)

Set a person substitution. This is equivalent to ! person in RiveScript code.

bool setUservar (string $USER, hash %DATA)

Set a variable for a user. $USER should be their User ID, and %DATA is a hash containing variable/value pairs.

This is like <set> for a specific user.

string getUservar (string $USER, string $VAR)

This is an alias for getUservars, and is here because it makes more grammatical sense.

data getUservars ([string $USER][, string $VAR])

Get all the variables about a user. If a username is provided, returns a hash reference containing that user's information. Else, a hash reference of all the users and their information is returned.

You can optionally pass a second argument, $VAR, to get a specific variable that belongs to the user. For instance, getUservars ("soandso", "age").

This is like <get> for a specific user or for all users.

bool clearUservars ([string $USER])

Clears all variables about $USER. If no $USER is provided, clears all variables about all users.

bool freezeUservars (string $USER)

Freeze the current state of variables for user $USER. This will back up the user's current state (their variables and reply history). This won't statically prevent the user's state from changing; it merely saves its current state. Then use thawUservars() to revert back to this previous state.

bool thawUservars (string $USER[, hash %OPTIONS])

If the variables for $USER were previously frozen, this method will restore them to the state they were in when they were last frozen. It will then delete the stored cache by default. The following options are accepted as an additional hash of parameters (these options are mutually exclusive and you shouldn't use both of them at the same time. If you do, "discard" will win.):

  discard: Don't restore the user's state from the frozen copy, just delete the
           frozen copy.
  keep:    Keep the frozen copy even after restoring the user's state. With this
           you can repeatedly thawUservars on the same user to revert their state
           without having to keep freezing them again. On the next freeze, the
           last frozen state will be replaced with the new current state.

Examples:

  # Delete the frozen cache but don't modify the user's variables.
  $rs->thawUservars ("soandso", discard => 1);

  # Restore the user's state from cache, but don't delete the cache.
  $rs->thawUservars ("soandso", keep => 1);
string lastMatch (string $USER)

After fetching a reply for user $USER, the lastMatch method will return the raw text of the trigger that the user has matched with their reply. This function may return undef in the event that the user did not match any trigger at all (likely the last reply was "ERR: No Reply Matched" as well).

string currentUser ()

Get the user ID of the current user chatting with the bot. This is mostly useful inside of a Perl object macro in RiveScript to get the user ID of the person who invoked the object macro (e.g., to get/set variables for them using the $rs instance).

This will return undef if used outside the context of a reply (the value is unset at the end of the reply() method).

INTERACTION

string reply (string $USER, string $MESSAGE)

Fetch a response to $MESSAGE from user $USER. RiveScript will take care of lowercasing, running substitutions, and removing punctuation from the message.

Returns a response from the RiveScript brain.

RIVESCRIPT

This interpreter tries its best to follow RiveScript standards. Currently it supports RiveScript 2.0 documents. A current copy of the RiveScript working draft is included with this package: see RiveScript::WD.

UTF-8 SUPPORT

RiveScript supports Unicode but it is not enabled by default. Enable it by passing a true value for the utf8 option in the constructor, or by using the --utf8 argument to the rivescript application.

In UTF-8 mode, most characters in a user's message are left intact, except for certain metacharacters like backslashes and common punctuation characters like /[.,!?;:]/.

If you want to override the punctuation regexp, you can provide a new one by assigning the `unicode_punctuation` attribute of the bot object after initialization. Example:

  my $bot = new RiveScript(utf8 => 1);
  $bot->{unicode_punctuation} = qr/[.,!?;:]/;

CONSTANTS

This module can export some constants.

  use RiveScript qw(:standard);

These constants include:

RS_ERR_MATCH

This is the reply text given when no trigger has matched the message. It equals "ERR: No Reply Matched".

  if ($reply eq RS_ERR_MATCH) {
    $reply = "I couldn't find a good reply for you!";
  }
RS_ERR_REPLY

This is the reply text given when a trigger was matched, but no reply was given from it (for example, the trigger only had conditionals and all of them were false, with no default replies to fall back on). It equals "ERR: No Reply Found".

  if ($reply eq RS_ERR_REPLY) {
    $reply = "I don't know what to say about that!";
  }

SEE ALSO

RiveScript::WD - A current snapshot of the Working Draft that defines the standards of RiveScript.

http://www.rivescript.com/ - The official homepage of RiveScript.

CHANGES

  2.0.2  Jan 11 2016
  - Fix typo in changelog.

  2.0.1  Jan 11 2016
  - When formatting a user's message, consolidate multiple consecutive spaces
    down to one.
  - Apply downstream Debian patch that fixes a typo in RiveScript::WD.

  2.0.0  Dec 28 2015
  - Switch from old-style floating point version number notation to dotted
    decimal notation. This bumps the version number to `2.0.0` because the next
    dotted-decimal version greater than `1.42` (`v1.420.0`) is `v1.421.0` and
    I don't like having that many digits in the version number. This release is
    simply a version update; no breaking API changes were introduced.

  1.42  Nov 20 2015
  - Add configurable `unicode_punctuation` attribute to strip out punctuation
    when running in UTF-8 mode.

  1.40  Oct 10 2015
  - Fix the regexp used when matching optionals so that the triggers don't match
    on inputs where they shouldn't. (RiveScript-JS issue #46)

  1.38  Jul 21 2015
  - New algorithm for handling variable tags (<get>, <set>, <add>, <sub>,
    <mult>, <div>, <bot> and <env>) that allows for iterative nesting of these
    tags (for example, <set copy=<get orig>> will work now).
  - Fix trigger sorting so that triggers with matching word counts are sorted
    by length descending.
  - Add support for `! local concat` option to override concatenation mode
    (file scoped)
  - Bugfix where Perl object macros set via `setSubroutine()` failed to load
    because they were missing a programming language internally.

  1.36  Nov 26 2014
  - Relicense under the MIT License.
  - Strip punctuation from the bot's responses in UTF-8 mode to
    support compatibility with %Previous.
  - Bugfix in deparse(): If you had two matching triggers, one with a %Previous
    and one without, you'd lose the data for one of them in the output.

  1.34  Feb 26 2014
  - Update README.md to include module documentation for github.
  - Fixes to META.yml

  1.32  Feb 24 2014
  - Maintenance release to fix some errors per the CPANTS.
  - Add license to Makefile.PL
  - Make Makefile.PL not executable
  - Make version numbers consistent

  1.30  Nov 25 2013
  - Added "TCP Mode" to the `rivescript` command so that it can listen on a
    socket instead of using standard input and output.
  - Added a "--data" option to the `rivescript` command for providing JSON
    input as a command line argument instead of standard input.
  - Added experimental UTF-8 support.
  - Bugfix: don't use hacky ROT13-encoded placeholders for message
    substitutions... use a null character method instead. ;)
  - Make .rive the default preferred file extension for RiveScript documents
    instead of .rs (which conflicts with the Rust programming language).
    Backwards compatibility remains to load .rs files, though.

See the `Changes` file for older change history.

AUTHOR

  Noah Petherbridge, http://www.kirsle.net

KEYWORDS

bot, chatbot, chatterbot, chatter bot, reply, replies, script, aiml, alpha

COPYRIGHT AND LICENSE

  The MIT License (MIT)

  Copyright (c) 2015 Noah Petherbridge

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
RiveScript-v2.0.2/docs/Tutorial.html000644 000765 000024 00000211236 12640307464 021414 0ustar00npetherbridgestaff000000 000000

NAME

RiveScript::Tutorial - Learn to write RiveScript code.

INTRODUCTION

This tutorial will help you learn how to write your own chatbot personalities using the RiveScript language.

What is RiveScript?

RiveScript is a text-based scripting language meant to aid in the development of interactive chatbots. A chatbot is a software application that can communicate with humans using natural languages such as English in order to provide entertainment, services or just have a conversation.

Getting Started

To write your own RiveScript code, you will only need a simple text editing program. You can use Notepad for Windows, or gedit for Linux, or any other text editors you have available.

A RiveScript document is a text file containing RiveScript code. These files will have a .rive extension. An example file name would be greetings.rive. If you're writing RiveScript documents on Windows, you may need to put the file name in quotes when you save it, for example "greetings.rive" to be sure it gets saved with the file extension (and not "greetings.rive.txt").

To execute and test your RiveScript code, you will need a RiveScript Interpreter, or, a program that uses a RiveScript library to read and execute RiveScript code.

The Perl RiveScript module comes with a ready-to-use interpreter that can be used for this tutorial! If you've installed the Perl RiveScript module on a Linux, Unix or Mac OS system, the RiveScript interpreter will probably be installed in a place like /usr/bin/rivescript or /usr/local/bin/rivescript. You can open a terminal window and run the command "rivescript" to use the RiveScript interpreter.

If you're using Windows, you can open a Command Prompt window and run the command "rivescript" to use the RiveScript interpreter.

If you are able to run the RiveScript interpreter that is shipped with the Perl distribution, you will be able to continue with this tutorial. If you're using a different RiveScript library, such as the Python version, please refer to the documentation of the library to see if it comes with a RiveScript interpreter that you can use.

If you need to write your own interpreter program, see "Writing an Interpreter" for an example of how to do this in Perl.

This tutorial will assume you are using the rivescript command shipped with the Perl RiveScript library.

Project Directory

For this tutorial, you should create a folder to save your RiveScript documents to. The following are some recommended locations, but you can place them wherever you like.

For Linux, Unix and Mac OS users, I recommend making a folder in your home directory, like so:

  Unix: /home/USER/rstut
  Mac:  /Users/USER/rstut

Substitute USER with your username of course.

For Windows users, make a directory in the C:\ drive, like so:

  Windows: C:\rstut

After you begin writing RiveScript documents, you can test your code at any time by running the RiveScript interpreter and pointing it at your reply directory.

For Linux, Unix and Mac OS users, open a terminal window and run a command such as the following:

  rivescript rstut

For Windows, open a command prompt window (push the Start button, type cmd and hit Enter. For Windows XP or older, push the Windows key + R on your keyboard, and type cmd and hit enter in the Run dialog). Navigate to the "bin" folder in the Perl RiveScript distribution by using the cd command, and then run the following command:

  rivescript C:/rstut

If you're having trouble getting this to work on Windows, see "Windows Troubleshooting" for help.

FIRST STEPS

Hello, Human!

Let's write our first few lines of RiveScript code!

In your text editor, create a new file and write the following in it:

  ! version = 2.0

  + hello bot
  - Hello, human!

Save this in your project directory as hello.rive, and then run the RiveScript interpreter on that directory (see "Project Directory" for reference). You should see something along these lines in your terminal window:

  RiveScript Interpreter - Interactive Mode
  -----------------------------------------
  RiveScript Version: 1.30
          Reply Root: rstut

  You are now chatting with the RiveScript bot. Type a message and press
  Return to send it.
  When finished, type '/quit' to exit the program. Type '/help' for other
  options.

  You>

At the prompt, type "Hello bot" and press Enter. The bot should respond with "Hello, human!"

Try saying something else to the bot, such as "how are you?" and see what it says. The bot should respond with, "ERR: No Reply Matched". This is because you said something that there was no RiveScript code written to handle. There is only a handler for "hello bot", and nothing else. Later you will learn how to write a "catch-all" response that will be used when you say something the bot wasn't programmed to handle. But first, let's go over the code you wrote for hello.rive.

The Code, Explained

RiveScript code is really simple. Each line of the text file is a separate entity (RiveScript is a line-based scripting language). Lines of RiveScript code always begin with a command symbol (in this example, the symbols we see are !, +, and -) and they always have some kind of data that follows them. The data depends on the command used.

The line, "! version = 2.0" tells the RiveScript interpreter that your code follows version 2.0 of the RiveScript specification. This way, future versions of the language can be backwards compatible with existing code by looking at the version number and responding accordingly. It is a good idea to always include the version line in your code (but it isn't the end of the world if you leave it out -- you'll just leave the interpreter to make its best guess about which version your code is using).

The + command is how you define a trigger. A trigger is a line of text that is used to match the user's message. In this case, "hello world" is exactly the message that we're matching. You will learn about some more complex features of triggers later in this tutorial.

Important Note: a trigger is ALWAYS lower cased, and it doesn't contain punctuation symbols. Even if you write a trigger that contains the proper noun "I", that "I" should be lowercased too. You may have noticed when testing your code that the interpreter doesn't care about the capitalizations used in your messages: you can say "Hello Bot", "HELLO BOT", or "hello bot" and it will match the trigger all the same.

The - command is how you define a response to a trigger. In this case, when the user matches the "hello bot" trigger, the bot should respond to the user by saying "Hello, human!"

Random Replies

Making your bot always respond exactly the same way to something the user says will get boring really quickly. For this reason, RiveScript makes it easy to add random responses to a trigger!

Getting random responses is as easy as entering multiple Response commands for the same trigger. To see this in action, open your hello.rive file from "Hello, Human!" and add the following lines to it:

  + how are you
  - I'm great, how are you?
  - I'm good, you?
  - Good :) you?
  - Great! You?
  - I'm fine, thanks for asking!

Save this and then test it with the RiveScript interpreter. Ask your bot, "How are you?" a few times and see how it responds. It will say one of these five things at random each time you ask!

You can also use random strings inside a reply by using the {random} tag (see "TAGS"). Example:

  + say something random
  - This {random}message|sentence{/random} has a random word.

Between the {random} and {/random} tags, you separate the strings with a pipe symbol and one will be chosen randomly.

A Note About Style

To keep your RiveScript documents nice and tidy and easy to read (and maintain!) you should follow these style guidelines:

  • Use blank lines to separate logical groups of code.

    For example, a trigger line and its responses should be grouped together, and a blank line should separate them from a different trigger and its responses.

  • Indent code inside a topic or begin block.

    You'll learn about these later in this tutorial.

So, our hello.rive should look like this now:

  ! version = 2.0

  + hello bot
  - Hello, human!

  + how are you
  - I'm great, how are you?
  - I'm good, you?
  - Good :) you?
  - Great! You?
  - I'm fine, thanks for asking!

Let's Talk About Weight

While random responses are certainly useful, there will be times when you would prefer that some replies would be chosen more frequently than others. For example, you might be writing a bot whose personality is that he's secretly an alien pretending to be a human that's pretending to be a bot, and you want the bot to respond in some unintelligible gibberish every once in a while.

You can use the {weight} tag in a reply to override how frequently that reply will be chosen compared to the others. For our alien gibberish example, you could write a reply like this:

  + greetings
  - Hi there!{weight=20}
  - Hello!{weight=25}
  - Yos kyoco duckeb!

Here, we've assigned a weight to each of the English responses, and left the gibberish one alone. The effect that this has is that "Hi there!" will be picked 20 times out of 46, "Hello!" will be picked 25 times out of 46, and "Yos kyoco duckeb!" will be chosen only 1 time out of 46.

You can test this by saying "greetings" to your bot over and over again. It should very rarely choose the "Yos kyoco duckeb!" response compared to the other two.

The weight value controls the probability that the reply is chosen. Replies that don't explicitly include a weight tag automatically have a weight of 1. The probability of each reply being chosen is the reply's weight divided by the sum of all the weights combined (in this example, 20 + 25 + 1 = 46, so each reply has its weight out of 46 chance of being chosen).

Weight values can't be zero and they can't be negative.

You can not use weights inside a {random} tag.

Line Breaking

There will be times when you're writing a really long line of RiveScript code and you'd like to break it to span multiple lines. For these cases, you can use the ^ command (Continuation). The ^ command automatically extends the data from the previous line. Here is an example:

  + tell me a poem
  - Little Miss Muffit sat on her tuffet,\n
  ^ In a nonchalant sort of way.\n
  ^ With her forcefield around her,\n
  ^ The Spider, the bounder,\n
  ^ Is not in the picture today.

Note that the Continuation command doesn't automatically insert a space between the previous line and the continuation line. Consider the following example:

  // There will be no space between "programmed" and "using"!
  + what are you
  - I am an artificial intelligence programmed
  ^ using RiveScript.

If you asked "what are you" with this reply, the bot would say, "I am an artificial intelligence programmedusing RiveScript.", with no space between "programmed" and "using".

To make sure there's a space between continuations, use the escape sequence \s where you want the space to appear.

  // This one will have a space.
  + what are you
  - I am an artificial intelligence programmed\s
  ^ using RiveScript.

From the "tell me a poem" example, the escape sequence \n inserts a line break instead of a space.

ANATOMY OF A RIVESCRIPT BRAIN

The Begin File

You now know some of the basics about how triggers and replies relate to each other. Before continuing, you should know how RiveScript brains are typically organized.

RiveScript brains (a "brain" is a set of RiveScript documents) should, by convention, include a document named begin.rive that contains some configuration settings for your bot's brain. The most useful settings that would be set here include substitutions, which are able to make changes to the user's message before a reply is looked for.

You may have noticed that the RiveScript interpreter doesn't care about punctuation in your messages (you can say "Hello bot!!!" and it ignores the exclamation marks), so what does that mean for things such as the word "what's"? By default, the word "what's" would be converted into "whats" before the interpreter looks for a reply for it. With substitutions, you can see to it that "what's" is expanded into "what is" instead -- allowing you better control over how you reply to the user's message!

Let's start with our begin.rive file. In your text editor, create a new document and write the following code into it (this code will be explained below):

  ! version = 2.0

  // Bot variables
  ! var name = Tutorial
  ! var age  = 5

  // Substitutions
  ! sub i'm     = i am
  ! sub i'd     = i would
  ! sub i've    = i have
  ! sub i'll    = i will
  ! sub don't   = do not
  ! sub isn't   = is not
  ! sub you'd   = you would
  ! sub you're  = you are
  ! sub you've  = you have
  ! sub you'll  = you will
  ! sub what's  = what is
  ! sub whats   = what is
  ! sub what're = what are
  ! sub what've = what have
  ! sub what'll = what will

Save this as begin.rive in your project directory. Now, for an explanation on what this code is doing.

Definitions

You've already seen the ! command used for the version line, but what are all these other lines? More generally, the ! command is used for Definitions (just like the + is for Triggers and the - is for Replies). In the version line, we are defining that the version is 2.0.

Also, you may be wondering what the // characters are for. Like in most programming languages, RiveScript allows you to include Comments in your source code. The // characters denote the start of a comment. The interpreter will ignore these comments when it reads your code; they're only there for the humans (you!) who have to read and maintain the code.

Now let's go over these new definition types.

First, we defined a couple of Bot Variables. These are pieces of information that describe your bot, such as its name and age in this example. These will come in handy later. With bot variables, we can write replies in which the bot can tell the user a little something about itself.

Then, we defined a handful of Substitutions. Substitutions are always lowercased. On the left side of the = sign, you write the "original text" that may appear in the user's message, and on the right you place the substituted text. The text on the right should not contain any special symbols or punctuation, and it should also be lowercased.

With these substitutions, if a user says to the bot, "what's up?" or "I've been at work all day", the RiveScript interpreter will expand these messages out to "what is up" and "i have been at work all day", respectively, before it starts looking for a reply.

Here is some more code that you can add to your hello.rive file from earlier that demonstrates how substitutions work:

  + what is up
  - Not much, you?
  - nm, you?
  - Not a lot, you?

  + you are a bot
  - How did you know I'm a machine?

You can then ask your bot, "What's up?" or "You're a bot" and see that it matches these replies accordingly. Notice that the substitutions didn't apply to the bot's response to "you're a bot" -- it says "How did you know I'm a machine?" -- substitutions only apply to the user's incoming message. They're also used with the % Previous command, but we'll get to that later.

There are other types of definition commands available too: array, global, and person. These are useful for more advanced replies and they'll be covered later in this tutorial.

Note: there will be many more examples of RiveScript code in this tutorial. You can put these in any RiveScript document you wish; you can create a new .rive file for them if you like. Now and then I'll mention a recommended file name, though, but the names don't really matter that much.

TRIGGERS

Open-Ended Triggers

So far, the triggers you've seen have been what I call "atomic" -- they describe a user's message perfectly. For example, the user must say exactly "hello bot"; they can't say "hello there" or "hello robot" and still match one of your triggers, unless you've written individual triggers for each possible thing they could say!

This is where Wildcards come into play. With wildcards, you can mark a part of the trigger as being open-ended. The best way to demonstrate this is with an example:

  + my name is *
  - Nice to meet you, <star1>!

  + * told me to say *
  - Why would <star1> tell you to say "<star2>"?
  - Did you say "<star2>" after <star1> told you to?

  + i am * years old
  - A lot of people are <star1> years old.

With these triggers, a user can say "My name is Noah", or "I am 24 years old", and the bot will be able to match these messages all the same. Wildcards are very useful to match messages that may contain "variable" data, such as names or numbers. They're also useful for your bot to be able to fake knowledge about a subject:

  + where is *
  - Where it belongs.
  - Where you left it.
  - Where the heart is.

You can write triggers for common questions like "who is", "where is", and "what is" by using wildcards; so, if the user asks your bot about something that your bot doesn't have a special trigger to handle, it can give a sort of "generic" response that will be at least somewhat relevant to the question.

You may have noticed the <star1> and <star2> tags that appeared in some of those replies up there. These tags can be used in a reply in order to repeat the words matched by the wildcards. When the user says "my name is Noah", the first wildcard in that trigger would catch the name, and <star1> would be "noah" in this case.

If you only have a single wildcard, you may just use the <star> tag without the number "1" as a shortcut:

  + who is *
  - I don't know who <star> is.

While we're on the topic of wildcards...

Catch-All Trigger

Remember back in "Hello, Human!" where the bot would say "ERR: No Reply Matched" whenever we said something it couldn't reply to? Well, we can remedy this by writing a catch-all trigger.

A catch-all trigger is one that simply consists of a single wildcard:

  + *
  - I'm not sure how to reply to that.
  - Try asking your question a different way.
  - Let's change the subject.

Anything the user says now that doesn't get matched by a more relevant trigger will fall back to the * trigger. This way, you can avoid letting the bot say "ERR: No Reply Matched", and use it to try to steer the conversation back on track.

Conventionally, your catch-all trigger should go into a file named star.rive, so that when you're looking for it later to make changes you'll know exactly where you put it.

Specialized Wildcards

Wildcards are great, but what if you want to restrict what a wildcard is allowed to match? For example, the trigger "i am * years old" would match a message like "I am twenty four years old" just as well as "I am 24 years old".

There are two other wildcard symbols you may use. The # symbol is a wildcard that will only match a number. The _ symbol is one that will only match a word with no numbers or spaces in it.

You can have multiple triggers that look the same but use different wildcards and they will work how you'd expect:

  + i am # years old
  - A lot of people are <star> years old.

  + i am _ years old
  - Tell me that again but with a number this time.

  + i am * years old
  - Can you use a number instead?

Regardless of the type of wildcard you use, you can use the <star> tags to pull them into the reply.

Alternatives and Optionals

What if you want to use something like a wildcard, but you want to limit the possible words to a select few? This is where optionals come into play.

The syntax for these is a little tricky. Let's start with some examples:

  + what is your (home|office|cell) number
  - You can reach me at: 1 (800) 555-1234.

  + i am (really|very|super) tired
  - I'm sorry to hear that you are <star> tired.

  + i (like|love) the color *
  - What a coincidence! I <star1> that color too!
  - I also have a soft spot for the color <star2>!
  - Really? I <star1> the color <star2> too!
  - Oh I <star1> <star2> too!

In these examples, a user can say "what is your home number", or "what is your office number", or "what is your cell number" and match the first trigger. Or they can say "I am really tired", "I am very tired", or "I am super tired" and match the second one. And so on. But, if the user says "I am extremely tired", it won't match because "extremely" wasn't listed in the alternatives!

The alternative that the user used in their message can be captured with a <star> tag too, just like wildcards. Alternatives don't have to be single words, either.

  + i (will|will not) *
  - It doesn't matter to me whether you <star2> or not.

Optionals are like alternatives, but they don't need to be present in the user's message at all! But, if the user does say them, it will help match the reply anyway.

  + how [are] you
  - I'm great, you?

  + what is your (home|office|cell) [phone] number
  - You can reach me at: 1 (800) 555-1234.

  + i have a [red|green|blue] car
  - I bet you like your car a lot.

Since optionals don't have to be present in the user's message, they can not be captured with <star> tags. If you had a wildcard or alternative before and after an optional, <star1> would be the first wildcard or alternative, and <star2> would be the second; the optional would be skipped.

A clever thing you can do with optionals is write "keyword" triggers: if the user says a magic word ANYWHERE in their message, your trigger will match!

  + [*] the machine [*]
  - How do you know about the machine!?

You can also use these [*] optionals to ignore parts of a message by putting it before or after your trigger instead of on both sides.

Arrays in Triggers

Consider something a human might say to a bot: "what color is my blue shirt?" You might be able to program a reply to this using wildcards, but alternatives would be even better, since you can limit the color to a small set.

  + what color is my (red|blue|green|yellow) *
  - Your <star2> is <star1>, silly!

Wouldn't it be useful to re-use this list of colors for other triggers without having to copy and paste it all over the place? Well, that's exactly the reason why Arrays exist! In RiveScript, you can make a list of words or phrases, give that list a name, and then use it in a trigger (or multiple triggers!)

You define an array using the ! array command, which was first mentioned in the "Definitions". By convention, all definitions belong in begin.rive, so write the following code in begin.rive:

  ! array colors = red blue green yellow

Now, you can refer to this array by name in your triggers. Here are a couple examples you can use (you can use arrays in as many triggers as you want):

  + what color is my (@colors) *
  - Your <star2> is <star1>, silly!
  - Do I look dumb to you? It's <star1>!

  + i am wearing a (@colors) shirt
  - Do you really like <star>?

Just like wildcards and alternatives, the word the user used out of the array can be captured in a <star> tag. If you don't want this to happen, you can use the array without the parenthesis around it:

  // Without parenthesis, the array doesn't go into a <star> tag.
  + what color is my @colors *
  - I don't know what color your <star> is.

Arrays can be used in optionals too. They don't go into <star> tags though, because optionals never do!

  // Arrays in an optional
  - i just bought a [@colors] *
  - Is that your first <star>?

When defining arrays, you can either separate the array items with spaces (useful for single words) or pipe symbols (for phrases). Examples:

  // Single word array items
  ! array colors = red blue green yellow

  // Multiple word items
  ! array blues = light blue|dark blue|medium blue

If you use Continuations when defining an array, you can swap between spaces and pipes on each line. Here is a very thorough array of colors:

  // A lot of colors!
  ! array colors = red blue green yellow orange cyan fuchsia magenta
  ^ light red|dark red|light blue|dark blue|light yellow|dark yellow
  ^ light orange|dark orange|light cyan|dark cyan|light fuchsia
  ^ dark fuchsia|light magenta|dark magenta
  ^ black gray white silver
  ^ light gray|dark gray

Priority Triggers

You're almost done learning about all the things that can be done to a trigger!

The last thing is weighted, or priority triggers. You've seen the {weight} tag applied to responses before. Well, the same tag can also be used in a trigger!

A weighted trigger has a higher matching priority than others. This is useful to "hand tune" how well a trigger matches the user's message. An example of this would be, suppose you have the following two triggers:

  + google *
  - Google search: <a href="http://google.com/search?q=<star>">Click Here</a>

  + * perl script
  - You need Perl to run a Perl script.

What if somebody asked the bot, "google write perl script"? They might expect the bot to provide them with a Google search link, but instead the bot replies talking about needing Perl. This is because "* perl script" has more words than "google *", and therefore would usually be a better match.

We can add a {weight} tag to the Google trigger to make that trigger "more important" than anything with a lower weight.

  + google *{weight=10}
  - Google search: <a href="http://google.com/search?q=<star>">Click Here</a>

  + * perl script
  - You need Perl to run a Perl script.

Now, if the user starts their message with "google", that trigger will have a higher priority for matching than anything else. The weights on triggers are arbitrary, and higher numbers just mean it has a higher priority than ones with lower numbers. Triggers that don't have a {weight} tag automatically have a weight of 1.

You can't have a zero or negative weight value.

If you have multiple triggers with the same weight value, these triggers are considered equals, and their matching order will be the same as usual (triggers with more words are tested first). If no triggers with a given weight can match the user's message, then triggers with a lower weight are tried.

See the "Sorting +Triggers" section of the RiveScript Working Draft for a detailed explanation of how triggers are sorted.

MORE COMMANDS

Redirections

If a user matches a trigger, you can have that trigger simply redirect them somewhere else, as though they had asked a different question. Example:

  + hello
  - Hi there!
  - Hey!
  - Howdy!

  + hey
  @ hello

  + hi
  @ hello

In this example, if the user says "hey" or "hi", the bot redirects them to the "hello" trigger, as though they had said hello to begin with.

Of course, with alternatives and the other advanced trigger features, redirects like this aren't always useful. But you can also use redirects inside of replies. One of my favorite examples of this:

  + * or something{weight=100}
  - Or something. {@ <star>}

If the user says, "Are you a bot or something?", the bot might reply, "Or something. How did you know I'm a machine?"

If you just want to use {@ <star>}, you can use a shortcut tag instead: <@>. Use the {@...} format for everything else:

  + hello *
  - {@ hello} <@>

  + hello
  - Hi there!

  + are you a bot
  - How did you know I'm a machine?

This trigger would reply to "Hello, are you a bot?" with a reply like "Hi there! How did you know I'm a machine?"

Short Discussions

Suppose you want to program your bot to be able to play along with a user who is telling a Knock-Knock joke? You can pull this off with the % command (Previous):

  + knock knock
  - Who's there?

  + *
  % who is there
  - <star> who?

  + *
  % * who
  - LOL! <star>! That's funny!

This example introduces the % command. The % command is similar to the + used for triggers, except it looks at the bot's previous response to the user instead. In the second trigger here, if the bot's previous response was "who is there", anything the user says (*) will match, and the bot will continue playing along with the joke.

In the % command, the bot's previous response is sent through the same substitutions as the user's messages are. Notice that the bot's reply was "Who's there?", but the % line on the next trigger says "who is there". This is because the "Who's" was substituted for "who is" due to the substitution defined in your begin.rive file!

% Previous lines need to be lowercased just like triggers do.

Here is another example:

  + i have a dog
  - What color is it?

  + (@colors)
  % what color is it
  - That's a silly color for a dog!

Now you can say "I have a dog" and the bot will ask what color it is. If you tell it the color, it will say "That's a silly color for a dog!" -- but, if you ignore the bot's question and say something else, the bot will just reply to your new message as usual.

Conditionals

Learning Things

You now know most of the RiveScript commands and how to use them. But what good is a chatbot if it can't even remember your name?

RiveScript has the capability to store and repeat variables about users. To set a user variable, we use the <set> tag, and to retrieve the variable we use <get>. Here are some examples of how we can learn and repeat information about the user.

  + my name is *
  - <set name=<star>>It's nice to meet you, <get name>.

  + what is my name
  - Your name is <get name>, silly!

  + i am # years old
  - <set age=<star>>I will remember that you are <get age> years old.

  + how old am i
  - You are <get age> years old.

While we're talking about variables, what about those bot variables we defined in begin.rive? You can retrieve them in a similar fashion:

  // The user can ask the bot its name too!
  + what is your name
  - You can call me <bot name>.
  - My name is <bot name>.

  + how old are you
  - I am <bot age> years old.

Now, you may notice that if you tell the bot, "My name is Noah", it will store your name as "noah" -- lowercased. To store the name as a proper noun instead, you can use the formal tag. See "TAGS".

  // Store the name with the correct casing
  + my name is *
  - <set name=<formal>>Nice to meet you, <get name>!

The <formal> tag is a shortcut for {formal}<star>{/formal}, so you will need to use the {formal}...{/formal} syntax to formalize other things. See "TAGS".

Writing Conditionals

And with learning information about the user, conditionals let us pick replies based on the values of those variables!

You may notice that if you asked the bot what your name was before you told the bot your name, it would say "Your name is undefined, silly!" This doesn't look very professional and would give away that the bot is just a program.

With conditionals, you can make sure the bot knows a user's name before it opens its big mouth, and say something else if it doesn't know.

Here is an example:

  + what is my name
  * <get name> == undefined => You never told me your name.
  - Your name is <get name>, silly!
  - Aren't you <get name>?

Now, if you ask the bot your name, it will see if your name is "undefined", and if so, it will say "You never told me your name." Otherwise, it will give one of the other replies.

Conditions are used to compare variables to values. You can also compare variables to other variables. Here is a more advanced way of telling the bot what your name is:

  + my name is *
  * <formal>   == <bot name> => Wow, we have the same name!<set name=<formal>>
  * <get name> == undefined  => <set name=<formal>>Nice to meet you!
  - <set oldname=<get name>><set name=<formal>>
  ^ I thought your name was <get oldname>?

If you're feeling a little bit cramped on the condition lines, using the ^ Continuation command is useful to get some more room.

Conditions are checked in order from top to bottom. If no condition turns out true, the normal - replies are used. If you want to have a random response when a condition is true, you need to use the {random} tag.

With conditionals, you can use the following equality tests:

  ==  equal to
  eq  equal to (alias)
  !=  not equal to
  ne  not equal to (alias)
  <>  not equal to (alias)

The following equality tests can be used on variables that contain numbers only:

  <   less than
  <=  less than or equal to
  >   greater than
  >=  greater than or equal to

Here is an example using different equality tests:

  + what am i old enough to do
  * <get age> >  25 => You can do anything you want.
  * <get age> == 25 => You're old enough to rent a car with no extra fees.
  * <get age> >  21 => You're old enough to drink, but not rent a car.
  * <get age> == 21 => You're exactly old enough to drink.
  * <get age> >  18 => You're old enough to gamble, but not drink.
  * <get age> == 18 => You're exactly old enough to gamble.
  * <get age> <  18 => You're not old enough to do much of anything yet.
  - I don't know how old you are.

LABELED SECTIONS

There are three types of labeled sections. Labels are defined using the > command symbol, and they're ended with <. All labeled sections should be properly closed when done (even if it's at the end of the file). For style purposes, you should indent the contents of a labeled section too. Labeled sections can not be embedded inside each other.

Topics

Topics are logical groupings of triggers. When a user is in a topic, they can only match triggers that belong to that topic. Here's an example:

  + i hate you
  - You're really mean! I'm not talking again until you apologize.{topic=sorry}

  > topic sorry

    // This will match if the word "sorry" exists ANYWHERE in their message
    + [*] sorry [*]
    - It's OK, I'll forgive you!{topic=random}

    + *
    - Nope, not until you apologize.
    - Say you're sorry!
    - Apologize!

  < topic

In this example, if the user tells the bot that they don't think very much of it, the bot will force the user to apologize before continuing conversation. Once the user has been put into the "sorry" topic, the ONLY triggers available for matching are the two in that topic.

The default topic is "random", and you change the user's topic using the {topic} tag, for example {topic=sorry} and {topic=random}.

You might be wondering why you can't use <set topic=random> instead. Well, you can, but there is a small difference in how the two tags will behave:

The <set> tag can appear multiple times in a reply and each one is processed in order. The {topic} tag can only appear once (if there are multiple ones, the first one wins). So, they'll both do the same job, but {topic} is a little shorter to type.

Topics are capable of inheriting and including triggers from other topics, too. But, this is for more advanced users and is outside the scope of this tutorial (see the file rpg.rive that comes with standard RiveScript distributions for a practical example of this).

You may be wondering what happens if you accidentally set the topic to one that doesn't exist? Would the user be unable to chat with the bot anymore since no replies can be matched? Fortunately, the RiveScript libraries are smart enough to detect this and will place the user back in the "random" topic automatically.

The Begin Block

The Begin Block is an optional feature of a RiveScript brain. The Begin Block will typically be found in begin.rive. They work in a similar fashion as topics. Here is an example:

  > begin

    + request
    - {ok}

  < begin

The Begin block serves as a pre-processor and post-processor for fetching a response. If the Begin Block is present, the request trigger will be tried for each message the user says. If the response to this contains the {ok} tag, then a reply is fetched for the user's message and the {ok} tag is substituted out.

Here is a longer example that use the pre-processing capabilities of the Begin Block to introduce itself to new users and interview them:

  > begin

    // If we don't know their name, set the new_user topic and continue.
    + request
    * <get name> == undefined => {topic=new_user}{ok}
    - {ok}

  < begin

  > topic new_user

    + *
    - Hi! I'm <bot name>! I'm a chatbot written in RiveScript.\s
    ^ What is your name?{topic=asked_name}

  < topic

  > topic asked_name

    + #
    - Your name is a number?

    + *
    - I only want your first name.

    + _
    - <set name=<formal>>Nice to meet you, <get name>!{topic=random}

  < topic

And here is another example that would use the post-processing capability for the Begin Block:

  > begin

    // Change the reply formatting based on the bot's mood
    + request
    * <bot mood> == happy => {sentence}{ok}{/sentence}
    * <bot mood> == angry => {uppercase}{ok}{/uppercase}
    * <bot mood> == sad   => {lowercase}{ok}{/lowercase}...
    - {ok}

  < begin

With this example, the bot would change the formatting of its response based on a "mood" variable. It will be left as an exercise for you to decide how the mood variable would be set (you can use e.g. <bot mood=happy> to change the value of a bot variable from a reply).

Object Macros

In RiveScript, an Object Macro is a programming function written in another programming language. Usually, a RiveScript library written in a dynamic programming language such as Perl or Python will support Object Macros of the same language. The RiveScript libraries will also allow you to add custom language handlers to support other languages, but this will be up to the programmer and is outside the scope of this tutorial.

Object macros allow you to do more powerful things with your bot's responses. For example, you can allow the user to ask the bot questions about the current weather or about movie ticket prices, and your bot can call an object macro that goes out to the Internet to fetch that information.

All object macros should define the programming language they're written in, in lowercase (e.g. "perl", "python", "javascript").

Here is an example of an object macro written in Perl (you will be able to run this code using the Perl RiveScript interpreter).

  // The object name is "hash", written in Perl
  > object hash perl
    my ($rs, $args) = @_;
    my $method = shift @{$args};
    my $string = join " ", @{$args};

    # Here, $method is a hashing method (MD5 or SHA1), and $string
    # is the text to hash.

    if ($method eq "MD5") {
      require Digest::MD5;
      return Digest::MD5::md5_hex($string);
    }
    elsif ($method eq "SHA1") {
      require Digest::SHA1;
      return Digest::SHA1::sha1_hex($string);
    }
  < object

  // You call an object using the <call> tag.
  + what is the md5 hash of *
  - The hash of "<star>" is: <call>hash MD5 <star></call>

  + what is the sha1 hash of *
  - The hash of "<star>" is: <call>hash SHA1 <star></call>

Here is another example of an object that would tell you the bot's public IP address, using icanhazip.com. You'd probably want to secure this question and only allow the bot's owner to get the IP address for obvious privacy reasons. So, you can have an "authentication" system before the bot will allow you to see its IP address.

  // To gain botmaster power, say "I am your master"...
  + i am your master
  - Then you must know the secret password:

  // And then enter the botmaster password...
  + *
  % then you must know the secret password
  * <star> == rivescript is awesome => Correct password!<set master=true>
  - That's not the right password. :-P

  // And after authenticated, let them get the bot's IP address!
  + what is your ip address
  * <get master> == true => My IP address is: <call>myip</call>
  - You're not my master so you don't need to know! :-P

  // The object macro that fetches an IP address.
  > object myip perl
    my ($rs, $args) = @_;

    # Fetch the IP.
    use LWP::Simple;
    my $ip = get "http://icanhazip.com";
    return $ip;
  < object

As you can see from this example, you don't need to declare your object macro before calling it with a <call> tag. The RiveScript interpreter parses all of your code before you can start getting replies, so it will find your object macro definition no matter where you put it.

You can test this code with the Perl RiveScript interpreter like so:

  You> What is your IP address?
  Bot> You're not my master so you don't need to know! :-P
  You> I am your master.
  Bot> Then you must know the secret password:
  You> RiveScript is awesome
  Bot> Correct password!
  You> What is your IP address?
  Bot> My IP address is: 67.205.20.243

See the documentation of your RiveScript library to see how to write object macros (in particular, to see the format in which arguments are passed to your object).

MORE DEFINITIONS

To go back to begin.rive definition commands, there are two more types that may come in handy.

Global Variables

Global variables are similar to bot variables, but they're not particularly related to your bot as an entity. Global variables may be defined by your RiveScript interpreter (a common example would be to make all the environment variables of your program available as globals in RiveScript, for example <env REMOTE_ADDR> could be used to show the user's IP address in a CGI environment). However, there are two special globals that affect the RiveScript interpreter directly:

Debug Mode
  ! global debug = true
  ! global debug = false

This global variable will turn debug mode on or off in the RiveScript interpreter. Debug mode typically will print lines of text to the terminal window that will thoroughly document what the interpreter is doing (for example, if debug mode is on while parsing files from disk, it will print every line of RiveScript code it sees and a short summary of what it's doing with it. When it's on while fetching a reply, it will print every trigger that it's trying to compare the user's message to, etc.)

The value should be true or false.

Recursion Depth
  ! global depth = 50

Because RiveScript replies can redirect to each other, there is some protection in place to avoid infinite recursion (for example, a trigger that redirects to another one, and that trigger redirects back to the first one, forever). By default the recursion depth limit is set to 50, meaning if RiveScript can't find a reply after 50 redirects it will give up and say "ERR: Deep Recursion Detected".

This value should be a positive number higher than zero.

Person Substitutions

These are a special kind of substitution that is intended to swap first- and second-person pronouns. Here is how you define the person substitutions (usually in your begin.rive file):

  ! person i am    = you are
  ! person you are = i am
  ! person i'm     = you're
  ! person you're  = I'm
  ! person my      = your
  ! person your    = my
  ! person you     = I
  ! person i       = you

Notice in this example that each pair of substitutions works in both directions.

To invoke person substitutions, you can use the <person> tag (to substitute on the <star>), or {person}...{/person} to substitute on something else. Here is a practical example to show why person substitutions can come in handy:

  + say *
  - Umm... "<person>"

Now, if the user says, "say I am the greatest", the bot will reply with "Umm... "you are the greatest"". Without person substitutions, the bot would repeat "i am the greatest" instead.

TAGS

By now, you've seen examples of all the different RiveScript commands. Sprinkled here and there throughout the examples were tags. Examples of tags you've seen are <star>, <get>, and {topic}. There are many more tags available! This section will teach you about all the tags and how to use them.

In general, a tag that has <angled> brackets are tags that insert text in their place, or tags that set a variable silently. Tags that have {curly} brackets modify the text around them.

Tags can be used with all the RiveScript commands except where explicitly noted.

<star>, <star1> - <starN>

The star tag is used for capturing values used in wildcards, alternatives and arrays present in the matched trigger. You've seen many examples of this in this tutorial. If you have multiple stars, you can use <star1>, <star2>, <star3>, etc. to use the stars in order from left to right. The <star> tag is an alias for <star1>, so if you only have one wildcard this tag can be useful.

These tags can not be used with + Trigger.

<botstar>, <botstar1> - <botstarN>

This tag is similar to <star>, but it captures wildcards present in a % Previous line. Here is an example:

  + i bought a new *
  - Oh? What color is your new <star>?

  + (@colors)
  % oh what color is your new *
  - <star> is a pretty color for a <botstar>.

Like the <star> tag, <botstar> is an alias for <botstar1>.

These tags can not be used with + Trigger.

<input>, <reply>

The input and reply tags are used for showing previous messages sent by the user and the bot, respectively. The previous 9 messages and responses are stored, so you can use the tags <input1> through <input9>, or <reply1> through <reply9> to get a particular message or reply. <input> is an alias for <input1>, and <reply> is an alias for <reply1>.

Here are a couple examples:

  // If the user repeats the bot's previous message
  + <reply>
  - Don't repeat what I say.

  // If the user keeps repeating themselves over and over.
  + <input1>
  * <input1> == <input2> => That's the second time you've repeated yourself.
  * <input1> == <input3> => If you repeat yourself again I'll stop talking.
  * <input1> == <input4> => That's it. I'm not talking.{topic=sorry}
  - Please don't repeat yourself.

  // An example that uses both tags
  + why did you say that
  - I said, "<reply>", because you said, "<input>".
<id>

This tag inserts the user's ID, which was passed in to the RiveScript interpreter when fetching a reply. With the interpreter shipped with the Perl RiveScript library, the <id> is, by default, localuser.

RiveScript uses user IDs to keep multiple users separate. You can use the same RiveScript interpreter to serve responses to multiple users, and it will keep their user variables separate based on their ID.

Here is an example of how you might distinguish the botmaster from other users based on a screen name (for example, for an instant messenger bot, where the user ID is set to the user's screen name).

  ! var master = kirsle

  + am i your master
  * <id> == <bot master> => Yes, you are. Hi Kirsle!
  - No, <bot master> is my master, and you are <id>.
<bot>

The <bot> tag is used for retrieving a bot variable. It can also be used to set a bot variable.

Bot variables can be considered "global" to the RiveScript interpreter instance. That is, if you set the bot's name to Aiden, its name will be Aiden for everybody who asks, regardless of the user's ID. This is in contrast to user variables which are tied to a specific user ID.

  + what is your name
  - You can call me <bot name>.

  + tell me about yourself
  - I am <bot name>, a chatterbot written by <bot master>.

  // Setting a bot variable dynamically
  + i hate you
  - Aww! You've just ruined my day.<bot mood=depressed>
<env>

The <env> tag is used for retrieving global variables. It can also be used to set a global variable.

For example, if the RiveScript interpreter copies all its environment variables into RiveScript globals, a CGI-based RiveScript bot could tell a user their IP address.

  + what is my ip
  - Your IP address is: <env REMOTE_ADDR>

And here is an example of how you can set a global using this tag. In this example, the bot's master is able to turn debug mode on or off dynamically.

  + set debug mode (true|false)
  * <id> == <bot master> => <env debug=<star>>Debug mode set to <star>.
  - You're not my master.
<get>, <set>

These tags are used to get or set a user variable, respectively. User variables are arbitrary name/value pairs. You can make up any variable name you want.

Here are some examples:

  + my name is *
  - <set name=<formal>>Nice to meet you, <get name>.

  + i am # years old
  - <set age=<star>>I will remember that you are <get age> years old.

  + what do you know about me
  - I know your name is <get name> and you are <get age> years old.

If you attempt to <get> a variable that had never been defined for the user before, it will insert the word "undefined" in its place instead. You can use this feature to test whether a variable has been defined:

  + do you know my name
  * <get name> != undefined => Yes, your name is <get name>.
  - No, you've never told me your name before.

<get> can be used in a trigger, but <set> can not.

<add>, <sub>, <mult>, <div>

These tags can add, subtract, multiply or divide a numeric user variable, respectively.

  + give me 5 points
  - <add points=5>You have been given 5 points. Your balance is: <get points>.

If you operate on a variable that isn't defined, it will be initialized to zero first. If you operate on a variable that doesn't currently contain a number, an error message will appear in the place of the tag.

These tags can not be used with + Trigger.

{topic}

This tag changes the client's topic.

  + play hangman
  - {topic=hangman}Now playing hangman. Type "quit" to quit.

  > topic hangman

    + quit
    - Quitting the game.{topic=random}

    + *
    - <call>hangman <star></call>

  < topic

This tag can not be used with + Trigger.

{weight}

This tag applies a weight to a trigger or response. When used with a trigger, it controls the matching priority of the trigger (a higher weight means higher priority). When used with a reply, it controls how frequently that reply will be randomly chosen.

  + * or something{weight=10}
  - Or something. <@>

  + hello
  - Hi there!{weight=20}
  - Hey!{weight=10}
  - Howdy!

This tag can only be used with + Trigger and - Reply.

{@}, <@>

This tag performs an inline redirection to a different trigger. <@> is an alias for {@ <star>}.

  + your *
  - I think you mean to say "you are" or "you're", not "your". {@you are <star>}

You don't need to include a space between the @ and the trigger text.

This tag can not be used with + Trigger.

{random}

Insert a sub-set of random text. This can come in handy in conditional lines, if you want a random reply for a condition.

Between the {random} and {/random} tags, separate your possible texts with a pipe symbol. Here is an example:

  + hello
  * <get name> != undefined => {random}
  ^ Hello there, <get name>!|
  ^ Nice to see you again, <get name>!|
  ^ Hey, <get name>!{/random}
  - Hello there!
  - Hi there!
  - Hello!

In this example, you give a random response that includes the user's name if the bot knows it, or a normal random response otherwise.

This tag can not be used with + Trigger.

{person}, <person>

Processes person substitutions on some text. See "Person Substitutions".

  + say *
  - <person>

<person> is an alias for {person}<star>{/person}.

This tag can not be used with + Trigger.

<formal>, <sentence>, <uppercase>, <lowercase>

Change the case formatting on some text. <formal> is an alias for {formal}<star>{/formal}. The same applies for the other tags.

Formal text makes the first letter of each word uppercase. This is useful for names and other proper nouns.

Sentence text makes the first word of each sentence uppercase.

Uppercase and lowercase make the entire string upper or lower case.

<call>

This tag is used to call an object macro. Example:

  // Call a macro named "reverse" and give it an argument
  + say * to me in reverse
  - <call>reverse <star></call>

The first word in the <call> tag is the name of an object macro to call (see "Object Macros"). The macros may optionally take some arguments, and you can pass them in by placing them in the <call> tag as shown in this example.

{ok}

This is only used in the Begin Block in response to the request trigger. It indicates that it's OK to fetch a reply for the user's message. The reply will replace the {ok} tag.

\s

This inserts a space character. It's useful when using the ^ Continue command to extend a line of RiveScript code.

\n

This inserts a line break.

CONCLUSION

This concludes the RiveScript tutorial. By now you should have a thorough understanding of how RiveScript code is written and you should be able to start creating replies for your own chatterbot.

For more information about RiveScript, see http://www.rivescript.com/

VERSION AND AUTHOR

This tutorial was last updated on June 8, 2012.

This tutorial was written by Noah Petherbridge, http://www.kirsle.net/

APPENDIX

Windows Troubleshooting

Command Prompt Help

If you're not familiar with DOS commands and how to change directories in a command prompt window, you should look up a quick tutorial on how to use the command line. One such tutorial can be found at http://www.computerhope.com/issues/chusedos.htm

For the lazy, you can create a simple batch file and place it in the same folder as the RiveScript interpreter that would open a command prompt window there, without needing to cd to that folder yourself.

Open Notepad and type the following code into a new file:

  @echo off
  cmd

Save the file as "cmd.bat" (with the quotation marks!) in the same folder as the rivescript file from the Perl RiveScript distribution. Now, navigate to that folder in Windows Explorer and double-click on the cmd.bat file (the file might appear as simply "cmd" if file extensions are hidden).

Writing an Interpreter

Here is an example of how to write your own RiveScript interpreter application in Perl 5.

  #!/usr/bin/perl

  use strict;
  use warnings;
  use RiveScript;

  # Create a new RiveScript interpreter object.
  my $rs = RiveScript->new();

  # Load a directory full of RiveScript documents.
  $rs->loadDirectory("./replies");

  # You must sort the replies before trying to fetch any!
  $rs->sortReplies();

  # Enter a loop to let the user chat with the bot using standard I/O.
  while (1) {
    print "You> ";
    chomp(my $message = <STDIN>);

    # Let the user type "/quit" to quit.
    if ($message eq "/quit") {
      exit(0);
    }

    # Fetch a reply from the bot.
    my $reply = $rs->reply("user", $message);
    print "Bot> $reply\n";
  }

SEE ALSO

RiveScript

RiveScript::WD - The RiveScript Working Draft - http://www.rivescript.com/wd/RiveScript.html

RiveScript-v2.0.2/docs/Tutorial.pod000644 000765 000024 00000155276 12640307464 021245 0ustar00npetherbridgestaff000000 000000 =head1 NAME RiveScript::Tutorial - Learn to write RiveScript code. =head1 INTRODUCTION This tutorial will help you learn how to write your own chatbot personalities using the RiveScript language. =head2 What is RiveScript? RiveScript is a text-based scripting language meant to aid in the development of interactive chatbots. A chatbot is a software application that can communicate with humans using natural languages such as English in order to provide entertainment, services or just have a conversation. =head2 Getting Started To B your own RiveScript code, you will only need a simple text editing program. You can use Notepad for Windows, or gedit for Linux, or any other text editors you have available. A RiveScript document is a text file containing RiveScript code. These files will have a C<.rive> extension. An example file name would be C. If you're writing RiveScript documents on Windows, you may need to put the file name in quotes when you save it, for example C<"greetings.rive"> to be sure it gets saved with the file extension (and not "C"). To B and test your RiveScript code, you will need a RiveScript Interpreter, or, a program that uses a RiveScript library to read and execute RiveScript code. The Perl L module comes with a ready-to-use interpreter that can be used for this tutorial! If you've installed the Perl RiveScript module on a Linux, Unix or Mac OS system, the RiveScript interpreter will probably be installed in a place like C or C. You can open a terminal window and run the command "C" to use the RiveScript interpreter. If you're using Windows, you can open a Command Prompt window and run the command "C" to use the RiveScript interpreter. If you are able to run the RiveScript interpreter that is shipped with the Perl distribution, you will be able to continue with this tutorial. If you're using a different RiveScript library, such as the Python version, please refer to the documentation of the library to see if it comes with a RiveScript interpreter that you can use. If you need to write your own interpreter program, see L<"Writing an Interpreter"> for an example of how to do this in Perl. This tutorial will assume you are using the C command shipped with the Perl RiveScript library. =head2 Project Directory For this tutorial, you should create a folder to save your RiveScript documents to. The following are some recommended locations, but you can place them wherever you like. For Linux, Unix and Mac OS users, I recommend making a folder in your home directory, like so: Unix: /home/USER/rstut Mac: /Users/USER/rstut Substitute C with your username of course. For Windows users, make a directory in the C drive, like so: Windows: C:\rstut After you begin writing RiveScript documents, you can test your code at any time by running the RiveScript interpreter and pointing it at your reply directory. For Linux, Unix and Mac OS users, open a terminal window and run a command such as the following: rivescript rstut For Windows, open a command prompt window (push the Start button, type C and hit Enter. For Windows XP or older, push the Windows key + R on your keyboard, and type C and hit enter in the Run dialog). Navigate to the "bin" folder in the Perl RiveScript distribution by using the C command, and then run the following command: rivescript C:/rstut If you're having trouble getting this to work on Windows, see L<"Windows Troubleshooting"> for help. =head1 FIRST STEPS =head2 Hello, Human! Let's write our first few lines of RiveScript code! In your text editor, create a new file and write the following in it: ! version = 2.0 + hello bot - Hello, human! Save this in your project directory as C, and then run the RiveScript interpreter on that directory (see L<"Project Directory"> for reference). You should see something along these lines in your terminal window: RiveScript Interpreter - Interactive Mode ----------------------------------------- RiveScript Version: 1.30 Reply Root: rstut You are now chatting with the RiveScript bot. Type a message and press Return to send it. When finished, type '/quit' to exit the program. Type '/help' for other options. You> At the prompt, type "Hello bot" and press Enter. The bot should respond with "Hello, human!" Try saying something else to the bot, such as "how are you?" and see what it says. The bot should respond with, "ERR: No Reply Matched". This is because you said something that there was no RiveScript code written to handle. There is only a handler for "hello bot", and nothing else. Later you will learn how to write a "catch-all" response that will be used when you say something the bot wasn't programmed to handle. But first, let's go over the code you wrote for C. =head3 The Code, Explained RiveScript code is really simple. Each line of the text file is a separate entity (RiveScript is a line-based scripting language). Lines of RiveScript code always begin with a command symbol (in this example, the symbols we see are C, C<+>, and C<->) and they always have some kind of data that follows them. The data depends on the command used. The line, "C" tells the RiveScript interpreter that your code follows version 2.0 of the RiveScript specification. This way, future versions of the language can be backwards compatible with existing code by looking at the version number and responding accordingly. It is a good idea to always include the version line in your code (but it isn't the end of the world if you leave it out -- you'll just leave the interpreter to make its best guess about which version your code is using). The C<+> command is how you define a B. A trigger is a line of text that is used to match the user's message. In this case, "hello world" is exactly the message that we're matching. You will learn about some more complex features of triggers later in this tutorial. B a trigger is ALWAYS lower cased, and it doesn't contain punctuation symbols. Even if you write a trigger that contains the proper noun "I", that "I" should be lowercased too. You may have noticed when testing your code that the interpreter doesn't care about the capitalizations used in your messages: you can say "Hello Bot", "HELLO BOT", or "hello bot" and it will match the trigger all the same. The C<-> command is how you define a response to a trigger. In this case, when the user matches the "hello bot" trigger, the bot should respond to the user by saying "Hello, human!" =head2 Random Replies Making your bot I respond exactly the same way to something the user says will get boring really quickly. For this reason, RiveScript makes it easy to add random responses to a trigger! Getting random responses is as easy as entering multiple Response commands for the same trigger. To see this in action, open your C file from L<"Hello, Human!"> and add the following lines to it: + how are you - I'm great, how are you? - I'm good, you? - Good :) you? - Great! You? - I'm fine, thanks for asking! Save this and then test it with the RiveScript interpreter. Ask your bot, "How are you?" a few times and see how it responds. It will say one of these five things at random each time you ask! You can also use random strings I a reply by using the C<{random}> tag (see L<"TAGS">). Example: + say something random - This {random}message|sentence{/random} has a random word. Between the C<{random}> and C<{/random}> tags, you separate the strings with a pipe symbol and one will be chosen randomly. =head3 A Note About Style To keep your RiveScript documents nice and tidy and easy to read (and maintain!) you should follow these style guidelines: =over 4 =item * Use blank lines to separate logical groups of code. For example, a trigger line and its responses should be grouped together, and a blank line should separate them from a different trigger and its responses. =item * Indent code inside a topic or begin block. You'll learn about these later in this tutorial. =back So, our C should look like this now: ! version = 2.0 + hello bot - Hello, human! + how are you - I'm great, how are you? - I'm good, you? - Good :) you? - Great! You? - I'm fine, thanks for asking! =head3 Let's Talk About Weight While random responses are certainly useful, there will be times when you would prefer that I replies would be chosen more frequently than others. For example, you might be writing a bot whose personality is that he's secretly an alien pretending to be a human that's pretending to be a bot, and you want the bot to respond in some unintelligible gibberish every once in a while. You can use the C<{weight}> tag in a reply to override how frequently that reply will be chosen compared to the others. For our alien gibberish example, you could write a reply like this: + greetings - Hi there!{weight=20} - Hello!{weight=25} - Yos kyoco duckeb! Here, we've assigned a weight to each of the English responses, and left the gibberish one alone. The effect that this has is that "Hi there!" will be picked 20 times out of 46, "Hello!" will be picked 25 times out of 46, and "Yos kyoco duckeb!" will be chosen only 1 time out of 46. You can test this by saying "greetings" to your bot over and over again. It should I choose the "Yos kyoco duckeb!" response compared to the other two. The weight value controls the probability that the reply is chosen. Replies that don't explicitly include a weight tag automatically have a weight of 1. The probability of each reply being chosen is the reply's weight divided by the sum of all the weights combined (in this example, 20 + 25 + 1 = 46, so each reply has its weight out of 46 chance of being chosen). Weight values can't be zero and they can't be negative. You B use weights inside a C<{random}> tag. =head2 Line Breaking There will be times when you're writing a really long line of RiveScript code and you'd like to break it to span multiple lines. For these cases, you can use the C<^> command (B). The C<^> command automatically extends the data from the previous line. Here is an example: + tell me a poem - Little Miss Muffit sat on her tuffet,\n ^ In a nonchalant sort of way.\n ^ With her forcefield around her,\n ^ The Spider, the bounder,\n ^ Is not in the picture today. Note that the Continuation command doesn't automatically insert a space between the previous line and the continuation line. Consider the following example: // There will be no space between "programmed" and "using"! + what are you - I am an artificial intelligence programmed ^ using RiveScript. If you asked "what are you" with this reply, the bot would say, "I am an artificial intelligence programmedusing RiveScript.", with no space between "programmed" and "using". To make sure there's a space between continuations, use the escape sequence C<\s> where you want the space to appear. // This one will have a space. + what are you - I am an artificial intelligence programmed\s ^ using RiveScript. From the "tell me a poem" example, the escape sequence C<\n> inserts a line break instead of a space. =head1 ANATOMY OF A RIVESCRIPT BRAIN =head2 The Begin File You now know some of the basics about how triggers and replies relate to each other. Before continuing, you should know how RiveScript brains are typically organized. RiveScript brains (a "brain" is a set of RiveScript documents) should, by convention, include a document named C that contains some configuration settings for your bot's brain. The most useful settings that would be set here include B, which are able to make changes to the user's message I a reply is looked for. You may have noticed that the RiveScript interpreter doesn't care about punctuation in your messages (you can say "Hello bot!!!" and it ignores the exclamation marks), so what does that mean for things such as the word "what's"? By default, the word "what's" would be converted into "whats" before the interpreter looks for a reply for it. With substitutions, you can see to it that "what's" is expanded into "what is" instead -- allowing you better control over how you reply to the user's message! Let's start with our C file. In your text editor, create a new document and write the following code into it (this code will be explained below): ! version = 2.0 // Bot variables ! var name = Tutorial ! var age = 5 // Substitutions ! sub i'm = i am ! sub i'd = i would ! sub i've = i have ! sub i'll = i will ! sub don't = do not ! sub isn't = is not ! sub you'd = you would ! sub you're = you are ! sub you've = you have ! sub you'll = you will ! sub what's = what is ! sub whats = what is ! sub what're = what are ! sub what've = what have ! sub what'll = what will Save this as C in your project directory. Now, for an explanation on what this code is doing. =head3 Definitions You've already seen the C command used for the version line, but what are all these other lines? More generally, the C command is used for B (just like the C<+> is for B and the C<-> is for B). In the version line, we are I that the version is 2.0. Also, you may be wondering what the C characters are for. Like in most programming languages, RiveScript allows you to include B in your source code. The C characters denote the start of a comment. The interpreter will ignore these comments when it reads your code; they're only there for the humans (you!) who have to read and maintain the code. Now let's go over these new definition types. First, we defined a couple of B. These are pieces of information that describe your bot, such as its name and age in this example. These will come in handy later. With bot variables, we can write replies in which the bot can tell the user a little something about itself. Then, we defined a handful of B. Substitutions are I. On the left side of the = sign, you write the "original text" that may appear in the user's message, and on the right you place the substituted text. The text on the right I contain any special symbols or punctuation, and it should also be lowercased. With these substitutions, if a user says to the bot, "what's up?" or "I've been at work all day", the RiveScript interpreter will expand these messages out to "what is up" and "i have been at work all day", respectively, before it starts looking for a reply. Here is some more code that you can add to your C file from earlier that demonstrates how substitutions work: + what is up - Not much, you? - nm, you? - Not a lot, you? + you are a bot - How did you know I'm a machine? You can then ask your bot, "What's up?" or "You're a bot" and see that it matches these replies accordingly. Notice that the substitutions didn't apply to the bot's response to "you're a bot" -- it says "How did you know I'm a machine?" -- substitutions only apply to the user's incoming message. They're also used with the C<% Previous> command, but we'll get to that later. There are other types of definition commands available too: C, C, and C. These are useful for more advanced replies and they'll be covered later in this tutorial. B there will be many more examples of RiveScript code in this tutorial. You can put these in any RiveScript document you wish; you can create a new C<.rive> file for them if you like. Now and then I'll mention a recommended file name, though, but the names don't really matter that much. =head1 TRIGGERS =head2 Open-Ended Triggers So far, the triggers you've seen have been what I call "atomic" -- they describe a user's message I. For example, the user must say exactly "hello bot"; they can't say "hello there" or "hello robot" and still match one of your triggers, unless you've written individual triggers for each possible thing they could say! This is where B come into play. With wildcards, you can mark a part of the trigger as being open-ended. The best way to demonstrate this is with an example: + my name is * - Nice to meet you, ! + * told me to say * - Why would tell you to say ""? - Did you say "" after told you to? + i am * years old - A lot of people are years old. With these triggers, a user can say "My name is Noah", or "I am 24 years old", and the bot will be able to match these messages all the same. Wildcards are very useful to match messages that may contain "variable" data, such as names or numbers. They're also useful for your bot to be able to fake knowledge about a subject: + where is * - Where it belongs. - Where you left it. - Where the heart is. You can write triggers for common questions like "who is", "where is", and "what is" by using wildcards; so, if the user asks your bot about something that your bot doesn't have a special trigger to handle, it can give a sort of "generic" response that will be at least somewhat relevant to the question. You may have noticed the Cstar1E> and Cstar2E> tags that appeared in some of those replies up there. These tags can be used in a reply in order to repeat the words matched by the wildcards. When the user says "my name is Noah", the first wildcard in that trigger would catch the name, and Cstar1E> would be "noah" in this case. If you only have a single wildcard, you may just use the CstarE> tag without the number "1" as a shortcut: + who is * - I don't know who is. While we're on the topic of wildcards... =head3 Catch-All Trigger Remember back in L<"Hello, Human!"> where the bot would say "ERR: No Reply Matched" whenever we said something it couldn't reply to? Well, we can remedy this by writing a catch-all trigger. A catch-all trigger is one that simply consists of a single wildcard: + * - I'm not sure how to reply to that. - Try asking your question a different way. - Let's change the subject. Anything the user says now that doesn't get matched by a more relevant trigger will fall back to the C<*> trigger. This way, you can avoid letting the bot say "ERR: No Reply Matched", and use it to try to steer the conversation back on track. Conventionally, your catch-all trigger should go into a file named C, so that when you're looking for it later to make changes you'll know exactly where you put it. =head3 Specialized Wildcards Wildcards are great, but what if you want to restrict what a wildcard is allowed to match? For example, the trigger "i am * years old" would match a message like "I am twenty four years old" just as well as "I am 24 years old". There are two other wildcard symbols you may use. The C<#> symbol is a wildcard that will I match a number. The C<_> symbol is one that will I match a word with no numbers or spaces in it. You can have multiple triggers that look the same but use different wildcards and they will work how you'd expect: + i am # years old - A lot of people are years old. + i am _ years old - Tell me that again but with a number this time. + i am * years old - Can you use a number instead? Regardless of the type of wildcard you use, you can use the CstarE> tags to pull them into the reply. =head2 Alternatives and Optionals What if you want to use something like a wildcard, but you want to limit the possible words to a select few? This is where optionals come into play. The syntax for these is a little tricky. Let's start with some examples: + what is your (home|office|cell) number - You can reach me at: 1 (800) 555-1234. + i am (really|very|super) tired - I'm sorry to hear that you are tired. + i (like|love) the color * - What a coincidence! I that color too! - I also have a soft spot for the color ! - Really? I the color too! - Oh I too! In these examples, a user can say "what is your home number", or "what is your office number", or "what is your cell number" and match the first trigger. Or they can say "I am really tired", "I am very tired", or "I am super tired" and match the second one. And so on. But, if the user says "I am extremely tired", it won't match because "extremely" wasn't listed in the alternatives! The alternative that the user used in their message can be captured with a CstarE> tag too, just like wildcards. Alternatives don't have to be single words, either. + i (will|will not) * - It doesn't matter to me whether you or not. Optionals are like alternatives, but they don't I to be present in the user's message I But, if the user does say them, it will help match the reply anyway. + how [are] you - I'm great, you? + what is your (home|office|cell) [phone] number - You can reach me at: 1 (800) 555-1234. + i have a [red|green|blue] car - I bet you like your car a lot. Since optionals don't have to be present in the user's message, they I be captured with CstarE> tags. If you had a wildcard or alternative before and after an optional, Cstar1E> would be the first wildcard or alternative, and Cstar2E> would be the second; the optional would be skipped. A clever thing you can do with optionals is write "keyword" triggers: if the user says a magic word ANYWHERE in their message, your trigger will match! + [*] the machine [*] - How do you know about the machine!? You can also use these C<[*]> optionals to ignore parts of a message by putting it before or after your trigger instead of on both sides. =head2 Arrays in Triggers Consider something a human might say to a bot: "what color is my blue shirt?" You might be able to program a reply to this using wildcards, but alternatives would be even better, since you can limit the color to a small set. + what color is my (red|blue|green|yellow) * - Your is , silly! Wouldn't it be useful to re-use this list of colors for other triggers without having to copy and paste it all over the place? Well, that's exactly the reason why B exist! In RiveScript, you can make a list of words or phrases, give that list a name, and then use it in a trigger (or multiple triggers!) You define an array using the C command, which was first mentioned in the L<"Definitions">. By convention, all definitions belong in C, so write the following code in C: ! array colors = red blue green yellow Now, you can refer to this array by name in your triggers. Here are a couple examples you can use (you can use arrays in as many triggers as you want): + what color is my (@colors) * - Your is , silly! - Do I look dumb to you? It's ! + i am wearing a (@colors) shirt - Do you really like ? Just like wildcards and alternatives, the word the user used out of the array can be captured in a CstarE> tag. If you don't want this to happen, you can use the array without the parenthesis around it: // Without parenthesis, the array doesn't go into a tag. + what color is my @colors * - I don't know what color your is. Arrays can be used in optionals too. They don't go into CstarE> tags though, because optionals I do! // Arrays in an optional - i just bought a [@colors] * - Is that your first ? When defining arrays, you can either separate the array items with spaces (useful for single words) or pipe symbols (for phrases). Examples: // Single word array items ! array colors = red blue green yellow // Multiple word items ! array blues = light blue|dark blue|medium blue If you use Continuations when defining an array, you can swap between spaces and pipes on each line. Here is a very thorough array of colors: // A lot of colors! ! array colors = red blue green yellow orange cyan fuchsia magenta ^ light red|dark red|light blue|dark blue|light yellow|dark yellow ^ light orange|dark orange|light cyan|dark cyan|light fuchsia ^ dark fuchsia|light magenta|dark magenta ^ black gray white silver ^ light gray|dark gray =head2 Priority Triggers You're almost done learning about all the things that can be done to a trigger! The last thing is weighted, or priority triggers. You've seen the C<{weight}> tag applied to responses before. Well, the same tag can also be used in a trigger! A weighted trigger has a higher matching priority than others. This is useful to "hand tune" how well a trigger matches the user's message. An example of this would be, suppose you have the following two triggers: + google * - Google search: Click Here + * perl script - You need Perl to run a Perl script. What if somebody asked the bot, "google write perl script"? They might expect the bot to provide them with a Google search link, but instead the bot replies talking about needing Perl. This is because "* perl script" has more words than "google *", and therefore would usually be a better match. We can add a C<{weight}> tag to the Google trigger to make that trigger "more important" than anything with a lower weight. + google *{weight=10} - Google search: Click Here + * perl script - You need Perl to run a Perl script. Now, if the user starts their message with "google", that trigger will have a higher priority for matching than anything else. The weights on triggers are arbitrary, and higher numbers just mean it has a higher priority than ones with lower numbers. Triggers that don't have a C<{weight}> tag automatically have a weight of 1. You can't have a zero or negative weight value. If you have multiple triggers with the same weight value, these triggers are considered equals, and their matching order will be the same as usual (triggers with more words are tested first). If no triggers with a given weight can match the user's message, then triggers with a lower weight are tried. See the "Sorting +Triggers" section of the RiveScript Working Draft for a detailed explanation of how triggers are sorted. =head1 MORE COMMANDS =head2 Redirections If a user matches a trigger, you can have that trigger simply redirect them somewhere else, as though they had asked a different question. Example: + hello - Hi there! - Hey! - Howdy! + hey @ hello + hi @ hello In this example, if the user says "hey" or "hi", the bot redirects them to the "hello" trigger, as though they had said hello to begin with. Of course, with alternatives and the other advanced trigger features, redirects like this aren't always useful. But you can also use redirects I of replies. One of my favorite examples of this: + * or something{weight=100} - Or something. {@ } If the user says, "Are you a bot or something?", the bot might reply, "Or something. How did you know I'm a machine?" If you just want to use C<{@ EstarE}>, you can use a shortcut tag instead: C@E>. Use the C<{@...}> format for everything else: + hello * - {@ hello} <@> + hello - Hi there! + are you a bot - How did you know I'm a machine? This trigger would reply to "Hello, are you a bot?" with a reply like "Hi there! How did you know I'm a machine?" =head2 Short Discussions Suppose you want to program your bot to be able to play along with a user who is telling a Knock-Knock joke? You can pull this off with the C<%> command (B): + knock knock - Who's there? + * % who is there - who? + * % * who - LOL! ! That's funny! This example introduces the C<%> command. The C<%> command is similar to the C<+> used for triggers, except it looks at the bot's previous response to the user instead. In the second trigger here, if the bot's previous response was "who is there", anything the user says (C<*>) will match, and the bot will continue playing along with the joke. In the C<%> command, the bot's previous response is sent through the same substitutions as the user's messages are. Notice that the bot's reply was "Who's there?", but the C<%> line on the next trigger says "who is there". This is because the "Who's" was substituted for "who is" due to the substitution defined in your C file! C<% Previous> lines need to be lowercased just like triggers do. Here is another example: + i have a dog - What color is it? + (@colors) % what color is it - That's a silly color for a dog! Now you can say "I have a dog" and the bot will ask what color it is. If you tell it the color, it will say "That's a silly color for a dog!" -- but, if you ignore the bot's question and say something else, the bot will just reply to your new message as usual. =head2 Conditionals =head3 Learning Things You now know most of the RiveScript commands and how to use them. But what good is a chatbot if it can't even remember your name? RiveScript has the capability to store and repeat variables about users. To set a user variable, we use the CsetE> tag, and to retrieve the variable we use CgetE>. Here are some examples of how we can learn and repeat information about the user. + my name is * - >It's nice to meet you, . + what is my name - Your name is , silly! + i am # years old - >I will remember that you are years old. + how old am i - You are years old. While we're talking about variables, what about those bot variables we defined in C? You can retrieve them in a similar fashion: // The user can ask the bot its name too! + what is your name - You can call me . - My name is . + how old are you - I am years old. Now, you may notice that if you tell the bot, "My name is Noah", it will store your name as "noah" -- lowercased. To store the name as a proper noun instead, you can use the formal tag. See L<"TAGS">. // Store the name with the correct casing + my name is * - >Nice to meet you, ! The CformalE> tag is a shortcut for C<{formal}EstarE{/formal}>, so you will need to use the C<{formal}...{/formal}> syntax to formalize other things. See L<"TAGS">. =head3 Writing Conditionals And with learning information about the user, conditionals let us pick replies based on the values of those variables! You may notice that if you asked the bot what your name was I you told the bot your name, it would say "Your name is undefined, silly!" This doesn't look very professional and would give away that the bot is just a program. With conditionals, you can make sure the bot knows a user's name before it opens its big mouth, and say something else if it doesn't know. Here is an example: + what is my name * == undefined => You never told me your name. - Your name is , silly! - Aren't you ? Now, if you ask the bot your name, it will see if your name is "undefined", and if so, it will say "You never told me your name." Otherwise, it will give one of the other replies. Conditions are used to compare variables to values. You can also compare variables to other variables. Here is a more advanced way of telling the bot what your name is: + my name is * * == => Wow, we have the same name!> * == undefined => >Nice to meet you! - >> ^ I thought your name was ? If you're feeling a little bit cramped on the condition lines, using the C<^ Continuation> command is useful to get some more room. Conditions are checked in order from top to bottom. If no condition turns out true, the normal C<-> replies are used. If you want to have a random response when a condition is true, you need to use the C<{random}> tag. With conditionals, you can use the following equality tests: == equal to eq equal to (alias) != not equal to ne not equal to (alias) <> not equal to (alias) The following equality tests can be used on variables that contain numbers only: < less than <= less than or equal to > greater than >= greater than or equal to Here is an example using different equality tests: + what am i old enough to do * > 25 => You can do anything you want. * == 25 => You're old enough to rent a car with no extra fees. * > 21 => You're old enough to drink, but not rent a car. * == 21 => You're exactly old enough to drink. * > 18 => You're old enough to gamble, but not drink. * == 18 => You're exactly old enough to gamble. * < 18 => You're not old enough to do much of anything yet. - I don't know how old you are. =head1 LABELED SECTIONS There are three types of labeled sections. Labels are defined using the C> command symbol, and they're ended with C>. All labeled sections should be properly closed when done (even if it's at the end of the file). For style purposes, you should indent the contents of a labeled section too. Labeled sections can not be embedded inside each other. =head2 Topics Topics are logical groupings of triggers. When a user is in a topic, they can only match triggers that belong to that topic. Here's an example: + i hate you - You're really mean! I'm not talking again until you apologize.{topic=sorry} > topic sorry // This will match if the word "sorry" exists ANYWHERE in their message + [*] sorry [*] - It's OK, I'll forgive you!{topic=random} + * - Nope, not until you apologize. - Say you're sorry! - Apologize! < topic In this example, if the user tells the bot that they don't think very much of it, the bot will force the user to apologize before continuing conversation. Once the user has been put into the "sorry" topic, the ONLY triggers available for matching are the two in that topic. The default topic is "random", and you change the user's topic using the C<{topic}> tag, for example C<{topic=sorry}> and C<{topic=random}>. You might be wondering why you can't use Cset topic=randomE> instead. Well, you I, but there is a small difference in how the two tags will behave: The CsetE> tag can appear multiple times in a reply and each one is processed in order. The C<{topic}> tag can only appear once (if there are multiple ones, the first one wins). So, they'll both do the same job, but C<{topic}> is a little shorter to type. Topics are capable of inheriting and including triggers from other topics, too. But, this is for more advanced users and is outside the scope of this tutorial (see the file C that comes with standard RiveScript distributions for a practical example of this). You may be wondering what happens if you accidentally set the topic to one that doesn't exist? Would the user be unable to chat with the bot anymore since no replies can be matched? Fortunately, the RiveScript libraries are smart enough to detect this and will place the user back in the "random" topic automatically. =head2 The Begin Block The Begin Block is an optional feature of a RiveScript brain. The Begin Block will typically be found in C. They work in a similar fashion as topics. Here is an example: > begin + request - {ok} < begin The Begin block serves as a pre-processor I post-processor for fetching a response. If the Begin Block is present, the C trigger will be tried for each message the user says. If the response to this contains the C<{ok}> tag, then a reply is fetched for the user's message and the C<{ok}> tag is substituted out. Here is a longer example that use the pre-processing capabilities of the Begin Block to introduce itself to new users and interview them: > begin // If we don't know their name, set the new_user topic and continue. + request * == undefined => {topic=new_user}{ok} - {ok} < begin > topic new_user + * - Hi! I'm ! I'm a chatbot written in RiveScript.\s ^ What is your name?{topic=asked_name} < topic > topic asked_name + # - Your name is a number? + * - I only want your first name. + _ - >Nice to meet you, !{topic=random} < topic And here is another example that would use the post-processing capability for the Begin Block: > begin // Change the reply formatting based on the bot's mood + request * == happy => {sentence}{ok}{/sentence} * == angry => {uppercase}{ok}{/uppercase} * == sad => {lowercase}{ok}{/lowercase}... - {ok} < begin With this example, the bot would change the formatting of its response based on a "mood" variable. It will be left as an exercise for you to decide how the mood variable would be set (you can use e.g. Cbot mood=happyE> to change the value of a bot variable from a reply). =head2 Object Macros In RiveScript, an Object Macro is a programming function written in another programming language. Usually, a RiveScript library written in a dynamic programming language such as Perl or Python will support Object Macros of the same language. The RiveScript libraries will also allow you to add custom language handlers to support other languages, but this will be up to the programmer and is outside the scope of this tutorial. Object macros allow you to do more powerful things with your bot's responses. For example, you can allow the user to ask the bot questions about the current weather or about movie ticket prices, and your bot can call an object macro that goes out to the Internet to fetch that information. All object macros should define the programming language they're written in, in lowercase (e.g. "perl", "python", "javascript"). Here is an example of an object macro written in Perl (you will be able to run this code using the Perl RiveScript interpreter). // The object name is "hash", written in Perl > object hash perl my ($rs, $args) = @_; my $method = shift @{$args}; my $string = join " ", @{$args}; # Here, $method is a hashing method (MD5 or SHA1), and $string # is the text to hash. if ($method eq "MD5") { require Digest::MD5; return Digest::MD5::md5_hex($string); } elsif ($method eq "SHA1") { require Digest::SHA1; return Digest::SHA1::sha1_hex($string); } < object // You call an object using the tag. + what is the md5 hash of * - The hash of "" is: hash MD5 + what is the sha1 hash of * - The hash of "" is: hash SHA1 Here is another example of an object that would tell you the bot's public IP address, using icanhazip.com. You'd probably want to secure this question and only allow the bot's owner to get the IP address for obvious privacy reasons. So, you can have an "authentication" system before the bot will allow you to see its IP address. // To gain botmaster power, say "I am your master"... + i am your master - Then you must know the secret password: // And then enter the botmaster password... + * % then you must know the secret password * == rivescript is awesome => Correct password! - That's not the right password. :-P // And after authenticated, let them get the bot's IP address! + what is your ip address * == true => My IP address is: myip - You're not my master so you don't need to know! :-P // The object macro that fetches an IP address. > object myip perl my ($rs, $args) = @_; # Fetch the IP. use LWP::Simple; my $ip = get "http://icanhazip.com"; return $ip; < object As you can see from this example, you don't need to declare your object macro before calling it with a CcallE> tag. The RiveScript interpreter parses I of your code before you can start getting replies, so it will find your object macro definition no matter where you put it. You can test this code with the Perl RiveScript interpreter like so: You> What is your IP address? Bot> You're not my master so you don't need to know! :-P You> I am your master. Bot> Then you must know the secret password: You> RiveScript is awesome Bot> Correct password! You> What is your IP address? Bot> My IP address is: 67.205.20.243 See the documentation of your RiveScript library to see how to write object macros (in particular, to see the format in which arguments are passed to your object). =head1 MORE DEFINITIONS To go back to C definition commands, there are two more types that may come in handy. =head2 Global Variables Global variables are similar to bot variables, but they're not particularly related to your bot as an entity. Global variables may be defined by your RiveScript interpreter (a common example would be to make all the environment variables of your program available as globals in RiveScript, for example Cenv REMOTE_ADDRE> could be used to show the user's IP address in a CGI environment). However, there are two special globals that affect the RiveScript interpreter directly: =over 4 =item Debug Mode ! global debug = true ! global debug = false This global variable will turn debug mode on or off in the RiveScript interpreter. Debug mode typically will print lines of text to the terminal window that will thoroughly document I the interpreter is doing (for example, if debug mode is on while parsing files from disk, it will print every line of RiveScript code it sees and a short summary of what it's doing with it. When it's on while fetching a reply, it will print every trigger that it's trying to compare the user's message to, etc.) The value should be C or C. =item Recursion Depth ! global depth = 50 Because RiveScript replies can redirect to each other, there is some protection in place to avoid infinite recursion (for example, a trigger that redirects to another one, and that trigger redirects back to the first one, forever). By default the recursion depth limit is set to 50, meaning if RiveScript can't find a reply after 50 redirects it will give up and say "ERR: Deep Recursion Detected". This value should be a positive number higher than zero. =back =head2 Person Substitutions These are a special kind of substitution that is intended to swap first- and second-person pronouns. Here is how you define the person substitutions (usually in your C file): ! person i am = you are ! person you are = i am ! person i'm = you're ! person you're = I'm ! person my = your ! person your = my ! person you = I ! person i = you Notice in this example that each pair of substitutions works in both directions. To invoke person substitutions, you can use the CpersonE> tag (to substitute on the CstarE>), or C<{person}...{/person}> to substitute on something else. Here is a practical example to show why person substitutions can come in handy: + say * - Umm... "" Now, if the user says, "say I am the greatest", the bot will reply with "Umm... "you are the greatest"". Without person substitutions, the bot would repeat "i am the greatest" instead. =head1 TAGS By now, you've seen examples of all the different RiveScript commands. Sprinkled here and there throughout the examples were tags. Examples of tags you've seen are CstarE>, CgetE>, and C<{topic}>. There are many more tags available! This section will teach you about all the tags and how to use them. In general, a tag that has EangledE brackets are tags that insert text in their place, or tags that set a variable silently. Tags that have {curly} brackets modify the text around them. Tags can be used with all the RiveScript commands except where explicitly noted. =over 4 =item CstarE>, Cstar1E> - CstarNE> The star tag is used for capturing values used in wildcards, alternatives and arrays present in the matched trigger. You've seen many examples of this in this tutorial. If you have multiple stars, you can use Cstar1E>, Cstar2E>, Cstar3E>, etc. to use the stars in order from left to right. The CstarE> tag is an alias for Cstar1E>, so if you only have one wildcard this tag can be useful. These tags can not be used with C<+ Trigger>. =item CbotstarE>, Cbotstar1E> - CbotstarNE> This tag is similar to CstarE>, but it captures wildcards present in a C<% Previous> line. Here is an example: + i bought a new * - Oh? What color is your new ? + (@colors) % oh what color is your new * - is a pretty color for a . Like the CstarE> tag, CbotstarE> is an alias for Cbotstar1E>. These tags can not be used with C<+ Trigger>. =item CinputE>, CreplyE> The input and reply tags are used for showing previous messages sent by the user and the bot, respectively. The previous 9 messages and responses are stored, so you can use the tags Cinput1E> through Cinput9E>, or Creply1E> through Creply9E> to get a particular message or reply. CinputE> is an alias for Cinput1E>, and CreplyE> is an alias for Creply1E>. Here are a couple examples: // If the user repeats the bot's previous message + - Don't repeat what I say. // If the user keeps repeating themselves over and over. + * == => That's the second time you've repeated yourself. * == => If you repeat yourself again I'll stop talking. * == => That's it. I'm not talking.{topic=sorry} - Please don't repeat yourself. // An example that uses both tags + why did you say that - I said, "", because you said, "". =item CidE> This tag inserts the user's ID, which was passed in to the RiveScript interpreter when fetching a reply. With the interpreter shipped with the Perl RiveScript library, the CidE> is, by default, C. RiveScript uses user IDs to keep multiple users separate. You can use the same RiveScript interpreter to serve responses to multiple users, and it will keep their user variables separate based on their ID. Here is an example of how you might distinguish the botmaster from other users based on a screen name (for example, for an instant messenger bot, where the user ID is set to the user's screen name). ! var master = kirsle + am i your master * == => Yes, you are. Hi Kirsle! - No, is my master, and you are . =item CbotE> The CbotE> tag is used for retrieving a bot variable. It can also be used to set a bot variable. Bot variables can be considered "global" to the RiveScript interpreter instance. That is, if you set the bot's name to Aiden, its name will be Aiden for everybody who asks, regardless of the user's ID. This is in contrast to user variables which are tied to a specific user ID. + what is your name - You can call me . + tell me about yourself - I am , a chatterbot written by . // Setting a bot variable dynamically + i hate you - Aww! You've just ruined my day. =item CenvE> The CenvE> tag is used for retrieving global variables. It can also be used to set a global variable. For example, if the RiveScript interpreter copies all its environment variables into RiveScript globals, a CGI-based RiveScript bot could tell a user their IP address. + what is my ip - Your IP address is: And here is an example of how you can set a global using this tag. In this example, the bot's master is able to turn debug mode on or off dynamically. + set debug mode (true|false) * == => >Debug mode set to . - You're not my master. =item CgetE>, CsetE> These tags are used to get or set a user variable, respectively. User variables are arbitrary name/value pairs. You can make up any variable name you want. Here are some examples: + my name is * - >Nice to meet you, . + i am # years old - >I will remember that you are years old. + what do you know about me - I know your name is and you are years old. If you attempt to CgetE> a variable that had never been defined for the user before, it will insert the word "undefined" in its place instead. You can use this feature to test whether a variable has been defined: + do you know my name * != undefined => Yes, your name is . - No, you've never told me your name before. CgetE> can be used in a trigger, but CsetE> can not. =item CaddE>, CsubE>, CmultE>, CdivE> These tags can add, subtract, multiply or divide a numeric user variable, respectively. + give me 5 points - You have been given 5 points. Your balance is: . If you operate on a variable that isn't defined, it will be initialized to zero first. If you operate on a variable that doesn't currently contain a number, an error message will appear in the place of the tag. These tags can not be used with C<+ Trigger>. =item C<{topic}> This tag changes the client's topic. + play hangman - {topic=hangman}Now playing hangman. Type "quit" to quit. > topic hangman + quit - Quitting the game.{topic=random} + * - hangman < topic This tag can not be used with C<+ Trigger>. =item C<{weight}> This tag applies a weight to a trigger or response. When used with a trigger, it controls the matching priority of the trigger (a higher weight means higher priority). When used with a reply, it controls how frequently that reply will be randomly chosen. + * or something{weight=10} - Or something. <@> + hello - Hi there!{weight=20} - Hey!{weight=10} - Howdy! This tag can only be used with C<+ Trigger> and C<- Reply>. =item C<{@}, E@E> This tag performs an inline redirection to a different trigger. C@E> is an alias for C<{@ EstarE}>. + your * - I think you mean to say "you are" or "you're", not "your". {@you are } You don't need to include a space between the C<@> and the trigger text. This tag can not be used with C<+ Trigger>. =item C<{random}> Insert a sub-set of random text. This can come in handy in conditional lines, if you want a random reply for a condition. Between the C<{random}> and C<{/random}> tags, separate your possible texts with a pipe symbol. Here is an example: + hello * != undefined => {random} ^ Hello there, !| ^ Nice to see you again, !| ^ Hey, !{/random} - Hello there! - Hi there! - Hello! In this example, you give a random response that includes the user's name if the bot knows it, or a normal random response otherwise. This tag can not be used with C<+ Trigger>. =item C<{person}>, CpersonE> Processes person substitutions on some text. See L<"Person Substitutions">. + say * - CpersonE> is an alias for C<{person}EstarE{/person}>. This tag can not be used with C<+ Trigger>. =item CformalE>, CsentenceE>, CuppercaseE>, ClowercaseE> Change the case formatting on some text. CformalE> is an alias for C<{formal}EstarE{/formal}>. The same applies for the other tags. B text makes the first letter of each word uppercase. This is useful for names and other proper nouns. B text makes the first word of each sentence uppercase. B and B make the entire string upper or lower case. =item CcallE> This tag is used to call an object macro. Example: // Call a macro named "reverse" and give it an argument + say * to me in reverse - reverse The first word in the CcallE> tag is the name of an object macro to call (see L<"Object Macros">). The macros may optionally take some arguments, and you can pass them in by placing them in the CcallE> tag as shown in this example. =item C<{ok}> This is only used in the Begin Block in response to the C trigger. It indicates that it's OK to fetch a reply for the user's message. The reply will replace the C<{ok}> tag. =item C<\s> This inserts a space character. It's useful when using the C<^ Continue> command to extend a line of RiveScript code. =item C<\n> This inserts a line break. =back =head1 CONCLUSION This concludes the RiveScript tutorial. By now you should have a thorough understanding of how RiveScript code is written and you should be able to start creating replies for your own chatterbot. For more information about RiveScript, see http://www.rivescript.com/ =head1 VERSION AND AUTHOR This tutorial was last updated on June 8, 2012. This tutorial was written by Noah Petherbridge, http://www.kirsle.net/ =head1 APPENDIX =head2 Windows Troubleshooting =head3 Command Prompt Help If you're not familiar with DOS commands and how to change directories in a command prompt window, you should look up a quick tutorial on how to use the command line. One such tutorial can be found at http://www.computerhope.com/issues/chusedos.htm For the lazy, you can create a simple batch file and place it in the same folder as the RiveScript interpreter that would open a command prompt window there, without needing to C to that folder yourself. Open Notepad and type the following code into a new file: @echo off cmd Save the file as C<"cmd.bat"> (I the quotation marks!) in the same folder as the C file from the Perl RiveScript distribution. Now, navigate to that folder in Windows Explorer and double-click on the C file (the file might appear as simply "C" if file extensions are hidden). =head2 Writing an Interpreter Here is an example of how to write your own RiveScript interpreter application in Perl 5. #!/usr/bin/perl use strict; use warnings; use RiveScript; # Create a new RiveScript interpreter object. my $rs = RiveScript->new(); # Load a directory full of RiveScript documents. $rs->loadDirectory("./replies"); # You must sort the replies before trying to fetch any! $rs->sortReplies(); # Enter a loop to let the user chat with the bot using standard I/O. while (1) { print "You> "; chomp(my $message = ); # Let the user type "/quit" to quit. if ($message eq "/quit") { exit(0); } # Fetch a reply from the bot. my $reply = $rs->reply("user", $message); print "Bot> $reply\n"; } =head1 SEE ALSO L L - The RiveScript Working Draft - http://www.rivescript.com/wd/RiveScript.html RiveScript-v2.0.2/bin/rivescript000755 000765 000024 00000040412 12640307464 020657 0ustar00npetherbridgestaff000000 000000 #!/usr/bin/perl # A front-end to RiveScript. # See `rivescript --help` for help. use 5.10.0; use strict; use warnings; use RiveScript; use Getopt::Long; use Pod::Text; use IO::Socket; use IO::Select; use JSON; #------------------------------------------------------------------------------# # Command Line Arguments # #------------------------------------------------------------------------------# my $opt = { debug => 0, # --debug, enables debug mode verbose => 1, # Private, verbose mode for RS log => "", # --log, debug logs to file instead of terminal json => 0, # --json, running in batch mode listen => "", # --listen, listen on a TCP port utf8 => 0, # --utf8, use UTF-8 mode in RiveScript depth => 50, # depth variable strict => 1, # --strict, strict mode data => "", # --data, provide JSON data via CLI instead of STDIN help => 0, # --help }; GetOptions ( 'debug|d' => \$opt->{debug}, 'log=s' => \$opt->{log}, 'help|h|?' => \$opt->{help}, 'json|j' => \$opt->{json}, 'utf8|u' => \$opt->{utf8}, 'listen|l=s' => \$opt->{listen}, 'depth=i' => \$opt->{depth}, 'data=s' => \$opt->{data}, 'strict!' => \$opt->{strict}, ); # Asking for help? if ($opt->{help}) { # Give them our POD instructions. my $pod = Pod::Text->new (sentence => 0, width => 78); $pod->parse_from_filehandle(*DATA); exit(0); } # Debug mode options. if ($opt->{log}) { # Logging automatically enables debugging. $opt->{debug} = 1; $opt->{verbose} = 0; } # UTF-8 support? if ($opt->{utf8}) { binmode(STDIN, ":utf8"); binmode(STDOUT, ":utf8"); binmode(STDERR, ":utf8"); } #------------------------------------------------------------------------------# # Main Program Begins Here # #------------------------------------------------------------------------------# # A brain has been specified? my $root = scalar(@ARGV) ? $ARGV[0] : $RiveScript::basedir . "/demo"; # Create the RiveScript interpreter. my $rs = init(); my $json; # JSON interpreter if we need it. my $server; # Server socket if we need it. my $select; # Selector object if we need it. # Interactive mode? if (!$opt->{json} && !$opt->{listen}) { # If called with no arguments, hint about the --help option. unless (scalar(@ARGV)) { print "Hint: use `rivescript --help` for documentation on this command.\n\n"; } print "RiveScript Interpreter - Interactive Mode\n" . "-----------------------------------------\n" . "RiveScript Version: $RiveScript::VERSION\n" . " Reply Root: $root\n\n" . "You are now chatting with the RiveScript bot. Type a message and press Return to send it.\n" . "When finished, type '/quit' to exit the program. Type '/help' for other options.\n\n"; while (1) { print "You> "; chomp(my $input = ); # Commands. if ($input =~ /^\/help/i) { print "> Supported Commands:\n" . "> /help - Displays this message.\n" . "> /reload - Reload the RiveScript brain.\n" . "> /quit - Exit the program.\n"; } elsif ($input =~ /^\/reload/i) { # Reload the brain. undef $rs; $rs = init(); print "> RiveScript has been reloaded.\n\n"; } elsif ($input =~ /^\/(?:quit|exit)/i) { # Quit. exit(0); } else { # Get a response. my $reply = $rs->reply("localuser", $input); print "Bot> $reply\n"; } } } else { # JSON mode. $json = JSON->new->pretty(); # Are we listening from a TCP socket or standard I/O? if ($opt->{listen}) { tcp_mode(); } else { json_mode(); } } # Handle JSON mode: standard input and output sub json_mode { my $buffer = ""; my $stateful = 0; # Did they provide us a complete message via --data? if ($opt->{data}) { $buffer = $opt->{data}; } else { # Nope. Read from standard input. This loop breaks when we # receive the EOF (Ctrl+D) signal. while (my $line = ) { chomp($line); $line =~ s/[\x0D\x0A]+//g; # Look for the __END__ line. if ($line =~ /^__END__$/i) { # Process it. $stateful = 1; # This is a stateful session. print json_in($buffer, 1); $buffer = ""; next; } $buffer .= "$line\n"; } } # If the session was stateful, just exit, otherwise # process what we just read. if ($stateful) { exit(0); } print json_in($buffer); exit(0); } # Handle TCP mode: using a TCP socket sub tcp_mode { # Validate the listen parameter. my $hostname = "localhost"; my $port = 2001; if ($opt->{listen} =~ /^(.+?):(\d+)$/) { $hostname = $1; $port = $2; } elsif ($opt->{listen} =~ /^\d+$/) { $port = $opt->{listen}; } else { print "The --listen option requires an address and/or port number. Examples:\n" . "--listen localhost:2001\n" . "--listen 2001\n"; exit(1); } # Create a listening socket. $server = IO::Socket::INET->new ( LocalAddr => $hostname, LocalPort => $port, Proto => 'tcp', Listen => 1, Reuse => 1, ); if (!$server) { say "Couldn't create socket on $hostname:$port: $@"; exit 1; } # Create a socket selector. $select = IO::Select->new($server); say "Listening for connections at $hostname:$port"; # Listen for events. while (do_one_loop()) { select(undef,undef,undef,0.001); } } # Main loop for TCP server. my $buffer = {}; # Message buffers per connection. sub do_one_loop { # Look for new events. my @ready = $select->can_read(.1); return 1 unless @ready; foreach my $socket (@ready) { my $id = $socket->fileno; if ($socket == $server) { # It's a new connection. $socket = $server->accept(); $select->add($socket); $id = $socket->fileno; # Get the correct fileno for the new socket. say ts() ."Connection created: $id"; # Initialize their buffers. $buffer->{$id} = { buffer => "", # Current line being read lines => [], # Previous lines being read }; } else { # Read what they have to say. my $buf; $socket->recv($buf, 1024); # Completely empty? They've disconnected. if (length $buf == 0) { # Note that even a "blank line" will still have \r\n characters, so there are # no false positives to be had here! disconnect($socket); next; } # Trim excess fat. $buf =~ s/\x0D\x0A/\x0A/g; # The \r characters # Any newlines here? if ($buf =~ /\n/) { my @pieces = split(/\n/, $buf); if (scalar(@pieces) > 1) { $buffer->{$id}->{buffer} = pop(@pieces); # Keep the most recent piece # Is this the end? if ($buffer->{$id}->{buffer} =~ /^__END__/) { # We want this piece after all! push(@pieces, $buffer->{$id}->{buffer}); $buffer->{$id}->{buffer} = ""; } } push (@{$buffer->{$id}->{lines}}, @pieces); # Stash the rest } # Are they done? if (scalar @{$buffer->{$id}->{lines}} > 0 && $buffer->{$id}->{lines}->[-1] eq "__END__") { # Get their response. my @lines = @{$buffer->{$id}->{lines}}; pop(@lines); # Remove the __END__ line. # Get the reply and send it. my $response = json_in(join("\n",@lines), 1, $id); sock_send($socket, $response); # Reset their line buffer. $buffer->{$id}->{lines} = []; } elsif (scalar @{$buffer->{$id}->{lines}} > 20 || length($buffer->{$id}->{buffer}) > 1024) { # This is getting ridiculous. sock_send($socket, $json->encode({ status => "error", reply => "Internal Error: Input stream too long. Giving up.", }), 1); next; } } } return 1; } # Send a message to a socket. sub sock_send { my ($socket,$msg,$disconnect_after) = @_; $socket->send($msg) or do { # They've been disconnected. disconnect($socket); return; }; # Disconnect after? if ($disconnect_after) { disconnect($socket); } } # Disconnect a socket. sub disconnect { my $socket = shift; my $id = $socket->fileno; say ts() . "Disconnected: $id"; # Forget we ever saw them. delete $buffer->{$id}; $select->remove($socket); $socket->close(); } # Initializes the RiveScript interpreter. sub init { my $rs = RiveScript->new ( debug => $opt->{debug}, verbose => $opt->{verbose}, debugfile => $opt->{log}, depth => $opt->{depth}, strict => $opt->{strict}, utf8 => $opt->{utf8}, ); $rs->loadDirectory($root); $rs->sortReplies(); return $rs; } sub json_in { my $buffer = shift; my $end = shift; my $tcp = shift; my $data = {}; my $reply = { status => "ok", }; # Try to decode their input. eval { $data = $json->decode($buffer); }; # Error? if ($@) { $reply->{status} = "error"; $reply->{reply} = "Failed to decode your input: $@"; if ($tcp) { say ts() . "$tcp: Failed to decode JSON input: $@"; } } else { # Decode their variables. my $username = exists $data->{username} ? $data->{username} : "localuser"; if (ref($data->{vars}) eq "HASH") { foreach my $key (keys %{$data->{vars}}) { next if ref($data->{vars}->{$key}); $rs->setUservar($username, $key, $data->{vars}->{$key}); } } # Get their answer. $reply->{reply} = $rs->reply($username, $data->{message}); if ($tcp) { say ts() . "$tcp: [$username] $data->{message}"; say ts() . "$tcp: [Response] $reply->{reply}"; } # Retrieve vars. $reply->{vars} = {}; my $vars = $rs->getUservars($username); foreach my $key (keys %{$vars}) { next if ref($vars->{$key}); $reply->{vars}->{$key} = $vars->{$key}; } } # Encode and print. my $return = $json->encode($reply); $return .= "__END__\n" if $end; return $return; } # Simple time stamp. sub ts { my @now = localtime(); return sprintf("[%02d:%02d:%02d] ", $now[2], $now[1], $now[0]); } __DATA__ =head1 NAME rivescript - A command line frontend to the Perl RiveScript interpreter. =head1 SYNOPSIS $ rivescript [options] [path to RiveScript documents] =head1 DESCRIPTION This is a command line front-end to the RiveScript interpreter. This script obsoletes the old C, and can also be used non-interactively by third party programs. To that end, it supports a variety of input/output and session handling methods. If no RiveScript document path is given, it will default to the example brain that ships with the RiveScript module, which is based on the Eliza bot. =head1 OPTIONS =over 4 =item --debug, -d Enables debug mode. This will print all debug data from RiveScript to your terminal. If you'd like it to log to a file instead, use the C<--log> option instead of C<--debug>. =item --log FILE Enables debug mode and prints the debug output to C instead of to your terminal. =item --json, -j Runs C in JSON mode, for running the script in a non-interactive way (for example, to use RiveScript in a programming language that doesn't have a native RiveScript library). See L<"JSON Mode"> for details. =item --data JSON_DATA When using the C<--json> option, you can provide the JSON input message as a command line argument with the C<--data> option. If not provided, then the JSON data will be read from standard input instead. This option is helpful, therefore, if you don't want to open a two-way pipe, but rather pass the message as a command line argument and just read the response from standard output. See L<"JSON Mode"> for more details. =item --listen, -l [ADDRESS:]PORT Runs C in TCP mode, for running the script as a server daemon. If an address isn't specified, it will bind to C. See L<"TCP Mode"> for details. =item --strict, --nostrict Enables strict mode for the RiveScript parser. It's enabled by default, use C<--nostrict> to disable it. Strict mode prevents the parser from continuing when it finds a syntax error in the RiveScript documents. =item --depth=50 Override the default recursion depth limit. This controls how many times RiveScript will recursively follow redirects to other replies. The default is C<50>. =item --utf8, -u Use the UTF-8 option in RiveScript. This allows triggers to contain foreign characters and relaxes the filtering of user messages. This is not enabled by default! =item --help Displays this documentation in your terminal. =back =head1 USAGE =head2 Interactive Mode This is the default mode used when you run C without specifying another mode. This mode behaves similarly to the old C script and lets you chat one-on-one with your RiveScript bot. This mode can be used to test your RiveScript bot. Example: $ rivescript /path/to/rs/files =head2 JSON Mode This mode should be used when calling from a third party program. In this mode, data that enters and leaves the script are encoded in JSON. Example: $ rivescript --json /path/to/rs/files The format for incoming JSON data is as follows: { "username": "localuser", "message": "Hello bot!", "vars": { "name": "Aiden" } } Here, C is a unique name for the user, C is their message to the bot, and C is a hash of any user variables your program might be keeping track of (such as the user's name and age). The response from C will look like the following: { "status": "ok", "reply": "Hello, human!", "vars": { "name": "Aiden" } } Here, C will be C<"ok"> or C<"error">, C is the bot's response to your message, and C is a hash of the current variables for the user (so that your program can save them somewhere). =head3 Standard Input or Data By default, JSON mode will read from standard input to receive your JSON message. As an alternative to this, you can provide the C<--data> option to C to present the incoming JSON data as a command line argument. This may be helpful if you don't want to open a two-way pipe to C, and would rather pass your input as a command line argument and simply read the response from standard output. Example: $ rivescript --json --data '{"username": "localuser", "message": "hello" }' \ /path/to/rs/files This will cause C to print its JSON response to standard output and exit. You can't have a stateful session using this method. =head3 End of Message There are two ways you can use the JSON mode: "fire and forget," or keep a stateful session open. In "fire and forget," you open the program, print your JSON input and send the EOF signal, and then C sends you the JSON response and exits. In a stateful session mode, you must send the text C<__END__> on a line by itself after you finish sending your JSON data. Then C will process it, return its JSON response and then also say C<__END__> at the end. Example: { "username": "localuser", "message": "Hello bot!", "vars": {} } __END__ And the response: { "status": "ok", "reply": "Hello, human!", "vars": {} } __END__ This way you can reuse the same pipe to send and receive multiple messages. =head2 TCP Mode TCP Mode will make C listen on a TCP socket for incoming connections. This way you can connect to it from a different program (for example, a CGI script or a program written in a different language). Example: $ rivescript --listen localhost:2001 TCP Mode behaves similarly to L<"JSON Mode">; the biggest difference is that it will read and write using a TCP socket instead of standard input and output. Unlike JSON Mode, however, TCP Mode I runs in a stateful way (the JSON messages must end with the text "C<__END__>" on a line by itself). See L<"End of Message">. If the C<__END__> line isn't found after 20 lines of text are read from the client, it will give up and send the client an error message (encoded in JSON) and disconnect it. =head1 SEE ALSO L, the Perl RiveScript interpreter. =head1 AUTHOR Noah Petherbridge, http://www.kirsle.net =head1 LICENSE RiveScript - Rendering Intelligence Very Easily Copyright (C) 2012 Noah Petherbridge This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA =cut