From f4d3c64a089d7e78d3fbabd01100375e5ed32694 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Tue, 15 Aug 2023 20:15:39 -0700 Subject: [PATCH] Various modernization efforts --- Changes.md | 79 ++++++++++++++++++++++++++++++++ README.md | 86 ++++++++++++++++++++++++++++++++++- cmd/rivescript/main.go | 23 ++++++---- config.go | 5 ++ eg/brain/javascript.rive | 12 ++--- go.mod | 5 +- go.sum | 78 +++++++++++++++++++++++++++---- lang/javascript/javascript.go | 29 ++++++------ parser/parser.go | 18 ++++++++ rivescript.go | 14 +++--- rsts_test.go | 2 +- tags.go | 11 ++++- 12 files changed, 310 insertions(+), 52 deletions(-) diff --git a/Changes.md b/Changes.md index 6037bfe..2987887 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,85 @@ This documents the history of significant changes to `rivescript-go`. +## v0.4.0 - Aug 15, 2023 + +This update will modernize the Go port of RiveScript bringing some of the +newer features that were available on the JavaScript or Python ports. + +### The ?Keyword Command + +This update adds support for the newer `?Keyword` command in RiveScript. + +This command works around a Unicode matching bug that affected the +Go, JavaScript and Python ports of RiveScript. For example: + +```rivescript +// You wanted this trigger to match if "你好" appears anywhere +// in a user's message, but it wasn't working before. ++ [*] 你好 [*] +- 你好! + +// Now: use the ?Keyword command in place of +Trigger +? 你好 +- 你好! +``` + +The optional wildcard `[*]` syntax didn't work when paired with Unicode +symbols because the regular expression that `[*]` is turned into (which +involves "word boundary" or `\b` characters) only worked with ASCII latin +characters. The ?Keyword command works around this by translating into a ++Trigger that tries _every_ combination of literal word, wildcard on either +or both sides, and optional wildcard, to ensure that your keyword trigger +will indeed match your keyword _anywhere_ in the user's message. + +### CaseSensitive User Message Support + +By default RiveScript would always lowercase the user's message as it +comes in. If this is undesirable and you'd like to preserve their _actual_ +capitalization (when it gets captured by wildcards and comes out in their +`` tags), provide the new CaseSensitive boolean to your RiveScript +constructor: + +```go +bot := rivescript.New(&rivescript.Config{ + CaseSensitive: true, +}) +``` + +The built-in `rivescript` command line program adds a `-case` parameter +to enable this option for testing: + +```bash +rivescript -case /path/to/brain +``` + +### JavaScript Object Macros + +The official JavaScript object macro support library has a couple of +exciting new updates. + +Firstly, the JavaScript engine has been replaced from +[github.com/robertkrimen/otto](https://github.com/robertkrimen/otto) with +[github.com/dop251/goja](https://github.com/dop251/goja) which should +provide a better quality of life for writing JavaScript functions for +your bot. Goja supports many of the modern ES6+ features including the +let and const keyword and arrow functions. + +Additionally, the Goja runtime will be exposed at the `.VM` accessor +for the JavaScriptHandler class, so you can directly play with it and +set global variables or Go functions that can be called from your +JavaScript macros in your bot, allowing for much greater extensibility. + +### Other Changes + +* Add shellword parsing for `` tags: you can pass "quoted strings" + in which will go in as one 'word' in the `args` array, instead of the + arguments being literally split by space characters. This brings the + Go port of RiveScript in line with the JavaScript port which has been + doing this for a while. +* Fix the rsts_test.go to properly load from the RSTS (RiveScript Test + Suite) git submodule. + ## v0.3.1 - Aug 20, 2021 This release simply adds a `go.mod` to this project so that it gets along well diff --git a/README.md b/README.md index 4613cbd..7af75ee 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ code to look up their answer via a web API. The Go version of RiveScript has support for object macros written in Go (at compile time of your application). It also has optional support for -JavaScript object macros using the Otto library. +JavaScript object macros using the [goja](https://github.com/dop251/goja) library. Here is how to define a Go object macro: @@ -166,6 +166,76 @@ bot.SetSubroutine(func(rs *rivescript.RiveScript, args []string) string { }) ``` +### JavaScript Object Macros + +Here is an example how to make JavaScript object macros available via +the [goja](https://github.com/dop251/goja) module: + +```go +package main + +import ( + "fmt" + "github.com/aichaos/rivescript-go" + "github.com/aichaos/rivescript-go/lang/javascript" +) + +func main() { + // Initialize RiveScript first. + bot := rivescript.New(rivescript.WithUTF8()) + + // Add the JavaScript object macro handler. + js := javascript.New(bot) + bot.SetHandler("javascript", js) + + // You can access the goja VM and set your own global + // variable or function bindings to be called from your + // object macros. + js.VM.Set("helloFunc", func(name string) string { + return fmt.Sprintf("Hello, %s!", name) + }) + + // Load some RiveScript code. This example just tests the + // JavaScript object macro support. + err := bot.Stream(` + > object add javascript + let a = args[0]; + let b = args[1]; + return parseInt(a) + parseInt(b); + < object + + > object fn javascript + let result = helloFunc(args[0]) + return result + < object + + + add # and # + - + = add + + + say hello * + - fn + `) + if err != nil { + fmt.Printf("Error loading RiveScript document: %s", err) + } + + // Sort the replies after loading them! + bot.SortReplies() + + // Get some replies! + inputs := []string{"add 5 and 12", "say hello goja"} + for _, message := range inputs { + fmt.Printf("You said: %s\n", message) + reply, err := bot.Reply("local-user", message) + if err != nil { + fmt.Printf("Error: %s\n", err) + } else { + fmt.Printf("The bot says: %s\n", reply) + } + } +} +``` + ## UTF-8 Support UTF-8 support in RiveScript is considered an experimental feature. It is @@ -216,6 +286,20 @@ relevant commands are: * `make test` - runs the unit tests. * `make clean` - cleans up the `.gopath`, `bin` and `dist` directories. +### Testing + +The rivescript-go repo submodules the RiveScript Test Suite (rsts) project. +If you didn't do a `git clone --recursive` for rivescript-go you can pull the +submodule via the following commands: + +```bash +git submodule init +git submodule update +``` + +Then `make test` (or `go test`) should show results from the tests run +out of the rsts/ folder. + ### Releasing You can build a release for an individual platform by running a command like diff --git a/cmd/rivescript/main.go b/cmd/rivescript/main.go index 539f21a..c201711 100644 --- a/cmd/rivescript/main.go +++ b/cmd/rivescript/main.go @@ -32,12 +32,13 @@ var Build = "-unknown-" var ( // Command line arguments. - version bool - debug bool - utf8 bool - depth uint - nostrict bool - nocolor bool + version bool + debug bool + utf8 bool + depth uint + caseSensitive bool + nostrict bool + nocolor bool ) func init() { @@ -45,6 +46,7 @@ func init() { flag.BoolVar(&debug, "debug", false, "Enable debug mode.") flag.BoolVar(&utf8, "utf8", false, "Enable UTF-8 mode.") flag.UintVar(&depth, "depth", 50, "Recursion depth limit") + flag.BoolVar(&caseSensitive, "case", false, "Enable the CaseSensitive flag, preserving capitalization in user messages") flag.BoolVar(&nostrict, "nostrict", false, "Disable strict syntax checking") flag.BoolVar(&nocolor, "nocolor", false, "Disable ANSI colors") } @@ -68,10 +70,11 @@ func main() { // Initialize the bot. bot := rivescript.New(&rivescript.Config{ - Debug: debug, - Strict: !nostrict, - Depth: depth, - UTF8: utf8, + Debug: debug, + Strict: !nostrict, + Depth: depth, + UTF8: utf8, + CaseSensitive: caseSensitive, }) // JavaScript object macro handler. diff --git a/config.go b/config.go index 3c58ce5..948b298 100644 --- a/config.go +++ b/config.go @@ -44,6 +44,11 @@ type Config struct { // RiveScript to choose its own seed, `time.Now().UnixNano()` Seed int64 + // Preserve the capitalization on a user's message instead of lowercasing it. + // By default RiveScript will lowercase all messages coming in - set this and + // the original casing will be preserved through wildcards and star tags. + CaseSensitive bool + // SessionManager is an implementation of the same name for managing user // variables for the bot. The default is the in-memory session handler. SessionManager sessions.SessionManager diff --git a/eg/brain/javascript.rive b/eg/brain/javascript.rive index 7b22edb..a65fa50 100644 --- a/eg/brain/javascript.rive +++ b/eg/brain/javascript.rive @@ -5,16 +5,16 @@ > object setvar javascript // Example of how to get the current user's ID and set // variables for them. - var uid = rs.CurrentUser(); - var name = args[0]; - var value = args[1]; + let uid = rs.CurrentUser(); + let name = args[0]; + let value = args[1]; rs.SetUservar(uid, name, value); < object > object add javascript // Demonstrats that JS objects can return numbers. - var a = args[0]; - var b = args[1]; + let a = args[0]; + let b = args[1]; return parseInt(a) + parseInt(b); < object @@ -22,4 +22,4 @@ - + = add + javascript set * to * -- Set user variable to .setvar +- Set user variable to .setvar "" diff --git a/go.mod b/go.mod index c0be919..d88b1a6 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,11 @@ module github.com/aichaos/rivescript-go go 1.16 require ( + github.com/dop251/goja v0.0.0-20230812105242-81d76064690d // indirect + github.com/mattn/go-shellwords v1.0.12 // indirect github.com/onsi/gomega v1.15.0 // indirect - github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f + github.com/robertkrimen/otto v0.2.1 + golang.org/x/text v0.12.0 // indirect gopkg.in/redis.v5 v5.2.9 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index bf46a45..311bc4e 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,25 @@ -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja v0.0.0-20230812105242-81d76064690d h1:9aaGwVf4q+kknu+mROAXUApJ1DoOwhE8dGj/XLBYzWg= +github.com/dop251/goja v0.0.0-20230812105242-81d76064690d/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= +github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -20,7 +34,18 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -34,27 +59,41 @@ github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f h1:a7clxaGmmqtdNTXyvrp/lVO/Gnkzlhc/+dLs5v965GM= -github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f/go.mod h1:/mK7FZ3mFYEn9zvNPhpngTyatyehSwte5bJZ4ehL5Xw= +github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0= +github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -65,16 +104,30 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -89,6 +142,10 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ= gopkg.in/redis.v5 v5.2.9 h1:MNZYOLPomQzZMfpN3ZtD1uyJ2IDonTTlxYiV/pEApiw= @@ -102,5 +159,6 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lang/javascript/javascript.go b/lang/javascript/javascript.go index 678eefa..5c6e258 100644 --- a/lang/javascript/javascript.go +++ b/lang/javascript/javascript.go @@ -52,11 +52,11 @@ import ( "strings" "github.com/aichaos/rivescript-go" - "github.com/robertkrimen/otto" + "github.com/dop251/goja" ) type JavaScriptHandler struct { - vm *otto.Otto + VM *goja.Runtime bot *rivescript.RiveScript functions map[string]string } @@ -64,7 +64,7 @@ type JavaScriptHandler struct { // New creates an object handler for JavaScript with its own Otto VM. func New(rs *rivescript.RiveScript) *JavaScriptHandler { js := new(JavaScriptHandler) - js.vm = otto.New() + js.VM = goja.New() js.bot = rs js.functions = map[string]string{} @@ -81,32 +81,31 @@ func (js JavaScriptHandler) Load(name string, code []string) { `, name, strings.Join(code, "\n")) // Run this code to load the function into the VM. - js.vm.Run(js.functions[name]) + js.VM.RunString(js.functions[name]) } // Call executes a JavaScript macro and returns its results. func (js JavaScriptHandler) Call(name string, fields []string) string { // Make the RiveScript object available to the JS. - v, err := js.vm.ToValue(js.bot) - if err != nil { - fmt.Printf("Error binding RiveScript object to Otto: %s", err) - } + v := js.VM.ToValue(js.bot) // Convert the fields into a JavaScript object. - jsFields, err := js.vm.ToValue(fields) - if err != nil { - fmt.Printf("Error binding fields to Otto: %s", err) - } + jsFields := js.VM.ToValue(fields) // Run the JS function call and get the result. - result, err := js.vm.Call(fmt.Sprintf("object_%s", name), nil, v, jsFields) + function, ok := goja.AssertFunction(js.VM.Get(fmt.Sprintf("object_%s", name))) + if !ok { + return fmt.Sprintf("[goja: error asserting function object_%s]", name) + } + + result, err := function(goja.Undefined(), v, jsFields) if err != nil { fmt.Printf("Error: %s", err) } reply := "" - if result.IsDefined() { - reply, _ = result.ToString() + if !goja.IsUndefined(result) { + reply = result.String() } // Return it. diff --git a/parser/parser.go b/parser/parser.go index 006cdea..2451bfc 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -181,6 +181,24 @@ func (self *Parser) Parse(filename string, code []string) (*ast.Root, error) { line = strings.TrimSpace(line) + // Allow the "?Keyword" command to work around UTF-8 bugs for users who + // wanted to use `+ [*] keyword [*]` with Unicode symbols that don't match + // properly with the usual "optional wildcard" syntax. + if cmd == "?" { + // The ?Keyword command is really an alias to a +Trigger with some workarounds + // to make it match the keyword _anywhere_, in every variation so it works with + // Unicode strings. + variants := []string{ + line, + fmt.Sprintf(`[*]%s[*]`, line), + fmt.Sprintf(`*%s*`, line), + fmt.Sprintf(`[*]%s*`, line), + fmt.Sprintf(`*%s[*]`, line), + } + cmd = "+" + line = "(" + strings.Join(variants, "|") + ")" + } + // TODO: check syntax // Reset the %Previous state if this is a new +Trigger. diff --git a/rivescript.go b/rivescript.go index c7a2916..d4d024c 100644 --- a/rivescript.go +++ b/rivescript.go @@ -25,7 +25,7 @@ import ( ) // Version number for the RiveScript library. -const Version = "0.3.1" +const Version = "0.4.0" // RiveScript is the bot instance. type RiveScript struct { @@ -34,6 +34,7 @@ type RiveScript struct { Strict bool // Strictly enforce RiveScript syntax Depth uint // Max depth for recursion UTF8 bool // Support UTF-8 RiveScript code + CaseSensitive bool // Preserve casing on incoming user messages Quiet bool // Suppress all warnings from being emitted UnicodePunctuation *regexp.Regexp @@ -100,11 +101,12 @@ func New(cfg *Config) *RiveScript { rs := &RiveScript{ // Set the default config objects that don't have good zero-values. - Debug: cfg.Debug, - Strict: cfg.Strict, - Depth: cfg.Depth, - UTF8: cfg.UTF8, - sessions: cfg.SessionManager, + Debug: cfg.Debug, + Strict: cfg.Strict, + Depth: cfg.Depth, + UTF8: cfg.UTF8, + CaseSensitive: cfg.CaseSensitive, + sessions: cfg.SessionManager, // Default punctuation that gets removed from messages in UTF-8 mode. UnicodePunctuation: regexp.MustCompile(`[.,!?;:]`), diff --git a/rsts_test.go b/rsts_test.go index f6031c5..96ee01a 100644 --- a/rsts_test.go +++ b/rsts_test.go @@ -222,7 +222,7 @@ func (t *TestCase) fail(err error) { } func TestRiveScript(t *testing.T) { - tests, err := filepath.Glob("../rsts/tests/*.yml") + tests, err := filepath.Glob("./rsts/tests/*.yml") if err != nil { panic(err) } diff --git a/tags.go b/tags.go index 4fdc876..968bd9d 100644 --- a/tags.go +++ b/tags.go @@ -9,12 +9,15 @@ import ( "strings" "github.com/aichaos/rivescript-go/sessions" + "github.com/mattn/go-shellwords" ) // formatMessage formats a user's message for safe processing. func (rs *RiveScript) formatMessage(msg string, botReply bool) string { // Lowercase it. - msg = strings.ToLower(msg) + if !rs.CaseSensitive { + msg = strings.ToLower(msg) + } // Run substitutions and sanitize what's left. msg = rs.substitute(msg, rs.sub, rs.sorted.sub) @@ -494,7 +497,11 @@ func (rs *RiveScript) processTags(username string, message string, reply string, } text := strings.TrimSpace(match[1]) - parts := strings.Split(text, " ") + parts, err := shellwords.Parse(text) + if err != nil { + rs.warn(" shellwords: %s", err) + parts = strings.Split(text, " ") + } obj := parts[0] args := []string{} if len(parts) > 1 {