aichaos / rivescript-js

A RiveScript interpreter for JavaScript. RiveScript is a scripting language for chatterbots.
https://www.rivescript.com/
MIT License
377 stars 144 forks source link

using parse / deparse #64

Closed dcsan closed 8 years ago

dcsan commented 8 years ago

trying to use the parse feature.

Parse RiveScript code and load it into memory

Is this for loading a json object, or rivescript code? If its 'code' how is it different from stream ?

I used brain.deparse() to dump to a JSON file, and then tried to load that back in again with brain.parse() but i get an error

> Rebot.parseTest()
TypeError: Cannot call method 'split' of undefined
    at Parser.parse (/Users/dc/dev/rikai/chatrobot/app/packages/rebot/.npm/package/node_modules/rivescript/lib/parser.js:54:18)
    at RiveScript.parse (/Users/dc/dev/rikai/chatrobot/app/packages/rebot/.npm/package/node_modules/rivescript/lib/rivescript.js:224:23)
    at Object.Rebot.parseTest (server/RebotParseTest.js:2296:15)

FYI the brain.json looks like this (I can upload the whole thing to a gist)

  var blob = {
      "begin": {
          "global": {
              "debug": true
          },
          "var": {
              "name": "EnglishChat",
              "botname": "EnglishChatBot",
              "grouplink": "https://telegram.me/joinchat/03326044003066291b2c290e44aa6dbc"
          },
          "sub": {
              "i'm": "i am",
              "i'd": "i would",
              "i've": "i have",
              "i'll": "i will",
dcsan commented 8 years ago

and I'm calling parse with these three args

  Rebot.brain.parse('testbrain', blob, (err, res) => {
    console.log('testParse result', err, res);
  });
dcsan commented 8 years ago

oh, i get it. I need to do this

  var rsCode = Rebot.brain.stringify(blob);
  Rebot.brain.stream(rsCode);
  Rebot.brain.sortReplies();
dcsan commented 8 years ago

and stream / parse is just async sync but otherwise does the same thing?

kirsle commented 8 years ago

Stream and parse are synchronous. The only reason the loadFile/loadDirectory are async is because they use underlying async functions (ajax requests or fs.readFile or w/e). You're correct that you can't populate the RiveScript brain using the deparsed json blob. Like in the Perl version those functions are for pretty special use cases, namely for assisting with a third party frontend for editing RiveScript replies. The json blob is easy to edit in code, and write() let's you write the resulting data to disk. In the Perl version I had a hosted bot platform for a while on RiveScript.com, and people would edit their bot one file at a time. So in that case I'd store individual json blobs for each file, and write them one at a time to RiveScript files for the user to export them or "publish" them to go live on their bot.

kirsle commented 8 years ago

As an aside, don't call the parse function directly, its a private function. ;) Its implementation could change at some point.

dcsan commented 8 years ago

oh, reading the docs parse

I thought the error callback was also an "on done" but i guess I misread that, its just for error handling

otherwise they look almost identical except for taking a 'name'

bool stream (string code[, func onError]) private bool parse (string name, string code[, func onError])

so

don't call the parse function directly

parse is like a _parse internal method that stream uses? and the stream method is public/safe to use?

I'm building a GUI (spreadsheet like) editor, so if I do want to go from a JSON object:

ie there's no way to feed the JSON object in directly?

kirsle commented 8 years ago

I could probably add a function to load the json in directly. It'd do the opposite of the deparse function, which basically just clones a dozen internal data structures. The parse function is essentially the same as stream, except stream provides the string "stream()" as the file name parameter (for syntax error reporting purposes). Use the stream function instead of parse.

dcsan commented 8 years ago

actually, ideally I would like to just pass in the individual trigger nodes, not the whole structure... something like just these nodes are mostly what would be gui edited.

But using the loader this way rather than stream() it will stomp on the whole brain, so I think need to pass a complete structure in?

The stuff like variables, substitutions etc is best left to some type of config. So I guess I could have a JSON config/template file based on a deparse, parse that to an object then just add the trigger nodes from the GUI editor to that object and stringify > stream

(or load JSON directly if that is implemented) going back and forth from JS to rivescript seems to have potential for gotchas, I've had problems already since I'm basically doing something like that now. esp around continuations. I have an added wrinkle that I'm using some things like ^ image: #filename for metadata, so i wanted to keep the continuations. but maybe that can work with just \n too.

      "topics": {
          "gotopic": [
              {
                  "trigger": "endlesson",
                  "reply": [
                      "Let's see how you did.\nYou scored <get score>\n{@scorefeedback}\ngoto: exitlesson"
                  ],
                  "condition": [],
                  "redirect": null,
                  "previous": null
              },
              {
                  "trigger": "scorefeedback",
                  "reply": [],
                  "condition": [
                      "<get score> < -5 => You need to study a lot more.",
                      "<get score> < -2 => Hmm, not great.",
                      "<get score> <= 1 => Hmm, not too bad",
                      "<get score>  > 1 => Good work!",
                      "<get score>  > 3 => Great job!"
                  ],
                  "redirect": null,
                  "previous": null
              },
...
      "inherits": {
          "global_commands": {},
          "gotopic": {
              "global_commands": 1
          },
          "lessons_shared": {
              "global_commands": 1
          },
kirsle commented 8 years ago

RiveScript doesn't keep track of continuations after the file has been parsed. When parsing the code, it looks ahead for continuations and concatenates them at the end of the current line of code it was otherwise looking at (joining them at the concat symbol, which can be set as the newline character). So a reply that continues across multiple lines ends up looking no different than a reply that was all on one line at the end of the parsing phase.

The Perl version of RiveScript pretty-prints the output from write() (essentially, word-wrapping it to keep lines under 80 characters long). This causes it to add continuation commands itself, but it puts them in only in the interest of word-wrapping a long line. I didn't implement the pretty-printing in the JS version because it requires a lot of very tedious code that I didn't feel like writing (keeping track of indentation levels, line buffers, etc. is mind numbing, and getting lengths of things in JavaScript is needlessly verbose). I may come back to that in the future, though.