Skip to content

Commit

Permalink
Merge pull request #154 from aichaos/bug/misc-fixes
Browse files Browse the repository at this point in the history
Misc low-hanging fruit, bug fixes and feature additions
  • Loading branch information
kirsle authored Aug 8, 2016
2 parents 5665873 + a548dbb commit 7118763
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 33 deletions.
20 changes: 20 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changes

* 1.15.0 2016-08-07
- Add a new contructor option, `forceCase`, which will force-lowercase your
triggers during parse time, enabling authors to use uppercase letters in
triggers without it being a syntax error. Do note however that Unicode
case folding can become an issue with certain symbols. (Bugs #143 and #69).
- Fix a bug where inline redirects, like `{@ hello}` would fail to match their
trigger due to the presence of a space between the `@` and text (bug #145).
- Add a non-fatal warning at parse time if it's detected that you used an
`@Redirect` command in conjunction with a `-Reply` or `*Condition`. In such
cases, the redirect "wins" and preempts the others, which may be surprising
behavior, and RiveScript will warn you about this now (bug #58).
- Prevent errors from arising when the user's history object is invalid, for
example if somebody manually overrode the `__history__` user
variable (PR #151).
- Fix a bug in the `write()` function where `-Replies` were being written when
a trigger actually had no reply (resulting in a single-character `-`
command which raises an error when re-parsed) (PR #141).
- Add more documentation to the `rs.Promise` function, including a full
example of how to use the `replyAsync()` method (bug #144).

* 1.14.0 2016-07-09
- Add a new API function: `getUserTopicTriggers` returns a list of triggers
available from a user's current topic, including triggers that came from
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ this project on GitHub that show how to interface with a RiveScript bot in
a variety of ways--such as through a web browser or a telnet server--and other
code snippets and useful tricks.

## RIVESCRIPT PLAYGROUND

For testing and sharing RiveScript snippets that use the JavaScript
implementation, check out the [RiveScript Playground](https://play.rivescript.com/).

It's a JSFiddle style web app for playing with RiveScript in your web browser
and sharing code with others.

<https://play.rivescript.com/>

## USAGE

The distribution of RiveScript.js includes an interactive shell for testing your
Expand Down
37 changes: 29 additions & 8 deletions docs/html/rivescript.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ <h1>RiveScript (hash options)</h1>
<p>Create a new RiveScript interpreter. <code>options</code> is an object with the
following keys:</p>
<ul>
<li>bool debug: Debug mode (default false)</li>
<li>int depth: Recursion depth limit (default 50)</li>
<li>bool strict: Strict mode (default true)</li>
<li>bool utf8: Enable UTF-8 mode (default false)</li>
<li>func onDebug: Set a custom handler to catch debug log messages (default null)</li>
<li>obj errors: Customize certain error messages (see below)</li>
<li>bool debug: Debug mode (default false)</li>
<li>int depth: Recursion depth limit (default 50)</li>
<li>bool strict: Strict mode (default true)</li>
<li>bool utf8: Enable UTF-8 mode (default false, see below)</li>
<li>bool forceCase: Force-lowercase triggers (default false, see below)</li>
<li>func onDebug: Set a custom handler to catch debug log messages (default null)</li>
<li>obj errors: Customize certain error messages (see below)</li>
</ul>
<h2>UTF-8 Mode</h2>
<p>In UTF-8 mode, most characters in a user's message are left intact, except for
Expand All @@ -28,6 +29,18 @@ <h2>UTF-8 Mode</h2>
bot.unicodePunctuation = new RegExp(/[.,!?;:]/g);</code></pre>


<h2>Force Case</h2>
<p>This option to the constructor will make RiveScript lowercase all the triggers
it sees during parse time. This may ease the pain point that authors
experience when they need to write a lowercase "i" in triggers, for example
a trigger of <code>i am *</code>, where the lowercase <code>i</code> feels unnatural to type.</p>
<p>By default a capital ASCII letter in a trigger would raise a parse error.
Setting the <code>forceCase</code> option to <code>true</code> will instead silently lowercase the
trigger and thus avoid the error.</p>
<p>Do note, however, that this can have side effects with certain Unicode symbols
in triggers, see <a href="https://www.w3.org/International/wiki/Case_folding">case folding in Unicode</a>.
If you need to support Unicode symbols in triggers this may cause problems with
certain symbols when made lowercase.</p>
<h2>Custom Error Messages</h2>
<p>You can provide any or all of the following properties in the <code>errors</code>
argument to the constructor to override certain internal error messages:</p>
Expand Down Expand Up @@ -77,15 +90,23 @@ <h1>Constructor and Debug Methods</h1>
<h2>string version ()</h2>
<p>Returns the version number of the RiveScript.js library.</p>
<h2>Promise Promise</h2>
<p>Alias for RSVP.Promise</p>
<p>You can use shortcut in your async subroutines</p>
<p>Alias for <code>RSVP.Promise</code> for use in async object macros.</p>
<p>This enables you to create a JavaScript object macro that returns a promise
for asynchronous tasks (e.g. polling a web API or database). Example:</p>
<pre class="codehilite"><code class="language-javascript">rs.setSubroutine(&quot;asyncHelper&quot;, function (rs, args) {
return new rs.Promise(function (resolve, reject) {
resolve(42);
});
});</code></pre>


<p>If you're using promises in your object macros, you need to get a reply from
the bot using the <code>replyAsync()</code> method instead of <code>reply()</code>, for example:</p>
<pre class="codehilite"><code class="language-javascript">rs.replyAsync(username, message, this).then(function(reply) {
console.log(&quot;Bot&gt; &quot;, reply);
});</code></pre>


<h2>private void runtime ()</h2>
<p>Detect the runtime environment of this module, to determine if we're
running in a web browser or from node.</p>
Expand Down
43 changes: 35 additions & 8 deletions docs/rivescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
Create a new RiveScript interpreter. `options` is an object with the
following keys:

* bool debug: Debug mode (default false)
* int depth: Recursion depth limit (default 50)
* bool strict: Strict mode (default true)
* bool utf8: Enable UTF-8 mode (default false)
* func onDebug: Set a custom handler to catch debug log messages (default null)
* obj errors: Customize certain error messages (see below)
* bool debug: Debug mode (default false)
* int depth: Recursion depth limit (default 50)
* bool strict: Strict mode (default true)
* bool utf8: Enable UTF-8 mode (default false, see below)
* bool forceCase: Force-lowercase triggers (default false, see below)
* func onDebug: Set a custom handler to catch debug log messages (default null)
* obj errors: Customize certain error messages (see below)

## UTF-8 Mode

Expand All @@ -25,6 +26,22 @@ var bot = new RiveScript({utf8: true});
bot.unicodePunctuation = new RegExp(/[.,!?;:]/g);
```

## Force Case

This option to the constructor will make RiveScript lowercase all the triggers
it sees during parse time. This may ease the pain point that authors
experience when they need to write a lowercase "i" in triggers, for example
a trigger of `i am *`, where the lowercase `i` feels unnatural to type.

By default a capital ASCII letter in a trigger would raise a parse error.
Setting the `forceCase` option to `true` will instead silently lowercase the
trigger and thus avoid the error.

Do note, however, that this can have side effects with certain Unicode symbols
in triggers, see [case folding in Unicode](https://www.w3.org/International/wiki/Case_folding).
If you need to support Unicode symbols in triggers this may cause problems with
certain symbols when made lowercase.

## Custom Error Messages

You can provide any or all of the following properties in the `errors`
Expand Down Expand Up @@ -94,9 +111,10 @@ Returns the version number of the RiveScript.js library.

## Promise Promise

Alias for RSVP.Promise
Alias for `RSVP.Promise` for use in async object macros.

You can use shortcut in your async subroutines
This enables you to create a JavaScript object macro that returns a promise
for asynchronous tasks (e.g. polling a web API or database). Example:

```javascript
rs.setSubroutine("asyncHelper", function (rs, args) {
Expand All @@ -106,6 +124,15 @@ rs.setSubroutine("asyncHelper", function (rs, args) {
});
```

If you're using promises in your object macros, you need to get a reply from
the bot using the `replyAsync()` method instead of `reply()`, for example:

```javascript
rs.replyAsync(username, message, this).then(function(reply) {
console.log("Bot> ", reply);
});
```

## private void runtime ()

Detect the runtime environment of this module, to determine if we're
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rivescript",
"version": "1.14.0",
"version": "1.15.0",
"description": "RiveScript is a scripting language for chatterbots, making it easy to write trigger/response pairs for building up a bot's intelligence.",
"keywords": [
"bot",
Expand Down
4 changes: 2 additions & 2 deletions src/brain.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -962,10 +962,10 @@ class Brain
@warn "Infinite loop looking for redirect tag!"
break

target = match[1]
target = utils.strip match[1]
@say "Inline redirection to: #{target}"
subreply = @_getReply(user, target, "normal", step+1, scope)
reply = reply.replace(new RegExp("\\{@" + utils.quotemeta(target) + "\\}", "i"), subreply)
reply = reply.replace(new RegExp("\\{@" + utils.quotemeta(match[1]) + "\\}", "i"), subreply)
match = reply.match(/\{@([^\}]*?)\}/)

return reply
Expand Down
17 changes: 17 additions & 0 deletions src/parser.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ class Parser
if line.indexOf(" //") > -1
line = utils.strip(line.split(" //")[0])

# In the event of a +Trigger, if we are force-lowercasing it, then do so
# now before the syntax check.
if @master._forceCase is true and cmd is "+"
line = line.toLowerCase()

# Run a syntax check on this line.
syntaxError = @checkSyntax cmd, line
if syntaxError isnt ""
Expand Down Expand Up @@ -412,6 +417,10 @@ class Parser
@warn "Response found before trigger", filename, lineno
continue

# Warn if we also saw a hard redirect.
if curTrig.redirect isnt null
@warn "You can't mix @Redirects with -Replies", filename, lineno

@say "\tResponse: #{line}"
curTrig.reply.push line

Expand All @@ -421,6 +430,10 @@ class Parser
@warn "Condition found before trigger", filename, lineno
continue

# Warn if we also saw a hard redirect.
if curTrig.redirect isnt null
@warn "You can't mix @Redirects with *Conditions", filename, lineno

@say "\tCondition: #{line}"
curTrig.condition.push line

Expand All @@ -434,6 +447,10 @@ class Parser

when "@"
# @ Redirect
# Make sure they didn't mix them with incompatible commands.
if curTrig.reply.length > 0 or curTrig.condition.length > 0
@warn "You can't mix @Redirects with -Replies or *Conditions", filename, lineno

@say "\tRedirect response to: #{line}"
curTrig.redirect = utils.strip line

Expand Down
56 changes: 42 additions & 14 deletions src/rivescript.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"use strict"

# Constants
VERSION = "1.14.0"
VERSION = "1.15.0"

# Helper modules
Parser = require "./parser"
Expand All @@ -25,12 +25,13 @@ readDir = require("fs-readdir-recursive")
# Create a new RiveScript interpreter. `options` is an object with the
# following keys:
#
# * bool debug: Debug mode (default false)
# * int depth: Recursion depth limit (default 50)
# * bool strict: Strict mode (default true)
# * bool utf8: Enable UTF-8 mode (default false)
# * func onDebug: Set a custom handler to catch debug log messages (default null)
# * obj errors: Customize certain error messages (see below)
# * bool debug: Debug mode (default false)
# * int depth: Recursion depth limit (default 50)
# * bool strict: Strict mode (default true)
# * bool utf8: Enable UTF-8 mode (default false, see below)
# * bool forceCase: Force-lowercase triggers (default false, see below)
# * func onDebug: Set a custom handler to catch debug log messages (default null)
# * obj errors: Customize certain error messages (see below)
#
# ## UTF-8 Mode
#
Expand All @@ -47,6 +48,22 @@ readDir = require("fs-readdir-recursive")
# bot.unicodePunctuation = new RegExp(/[.,!?;:]/g);
# ```
#
# ## Force Case
#
# This option to the constructor will make RiveScript lowercase all the triggers
# it sees during parse time. This may ease the pain point that authors
# experience when they need to write a lowercase "i" in triggers, for example
# a trigger of `i am *`, where the lowercase `i` feels unnatural to type.
#
# By default a capital ASCII letter in a trigger would raise a parse error.
# Setting the `forceCase` option to `true` will instead silently lowercase the
# trigger and thus avoid the error.
#
# Do note, however, that this can have side effects with certain Unicode symbols
# in triggers, see [case folding in Unicode](https://www.w3.org/International/wiki/Case_folding).
# If you need to support Unicode symbols in triggers this may cause problems with
# certain symbols when made lowercase.
#
# ## Custom Error Messages
#
# You can provide any or all of the following properties in the `errors`
Expand Down Expand Up @@ -120,11 +137,12 @@ class RiveScript
opts = {}

# Default parameters
@_debug = if opts.debug then opts.debug else false
@_strict = if opts.strict then opts.strict else true
@_depth = if opts.depth then parseInt(opts.depth) else 50
@_utf8 = if opts.utf8 then opts.utf8 else false
@_onDebug = if opts.onDebug then opts.onDebug else null
@_debug = if opts.debug then opts.debug else false
@_strict = if opts.strict then opts.strict else true
@_depth = if opts.depth then parseInt(opts.depth) else 50
@_utf8 = if opts.utf8 then opts.utf8 else false
@_forceCase = if opts.forceCase then opts.forceCase else false
@_onDebug = if opts.onDebug then opts.onDebug else null

# UTF-8 punctuation, overridable by the user.
@unicodePunctuation = new RegExp(/[.,!?;:]/g)
Expand Down Expand Up @@ -194,9 +212,10 @@ class RiveScript
##
# Promise Promise
#
# Alias for RSVP.Promise
# Alias for `RSVP.Promise` for use in async object macros.
#
# You can use shortcut in your async subroutines
# This enables you to create a JavaScript object macro that returns a promise
# for asynchronous tasks (e.g. polling a web API or database). Example:
#
# ```javascript
# rs.setSubroutine("asyncHelper", function (rs, args) {
Expand All @@ -205,6 +224,15 @@ class RiveScript
# });
# });
# ```
#
# If you're using promises in your object macros, you need to get a reply from
# the bot using the `replyAsync()` method instead of `reply()`, for example:
#
# ```javascript
# rs.replyAsync(username, message, this).then(function(reply) {
# console.log("Bot> ", reply);
# });
# ```
##
Promise: RSVP.Promise

Expand Down
28 changes: 28 additions & 0 deletions test/test-options.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,31 @@ exports.test_concat_newline_stringify = (test) ->
expect = '! version = 2.0\n! local concat = none\n\n+ test *\n- First B line\\nSecond B line\\nThird B line\n\n+ status is *\n* <star1> == good => All good!\\nCongrats!\\nHave fun!\n* <star1> == bad => Oh no.\\nThat sucks.\\nTry again.\n- I didn\'t get that.\\nWhat did you say?\n\n> topic a_cool_topic\n\n\t+ hello\n\t- Oh hi there.\\nDo you liek turtles?\n\n< topic\n'
test.equal(src, expect)
test.done()

exports.test_force_case = (test) ->
bot = new TestCase(test, """
+ hello bot
- Hello human!
// Note the capital "I", this would raise a parse error normally.
+ I am # years old
- <set age=<star>>A lot of people are <get age>.
""", { forceCase: true })

bot.reply("hello bot", "Hello human!")
bot.reply("i am 5 years old", "A lot of people are 5.")
bot.reply("I am 6 years old", "A lot of people are 6.")
test.done()

exports.test_no_force_case = (test) ->
bot = new TestCase(test, "")
try
bot.extend("""
+ I am # years old
- <set age=<star>>A lot of people are <get age>.
""")
catch e
# An exception was expected here.
test.equal(e, "Syntax error: Triggers may only contain lowercase letters, numbers, and these symbols: ( | ) [ ] * _ # { } < > = at stream() line 1 near + I am # years old")

test.done()
9 changes: 9 additions & 0 deletions test/test-replies.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,21 @@ exports.test_redirects = (test) ->
+ hey
@ hello
// Test the {@} tag with and without spaces.
+ hi there
- {@hello}
+ howdy
- {@ hello}
+ hola
- {@ hello }
""")
bot.reply("hello", "Hi there!")
bot.reply("hey", "Hi there!")
bot.reply("hi there", "Hi there!")
bot.reply("howdy", "Hi there!")
bot.reply("hola", "Hi there!")
test.done()

exports.test_conditionals = (test) ->
Expand Down

0 comments on commit 7118763

Please sign in to comment.